South Plus +++

South Plus +++ 是一款集界面与阅读优化、帖子筛选屏蔽、快捷导航回复及自动购买等功能于一体的 South Plus 系列论坛增强脚本。

As of 15. 08. 2026. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         South Plus +++
// @namespace    https://south-plus.org/
// @version      0.4.12
// @description  South Plus +++ 是一款集界面与阅读优化、帖子筛选屏蔽、快捷导航回复及自动购买等功能于一体的 South Plus 系列论坛增强脚本。
// @author       local
// @match        *://*.south-plus.net/*
// @match        *://south-plus.net/*
// @match        *://*.south-plus.org/*
// @match        *://south-plus.org/*
// @match        *://*.white-plus.net/*
// @match        *://white-plus.net/*
// @match        *://*.north-plus.net/*
// @match        *://north-plus.net/*
// @match        *://*.level-plus.net/*
// @match        *://level-plus.net/*
// @match        *://*.soul-plus.net/*
// @match        *://soul-plus.net/*
// @match        *://*.snow-plus.net/*
// @match        *://snow-plus.net/*
// @match        *://*.spring-plus.net/*
// @match        *://spring-plus.net/*
// @match        *://*.summer-plus.net/*
// @match        *://summer-plus.net/*
// @match        *://*.blue-plus.net/*
// @match        *://blue-plus.net/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

/*
 * 使用说明:
 * 1. 将本脚本安装到 Tampermonkey / Violentmonkey 等用户脚本管理器后启用。
 * 2. 打开匹配的 South Plus 站点页面,脚本会自动生效,无需手动执行。
 * 3. 默认提供紧凑布局、阅读优化、快速导航、已读标记、关注主题和本地屏蔽规则等增强功能。
 * 4. 所有设置与已读/关注状态仅保存在当前浏览器本地,不会上传到服务器。
 */

(function factoryWrapper(root, factory) {
  if (typeof module === 'object' && module.exports) {
    module.exports = factory(true);
    return;
  }

  var api = factory(false);
  api.init();
})(typeof globalThis !== 'undefined' ? globalThis : this, function createSouthPlusEnhancer(testMode) {
  'use strict';

  var APP = 'spEnhancer';
  var STORE_KEY = APP + ':settings:v1';
  var READ_KEY = APP + ':readThreads:v1';
  var WATCH_KEY = APP + ':watchThreads:v1';
  var PROGRESS_KEY = APP + ':readProgress:v1';
  var RESTORE_PROGRESS_KEY = APP + ':restoreProgressTid:v1';
  var AUTO_BUY_KEY = APP + ':autoBuyAttempts:v1';
  var RESOURCE_KEY = APP + ':resources:v1';
  var READ_RESOURCE_RAIL_COLLAPSED_KEY = APP + ':readResourceRailCollapsed:v1';
  var NAVIGATION_KEY = APP + ':navigation:v1';
  var NAVIGATION_COLLAPSE_KEY = APP + ':navigationCollapse:v1';
  var NAVIGATION_PIN_KEY = APP + ':navigationPins:v1';
  var NAVIGATION_REFRESH_KEY = APP + ':navigationRefresh:v1';
  var FAVORITE_NAV_COUNT_CACHE_KEY = APP + ':favoriteNavCountCache:v1';
  var FAVORITE_NAV_SEEN_KEY = APP + ':favoriteNavSeen:v1';
  var AUTO_BUY_CHECK_TTL = 10 * 60 * 1000;
  var AUTO_BUY_ATTEMPT_LIMIT = 100;
  var RESOURCE_LIMIT = 500;
  var NAVIGATION_POOL_LIMIT = 160;
  var NAVIGATION_REFRESH_TTL = 6 * 60 * 60 * 1000;
  var FAVORITE_NAV_COUNT_CACHE_TTL = 2 * 60 * 1000;
  var FAVORITE_NAV_COUNT_RETRY_TTL = 60 * 1000;
  var READ_PROGRESS_LIMIT = 200;
  var STALE_PROGRESS_MAX_AGE = 180 * 24 * 60 * 60 * 1000;
  var LOCAL_STORAGE_WARNING_BYTES = 4 * 1024 * 1024;
  var PREVIEW_GALLERY_BATCH_SIZE = 36;
  var PREVIEW_DOWNLOAD_MAX_RETRIES = 6;
  var PREVIEW_DOWNLOAD_CONCURRENCY = 3;
  var FAVORITE_NAV_BATCH_SIZE = 50;
  var FAVORITE_NAV_MAX_FETCHED_ITEMS = 120;
  var THREAD_PREVIEW_CACHE_TTL = 20 * 60 * 1000;
  var THREAD_PREVIEW_FAILURE_TTL = 3 * 60 * 1000;
  var THREAD_PREVIEW_CACHE_LIMIT = 50;
  var THREAD_PREVIEW_IMAGE_LIMIT = 6;
  var THREAD_PREVIEW_HOVER_DELAY = 520;
  var THREAD_PREVIEW_FAST_HOVER_DELAY = 260;
  var THREAD_PREVIEW_IMAGE_LOAD_DELAY = 180;
  var THREAD_PREVIEW_IMAGE_BATCH_SIZE = 2;
  var SP_BALANCE_CACHE_TTL = 90 * 1000;
  var TOOLBOX_BUTTON_SELECTOR = '[data-spx-toolbox-button="1"]';
  var SETTINGS_BUTTON_SELECTOR = '[data-spx-settings-button="1"]';
  var COMMAND_PALETTE_BUTTON_SELECTOR = '[data-spx-command-palette-button="1"]';
  var THREAD_ROW_HIDDEN_CLASSES = ['spx-filter-hidden', 'spx-resource-filter-hidden', 'spx-hidden-rule', 'spx-unread-hidden', 'spx-thread-row-hidden'];
  var pendingModuleNavigationConfigs = [];
  var scriptRequestState = {
    queue: [],
    running: false,
    timer: null,
    lastStartedAt: 0,
    cooldownUntil: 0,
    sequence: 0,
  };
  var threadPreviewCache = {};
  var threadPreviewCacheOrder = [];
  var spBalanceCache = { value: null, expiresAt: 0 };
  var readResourceRailContext = { posts: null, state: null, filter: 'all' };
  var enhanceCycle = 0;
  var RESOURCE_STATUSES = {
    saved: '已保存',
    todo: '待下载',
    done: '已处理',
    invalid: '已失效',
  };
  var RESOURCE_CATEGORIES = {
    magnet: '磁力',
    ed2k: '电驴',
    torrent: '种子',
    archive: '压缩包',
    cloud: '网盘',
    image: '图片',
    external: '外链',
  };
  var RESOURCE_BADGE_ORDER = ['baidu', 'quark', 'pikpak', 'magnet', 'torrent', 'archive', 'ed2k', 'external', 'cloud'];
  var RESOURCE_BADGE_DEFINITIONS = {
    baidu: { label: '百度', pattern: /百度|baidu|pan\.baidu|yun\.baidu/i },
    quark: { label: '夸克', pattern: /夸克|quark|pan\.quark/i },
    pikpak: { label: 'PikPak', pattern: /pikpak/i },
    magnet: { label: '磁力', pattern: /磁力|magnet/i },
    torrent: { label: '种子', pattern: /种子|torrent/i },
    archive: { label: '压缩包', pattern: /压缩包|解压|zip|rar|7z|iso/i },
    ed2k: { label: '电驴', pattern: /电驴|ed2k/i },
    external: { label: '外链', pattern: /外链|直链|镜像/i },
    cloud: { label: '网盘', pattern: /网盘|云盘/i },
  };
  var CLOUD_RESOURCE_HOST_PATTERN = [
    'pan\\.baidu\\.com',
    'yun\\.baidu\\.com',
    'aliyundrive\\.com',
    'alipan\\.com',
    'quark\\.cn',
    'drive\\.uc\\.cn',
    '115\\.com',
    'mega\\.nz',
    'terabox\\.com',
    'lanzou[a-z]?\\.com',
    'ilanzou\\.com',
    'weiyun\\.com',
    'cloud\\.189\\.cn',
    '123pan\\.com',
    'mypikpak\\.com',
    'pikpak\\.com',
    'mediafire\\.com',
    '4shared\\.com',
    'onedrive\\.live\\.com',
    'pan\\.xunlei\\.com',
    'cowtransfer\\.com',
    'feijipan\\.com',
  ].join('|');
  var CLOUD_RESOURCE_HOST_RE = new RegExp('(?:^|\\.)(?:' + CLOUD_RESOURCE_HOST_PATTERN + ')$', 'i');
  var CLOUD_RESOURCE_PATH_RE = new RegExp('^(?:(?:www|share|pan)\\.)?(?:' + CLOUD_RESOURCE_HOST_PATTERN + ')\\/', 'i');
  var TORRENT_RESOURCE_EXT_RE = /\.torrent(?:[?#&]|$)/i;
  var ARCHIVE_RESOURCE_EXT_RE = /\.(?:zip|rar|7z|tar|tgz|gz|bz2|xz|iso|001)(?:[?#&]|$)/i;
  var DEFAULT_SETTINGS = {
    cleanMode: true,
    readerMode: true,
    immersiveRead: true,
    nightMode: false,
    immersiveFontSize: 16,
    unifiedPreviewGallery: true,
    homeDashboard: true,
    forumDashboard: true,
    moduleNavDensity: 'comfortable',
    adBlock: true,
    compactRead: true,
    unreadOnly: false,
    onlyOriginalAuthor: false,
    foldQuotes: true,
    hideUserProfile: false,
    networkFriendly: true,
    autoBuyPost: false,
    autoBuyMaxSp: 5,
    titleKeywords: [],
    authorKeywords: [],
    quickReplies: [
      '感谢分享',
      '支持一下',
      '楼主辛苦了',
      '感谢楼主分享',
      '收藏备用',
      '马克一下',
      '先顶后看',
      '感谢整理',
      '资源很棒',
      '正好需要',
      '学习一下',
      '感谢补档',
    ],
  };

  var QUICK_REPLY_EMOTES = [
    '001.jpg', '002.jpg', '003.jpg', '005.jpg', '006.jpg', '007.jpg', '008.jpg', '009.jpg', '010.jpg', '012.jpg',
    '013.gif', '014.jpg', '015.jpg', '016.jpg', '017.jpg', '018.jpg', '019.jpg', '020.jpg', '021.gif', '022.gif',
    '023.jpg', '024.jpg', '025.jpg', '026.jpg', '027.jpg', '028.jpg', '029.jpg', '030.jpg', '031.gif', '032.jpg',
    '033.jpg', '034.jpg', '035.jpg', '036.jpg', '037.gif', '038.gif', '039.jpg', '040.jpg',
  ].map(function mapQuickReplyEmote(fileName) {
    var code = fileName.replace(/\D/g, '').replace(/^0+/, '') || '1';
    return {
      code: '[s:' + code + ']',
      fileName: fileName,
      src: 'https://south-plus.org/images/post/smile/smallface/face' + fileName,
    };
  });

  function parseThreadId(value) {
    var text = String(value || '');
    var match =
      text.match(/^(\d+)$/) ||
      text.match(/(?:^|[_-])(?:td|ajax)_(\d+)(?:\D|$)/) ||
      text.match(/(?:td|a_ajax)_(\d+)/) ||
      text.match(/[?&]tid=(\d+)/) ||
      text.match(/[?&]tid-(\d+)/) ||
      text.match(/read\.php\?tid[=-](\d+)/);
    return match ? match[1] : '';
  }

  function parseLineList(value) {
    var seen = {};
    return String(value || '')
      .split(/\r?\n/)
      .map(function trimLine(line) {
        return line.trim();
      })
      .filter(function keepUnique(line) {
        if (!line || seen[line]) return false;
        seen[line] = true;
        return true;
      });
  }

  function parseQuickReplyList(value) {
    return parseLineList(value).slice(0, 30);
  }

  function normalizeModuleNavDensity(value) {
    var text = String(value || '').trim();
    if (text === 'compact' || text === 'standard' || text === 'comfortable') return text;
    return 'comfortable';
  }

  function getModuleNavigationDensityConfig(value) {
    var density = normalizeModuleNavDensity(value);
    var configs = {
      compact: {
        width: 220,
        maxOffset: 76,
        sectionHeight: 28,
        parentHeight: 30,
        itemHeight: 34,
        childHeight: 30,
        itemPadding: 10,
        sectionPadding: 9,
        radius: 9,
      },
      standard: {
        width: 240,
        maxOffset: 64,
        sectionHeight: 30,
        parentHeight: 36,
        itemHeight: 40,
        childHeight: 32,
        itemPadding: 12,
        sectionPadding: 11,
        radius: 10,
      },
      comfortable: {
        width: 260,
        maxOffset: 52,
        sectionHeight: 34,
        parentHeight: 40,
        itemHeight: 44,
        childHeight: 36,
        itemPadding: 14,
        sectionPadding: 12,
        radius: 12,
      },
    };
    return Object.assign({ density: density }, configs[density]);
  }

  function normalizeListValue(value, parser) {
    var source = value || [];
    return parser(source.join ? source.join('\n') : source);
  }

  function parseTagList(value) {
    var seen = {};
    return String(value || '')
      .replace(/\\n/g, '\n')
      .split(/[\n,,、#]+/)
      .map(function trimTag(tag) {
        return tag.trim().replace(/\s+/g, ' ');
      })
      .filter(function keepTag(tag) {
        var key = tag.toLowerCase();
        if (!tag || seen[key]) return false;
        seen[key] = true;
        return true;
      })
      .slice(0, 12);
  }

  function mergeTagLists() {
    var seen = {};
    var result = [];
    Array.prototype.slice.call(arguments).forEach(function readTags(tags) {
      normalizeListValue(tags, parseTagList).forEach(function addTag(tag) {
        var key = tag.toLowerCase();
        if (seen[key]) return;
        seen[key] = true;
        result.push(tag);
      });
    });
    return result;
  }

  function formatTags(tags) {
    var values = normalizeListValue(tags, parseTagList);
    return values.length ? values.join(' / ') : '';
  }

  function clampRatio(value) {
    var ratio = Number(value);
    if (!isFinite(ratio)) return 0;
    return Math.min(1, Math.max(0, ratio));
  }

  function getReadProgressPercent(record) {
    return Math.round(clampRatio(record && record.progress) * 100);
  }

  function isCompletedReadProgress(record) {
    return !!(record && record.updatedAt && getReadProgressPercent(record) >= 100);
  }

  function mergeReadProgressRecord(previous, next) {
    if (!next) return previous || null;
    if (isCompletedReadProgress(previous) && !isCompletedReadProgress(next)) return previous;
    if (previous && previous.tags && !next.tags) {
      next.tags = normalizeListValue(previous.tags, parseTagList);
    }
    return next;
  }

  function formatReadProgress(record) {
    if (!record || !record.updatedAt) return '';
    var percent = getReadProgressPercent(record);
    var page = Number(record.page) || 1;
    return (page > 1 ? '第 ' + page + ' 页 · ' : '') + percent + '%';
  }

  function getReadProgressFloorLabels(record) {
    return {
      last: String((record && record.floorLabel) || ''),
      next: String((record && (record.nextFloorLabel || record.floorLabel)) || ''),
    };
  }

  function getReadProgressRestoreTarget(record, mode) {
    var currentMode = mode === 'last' ? 'last' : 'next';
    var lastTop = Math.max(0, Number(record && (record.floorTop !== undefined ? record.floorTop : record.scrollY)) || 0);
    var nextTop = Math.max(
      0,
      Number(record && (record.nextFloorTop !== undefined ? record.nextFloorTop : record.floorTop !== undefined ? record.floorTop : record.scrollY)) || 0
    );
    if (currentMode === 'last') {
      return {
        mode: 'last',
        hash: String((record && record.floorHash) || ''),
        top: lastTop,
        label: String((record && record.floorLabel) || ''),
      };
    }
    return {
      mode: 'next',
      hash: String((record && (record.nextFloorHash || record.floorHash)) || ''),
      top: nextTop,
      label: String((record && (record.nextFloorLabel || record.floorLabel)) || ''),
    };
  }

  function pruneReadProgress(progress, limit) {
    var source = progress || {};
    var max = Math.max(1, Number(limit) || READ_PROGRESS_LIMIT);
    var keys = Object.keys(source)
      .filter(function hasProgress(key) {
        return source[key] && source[key].updatedAt;
      })
      .sort(function sortByUpdatedAt(left, right) {
        return (Number(source[right].updatedAt) || 0) - (Number(source[left].updatedAt) || 0);
      })
      .slice(0, max);
    var result = {};
    keys.forEach(function keepProgress(key) {
      result[key] = source[key];
    });
    return result;
  }

  function getWatchCenterEntries(watch, progress) {
    var progressMap = progress || {};
    return Object.keys(watch || {})
      .filter(function hasWatchItem(key) {
        var item = watch[key];
        return item && (item.title || item.url);
      })
      .map(function toWatchEntry(key) {
        var item = watch[key] || {};
        var record = progressMap[key] || {};
        var floorLabels = getReadProgressFloorLabels(record);
        var tags = mergeTagLists(item.tags, record.tags);
        return {
          id: key,
          title: String(record.title || item.title || '未命名帖子'),
          url: String(item.url || record.url || ''),
          savedAt: Number(item.savedAt) || 0,
          progressAt: Number(record.updatedAt) || 0,
          progressUrl: String(record.url || item.url || ''),
          progressText: formatReadProgress(record),
          progressPercent: getReadProgressPercent(record),
          isCompleted: isCompletedReadProgress(record),
          floorLabel: floorLabels.last,
          nextFloorLabel: floorLabels.next,
          floorHash: String(record.floorHash || ''),
          nextFloorHash: String(record.nextFloorHash || record.floorHash || ''),
          tags: tags,
          tagText: formatTags(tags),
        };
      })
      .sort(function sortBySavedAt(left, right) {
        return (right.savedAt || 0) - (left.savedAt || 0);
      });
  }

  function getHistoryCenterEntries(progress) {
    return Object.keys(progress || {})
      .filter(function hasHistoryItem(key) {
        var item = progress[key];
        return item && (item.title || item.url) && item.updatedAt;
      })
      .map(function toHistoryEntry(key) {
        var item = progress[key] || {};
        var floorLabels = getReadProgressFloorLabels(item);
        var tags = parseTagList(item.tags && item.tags.join ? item.tags.join('\n') : item.tags);
        return {
          id: key,
          title: String(item.title || '未命名帖子'),
          url: String(item.url || ''),
          page: Number(item.page) || 1,
          scrollY: Math.max(0, Number(item.scrollY) || 0),
          progressAt: Number(item.updatedAt) || 0,
          progressText: formatReadProgress(item),
          progressPercent: getReadProgressPercent(item),
          isCompleted: isCompletedReadProgress(item),
          floorLabel: floorLabels.last,
          nextFloorLabel: floorLabels.next,
          floorHash: String(item.floorHash || ''),
          nextFloorHash: String(item.nextFloorHash || item.floorHash || ''),
          tags: tags,
          tagText: formatTags(tags),
        };
      })
      .sort(function sortByProgressAt(left, right) {
        return (right.progressAt || 0) - (left.progressAt || 0);
      });
  }

  function getAutoBuyStatusLabel(status) {
    var labels = {
      checking: '检查中',
      skipped: '已跳过',
      buying: '购买中',
      done: '已完成',
      failed: '失败',
    };
    return labels[status] || '未知';
  }

  function getAutoBuyCenterEntries(attempts) {
    return Object.keys(attempts || {})
      .filter(function hasAttempt(key) {
        return attempts[key] && attempts[key].status;
      })
      .map(function toAutoBuyEntry(key) {
        var item = attempts[key] || {};
        return {
          key: key,
          status: String(item.status || ''),
          statusLabel: getAutoBuyStatusLabel(item.status),
          message: String(item.message || ''),
          url: String(item.url || ''),
          price: item.price === undefined ? null : Number(item.price),
          balance: item.balance === undefined ? null : Number(item.balance),
          updatedAt: Number(item.updatedAt) || 0,
        };
      })
      .sort(function sortByUpdatedAt(left, right) {
        return (right.updatedAt || 0) - (left.updatedAt || 0);
      });
  }

  function getDayStart(timestamp) {
    var date = new Date(Number(timestamp) || Date.now());
    return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
  }

  function isTimestampToday(timestamp, now) {
    var value = Number(timestamp) || 0;
    return value >= getDayStart(now || Date.now());
  }

  function getForumDashboardTopicKey(item) {
    var source = item || {};
    var id = parseThreadId(source.id || source.url || source.progressUrl || source.sourceUrl || '');
    if (id) return 'tid:' + id;
    var baseUrl = typeof location !== 'undefined' ? location.href : 'https://south-plus.org/';
    var url = normalizeNavigationHref(source.url || source.progressUrl || source.sourceUrl || '', baseUrl);
    if (url) return 'url:' + url.replace(/#.*$/, '').toLowerCase();
    var title = compactText(source.title || source.sourceTitle || '');
    return title ? 'title:' + title.toLowerCase() : '';
  }

  function getForumDashboardTopicTags(item) {
    var tags = mergeTagLists(item && item.tags, item && item.resourceTags);
    var text = [item && item.title, item && item.meta, item && item.sourceText].filter(Boolean).join(' ');
    inferFavoriteNavTags(text, '').forEach(function appendInferredTag(tag) {
      if (tags.indexOf(tag) === -1) tags.push(tag);
    });
    return tags;
  }

  function mergeForumDashboardTopic(target, source) {
    var next = source || {};
    if (!target.title && next.title) target.title = next.title;
    if (!target.url && next.url) target.url = next.url;
    if (!target.author && next.author) target.author = next.author;
    if (!target.meta && next.meta) target.meta = next.meta;
    target.savedAt = Math.max(Number(target.savedAt) || 0, Number(next.savedAt) || 0);
    target.progressAt = Math.max(Number(target.progressAt) || 0, Number(next.progressAt) || 0);
    target.favoriteAt = Math.max(Number(target.favoriteAt) || 0, Number(next.favoriteAt) || 0);
    target.resourceAt = Math.max(Number(target.resourceAt) || 0, Number(next.resourceAt) || 0);
    target.progressPercent = Math.max(Number(target.progressPercent) || 0, Number(next.progressPercent) || 0);
    target.isCompleted = !!(target.isCompleted || next.isCompleted);
    target.hasWatch = !!(target.hasWatch || next.hasWatch);
    target.hasHistory = !!(target.hasHistory || next.hasHistory);
    target.hasSiteFavorite = !!(target.hasSiteFavorite || next.hasSiteFavorite);
    target.resourceCount = (Number(target.resourceCount) || 0) + (Number(next.resourceCount) || 0);
    target.tags = mergeTagLists(target.tags, getForumDashboardTopicTags(next));
    target.lastAt = Math.max(target.savedAt || 0, target.progressAt || 0, target.favoriteAt || 0, target.resourceAt || 0);
    return target;
  }

  function addForumDashboardTopic(map, item) {
    var key = getForumDashboardTopicKey(item);
    if (!key) return null;
    if (!map[key]) {
      map[key] = {
        key: key,
        id: parseThreadId(item && (item.id || item.url || item.progressUrl || item.sourceUrl)),
        title: '',
        url: '',
        author: '',
        meta: '',
        savedAt: 0,
        progressAt: 0,
        favoriteAt: 0,
        resourceAt: 0,
        progressPercent: 0,
        isCompleted: false,
        hasWatch: false,
        hasHistory: false,
        hasSiteFavorite: false,
        resourceCount: 0,
        tags: [],
        lastAt: 0,
      };
    }
    return mergeForumDashboardTopic(map[key], item);
  }

  function getForumDashboardTopicScore(item, now) {
    var topic = item || {};
    var currentTime = Number(now) || Date.now();
    var score = 0;
    if (topic.hasSiteFavorite && !topic.isCompleted) score += 30;
    if (topic.hasWatch && !topic.isCompleted) score += 25;
    if (topic.progressAt >= currentTime - 7 * 24 * 60 * 60 * 1000) score += 18;
    if (topic.progressPercent > 0 && topic.progressPercent < 100) score += 16;
    if (topic.resourceCount > 0) score += 15 + Math.min(10, topic.resourceCount * 2);
    if (isTimestampToday(topic.savedAt, currentTime) || isTimestampToday(topic.favoriteAt, currentTime)) score += 12;
    if (topic.tags && topic.tags.length) score += Math.min(12, topic.tags.length * 3);
    if (topic.isCompleted) score -= 18;
    if (topic.lastAt && topic.lastAt < currentTime - 30 * 24 * 60 * 60 * 1000) score -= 10;
    return score;
  }

  function formatForumDashboardRelativeTime(timestamp, now) {
    var value = Number(timestamp) || 0;
    if (!value) return '';
    var diff = Math.max(0, (Number(now) || Date.now()) - value);
    if (diff < 60 * 1000) return '刚刚';
    if (diff < 60 * 60 * 1000) return Math.max(1, Math.round(diff / 60 / 1000)) + ' 分钟前';
    if (diff < 24 * 60 * 60 * 1000) return Math.max(1, Math.round(diff / 60 / 60 / 1000)) + ' 小时前';
    if (diff < 30 * 24 * 60 * 60 * 1000) return Math.max(1, Math.round(diff / 24 / 60 / 60 / 1000)) + ' 天前';
    return formatShortTime(value);
  }

  function getForumDashboardRequestSummary(requestState, now) {
    var source = requestState || scriptRequestState || {};
    var queueCount = Array.isArray(source.queue) ? source.queue.length : Math.max(0, Number(source.queueCount) || 0);
    var cooldownRemainingMs = Math.max(0, Number(source.cooldownUntil || 0) - (Number(now) || Date.now()));
    return {
      queueCount: queueCount,
      cooldownRemainingMs: cooldownRemainingMs,
      status: cooldownRemainingMs > 0 ? '冷却中' : (queueCount > 0 || source.running ? '排队中' : '正常'),
      detail: '队列 ' + queueCount + ' / 冷却 ' + Math.ceil(cooldownRemainingMs / 1000) + ' 秒',
    };
  }

  function getForumDashboardActivityItems(report, now) {
    var items = [];
    (report.worthReviewing || []).slice(0, 2).forEach(function appendWorthActivity(topic) {
      items.push({
        type: topic.resourceCount ? 'resource' : 'progress',
        title: (topic.progressPercent ? '继续阅读 ' : '回看 ') + topic.title,
        meta: topic.meta || topic.reason || '',
        time: formatForumDashboardRelativeTime(topic.lastAt, now),
      });
    });
    (report.resources || []).slice(0, 2).forEach(function appendResourceActivity(entry) {
      items.push({
        type: 'resource',
        title: '资源待处理:' + (entry.sourceTitle || entry.label || '未命名资源'),
        meta: [entry.label, entry.provider, entry.statusLabel].filter(Boolean).join(' · '),
        time: formatForumDashboardRelativeTime(entry.updatedAt || entry.savedAt, now),
      });
    });
    if (report.request.status !== '正常') {
      items.push({ type: 'request', title: '后台请求' + report.request.status, meta: report.request.detail, time: '现在' });
    }
    return items.slice(0, 4);
  }

  function getForumDashboardTagStats(topics, resources) {
    var counts = {};
    (topics || []).forEach(function collectTopicTags(topic) {
      (topic.tags || []).forEach(function countTag(tag) {
        counts[tag] = (counts[tag] || 0) + 1;
      });
    });
    (resources || []).forEach(function collectResourceTag(entry) {
      var label = entry.type === 'cloud' ? '网盘' : entry.label;
      if (label) counts[label] = (counts[label] || 0) + 1;
    });
    return Object.keys(counts).map(function mapTagStat(tag) {
      return { label: tag, count: counts[tag] };
    }).sort(function sortTagStats(left, right) {
      return right.count - left.count || left.label.localeCompare(right.label, 'zh-Hans-CN');
    }).slice(0, 4);
  }

  function collectForumDashboardReport(data, now) {
    var source = data || {};
    var currentTime = Number(now) || Date.now();
    var origin = typeof location !== 'undefined' && location.origin ? location.origin : 'https://south-plus.org';
    var todayStart = getDayStart(currentTime);
    var readMap = source.read || {};
    var progressMap = source.progress || {};
    var watchMap = source.watch || {};
    var favoriteSeen = normalizeFavoriteNavSeenMap(source.favoriteSeen || {});
    var resources = getResourceCenterEntries(source.resources || {});
    var topics = {};

    getWatchCenterEntries(watchMap, progressMap).forEach(function collectWatchTopic(entry) {
      addForumDashboardTopic(topics, Object.assign({}, entry, {
        hasWatch: true,
        url: entry.progressUrl || entry.url,
        meta: [entry.progressText, entry.nextFloorLabel ? '续读 ' + entry.nextFloorLabel : '', entry.tagText].filter(Boolean).join(' · '),
      }));
    });

    getHistoryCenterEntries(progressMap).forEach(function collectHistoryTopic(entry) {
      addForumDashboardTopic(topics, Object.assign({}, entry, {
        hasHistory: true,
        url: entry.url,
        meta: [entry.progressText, entry.nextFloorLabel ? '续读 ' + entry.nextFloorLabel : '', entry.tagText].filter(Boolean).join(' · '),
      }));
    });

    resources.forEach(function collectResourceTopic(entry) {
      addForumDashboardTopic(topics, {
        id: parseThreadId(entry.sourceUrl),
        title: entry.sourceTitle || entry.sourceUrl || entry.label,
        url: entry.sourceUrl || entry.url,
        author: entry.author,
        sourceText: entry.sourceText,
        resourceTags: entry.tags,
        resourceCount: 1,
        resourceAt: entry.updatedAt || entry.savedAt,
        meta: [entry.label, entry.provider, entry.statusLabel].filter(Boolean).join(' · '),
      });
    });

    Object.keys(favoriteSeen).forEach(function markFavoriteTopic(id) {
      addForumDashboardTopic(topics, {
        id: id,
        hasSiteFavorite: true,
        favoriteAt: favoriteSeen[id],
      });
    });

    var topicList = Object.keys(topics).map(function finalizeTopic(key) {
      var topic = topics[key];
      topic.title = topic.title || (topic.id ? '收藏主题 #' + topic.id : '未命名主题');
      topic.url = topic.url || (topic.id ? origin + '/read.php?tid-' + topic.id + '.html' : '');
      topic.score = getForumDashboardTopicScore(topic, currentTime);
      var reasons = [];
      if (topic.hasSiteFavorite) reasons.push('站内收藏');
      if (topic.hasWatch) reasons.push('稍后看');
      if (topic.resourceCount) reasons.push('资源 ' + topic.resourceCount);
      if (topic.progressPercent) reasons.push('进度 ' + topic.progressPercent + '%');
      if (topic.tags && topic.tags.length) reasons.push('标签 ' + formatTags(topic.tags.slice(0, 3)));
      topic.reason = reasons.join(' · ') || '本地记录';
      return topic;
    }).filter(function keepScoredTopic(topic) {
      return topic.score > 0 && topic.title;
    }).sort(function sortDashboardTopics(left, right) {
      return right.score - left.score || right.lastAt - left.lastAt || String(left.title).localeCompare(String(right.title), 'zh-Hans-CN');
    });

    var todayViewed = {};
    Object.keys(readMap).forEach(function collectReadToday(id) {
      if (Number(readMap[id]) >= todayStart) todayViewed[id] = true;
    });
    Object.keys(progressMap).forEach(function collectProgressToday(id) {
      if (Number(progressMap[id] && progressMap[id].updatedAt) >= todayStart) todayViewed[id] = true;
    });

    var watchEntries = getWatchCenterEntries(watchMap, progressMap);
    var siteFavoriteToday = Object.keys(favoriteSeen).filter(function countFavoriteToday(id) {
      return Number(favoriteSeen[id]) >= todayStart;
    }).length;
    var watchToday = watchEntries.filter(function countWatchToday(entry) {
      return Number(entry.savedAt) >= todayStart;
    }).length;
    var unreadFavorites = topicList.filter(function countUnreadFavorite(topic) {
      return (topic.hasSiteFavorite || topic.hasWatch) && !topic.isCompleted;
    }).length;
    var watchBacklog = watchEntries.filter(function countWatchBacklog(entry) {
      return !entry.isCompleted;
    }).length;
    var resourceToday = resources.filter(function countResourceToday(entry) {
      return Math.max(Number(entry.updatedAt) || 0, Number(entry.savedAt) || 0) >= todayStart;
    }).length;
    var request = getForumDashboardRequestSummary(source.requestState, currentTime);
    var tagStats = getForumDashboardTagStats(topicList, resources);
    var report = {
      stats: {
        todayViewed: Object.keys(todayViewed).length,
        favoriteAdded: siteFavoriteToday + watchToday,
        unreadFavorites: unreadFavorites,
        resourceAdded: resourceToday,
        watchBacklog: watchBacklog,
        requestStatus: request.status,
      },
      request: request,
      worthReviewing: topicList.slice(0, 5),
      resources: resources.slice(0, 4),
      tagStats: tagStats,
      backlogStats: [
        { label: '稍后看', count: watchBacklog },
        { label: '未读收藏', count: unreadFavorites },
        { label: '待处理资源', count: resources.filter(function countTodoResource(entry) { return entry.status === 'todo' || entry.status === 'saved'; }).length },
        { label: '失败请求', count: getAutoBuyCenterEntries(source.autoBuyAttempts || {}).filter(function countFailedAttempt(entry) { return entry.status === 'failed'; }).length },
      ],
    };
    report.activities = getForumDashboardActivityItems(report, currentTime);
    return report;
  }

  function normalizeCenterSearchQuery(value) {
    return String(value || '')
      .replace(/\s+/g, ' ')
      .trim()
      .toLowerCase();
  }

  function matchesCenterSearch(query, values) {
    if (!query) return true;
    return (values || []).some(function hasSearchValue(value) {
      return String(value || '').toLowerCase().indexOf(query) !== -1;
    });
  }

  function filterWatchCenterEntries(entries, options) {
    var query = normalizeCenterSearchQuery(options && options.query);
    var filter = String((options && options.filter) || 'all');
    var tag = String((options && options.tag) || 'all').toLowerCase();
    return (entries || []).filter(function matchWatchEntry(entry) {
      if (!matchesCenterSearch(query, [entry.title, entry.progressText, entry.floorLabel, entry.nextFloorLabel, entry.tagText])) return false;
      if (tag !== 'all' && parseTagList(entry.tags).map(function lower(item) { return item.toLowerCase(); }).indexOf(tag) === -1) return false;
      if (filter === 'todo') return !entry.isCompleted;
      if (filter === 'progress') return !!entry.progressAt;
      if (filter === 'done') return !!entry.isCompleted;
      return true;
    });
  }

  function filterHistoryCenterEntries(entries, options) {
    var query = normalizeCenterSearchQuery(options && options.query);
    var filter = String((options && options.filter) || 'all');
    var tag = String((options && options.tag) || 'all').toLowerCase();
    return (entries || []).filter(function matchHistoryEntry(entry) {
      if (!matchesCenterSearch(query, [entry.title, entry.progressText, entry.floorLabel, entry.nextFloorLabel, entry.tagText])) return false;
      if (tag !== 'all' && parseTagList(entry.tags).map(function lower(item) { return item.toLowerCase(); }).indexOf(tag) === -1) return false;
      if (filter === 'todo') return !entry.isCompleted;
      if (filter === 'done') return !!entry.isCompleted;
      return true;
    });
  }

  function getCenterTagOptions(entries) {
    var tags = [];
    (entries || []).forEach(function collectEntryTags(entry) {
      tags = mergeTagLists(tags, entry && entry.tags);
    });
    return tags
      .sort(function sortTags(left, right) {
        return left.localeCompare(right, 'zh-Hans-CN');
      })
      .map(function toOption(tag) {
        return { value: tag, label: tag };
      });
  }

  function filterAutoBuyCenterEntries(entries, options) {
    var query = normalizeCenterSearchQuery(options && options.query);
    var filter = String((options && options.filter) || 'all');
    return (entries || []).filter(function matchAutoBuyEntry(entry) {
      if (!matchesCenterSearch(query, [entry.key, entry.statusLabel, entry.message, entry.url, entry.price, entry.balance])) return false;
      if (filter === 'all') return true;
      return entry.status === filter;
    });
  }

  function normalizeCommandPaletteFilter(value) {
    var text = String(value || 'all');
    return /^(all|navigate|center|resource|setting|action)$/.test(text) ? text : 'all';
  }

  function getCommandPaletteCategoryLabel(value) {
    var labels = {
      navigate: '导航',
      center: '我的中心',
      resource: '资源',
      setting: '设置',
      action: '页面动作',
    };
    return labels[normalizeCommandPaletteFilter(value)] || '全部';
  }

  function getCommandPaletteSourceLabel(entry) {
    var item = entry || {};
    return item.source || getCommandPaletteCategoryLabel(item.category);
  }

  function createCommandPaletteEntry(config) {
    var source = config || {};
    var title = compactText(source.title || source.label || '未命名命令');
    if (!title) return null;
    var category = normalizeCommandPaletteFilter(source.category);
    var id = String(source.id || [category, title, source.href || source.key || source.panelId || source.recordId || ''].join('|'));
    return {
      id: id,
      category: category,
      icon: compactText(source.icon || title.slice(0, 1)).slice(0, 2),
      title: title,
      description: compactText(source.description || source.meta || getCommandPaletteCategoryLabel(category)),
      meta: compactText(source.meta || ''),
      source: compactText(source.source || getCommandPaletteCategoryLabel(category)),
      target: compactText(source.target || ''),
      risk: compactText(source.risk || '低'),
      href: String(source.href || ''),
      action: String(source.action || ''),
      key: String(source.key || ''),
      panelId: String(source.panelId || ''),
      recordId: String(source.recordId || ''),
      resourceKey: String(source.resourceKey || ''),
      keywords: compactText(source.keywords || ''),
      order: Number(source.order) || 0,
      payload: source.payload || null,
    };
  }

  function getCommandPaletteEntrySearchText(entry) {
    var item = entry || {};
    return compactText([
      item.title,
      item.description,
      item.meta,
      item.source,
      item.target,
      item.href,
      item.keywords,
      getCommandPaletteCategoryLabel(item.category),
    ].join(' ')).toLowerCase();
  }

  function getCommandPaletteConfigCategory(config) {
    var item = config || {};
    if (item.panelId) return 'center';
    if (item.key || item.kind === 'settings') return 'setting';
    if (item.group === '页面导航' || item.href) return 'navigate';
    return 'action';
  }

  function getCommandPaletteConfigAction(config) {
    var item = config || {};
    if (item.href) return 'open-url';
    if (item.panelId) return 'open-panel';
    if (item.kind === 'settings') return 'open-settings';
    if (item.key) return 'toggle-setting';
    if (typeof item.onClick === 'function') return 'run-action';
    return '';
  }

  function createCommandPaletteEntryFromToolboxConfig(config, index) {
    if (!config || config.show === false) return null;
    var category = getCommandPaletteConfigCategory(config);
    return createCommandPaletteEntry({
      id: 'toolbox|' + (config.key || config.panelId || config.kind || config.href || config.label || index),
      category: category,
      icon: config.text || config.label,
      title: config.label || config.title || config.text,
      description: config.description || config.title || '',
      source: config.group || getCommandPaletteCategoryLabel(category),
      target: config.href || config.panelId || config.key || '',
      risk: /清空|删除|购买/.test(config.label || config.title || '') ? '中' : '低',
      href: config.href,
      action: getCommandPaletteConfigAction(config),
      key: config.key,
      panelId: config.panelId,
      keywords: [config.group, config.title, config.description].join(' '),
      order: 100 + index,
      payload: config,
    });
  }

  function createCommandPaletteNavigationEntries(configs) {
    var seen = {};
    return (configs || []).map(function mapNavigationCommand(config, index) {
      if (!config || !config.href || !config.label) return null;
      var key = normalizeNavigationHref(config.href, typeof location !== 'undefined' ? location.href : 'https://south-plus.org/').replace(/#.*$/, '') + '|' + normalizeNavigationLabel(config.label);
      if (seen[key]) return null;
      seen[key] = true;
      return createCommandPaletteEntry({
        id: 'nav|' + key,
        category: 'navigate',
        icon: '导',
        title: config.label,
        description: [config.section || '导航中心', config.parentLabel, config.title && config.title !== config.label ? config.title : ''].filter(Boolean).join(' · '),
        source: config.section || '导航中心',
        target: config.href,
        href: config.href,
        action: 'open-url',
        keywords: [config.parentLabel, config.title].join(' '),
        order: 300 + index,
      });
    }).filter(Boolean);
  }

  function createCommandPaletteWatchEntries(watch, progress) {
    return getWatchCenterEntries(watch, progress).slice(0, 40).map(function mapWatchCommand(entry, index) {
      return createCommandPaletteEntry({
        id: 'watch|' + entry.id,
        category: 'center',
        icon: '存',
        title: entry.title,
        description: ['稍后看', entry.progressText, entry.nextFloorLabel, entry.tagText].filter(Boolean).join(' · '),
        source: '稍后看',
        target: entry.progressUrl || entry.url,
        href: entry.progressUrl || entry.url,
        action: 'open-watch-entry',
        recordId: entry.id,
        keywords: [entry.floorLabel, entry.nextFloorLabel, entry.tagText].join(' '),
        order: 500 + index,
      });
    });
  }

  function createCommandPaletteHistoryEntries(progress) {
    return getHistoryCenterEntries(progress).slice(0, 40).map(function mapHistoryCommand(entry, index) {
      return createCommandPaletteEntry({
        id: 'history|' + entry.id,
        category: 'center',
        icon: '历',
        title: entry.title,
        description: ['最近浏览', entry.progressText, entry.nextFloorLabel, entry.tagText].filter(Boolean).join(' · '),
        source: '最近浏览',
        target: entry.url,
        href: entry.url,
        action: 'open-history-entry',
        recordId: entry.id,
        keywords: [entry.floorLabel, entry.nextFloorLabel, entry.tagText].join(' '),
        order: 560 + index,
      });
    });
  }

  function createCommandPaletteResourceEntries(resources) {
    return getResourceCenterEntries(resources).slice(0, 60).map(function mapResourceCommand(entry, index) {
      return createCommandPaletteEntry({
        id: 'resource|' + entry.key,
        category: 'resource',
        icon: '源',
        title: entry.sourceTitle || entry.label || entry.url,
        description: [entry.label, entry.provider, entry.statusLabel, entry.accessCode ? ('提取码 ' + entry.accessCode) : '', entry.tagText].filter(Boolean).join(' · '),
        source: '资源工作台',
        target: entry.url,
        href: entry.url,
        action: 'open-resource-url',
        resourceKey: entry.key,
        keywords: [entry.url, entry.sourceUrl, entry.floorLabel, entry.author, entry.note, entry.text, entry.tagText].join(' '),
        order: 620 + index,
      });
    });
  }

  function createCommandPalettePageEntries(items) {
    var seen = {};
    return (items || []).map(function mapPageCommand(item, index) {
      if (!item || !item.title || !item.href) return null;
      var href = String(item.href || '');
      var key = href.replace(/#.*$/, '') + '|' + item.title;
      if (seen[key]) return null;
      seen[key] = true;
      return createCommandPaletteEntry({
        id: 'page|' + key,
        category: item.category || 'navigate',
        icon: item.icon || '页',
        title: item.title,
        description: item.description || '当前页面结果',
        source: item.source || '当前页面',
        target: href,
        href: href,
        action: 'open-url',
        keywords: item.keywords || '',
        order: 700 + index,
      });
    }).filter(Boolean);
  }

  function collectCommandPaletteEntries(data) {
    var source = data || {};
    var entries = [];
    (source.toolboxConfigs || []).forEach(function appendToolboxCommand(config, index) {
      var entry = createCommandPaletteEntryFromToolboxConfig(config, index);
      if (entry) entries.push(entry);
    });
    entries = entries
      .concat(createCommandPaletteNavigationEntries(source.navigationConfigs || []))
      .concat(createCommandPaletteWatchEntries(source.watch || {}, source.progress || {}))
      .concat(createCommandPaletteHistoryEntries(source.progress || {}))
      .concat(createCommandPaletteResourceEntries(source.resources || {}))
      .concat(createCommandPalettePageEntries(source.pageItems || []));
    return entries.filter(Boolean).sort(function sortCommandEntries(left, right) {
      return (left.order || 0) - (right.order || 0) || left.title.localeCompare(right.title, 'zh-Hans-CN');
    });
  }

  function getCommandPaletteQueryScore(entry, query) {
    var term = normalizeCenterSearchQuery(query);
    if (!term) return 0;
    var title = String(entry && entry.title || '').toLowerCase();
    if (title === term) return -30;
    if (title.indexOf(term) === 0) return -20;
    if (title.indexOf(term) >= 0) return -10;
    return 0;
  }

  function filterCommandPaletteEntries(entries, options) {
    var query = normalizeCenterSearchQuery(options && options.query);
    var filter = normalizeCommandPaletteFilter(options && options.filter);
    return (entries || []).filter(function matchCommandEntry(entry) {
      if (filter !== 'all' && entry.category !== filter) return false;
      return matchesCenterSearch(query, [getCommandPaletteEntrySearchText(entry)]);
    }).sort(function sortFilteredCommandEntries(left, right) {
      return getCommandPaletteQueryScore(left, query) - getCommandPaletteQueryScore(right, query) ||
        (left.order || 0) - (right.order || 0) ||
        left.title.localeCompare(right.title, 'zh-Hans-CN');
    });
  }

  function formatCommandPaletteResultSummary(visibleEntries, allEntries, options) {
    var visibleCount = (visibleEntries || []).length;
    var allCount = (allEntries || []).length;
    var filter = normalizeCommandPaletteFilter(options && options.filter);
    var prefix = filter === 'all' ? '全部命令' : getCommandPaletteCategoryLabel(filter);
    return prefix + ' · ' + visibleCount + ' / ' + allCount + ' 项';
  }

  function containsAny(value, needles) {
    var haystack = String(value || '').toLowerCase();
    return (needles || []).some(function hasNeedle(needle) {
      return haystack.indexOf(String(needle || '').toLowerCase()) !== -1;
    });
  }

  function matchesBlockRules(item, rules) {
    var data = item || {};
    var config = rules || {};
    return (
      containsAny(data.title, config.titleKeywords) ||
      containsAny(data.author, config.authorKeywords)
    );
  }

  function parseForumFilterQuery(query) {
    var result = {
      includes: [],
      excludes: [],
      author: '',
    };
    String(query || '')
      .split(/\s+/)
      .map(function trimToken(token) {
        return token.trim();
      })
      .filter(Boolean)
      .forEach(function parseToken(token) {
        if (/^(?:作者|author):/i.test(token)) {
          result.author = token.replace(/^(?:作者|author):/i, '').toLowerCase();
          return;
        }
        if (token.charAt(0) === '!') {
          var excluded = token.slice(1);
          if (excluded) result.excludes.push(excluded.toLowerCase());
          return;
        }
        result.includes.push(token.toLowerCase());
      });
    return result;
  }

  function matchesForumFilter(item, query) {
    var parsed = typeof query === 'string' ? parseForumFilterQuery(query) : (query || {});
    var data = item || {};
    var title = String(data.title || '').toLowerCase();
    var author = String(data.author || '').toLowerCase();
    var resourceText = (data.resourceBadges || []).map(function mapFilterResourceBadge(badge) {
      return [badge.type, badge.label].filter(Boolean).join(' ');
    }).join(' ').toLowerCase();
    var combined = title + ' ' + author + ' ' + resourceText;

    if (parsed.author && author.indexOf(parsed.author) === -1) return false;
    if ((parsed.excludes || []).some(function hasExcluded(token) {
      return token && combined.indexOf(token) !== -1;
    })) {
      return false;
    }
    return (parsed.includes || []).every(function hasIncluded(token) {
      return !token || combined.indexOf(token) !== -1;
    });
  }

  function extractPreviewImageUrls(images, pageUrl) {
    var seen = {};
    return (images || [])
      .filter(isPreviewImageCandidate)
      .map(function toAbsoluteUrl(image) {
        try {
          return new URL(String(image.src || ''), pageUrl || location.href).href;
        } catch (error) {
          return '';
        }
      })
      .filter(function keepUnique(url) {
        if (!url || seen[url]) return false;
        seen[url] = true;
        return true;
      })
      .slice(0, 6);
  }

  function getPreviewImageSource(image) {
    var data = image || {};
    var node = data.node || {};
    return String(data.src || data.currentSrc || node.currentSrc || node.src || '').trim();
  }

  function getPreviewImageSize(image) {
    var data = image || {};
    var node = data.node || {};
    return {
      width: Number(data.naturalWidth || node.naturalWidth || data.width || node.width || 0),
      height: Number(data.naturalHeight || node.naturalHeight || data.height || node.height || 0),
    };
  }

  function markPreviewImageLoaded(image) {
    if (!image || !image.dataset) return;
    image.dataset.spxPreviewReady = '1';
    image.dataset.spxPreviewLoaded = '1';
  }

  function preparePreviewImageReveal(image) {
    if (!image || !image.dataset) return image;
    image.dataset.spxPreviewReady = '0';

    var markLoaded = function markLoadedPreviewImage() {
      markPreviewImageLoaded(image);
      image.removeEventListener('load', markLoaded);
      image.removeEventListener('error', markLoaded);
    };

    image.addEventListener('load', markLoaded);
    image.addEventListener('error', markLoaded);
    if (image.src && image.complete && image.naturalWidth) markLoaded();
    return image;
  }

  function isLargePreviewImage(image) {
    var size = getPreviewImageSize(image);
    if (!size.width && !size.height) return true;
    return Math.max(size.width, size.height) >= 600 || (size.width >= 480 && size.height >= 360);
  }

  function formatPreviewImageLinks(images) {
    var seen = {};
    return (images || [])
      .map(getPreviewImageSource)
      .filter(function keepUniquePreviewLink(url) {
        if (!url || seen[url]) return false;
        seen[url] = true;
        return true;
      })
      .join('\n');
  }

  function getPreviewImageMetaText(image, index) {
    var item = image || {};
    return ['图 ' + ((Number(index) || 0) + 1), item.floorLabel, item.author].filter(Boolean).join(' · ');
  }

  function formatPreviewImageMarkdownLinks(images) {
    var seen = {};
    return (images || [])
      .map(function formatPreviewMarkdownItem(item, index) {
        var url = getPreviewImageSource(item);
        if (!url || seen[url]) return '';
        seen[url] = true;
        return '- ![' + getPreviewImageMetaText(item, index) + '](' + url + ')';
      })
      .filter(Boolean)
      .join('\n');
  }

  function formatPreviewImageLinksByFloor(images) {
    var groups = {};
    var order = [];
    var seen = {};
    (images || []).forEach(function collectPreviewFloor(item, index) {
      var url = getPreviewImageSource(item);
      if (!url || seen[url]) return;
      seen[url] = true;
      var label = item && item.floorLabel ? item.floorLabel : '未知楼层';
      var author = item && item.author ? item.author : '';
      var key = label + '\n' + author;
      if (!groups[key]) {
        groups[key] = {
          label: label,
          author: author,
          urls: [],
        };
        order.push(key);
      }
      groups[key].urls.push('#' + (index + 1) + ' ' + url);
    });
    return order
      .map(function formatPreviewFloorGroup(key) {
        var group = groups[key];
        return '[' + [group.label, group.author].filter(Boolean).join(' · ') + ']\n' + group.urls.join('\n');
      })
      .join('\n\n');
  }

  function getPreviewGalleryRenderState(total, limit, batchSize) {
    var count = Math.max(0, Number(total) || 0);
    var step = Math.max(1, Number(batchSize) || PREVIEW_GALLERY_BATCH_SIZE);
    var nextLimit = Math.max(0, Number(limit) || step);
    var rendered = Math.min(count, nextLimit);
    return {
      total: count,
      rendered: rendered,
      hasMore: rendered < count,
      nextLimit: Math.min(count, rendered + step),
    };
  }

  function formatPreviewGallerySummary(total, visibleCount, renderedCount, largeOnly) {
    var pageTotal = Math.max(0, Number(total) || 0);
    var visible = Math.max(0, Number(visibleCount) || 0);
    var renderedValue = renderedCount == null ? visible : Number(renderedCount);
    var rendered = Math.min(visible, Math.max(0, renderedValue || 0));
    if (largeOnly) {
      return rendered < visible
        ? '大图已显示 ' + rendered + ' / ' + visible + '(当前页 ' + pageTotal + ' 张),点击进入灯箱'
        : '大图 ' + visible + ' / 当前页 ' + pageTotal + ' 张,点击进入灯箱';
    }
    return rendered < visible
      ? '已显示 ' + rendered + ' / 当前页 ' + pageTotal + ' 张,点击进入灯箱'
      : '当前页 ' + pageTotal + ' 张,点击进入灯箱';
  }

  function padPreviewDownloadPart(value, size) {
    return String(Math.max(0, Number(value) || 0)).padStart(size || 2, '0');
  }

  function formatPreviewImageArchiveFileName(timestamp) {
    var rawTime = Number(timestamp);
    var date = new Date(isFinite(rawTime) ? rawTime : Date.now());
    return [
      'southplus-images-',
      date.getFullYear(),
      padPreviewDownloadPart(date.getMonth() + 1),
      padPreviewDownloadPart(date.getDate()),
      '-',
      padPreviewDownloadPart(date.getHours()),
      padPreviewDownloadPart(date.getMinutes()),
      '.zip',
    ].join('');
  }

  function sanitizePreviewDownloadName(value) {
    return String(value || '')
      .replace(/[\\/:*?"<>|]+/g, '-')
      .replace(/\s+/g, '-')
      .replace(/-+/g, '-')
      .replace(/^-|-$/g, '')
      .slice(0, 72) || 'image';
  }

  function getPreviewImageDownloadExtension(url, contentType) {
    var type = String(contentType || '').toLowerCase();
    if (/image\/png/.test(type)) return '.png';
    if (/image\/webp/.test(type)) return '.webp';
    if (/image\/gif/.test(type)) return '.gif';
    if (/image\/(?:jpeg|jpg|pjpeg)/.test(type)) return '.jpg';
    if (/image\/bmp/.test(type)) return '.bmp';
    try {
      var pathname = new URL(String(url || ''), 'https://south-plus.org/').pathname;
      var match = pathname.match(/\.([a-z0-9]{2,5})$/i);
      if (match && /^(?:jpg|jpeg|png|webp|gif|bmp|avif)$/.test(match[1].toLowerCase())) {
        return match[1].toLowerCase() === 'jpeg' ? '.jpg' : '.' + match[1].toLowerCase();
      }
    } catch (error) {}
    return '.jpg';
  }

  function formatPreviewImageDownloadFileName(item, index, contentType) {
    var image = item || {};
    var prefix = padPreviewDownloadPart((Number(index) || 0) + 1, 3);
    var meta = sanitizePreviewDownloadName([image.floorLabel, image.author].filter(Boolean).join('-'));
    return prefix + '-' + meta + getPreviewImageDownloadExtension(getPreviewImageSource(image), contentType);
  }

  function getPreviewDownloadStatusSummary(counts) {
    var data = counts || {};
    var total = Math.max(0, Number(data.total) || 0);
    var done = Math.max(0, Number(data.done) || 0);
    var failed = Math.max(0, Number(data.failed) || 0);
    var active = Math.max(0, Number(data.active) || 0);
    var queued = Math.max(0, Number(data.queued) || 0);
    if (!total) return '没有可下载图片';
    if (data.packing) return '正在打包 ' + done + ' 张图片';
    if (active || queued) return '下载中 ' + done + ' / ' + total + (failed ? ' · ' + failed + ' 张失败' : '');
    if (failed) return '已完成 ' + done + ' / ' + total + ' · ' + failed + ' 张失败可跳过';
    return '已完成 ' + done + ' / ' + total + ' · 准备 ZIP';
  }

  function formatPreviewDownloadReport(entries) {
    var rows = ['SouthPlus Lite 预览图下载报告', '生成时间:' + new Date().toLocaleString(), ''];
    var failed = (entries || []).filter(function keepFailedPreviewDownload(entry) {
      return entry && entry.status === 'failed';
    });
    rows.push('失败图片:' + failed.length + ' 张');
    failed.forEach(function appendFailedPreviewDownload(entry) {
      rows.push('');
      rows.push('#' + (Number(entry.index) + 1 || 1));
      rows.push('地址:' + (entry.src || getPreviewImageSource(entry.item)));
      rows.push('楼层:' + ((entry.item && entry.item.floorLabel) || '未知楼层'));
      rows.push('作者:' + ((entry.item && entry.item.author) || '未知作者'));
      rows.push('重试:' + (entry.attempts || 0) + ' / ' + PREVIEW_DOWNLOAD_MAX_RETRIES);
      rows.push('原因:' + (entry.error || '下载失败'));
    });
    return rows.join('\n');
  }

  var zipCrcTable = null;

  function getZipCrcTable() {
    if (zipCrcTable) return zipCrcTable;
    zipCrcTable = [];
    for (var i = 0; i < 256; i += 1) {
      var crc = i;
      for (var bit = 0; bit < 8; bit += 1) {
        crc = (crc & 1) ? (0xedb88320 ^ (crc >>> 1)) : (crc >>> 1);
      }
      zipCrcTable[i] = crc >>> 0;
    }
    return zipCrcTable;
  }

  function getZipCrc32(bytes) {
    var table = getZipCrcTable();
    var crc = 0xffffffff;
    var data = bytes || [];
    for (var i = 0; i < data.length; i += 1) {
      crc = table[(crc ^ data[i]) & 0xff] ^ (crc >>> 8);
    }
    return (crc ^ 0xffffffff) >>> 0;
  }

  function encodeZipText(text) {
    if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(String(text || ''));
    var encoded = unescape(encodeURIComponent(String(text || '')));
    var bytes = new Uint8Array(encoded.length);
    for (var i = 0; i < encoded.length; i += 1) bytes[i] = encoded.charCodeAt(i);
    return bytes;
  }

  function getZipDosDateTime(timestamp) {
    var date = new Date(timestamp || Date.now());
    var year = Math.max(1980, date.getFullYear());
    return {
      time: (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2),
      date: ((year - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate(),
    };
  }

  function setZipUint16(view, offset, value) {
    view.setUint16(offset, value, true);
  }

  function setZipUint32(view, offset, value) {
    view.setUint32(offset, value >>> 0, true);
  }

  function getBlobBytes(blob) {
    if (blob instanceof Uint8Array) return Promise.resolve(blob);
    if (blob && typeof blob.arrayBuffer === 'function') {
      return blob.arrayBuffer().then(function convertBlobArrayBuffer(buffer) {
        return new Uint8Array(buffer);
      });
    }
    return new Promise(function readBlobWithFileReader(resolve, reject) {
      if (typeof FileReader === 'undefined' || !blob) {
        resolve(new Uint8Array(0));
        return;
      }
      var reader = new FileReader();
      reader.onload = function handleBlobRead() { resolve(new Uint8Array(reader.result || [])); };
      reader.onerror = function handleBlobReadError() { reject(reader.error || new Error('读取文件失败')); };
      reader.readAsArrayBuffer(blob);
    });
  }

  function createPreviewZipBlob(files) {
    if (typeof Blob === 'undefined' || typeof Uint8Array === 'undefined' || typeof DataView === 'undefined') {
      return Promise.reject(new Error('当前浏览器不支持 ZIP 打包'));
    }
    var sourceFiles = (files || []).filter(function keepZipFile(file) {
      return file && file.name && file.blob;
    });
    return Promise.all(sourceFiles.map(function normalizeZipFile(file) {
      return getBlobBytes(file.blob).then(function attachZipBytes(bytes) {
        return {
          name: String(file.name || 'image'),
          bytes: bytes,
          timestamp: file.timestamp || Date.now(),
        };
      });
    })).then(function buildZipFromBytes(normalizedFiles) {
      var chunks = [];
      var central = [];
      var offset = 0;
      normalizedFiles.forEach(function appendZipFile(file) {
        var nameBytes = encodeZipText(file.name);
        var data = file.bytes || new Uint8Array(0);
        var crc = getZipCrc32(data);
        var timeParts = getZipDosDateTime(file.timestamp);
        var header = new Uint8Array(30 + nameBytes.length);
        var view = new DataView(header.buffer);
        setZipUint32(view, 0, 0x04034b50);
        setZipUint16(view, 4, 20);
        setZipUint16(view, 6, 0x0800);
        setZipUint16(view, 8, 0);
        setZipUint16(view, 10, timeParts.time);
        setZipUint16(view, 12, timeParts.date);
        setZipUint32(view, 14, crc);
        setZipUint32(view, 18, data.length);
        setZipUint32(view, 22, data.length);
        setZipUint16(view, 26, nameBytes.length);
        setZipUint16(view, 28, 0);
        header.set(nameBytes, 30);
        chunks.push(header, data);
        central.push({ nameBytes: nameBytes, crc: crc, size: data.length, offset: offset, time: timeParts.time, date: timeParts.date });
        offset += header.length + data.length;
      });

      var centralOffset = offset;
      central.forEach(function appendCentralZipRecord(record) {
        var header = new Uint8Array(46 + record.nameBytes.length);
        var view = new DataView(header.buffer);
        setZipUint32(view, 0, 0x02014b50);
        setZipUint16(view, 4, 20);
        setZipUint16(view, 6, 20);
        setZipUint16(view, 8, 0x0800);
        setZipUint16(view, 10, 0);
        setZipUint16(view, 12, record.time);
        setZipUint16(view, 14, record.date);
        setZipUint32(view, 16, record.crc);
        setZipUint32(view, 20, record.size);
        setZipUint32(view, 24, record.size);
        setZipUint16(view, 28, record.nameBytes.length);
        setZipUint16(view, 30, 0);
        setZipUint16(view, 32, 0);
        setZipUint16(view, 34, 0);
        setZipUint16(view, 36, 0);
        setZipUint32(view, 38, 0);
        setZipUint32(view, 42, record.offset);
        header.set(record.nameBytes, 46);
        chunks.push(header);
        offset += header.length;
      });
      var centralSize = offset - centralOffset;
      var footer = new Uint8Array(22);
      var footerView = new DataView(footer.buffer);
      setZipUint32(footerView, 0, 0x06054b50);
      setZipUint16(footerView, 4, 0);
      setZipUint16(footerView, 6, 0);
      setZipUint16(footerView, 8, central.length);
      setZipUint16(footerView, 10, central.length);
      setZipUint32(footerView, 12, centralSize);
      setZipUint32(footerView, 16, centralOffset);
      setZipUint16(footerView, 20, 0);
      chunks.push(footer);
      return new Blob(chunks, { type: 'application/zip' });
    });
  }

  function hasThreadRowHiddenClass(row) {
    if (!row || !row.classList) return false;
    return THREAD_ROW_HIDDEN_CLASSES.some(function hasHiddenClass(className) {
      return row.classList.contains(className);
    });
  }

  function syncThreadRowDisplay(row) {
    if (!row || !row.style) return;
    if (hasThreadRowHiddenClass(row)) {
      setImportantStyle(row, 'display', 'none');
      return;
    }
    if (row.dataset && row.dataset.spxThreadListRow === '1') {
      setImportantStyle(row, 'display', 'grid');
      return;
    }
    if (typeof row.style.removeProperty === 'function') {
      row.style.removeProperty('display');
    } else {
      row.style.display = '';
    }
  }

  function setThreadRowHiddenClass(row, className, hidden) {
    if (!row || !row.classList || !className) return;
    row.classList.toggle(className, !!hidden);
    syncThreadRowDisplay(row);
  }

  function isVisibleThreadRow(row) {
    if (!row) return false;
    if (hasThreadRowHiddenClass(row)) return false;
    if (row.offsetParent === null) return false;
    return true;
  }

  function markThreadsRead(items, state, timestamp) {
    var target = state || {};
    target.read = target.read || {};
    (items || []).forEach(function markItemRead(item) {
      if (!item || !item.id || !isVisibleThreadRow(item.row)) return;
      target.read[item.id] = timestamp || Date.now();
    });
    return target.read;
  }

  function findThreadIdsByAuthor(items, author) {
    var needle = String(author || '').toLowerCase();
    if (!needle) return [];
    return (items || [])
      .filter(function sameAuthor(item) {
        return String((item && item.author) || '').toLowerCase() === needle;
      })
      .map(function getThreadId(item) {
        return item.id;
      })
      .filter(Boolean);
  }

  function isPreviewImageCandidate(image) {
    var data = image || {};
    var src = String(data.src || '');
    var width = Number(data.naturalWidth || data.width || 0);
    var height = Number(data.naturalHeight || data.height || 0);
    var className = String(data.className || '');
    var alt = String(data.alt || '');

    if (!src) return false;
    if (/\/(?:face|avatar|avatars|uc_server)\/|\/images\/face\//i.test(src)) return false;
    if (/\/images\/post\/smile\//i.test(src)) return false;
    if (/\/images\/.*(?:face|smile|emotion|icon|common)/i.test(src)) return false;
    if (/(?:smile|emotion|face|avatar|head|icon)/i.test(className + ' ' + alt)) return false;
    if (width === 0 && height === 0) return true;
    if (width <= 80 && height <= 80) return false;
    return width >= 120 || height >= 120;
  }

  function trimResourceUrlToken(value) {
    return String(value || '')
      .replace(/&amp;/g, '&')
      .trim()
      .replace(/^[<"'“”‘’]+|[<>"'“”‘’]+$/g, '')
      .replace(/[,。;、\s]+$/g, '')
      .replace(/[)\])】]+$/g, '');
  }

  function decodeResourceUrlToken(value) {
    var text = trimResourceUrlToken(value);
    for (var index = 0; index < 2; index += 1) {
      try {
        var decoded = decodeURIComponent(text);
        if (decoded === text) break;
        text = decoded;
      } catch (error) {
        break;
      }
    }
    return text;
  }

  function isResourceLikeToken(value) {
    var text = trimResourceUrlToken(value);
    return /^magnet:\?/i.test(text) ||
      /^ed2k:\/\//i.test(text) ||
      /^https?:\/\//i.test(text) ||
      /^\/\//.test(text) ||
      CLOUD_RESOURCE_PATH_RE.test(text);
  }

  function unwrapResourceRedirectUrl(value, pageUrl) {
    var text = trimResourceUrlToken(value);
    if (!text) return '';

    var decoded = decodeResourceUrlToken(text);
    var parsed;
    try {
      parsed = new URL(decoded, pageUrl || 'https://south-plus.org/');
    } catch (error) {
      return decoded;
    }

    var paramNames = [
      'url',
      'u',
      'target',
      'to',
      'link',
      'href',
      'go',
      'jump',
      'redirect',
      'redirect_url',
      'redirect_uri',
      'r',
      'site',
      'src',
      'source',
      'file',
      'down',
      'download',
    ];
    for (var index = 0; index < paramNames.length; index += 1) {
      var raw = parsed.searchParams.get(paramNames[index]);
      var candidate = decodeResourceUrlToken(raw);
      if (candidate && isResourceLikeToken(candidate)) return candidate;
    }

    var queryText = [
      decodeResourceUrlToken(parsed.search.slice(1)),
      decodeResourceUrlToken(parsed.hash.replace(/^#/, '')),
      decoded,
    ].join(' ');
    var resourceMatch = queryText.match(new RegExp(
      "(magnet:\\?xt=urn:[^\\s<>\"',。;、))\\]]+|ed2k:\\/\\/\\|file\\|[^\\s<>\"',。;、))\\]]+|https?:\\/\\/[^\\s<>\"',。;、))\\]]+|(?:(?:www|share|pan)\\.)?(?:" +
        CLOUD_RESOURCE_HOST_PATTERN +
        ")\\/[^\\s<>\"',。;、))\\]]+)",
      'i'
    ));
    return resourceMatch ? resourceMatch[1] : decoded;
  }

  function normalizeResourceUrl(value, pageUrl) {
    var text = unwrapResourceRedirectUrl(value, pageUrl);
    if (!text) return '';
    if (/^(?:javascript|mailto|tel|data):/i.test(text) || text.charAt(0) === '#') return '';
    if (/^magnet:\?/i.test(text)) return text;
    if (/^ed2k:\/\//i.test(text)) return text;
    if (CLOUD_RESOURCE_PATH_RE.test(text)) {
      text = 'https://' + text;
    }
    if (/^\/\//.test(text)) text = 'https:' + text;
    try {
      var fallbackBase = 'https://south-plus.org/';
      var base = pageUrl || (typeof location !== 'undefined' ? location.href : fallbackBase);
      if (base && !/^[a-z][a-z0-9+.-]*:/i.test(String(base))) {
        base = new URL(String(base), fallbackBase).href;
      }
      return new URL(text, base || fallbackBase).href;
    } catch (error) {
      return '';
    }
  }

  function classifyResourceLink(url) {
    var text = String(url || '');
    var lower = text.toLowerCase();
    if (/^magnet:\?/i.test(text)) return 'magnet';
    if (/^ed2k:\/\//i.test(text)) return 'ed2k';
    if (TORRENT_RESOURCE_EXT_RE.test(lower)) return 'torrent';
    if (ARCHIVE_RESOURCE_EXT_RE.test(lower)) return 'archive';
    try {
      var parsedUrl = new URL(text);
      var host = parsedUrl.hostname.toLowerCase();
      var path = decodeURIComponent(parsedUrl.pathname || '').toLowerCase();
      if (TORRENT_RESOURCE_EXT_RE.test(path)) return 'torrent';
      if (ARCHIVE_RESOURCE_EXT_RE.test(path)) return 'archive';
      if (CLOUD_RESOURCE_HOST_RE.test(host)) return 'cloud';
    } catch (error) {
      if (CLOUD_RESOURCE_PATH_RE.test(lower)) return 'cloud';
    }
    if (/\.(?:jpe?g|png|gif|webp|bmp|avif)(?:[?#]|$)/i.test(lower)) return 'image';
    if (/^https?:\/\//i.test(text)) return 'external';
    return '';
  }

  function getCloudProviderLabel(url) {
    var host = '';
    try {
      host = new URL(normalizeResourceUrl(url)).hostname.toLowerCase();
    } catch (error) {
      host = String(url || '').toLowerCase();
    }

    if (/baidu\.com$/.test(host)) return '百度网盘';
    if (/(?:aliyundrive|alipan)\.com$/.test(host)) return '阿里云盘';
    if (/quark\.cn$/.test(host)) return '夸克网盘';
    if (/drive\.uc\.cn$/.test(host)) return 'UC网盘';
    if (/115\.com$/.test(host)) return '115网盘';
    if (/mega\.nz$/.test(host)) return 'MEGA';
    if (/terabox\.com$/.test(host)) return 'Terabox';
    if (/lanzou[a-z]?\.com$|ilanzou\.com$/.test(host)) return '蓝奏云';
    if (/weiyun\.com$/.test(host)) return '腾讯微云';
    if (/cloud\.189\.cn$/.test(host)) return '天翼云盘';
    if (/123pan\.com$/.test(host)) return '123云盘';
    if (/(?:mypikpak|pikpak)\.com$/.test(host)) return 'PikPak';
    if (/mediafire\.com$/.test(host)) return 'MediaFire';
    if (/4shared\.com$/.test(host)) return '4shared';
    if (/onedrive\.live\.com$/.test(host)) return 'OneDrive';
    if (/pan\.xunlei\.com$/.test(host)) return '迅雷云盘';
    if (/cowtransfer\.com$/.test(host)) return '奶牛快传';
    if (/feijipan\.com$/.test(host)) return '小飞机网盘';
    return '其他网盘';
  }

  function getResourceDisplayLabel(item) {
    var data = typeof item === 'string' ? { url: item } : (item || {});
    var type = data.type || classifyResourceLink(data.url);
    if (type === 'cloud') return getCloudProviderLabel(data.url);
    return RESOURCE_CATEGORIES[type] || data.label || '链接';
  }

  function extractResourceAccessCode(value) {
    var text = String(value || '').replace(/\s+/g, ' ');
    var compactText = text.replace(/\s+/g, '');
    var match = text.match(/(?:提取码|提取碼|提取密码|访问码|访问密码|分享码|分享密码|取件码|取件密码|解压码|解压密码|密码|pass(?:word)?|pwd|code|access\s*code)\s*(?:是|为)?\s*[::=]?\s*([A-Za-z0-9]{3,12})/i) ||
      compactText.match(/(?:提取码|提取碼|提取密码|访问码|访问密码|分享码|分享密码|取件码|取件密码|解压码|解压密码|密码)(?:是|为)?[::=]?([A-Za-z0-9]{3,12})/i);
    return match ? match[1] : '';
  }

  function createResourceLink(url, meta) {
    var normalized = normalizeResourceUrl(url, meta && meta.pageUrl);
    var type = (meta && meta.type) || classifyResourceLink(normalized);
    if (!normalized || !type) return null;
    var accessCode = type === 'cloud'
      ? extractResourceAccessCode(normalized + ' ' + ((meta && meta.text) || '') + ' ' + ((meta && meta.context) || ''))
      : '';
    return {
      url: normalized,
      type: type,
      label: getResourceDisplayLabel({ url: normalized, type: type }),
      accessCode: accessCode,
      text: String((meta && meta.text) || '').replace(/\s+/g, ' ').trim().slice(0, 80),
      floorLabel: String((meta && meta.floorLabel) || ''),
      author: String((meta && meta.author) || ''),
      postIndex: Number(meta && meta.postIndex) || 0,
      sourceIndex: Number(meta && meta.sourceIndex) || 0,
    };
  }

  function dedupeResourceLinks(links) {
    var seen = {};
    var result = [];
    (links || []).forEach(function keepUniqueResourceLink(item) {
      if (!item || !item.url) return;
      var type = item.type || classifyResourceLink(item.url);
      if (!type) return;
      var key = type + '|' + item.url.toLowerCase();
      if (seen[key]) return;
      seen[key] = true;
      result.push(Object.assign({}, item, {
        type: type,
        label: getResourceDisplayLabel(Object.assign({}, item, { type: type })),
      }));
    });
    return result;
  }

  function extractResourceLinksFromText(text, pageUrl, meta) {
    var source = String(text || '');
    var links = [];
    var patterns = [
      /magnet:\?xt=urn:[^\s<>"',。;、))\]]+/ig,
      /ed2k:\/\/\|file\|[^\s<>"',。;、))\]]+/ig,
      /https?:\/\/[^\s<>"',。;、))\]]+/ig,
      new RegExp(
        "(?:(?:www|share|pan)\\.)?(?:" + CLOUD_RESOURCE_HOST_PATTERN + ")\\/[^\\s<>\"',。;、))\\]]+",
        'ig'
      ),
    ];
    patterns.forEach(function scanPattern(pattern) {
      var match;
      while ((match = pattern.exec(source))) {
        var item = createResourceLink(match[0], Object.assign({}, meta || {}, {
          pageUrl: pageUrl,
          sourceIndex: match.index,
          context: source.slice(match.index, match.index + match[0].length + 96),
        }));
        if (item) links.push(item);
      }
    });
    return dedupeResourceLinks(links.sort(function sortBySourceIndex(left, right) {
      return (Number(left.sourceIndex) || 0) - (Number(right.sourceIndex) || 0);
    }));
  }

  function extractResourceLinksFromNode(node, pageUrl, meta) {
    if (!node) return [];
    var links = [];
    qsa('a[href],area[href]', node).forEach(function collectAnchor(link) {
      var item = createResourceLink(link.getAttribute('href') || link.href, Object.assign({}, meta || {}, {
        pageUrl: pageUrl,
        text: link.textContent,
      }));
      if (item) links.push(item);
    });
    qsa('img[src],source[src]', node).forEach(function collectMedia(media) {
      var mediaMeta = Object.assign({}, meta || {}, {
        pageUrl: pageUrl,
        type: 'image',
        text: media.getAttribute('alt') || media.getAttribute('title') || '',
      });
      if (media.tagName === 'IMG' && !isPreviewImageCandidate({
        src: media.currentSrc || media.src,
        naturalWidth: media.naturalWidth,
        naturalHeight: media.naturalHeight,
        width: media.width,
        height: media.height,
        className: media.className,
        alt: media.alt,
      })) return;
      var item = createResourceLink(media.getAttribute('src') || media.currentSrc || media.src, mediaMeta);
      if (item) links.push(item);
    });
    return dedupeResourceLinks(links.concat(extractResourceLinksFromText(node.textContent, pageUrl, meta)));
  }

  function extractReadPageResourceLinks(posts, pageUrl) {
    var list = posts || [];
    var links = [];
    list.forEach(function collectPostResources(post, index) {
      var content = qs('.tpc_content', post) || post;
      var meta = {
        pageUrl: pageUrl,
        floorLabel: getPostFloorLabel(index),
        author: getPostAuthor(post),
        postIndex: index,
      };
      links = links.concat(extractResourceLinksFromNode(content, pageUrl, meta));
    });
    return dedupeResourceLinks(links);
  }

  function filterResourceLinks(links, options) {
    var config = options || {};
    var scope = config.scope || 'all';
    var category = config.category || 'all';
    return (links || []).filter(function keepResourceLink(item) {
      if (!item || !item.url) return false;
      if (category !== 'all' && item.type !== category) return false;
      if (scope === 'floor') return Number(item.postIndex) === Number(config.postIndex);
      if (scope === 'author') return !!config.author && item.author === config.author;
      return true;
    });
  }

  function formatResourceLinks(links) {
    return dedupeResourceLinks(links)
      .map(function formatResourceLine(item) {
        var parts = ['[' + (item.label || getResourceDisplayLabel(item)) + ']'];
        if (item.floorLabel) parts.push(item.floorLabel);
        if (item.author) parts.push(item.author);
        parts.push(item.url);
        if (item.accessCode) parts.push('提取码 ' + item.accessCode);
        return parts.join(' ');
      })
      .join('\n');
  }

  function normalizeResourceTags(value) {
    return normalizeListValue(value, parseTagList);
  }

  function formatResourceTags(tags) {
    return formatTags(normalizeResourceTags(tags));
  }

  function getJumpResourceLinks(links) {
    return dedupeResourceLinks(links).filter(function keepJumpResource(item) {
      return item && /^(?:magnet|ed2k|torrent|archive|cloud|external)$/.test(item.type || '');
    });
  }

  function getResourceDownloadQueueEntries(entries) {
    return getJumpResourceLinks(entries).filter(function keepTodoResource(item) {
      return normalizeResourceStatus(item.status) === 'todo';
    });
  }

  function formatResourceDownloadList(entries) {
    return getJumpResourceLinks(entries)
      .map(function formatResourceDownloadItem(item, index) {
        var lines = [
          '#' + (index + 1) + ' [' + (item.label || getResourceDisplayLabel(item)) + '] ' + item.url,
        ];
        var source = [item.sourceTitle, item.floorLabel, item.author].filter(Boolean).join(' · ');
        if (item.accessCode) lines.push('提取码:' + item.accessCode);
        if (source) lines.push('来源:' + source);
        if (item.sourceUrl) lines.push('来源链接:' + item.sourceUrl);
        if (item.note) lines.push('备注:' + item.note);
        if (formatResourceTags(item.tags)) lines.push('标签:' + formatResourceTags(item.tags));
        if (item.status) lines.push('状态:' + getResourceStatusLabel(item.status));
        return lines.join('\n');
      })
      .join('\n\n');
  }

  function formatResourceMarkdownList(entries) {
    return getJumpResourceLinks(entries)
      .map(function formatResourceMarkdownItem(item, index) {
        var lines = [
          (index + 1) + '. **' + (item.label || getResourceDisplayLabel(item)) + '**:' + item.url,
        ];
        var source = [item.sourceTitle, item.floorLabel, item.author].filter(Boolean).join(' · ');
        if (item.accessCode) lines.push('   - 提取码:' + item.accessCode);
        if (source) lines.push('   - 来源:' + source);
        if (item.sourceUrl) lines.push('   - 来源链接:' + item.sourceUrl);
        if (item.note) lines.push('   - 备注:' + item.note);
        if (formatResourceTags(item.tags)) lines.push('   - 标签:' + formatResourceTags(item.tags));
        if (item.status) lines.push('   - 状态:' + getResourceStatusLabel(item.status));
        return lines.join('\n');
      })
      .join('\n\n');
  }

  function formatResourceJumpSummary(links) {
    var counts = {};
    var order = [];
    getJumpResourceLinks(links).forEach(function countResourceLabel(item) {
      var label = item.label || getResourceDisplayLabel(item);
      if (!counts[label]) {
        counts[label] = 0;
        order.push(label);
      }
      counts[label] += 1;
    });
    return order.map(function formatSummary(label) {
      return label + ' ' + counts[label];
    }).join(' / ');
  }

  function normalizeResourceStatus(status) {
    var value = String(status || 'saved');
    return RESOURCE_STATUSES[value] ? value : 'saved';
  }

  function getResourceStatusLabel(status) {
    return RESOURCE_STATUSES[normalizeResourceStatus(status)] || RESOURCE_STATUSES.saved;
  }

  function getResourceLibraryKey(item) {
    var data = item || {};
    var url = normalizeResourceUrl(data.url, data.pageUrl || data.sourceUrl);
    var type = data.type || classifyResourceLink(url);
    if (!url || !type) return '';
    return type + '|' + url.toLowerCase();
  }

  function normalizeResourceRecord(record, key) {
    var source = record || {};
    var url = normalizeResourceUrl(source.url, source.pageUrl || source.sourceUrl);
    var type = source.type || classifyResourceLink(url);
    if (!url || !type) return null;
    var label = source.label || getResourceDisplayLabel({ url: url, type: type });
    var provider = type === 'cloud' ? getCloudProviderLabel(url) : label;
    var savedAt = Number(source.savedAt || source.updatedAt || Date.now()) || Date.now();
    var updatedAt = Number(source.updatedAt || savedAt) || savedAt;
    var canonicalKey = getResourceLibraryKey({ url: url, type: type });
    return {
      key: canonicalKey || key || '',
      url: url,
      type: type,
      label: label,
      provider: provider,
      accessCode: String(source.accessCode || ''),
      sourceTitle: String(source.sourceTitle || source.title || ''),
      sourceUrl: String(source.sourceUrl || source.pageUrl || ''),
      floorLabel: String(source.floorLabel || ''),
      author: String(source.author || ''),
      text: String(source.text || '').replace(/\s+/g, ' ').trim().slice(0, 120),
      note: String(source.note || source.memo || '').replace(/\s+/g, ' ').trim().slice(0, 160),
      tags: normalizeResourceTags(source.tags),
      status: normalizeResourceStatus(source.status),
      savedAt: savedAt,
      updatedAt: updatedAt,
    };
  }

  function createResourceRecord(item, meta, timestamp) {
    var source = item || {};
    var context = meta || {};
    var now = Number(timestamp) || Date.now();
    return normalizeResourceRecord({
      url: source.url,
      type: source.type,
      label: source.label,
      accessCode: source.accessCode,
      sourceTitle: context.sourceTitle || source.sourceTitle,
      sourceUrl: context.sourceUrl || source.sourceUrl || source.pageUrl,
      floorLabel: source.floorLabel || context.floorLabel,
      author: source.author || context.author,
      text: source.text,
      note: source.note || context.note,
      tags: mergeTagLists(source.tags, context.tags),
      status: source.status || context.status || 'saved',
      savedAt: now,
      updatedAt: now,
    });
  }

  function mergeResourceRecord(previous, next) {
    if (!next) return previous || null;
    if (!previous) return next;
    return Object.assign({}, previous, next, {
      status: previous.status || next.status,
      savedAt: Number(previous.savedAt || next.savedAt) || next.savedAt,
      accessCode: next.accessCode || previous.accessCode || '',
      sourceTitle: next.sourceTitle || previous.sourceTitle || '',
      sourceUrl: next.sourceUrl || previous.sourceUrl || '',
      floorLabel: next.floorLabel || previous.floorLabel || '',
      author: next.author || previous.author || '',
      text: next.text || previous.text || '',
      note: next.note || previous.note || '',
      tags: mergeTagLists(previous.tags, next.tags),
      updatedAt: Math.max(Number(previous.updatedAt) || 0, Number(next.updatedAt) || 0),
    });
  }

  function pruneResourceLibrary(resources, limit) {
    var source = resources || {};
    var max = Math.max(1, Number(limit) || RESOURCE_LIMIT);
    var normalized = {};
    Object.keys(source).forEach(function normalizeResourceKey(key) {
      var record = normalizeResourceRecord(source[key], key);
      if (!record || !record.key) return;
      normalized[record.key] = mergeResourceRecord(normalized[record.key], record);
    });
    var keys = Object.keys(normalized)
      .sort(function sortByResourceFreshness(left, right) {
        return (Number(normalized[right].updatedAt || normalized[right].savedAt) || 0) -
          (Number(normalized[left].updatedAt || normalized[left].savedAt) || 0);
      })
      .slice(0, max);
    var result = {};
    keys.forEach(function keepResource(key) {
      result[key] = normalized[key];
    });
    return result;
  }

  function saveResourceLinksToLibrary(links, resources, meta, timestamp) {
    var target = pruneResourceLibrary(resources || {});
    var saved = 0;
    getJumpResourceLinks(links).forEach(function saveResourceLink(item) {
      var record = createResourceRecord(item, meta, timestamp);
      if (!record || !record.key) return;
      target[record.key] = mergeResourceRecord(target[record.key], record);
      saved += 1;
    });
    return {
      resources: pruneResourceLibrary(target),
      saved: saved,
    };
  }

  function getResourceCenterEntries(resources) {
    var library = pruneResourceLibrary(resources || {});
    return Object.keys(library)
      .map(function toResourceEntry(key) {
        var item = normalizeResourceRecord(library[key], key);
        if (!item) return null;
        return Object.assign({}, item, {
          statusLabel: getResourceStatusLabel(item.status),
          sourceText: [item.sourceTitle, item.floorLabel, item.author].filter(Boolean).join(' · '),
          tagText: formatResourceTags(item.tags),
          providerKey: (item.type === 'cloud' ? item.provider : item.label).toLowerCase(),
        });
      })
      .filter(Boolean)
      .sort(function sortResourceEntries(left, right) {
        return (right.updatedAt || right.savedAt || 0) - (left.updatedAt || left.savedAt || 0);
      });
  }

  function getResourceBadgeDefinition(type) {
    return RESOURCE_BADGE_DEFINITIONS[type] || null;
  }

  function getResourceBadgeOrderIndex(type) {
    var index = RESOURCE_BADGE_ORDER.indexOf(type);
    return index === -1 ? RESOURCE_BADGE_ORDER.length : index;
  }

  function getCloudResourceBadgeType(value) {
    var text = String(value || '');
    if (/百度|baidu/i.test(text)) return 'baidu';
    if (/夸克|quark/i.test(text)) return 'quark';
    if (/pikpak/i.test(text)) return 'pikpak';
    return 'cloud';
  }

  function getResourceBadgeFromResourceItem(item, source) {
    var data = item || {};
    var type = data.type || classifyResourceLink(data.url);
    var badgeType = type === 'cloud'
      ? getCloudResourceBadgeType([data.provider, data.label, data.url].filter(Boolean).join(' '))
      : type;
    var definition = getResourceBadgeDefinition(badgeType);
    if (!definition) return null;
    return {
      type: badgeType,
      label: definition.label,
      source: source || 'resource',
      guessed: false,
    };
  }

  function mergeResourceBadges() {
    var map = {};
    Array.prototype.slice.call(arguments).forEach(function mergeBadgeList(list) {
      (list || []).forEach(function mergeBadge(badge) {
        if (!badge || !badge.type || !getResourceBadgeDefinition(badge.type)) return;
        var previous = map[badge.type];
        if (!previous || (previous.guessed && !badge.guessed)) {
          map[badge.type] = Object.assign({}, badge, {
            label: badge.label || getResourceBadgeDefinition(badge.type).label,
            guessed: !!badge.guessed,
          });
        }
      });
    });
    return Object.keys(map)
      .sort(function sortResourceBadges(left, right) {
        return getResourceBadgeOrderIndex(left) - getResourceBadgeOrderIndex(right);
      })
      .map(function mapResourceBadge(type) {
        return map[type];
      });
  }

  function getResourceBadgesFromItems(items, source) {
    return mergeResourceBadges((items || []).map(function mapResourceBadgeItem(item) {
      return getResourceBadgeFromResourceItem(item, source);
    }));
  }

  function inferResourceBadgesFromText(text) {
    var source = String(text || '');
    var badges = RESOURCE_BADGE_ORDER.map(function inferResourceBadge(type) {
      var definition = getResourceBadgeDefinition(type);
      if (!definition || !definition.pattern.test(source)) return null;
      return {
        type: type,
        label: definition.label,
        source: 'title',
        guessed: true,
      };
    });
    var merged = mergeResourceBadges(badges);
    if (merged.some(function hasSpecificCloudBadge(badge) {
      return badge.type === 'baidu' || badge.type === 'quark' || badge.type === 'pikpak';
    })) {
      return merged.filter(function dropGenericCloudBadge(badge) {
        return badge.type !== 'cloud';
      });
    }
    return merged;
  }

  function getThreadResourceBadgeIndex(resources) {
    var index = {};
    getResourceCenterEntries(resources || {}).forEach(function indexResourceEntry(entry) {
      var tid = parseThreadId(entry.sourceUrl || entry.sourceTitle || '');
      var badge = getResourceBadgeFromResourceItem(entry, 'resource');
      if (!tid || !badge) return;
      index[tid] = mergeResourceBadges(index[tid], [badge]);
    });
    return index;
  }

  function getThreadResourceBadges(item, badgeIndex, previewPayload) {
    var data = item || {};
    var index = badgeIndex || {};
    var titleBadges = inferResourceBadgesFromText(data.title || '');
    var previewBadges = previewPayload && previewPayload.resourceBadges
      ? (previewPayload.resourceBadges || []).map(function markPreviewBadge(badge) {
        return Object.assign({}, badge, { source: badge.source || 'preview', guessed: !!badge.guessed });
      })
      : [];
    return mergeResourceBadges(index[data.id], previewBadges, titleBadges);
  }

  function getResourceBadgeTypes(badges) {
    return (badges || []).map(function mapBadgeType(badge) {
      return badge.type;
    }).filter(Boolean);
  }

  function getResourceRailTypeKey(item) {
    var data = item || {};
    var badge = getResourceBadgeFromResourceItem(data, 'resource');
    if (badge && badge.type) return badge.type;
    var type = data.type || classifyResourceLink(data.url);
    if (type === 'cloud') {
      return getCloudResourceBadgeType([data.provider, data.label, data.url].filter(Boolean).join(' '));
    }
    return getResourceBadgeDefinition(type) ? type : 'external';
  }

  function getResourceRailTypeLabel(type) {
    var definition = getResourceBadgeDefinition(type);
    if (definition && definition.label) return definition.label;
    return RESOURCE_CATEGORIES[type] || '资源';
  }

  function getResourceRailEntries(links, resources) {
    var library = pruneResourceLibrary(resources || {});
    return getJumpResourceLinks(links).map(function mapResourceRailEntry(item, index) {
      var key = getResourceLibraryKey(item);
      var savedRecord = key && library[key] ? normalizeResourceRecord(library[key], key) : null;
      var source = savedRecord || item || {};
      var type = getResourceRailTypeKey(source);
      var status = savedRecord ? normalizeResourceStatus(savedRecord.status) : 'todo';
      var accessCode = String(source.accessCode || item.accessCode || '');
      var floorLabel = String(item.floorLabel || source.floorLabel || '');
      var author = String(item.author || source.author || '');
      return {
        key: key || ('resource-rail-' + index),
        url: item.url || source.url || '',
        type: type,
        typeLabel: getResourceRailTypeLabel(type),
        label: item.label || source.label || getResourceDisplayLabel(source),
        accessCode: accessCode,
        floorLabel: floorLabel,
        author: author,
        postIndex: Number(item.postIndex) || 0,
        status: status,
        statusLabel: savedRecord ? getResourceStatusLabel(status) : '待保存',
        saved: !!savedRecord,
        sourceTitle: String(source.sourceTitle || ''),
        sourceUrl: String(source.sourceUrl || item.sourceUrl || item.pageUrl || ''),
        sourceText: [source.sourceTitle, floorLabel, author].filter(Boolean).join(' · '),
        note: String((savedRecord && savedRecord.note) || ''),
        tags: normalizeResourceTags(savedRecord && savedRecord.tags),
      };
    });
  }

  function filterResourceRailEntries(entries, filter) {
    var type = String(filter || 'all');
    if (type === 'all') return (entries || []).slice();
    return (entries || []).filter(function keepResourceRailEntry(entry) {
      return entry && entry.type === type;
    });
  }

  function formatResourceRailSummary(entries) {
    var floors = {};
    var codes = 0;
    var todo = 0;
    var saved = 0;
    (entries || []).forEach(function countResourceRailEntry(entry) {
      if (!entry) return;
      if (entry.floorLabel) floors[entry.floorLabel] = true;
      if (entry.accessCode) codes += 1;
      if (entry.saved) saved += 1;
      if (!entry.saved || normalizeResourceStatus(entry.status) === 'todo') todo += 1;
    });
    return {
      total: (entries || []).length,
      floors: Object.keys(floors).length,
      codes: codes,
      todo: todo,
      saved: saved,
    };
  }

  function formatResourceRailSummaryText(entries) {
    var summary = formatResourceRailSummary(entries);
    return summary.total + ' 条 · ' + summary.floors + ' 个楼层 · ' + summary.codes + ' 个口令 · 待处理 ' + summary.todo;
  }

  function formatResourceRailCodes(entries) {
    return (entries || []).filter(function hasResourceRailCode(entry) {
      return entry && entry.accessCode;
    }).map(function formatResourceRailCode(entry) {
      return [
        '[' + (entry.typeLabel || entry.label || '资源') + ']',
        entry.floorLabel,
        entry.author,
        entry.url,
        '提取码 ' + entry.accessCode,
      ].filter(Boolean).join(' ');
    }).join('\n');
  }

  function getAvailableResourceRailFilterTypes(entries) {
    var seen = {};
    (entries || []).forEach(function collectResourceRailType(entry) {
      if (entry && entry.type && getResourceBadgeDefinition(entry.type)) seen[entry.type] = true;
    });
    return RESOURCE_BADGE_ORDER.filter(function keepResourceRailType(type) {
      return !!seen[type];
    });
  }

  function getResourceProviderOptions(entries) {
    var seen = {};
    return (entries || [])
      .map(function toProviderOption(entry) {
        var label = entry.type === 'cloud' ? entry.provider : entry.label;
        var value = String(label || '').toLowerCase();
        return { value: value, label: label || '未知来源' };
      })
      .filter(function keepUniqueProvider(option) {
        if (!option.value || seen[option.value]) return false;
        seen[option.value] = true;
        return true;
      })
      .sort(function sortProvider(left, right) {
        return left.label.localeCompare(right.label, 'zh-Hans-CN');
      });
  }

  function getResourceSourceGroupKey(entry) {
    var item = entry || {};
    return String(item.sourceUrl || item.sourceTitle || 'unknown');
  }

  function getResourceSourceGroupLabel(entry) {
    var item = entry || {};
    return String(item.sourceTitle || item.sourceUrl || '未知来源');
  }

  function groupResourceCenterEntries(entries) {
    var groups = {};
    var order = [];
    (entries || []).forEach(function collectResourceGroup(entry) {
      if (!entry) return;
      var key = getResourceSourceGroupKey(entry);
      if (!groups[key]) {
        groups[key] = {
          key: key,
          label: getResourceSourceGroupLabel(entry),
          sourceUrl: entry.sourceUrl || '',
          entries: [],
          updatedAt: 0,
        };
        order.push(key);
      }
      groups[key].entries.push(entry);
      groups[key].updatedAt = Math.max(groups[key].updatedAt, Number(entry.updatedAt || entry.savedAt) || 0);
    });
    return order.map(function mapResourceGroup(key) {
      return groups[key];
    }).sort(function sortResourceGroups(left, right) {
      return right.updatedAt - left.updatedAt;
    });
  }

  function filterResourceCenterEntries(entries, options) {
    var query = normalizeCenterSearchQuery(options && options.query);
    var status = String((options && options.filter) || 'all');
    var provider = String((options && options.provider) || 'all').toLowerCase();
    var tag = String((options && options.tag) || 'all').toLowerCase();
    return (entries || []).filter(function matchResourceEntry(entry) {
      if (!matchesCenterSearch(query, [
        entry.url,
        entry.label,
        entry.provider,
        entry.statusLabel,
        entry.sourceTitle,
        entry.floorLabel,
        entry.author,
        entry.accessCode,
        entry.text,
        entry.note,
        entry.tagText,
      ])) return false;
      if (status !== 'all' && entry.status !== status) return false;
      if (provider !== 'all' && entry.providerKey !== provider) return false;
      if (tag !== 'all' && normalizeResourceTags(entry.tags).map(function lowerResourceTag(item) {
        return item.toLowerCase();
      }).indexOf(tag) === -1) return false;
      return true;
    });
  }

  function ensureResourceSelection(panelState) {
    if (!panelState.selectedResources || typeof panelState.selectedResources !== 'object') {
      panelState.selectedResources = {};
    }
    return panelState.selectedResources;
  }

  function getSelectedResourceKeys(entries, panelState) {
    var selection = ensureResourceSelection(panelState || {});
    var valid = {};
    (entries || []).forEach(function collectValidResourceKey(entry) {
      if (entry && entry.key) valid[entry.key] = true;
    });
    return Object.keys(selection).filter(function keepSelectedResourceKey(key) {
      return !!selection[key] && !!valid[key];
    });
  }

  function setResourceSelection(entries, panelState, selected) {
    var selection = ensureResourceSelection(panelState || {});
    (entries || []).forEach(function setResourceSelected(entry) {
      if (!entry || !entry.key) return;
      if (selected) selection[entry.key] = true;
      else delete selection[entry.key];
    });
    return selection;
  }

  function getResourceEntriesByKeys(entries, keys) {
    var selected = {};
    (keys || []).forEach(function keepKey(key) {
      selected[key] = true;
    });
    return (entries || []).filter(function isSelectedResourceEntry(entry) {
      return entry && selected[entry.key];
    });
  }

  function clampPreviewZoom(value) {
    var zoom = Number(value);
    if (!isFinite(zoom)) return 1;
    return Math.min(4, Math.max(0.5, Math.round(zoom * 100) / 100));
  }

  function getPreviewLightboxKeyAction(event) {
    if (!event || event.altKey || event.ctrlKey || event.metaKey) return '';
    var key = event.key || event.code || '';
    if (key === 'Escape' || key === 'Esc') return 'close';
    if (key === 'ArrowLeft' || key === 'Left') return 'previous';
    if (key === 'ArrowRight' || key === 'Right') return 'next';
    if (key === '+' || key === '=' || key === 'Add') return 'zoomIn';
    if (key === '-' || key === '_' || key === 'Subtract') return 'zoomOut';
    if (key === '0' || key === 'Digit0' || key === 'Numpad0') return 'zoomReset';
    return '';
  }

  function parsePostPrice(value) {
    var text = String(value || '').replace(/\s+/g, ' ');
    var patterns = [
      /(?:此帖|本帖|帖子)?\s*(?:售价|价格)\s*[::]?\s*([0-9]+(?:\.[0-9]+)?)\s*SP(?:币)?/i,
      /(?:购买(?:此帖)?(?:需要)?|购买需要|需要|需支付|花费)\s*[::]?\s*([0-9]+(?:\.[0-9]+)?)\s*SP(?:币)?/i,
    ];
    for (var index = 0; index < patterns.length; index += 1) {
      var match = text.match(patterns[index]);
      if (match) return Number(match[1]);
    }
    return null;
  }

  function parseUserSpBalance(value) {
    var text = String(value || '').replace(/\s+/g, ' ');
    var patterns = [
      /SP(?:币)?\s*(?:余额|结余|可用)?\s*[::]\s*([0-9]+(?:\.[0-9]+)?)/i,
      /(?:当前(?:拥有|余额)?|账户余额|帐户余额|可用余额|余额)\s*[::]?\s*([0-9]+(?:\.[0-9]+)?)\s*SP(?:币)?/i,
    ];
    for (var index = 0; index < patterns.length; index += 1) {
      var match = text.match(patterns[index]);
      if (match) return Number(match[1]);
    }
    return null;
  }

  function shouldAutoBuyPost(settings, price, balance) {
    var config = settings || {};
    var maxPrice = Number(config.autoBuyMaxSp);
    var postPrice = Number(price);
    var currentBalance = Number(balance);
    return !!config.autoBuyPost &&
      maxPrice > 0 &&
      postPrice >= 0 &&
      postPrice < maxPrice &&
      currentBalance >= postPrice;
  }

  function extractBuyTopicUrl(value, pageUrl) {
    var text = String(value || '').replace(/&amp;/g, '&');
    var quoted = text.match(/['"]([^'"]*job\.php\?[^'"]*action=buytopic[^'"]*)['"]/i);
    var direct = text.match(/(?:https?:\/\/[^'"\s]*job\.php\?[^'"\s]*action=buytopic[^'"\s]*|\/?job\.php\?[^'"\s]*action=buytopic[^'"\s]*)/i);
    var target = quoted ? quoted[1] : (direct ? direct[0] : '');
    if (!target) return '';
    try {
      return new URL(target, pageUrl || 'https://south-plus.org/').href;
    } catch (error) {
      return '';
    }
  }

  function getAutoBuyAttemptKey(targetUrl, pageUrl) {
    var tid = '';
    var pid = '';
    try {
      var url = new URL(String(targetUrl || ''), pageUrl || 'https://south-plus.org/');
      tid = url.searchParams.get('tid') || parseThreadId(pageUrl) || parseThreadId(targetUrl);
      pid = url.searchParams.get('pid') || 'tpc';
    } catch (error) {
      tid = parseThreadId(targetUrl) || parseThreadId(pageUrl);
      pid = 'tpc';
    }
    if (!tid) return '';
    return tid + ':' + (pid || 'tpc');
  }

  function isAutoBuyAttemptBlocked(record, now) {
    if (!record || !record.status) return false;
    if (record.status === 'skipped') return false;
    if (record.status === 'checking') {
      var checkedAt = Number(record.updatedAt) || 0;
      var currentTime = now === undefined ? Date.now() : Number(now);
      return !checkedAt || (currentTime - checkedAt) <= AUTO_BUY_CHECK_TTL;
    }
    return record.status === 'buying' || record.status === 'done' || record.status === 'failed';
  }

  function formatAutoBuyAttemptMessage(record) {
    var data = record || {};
    var labels = {
      checking: '上次检查尚未完成',
      buying: '已发起过购买请求',
      done: '已记录购买完成',
      failed: '上次自动购买失败',
    };
    var label = labels[data.status] || '已有自动购买记录';
    var detail = data.message ? ',记录:' + data.message : '';
    return '自动购买未重复执行:' + label + detail + '。如需重试,请清空自动购买记录后再操作。';
  }

  function parseTodayCount(text) {
    var match = String(text || '').match(/\((\d+)\)\s*$/);
    return match ? Number(match[1]) : 0;
  }

  function isAdUrl(url) {
    var text = String(url || '').toLowerCase();
    return /(?:taobao|tmall|alimama|doubleclick|googlesyndication|adservice)/.test(text);
  }

  function detectPageType(url) {
    var text = String(url || '');
    if (/\/simple\//.test(text)) return 'simple';
    if (/\/read\.php\?tid[=-]\d+/.test(text)) return 'read';
    if (/\/post\.php(?:[?#].*)?$/.test(text) || /\/post\.php\?/.test(text)) return 'post';
    if (/\/thread(?:_new)?\.php\?fid[=-]\d+/.test(text)) return 'forum';
    if (/\/search2?\.php(?:[?#].*)?$/.test(text)) return 'search';
    if (/\/(?:u|profile|userpay|message)\.php(?:[?#].*)?$/.test(text)) return 'profile';
    if (/\/(?:hack|plugin)\.php/i.test(text) && /(?:H_name|h_name)[=-]?tasks|tasks/i.test(text)) return 'task';
    if (/\/(?:index\.php)?(?:[?#].*)?$/.test(text)) return 'home';
    return 'other';
  }

  function shouldUseImmersiveRead(settings, url) {
    return !!(settings && settings.immersiveRead) && detectPageType(url) === 'read';
  }

  function shouldUseReaderMode(settings, url) {
    var type = detectPageType(url);
    return !!(settings && settings.readerMode) && (type === 'read' || type === 'forum');
  }

  function shouldUseSiteShell(url) {
    return true;
  }

  function hasForumThreadList(root) {
    if (!root || typeof root.querySelectorAll !== 'function') return true;
    var hasListRows = qsa('td[id^="td_"]', root).some(function hasThreadTitle(cell) {
      return parseThreadId(cell.id) && !!qs('a[id^="a_ajax_"]', cell);
    });
    if (hasListRows) return true;
    return qsa('#wall .stream li a[href*="read.php?tid"]', root).some(function hasGalleryThread(link) {
      return !!parseThreadId(link.getAttribute('href') || link.href);
    });
  }

  function shouldUseForumDashboard(url, root) {
    return detectPageType(url) === 'forum' && hasForumThreadList(root);
  }

  function shouldUseForumKeyboardPaging(url) {
    return detectPageType(url) === 'forum';
  }

  function shouldUseSearchPage(url) {
    return detectPageType(url) === 'search';
  }

  function shouldUseProfilePage(url) {
    return detectPageType(url) === 'profile';
  }

  function shouldUseTaskPage(url) {
    return detectPageType(url) === 'task';
  }

  function shouldUseHomeDashboard(settings, url) {
    return !!(settings && settings.homeDashboard) && detectPageType(url) === 'home';
  }

  function shouldUseProfileListNavigation(url) {
    return detectPageType(url) === 'profile' && /action-(?:topic|post)/.test(String(url || ''));
  }

  function shouldUseModuleNavigation(settings, url, root) {
    var type = detectPageType(url);
    if (type === 'home') return shouldUseHomeDashboard(settings, url);
    if (type === 'forum') return shouldUseForumDashboard(url, root);
    if (type === 'read') return shouldUseReaderMode(settings, url) || shouldUseImmersiveRead(settings, url);
    if (type === 'search') return true;
    if (type === 'task') return true;
    if (type === 'profile') return true;
    return false;
  }

  function shouldShowToolbarFeature(feature) {
    return feature !== 'latest' && feature !== 'clean';
  }

  function hasPreviewGalleryImages(root) {
    var scope = root || document;
    var firstPost = qsa('table.js-post', scope)[0];
    var content = firstPost ? qs('.tpc_content', firstPost) : null;
    if (!content) return false;

    return qsa('img', content)
      .map(function mapPreviewCandidate(img) {
        return {
          src: img.currentSrc || img.src,
          naturalWidth: img.naturalWidth,
          naturalHeight: img.naturalHeight,
          postIndex: 0,
        };
      })
      .some(isPreviewImageCandidate);
  }

  function shouldShowToolbarAction(action, url, root) {
    var type = detectPageType(url);
    var page = currentPageNumber(url);

    if (action === 'prevPage') return (type === 'forum' || type === 'read') && page > 1;
    if (action === 'nextPage') return type === 'forum' || type === 'read';
    if (action === 'home') return type !== 'home';
    if (action === 'reader') return type === 'forum' || type === 'read';
    if (action === 'adBlock') return type !== 'simple';
    if (action === 'homeDashboard') return type === 'home';
    if (action === 'immersiveRead') return type === 'read';
    if (action === 'previewGallery') return type === 'read' && hasPreviewGalleryImages(root);
    if (action === 'unreadOnly') return type === 'forum';
    if (action === 'onlyOriginalAuthor') return type === 'read';
    return true;
  }

  function getSettingsPanelKeys(url, root) {
    var keys = [];
    var allKeys = [
      'cleanMode',
      'readerMode',
      'immersiveRead',
      'nightMode',
      'unifiedPreviewGallery',
      'forumDashboard',
      'compactRead',
      'foldQuotes',
      'hideUserProfile',
      'networkFriendly',
      'autoBuyPost',
      'unreadOnly',
      'onlyOriginalAuthor',
    ];

    allKeys.forEach(function keepUsefulSetting(key) {
      if (key === 'cleanMode' && shouldShowToolbarFeature('clean')) keys.push(key);
      if (key === 'readerMode' && shouldShowToolbarAction('reader', url, root)) keys.push(key);
      if (key === 'immersiveRead' && shouldShowToolbarAction('immersiveRead', url, root)) keys.push(key);
      if (key === 'nightMode') keys.push(key);
      if (key === 'unifiedPreviewGallery' && shouldShowToolbarAction('previewGallery', url, root)) keys.push(key);
      if (key === 'forumDashboard' && shouldShowToolbarAction('homeDashboard', url, root)) keys.push(key);
      if (key === 'compactRead' && detectPageType(url) === 'read') keys.push(key);
      if (key === 'foldQuotes' && detectPageType(url) === 'read') keys.push(key);
      if (key === 'hideUserProfile' && detectPageType(url) === 'read') keys.push(key);
      if (key === 'networkFriendly') keys.push(key);
      if (key === 'autoBuyPost') keys.push(key);
      if (key === 'unreadOnly' && shouldShowToolbarAction('unreadOnly', url, root)) keys.push(key);
      if (key === 'onlyOriginalAuthor' && shouldShowToolbarAction('onlyOriginalAuthor', url, root)) keys.push(key);
    });

    return keys;
  }

  function buildPageUrl(url, page) {
    var targetPage = Math.max(1, Number(page) || 1);
    var parsed = new URL(String(url || ''), 'https://south-plus.org/');
    var href = parsed.href;
    var thread = href.match(/read\.php\?tid[=-](\d+)/);
    var galleryForum = href.match(/thread_new\.php\?fid[=-](\d+)/);
    var forum = href.match(/thread\.php\?fid[=-](\d+)/);

    if (thread) {
      return parsed.origin + '/read.php?tid-' + thread[1] + '-page-' + targetPage + '.html';
    }

    if (galleryForum) {
      return buildForumModePageUrl(href, targetPage, true);
    }

    if (forum) {
      return buildForumModePageUrl(href, targetPage, false);
    }

    return href;
  }

  function buildForumModePageUrl(url, page, galleryMode) {
    var parsed = new URL(String(url || ''), 'https://south-plus.org/');
    var fid = getCurrentForumId(parsed.href);
    var targetPage = Math.max(1, Number(page) || currentPageNumber(parsed.href) || 1);
    if (!fid) return parsed.href;
    if (galleryMode) {
      return parsed.origin + '/thread_new.php?fid-' + fid + '-page-' + targetPage + '.html';
    }
    if (targetPage === 1) return parsed.origin + '/thread.php?fid-' + fid + '.html';
    return parsed.origin + '/thread.php?fid-' + fid + '-page-' + targetPage + '.html';
  }

  function getForumListModeUrl(url, page) {
    return buildForumModePageUrl(url, page || currentPageNumber(url), false);
  }

  function getForumGalleryModeUrl(url, page) {
    return buildForumModePageUrl(url, page || currentPageNumber(url), true);
  }

  function isNetworkFriendlyMode(settings) {
    return !settings || settings.networkFriendly !== false;
  }

  function getScriptRequestPolicyConfig(policy) {
    var source = policy || {};
    var mode = source.mode || source.type || 'background';
    var friendly = source.networkFriendly !== false;
    var minDelay = Number(source.minDelay);
    var priority = Number(source.priority);
    var cooldownMs = Number(source.cooldownMs);

    if (!(minDelay >= 0)) {
      if (mode === 'action' || mode === 'interactive') minDelay = friendly ? 850 : 300;
      else if (mode === 'preview') minDelay = friendly ? 1200 : 500;
      else minDelay = friendly ? 1500 : 650;
    }

    if (!isFinite(priority)) {
      if (mode === 'action' || mode === 'interactive') priority = 30;
      else if (mode === 'preview') priority = 20;
      else priority = 5;
    }

    if (!(cooldownMs > 0)) cooldownMs = friendly ? 20 * 1000 : 10 * 1000;

    return {
      mode: mode,
      label: source.label || '脚本请求',
      priority: priority,
      minDelay: minDelay,
      cooldownMs: cooldownMs,
      networkFriendly: friendly,
      allowDuringCooldown: !!source.allowDuringCooldown,
    };
  }

  function isScriptRateLimitStatus(status) {
    var code = Number(status);
    return code === 429 || code === 503 || code === 504 || code === 520;
  }

  function isScriptRateLimitHtml(html) {
    var text = String(html || '').replace(/\s+/g, ' ').slice(0, 4000);
    return /(?:1\s*秒内操作频繁|操作频繁|访问过于频繁|请求过于频繁|系统繁忙|504\s*Gateway|Gateway\s*Timeout|HTTP\s*504|Error\s*code\s*520|HTTP\s*520|Web\s*server\s*is\s*returning\s*an\s*unknown\s*error)/i.test(text);
  }

  function createScriptRateLimitError(message) {
    var error = new Error(message || '请求过于频繁,脚本已暂停后台请求,请稍后再试。');
    error.spxRateLimited = true;
    return error;
  }

  function getScriptRequestDelay(policy, state, now) {
    var config = getScriptRequestPolicyConfig(policy);
    var source = state || {};
    var currentTime = now === undefined ? Date.now() : Number(now);
    var cooldownDelay = config.allowDuringCooldown ? 0 : Math.max(0, Number(source.cooldownUntil || 0) - currentTime);
    var intervalDelay = Math.max(0, Number(source.lastStartedAt || 0) + config.minDelay - currentTime);
    return Math.max(cooldownDelay, intervalDelay);
  }

  function isScriptRequestCoolingDown(now) {
    return Number(scriptRequestState.cooldownUntil || 0) > (now === undefined ? Date.now() : Number(now));
  }

  function getRateLimitErrorMessage(policy, reason) {
    var config = getScriptRequestPolicyConfig(policy);
    var label = config.label || '脚本请求';
    var detail = reason ? ':' + reason : '';
    return label + '触发站点限流' + detail + ',已暂停后台请求,请稍后再试。';
  }

  function markScriptRequestCooldown(policy, reason) {
    var config = getScriptRequestPolicyConfig(policy);
    scriptRequestState.cooldownUntil = Math.max(
      Number(scriptRequestState.cooldownUntil || 0),
      Date.now() + config.cooldownMs
    );
    return createScriptRateLimitError(getRateLimitErrorMessage(config, reason));
  }

  function getSchedulerTimerHost() {
    if (typeof window !== 'undefined' && typeof window.setTimeout === 'function') return window;
    return typeof globalThis !== 'undefined' ? globalThis : null;
  }

  function pumpScriptRequestQueue() {
    var timerHost = getSchedulerTimerHost();
    if (scriptRequestState.running || !scriptRequestState.queue.length) return;
    var next = scriptRequestState.queue[0];
    var delay = getScriptRequestDelay(next.policy, scriptRequestState, Date.now());

    if (delay > 0) {
      if (!timerHost || typeof timerHost.setTimeout !== 'function' || scriptRequestState.timer) return;
      scriptRequestState.timer = timerHost.setTimeout(function resumeScriptRequestQueue() {
        scriptRequestState.timer = null;
        pumpScriptRequestQueue();
      }, delay);
      return;
    }

    scriptRequestState.queue.shift();
    scriptRequestState.running = true;
    scriptRequestState.lastStartedAt = Date.now();
    Promise.resolve()
      .then(next.task)
      .then(function resolveScriptRequest(value) {
        scriptRequestState.running = false;
        next.resolve(value);
        pumpScriptRequestQueue();
      }, function rejectScriptRequest(error) {
        scriptRequestState.running = false;
        next.reject(error);
        pumpScriptRequestQueue();
      });
  }

  function scheduleScriptRequest(task, policy) {
    if (typeof Promise !== 'function') return task();
    var config = getScriptRequestPolicyConfig(policy);
    return new Promise(function enqueueScriptRequest(resolve, reject) {
      var timerHost = getSchedulerTimerHost();
      scriptRequestState.sequence += 1;
      scriptRequestState.queue.push({
        task: task,
        policy: config,
        resolve: resolve,
        reject: reject,
        sequence: scriptRequestState.sequence,
      });
      scriptRequestState.queue.sort(function sortScriptRequestQueue(left, right) {
        if (right.policy.priority !== left.policy.priority) return right.policy.priority - left.policy.priority;
        return left.sequence - right.sequence;
      });
      if (scriptRequestState.timer && timerHost && typeof timerHost.clearTimeout === 'function') {
        timerHost.clearTimeout(scriptRequestState.timer);
        scriptRequestState.timer = null;
      }
      pumpScriptRequestQueue();
    });
  }

  function requestWithPolicy(url, options, policy) {
    var fetchImpl = policy && policy.fetch;
    if (!fetchImpl && typeof window !== 'undefined' && typeof window.fetch === 'function') {
      fetchImpl = window.fetch.bind(window);
    }
    if (typeof fetchImpl !== 'function') return Promise.reject(new Error('当前浏览器不支持 fetch'));
    var config = getScriptRequestPolicyConfig(policy);
    return scheduleScriptRequest(function executeScriptFetch() {
      return fetchImpl(url, options).then(function inspectScriptResponse(response) {
        if (response && isScriptRateLimitStatus(response.status)) {
          throw markScriptRequestCooldown(config, 'HTTP ' + response.status);
        }
        return response;
      });
    }, config);
  }

  function readScriptResponseText(response, policy) {
    if (!response || typeof response.text !== 'function') return Promise.resolve('');
    return response.text().then(function inspectScriptResponseText(html) {
      if (isScriptRateLimitHtml(html)) throw markScriptRequestCooldown(policy, '页面提示频繁或 504');
      return html;
    });
  }

  function getCachedThreadPreview(url, now) {
    var key = String(url || '');
    var entry = threadPreviewCache[key];
    if (!entry) return null;
    if (Number(entry.expiresAt || 0) <= (now === undefined ? Date.now() : Number(now))) {
      delete threadPreviewCache[key];
      threadPreviewCacheOrder = threadPreviewCacheOrder.filter(function keepThreadPreviewKey(item) {
        return item !== key;
      });
      return null;
    }
    return cloneJson(entry.payload, entry.payload);
  }

  function rememberThreadPreview(url, payload, now, ttl) {
    var key = String(url || '');
    if (!key || !payload) return;
    if (!threadPreviewCache[key]) threadPreviewCacheOrder.push(key);
    threadPreviewCache[key] = {
      expiresAt: (now === undefined ? Date.now() : Number(now)) + (Number(ttl) > 0 ? Number(ttl) : THREAD_PREVIEW_CACHE_TTL),
      payload: cloneJson(payload, payload),
    };
    while (threadPreviewCacheOrder.length > THREAD_PREVIEW_CACHE_LIMIT) {
      delete threadPreviewCache[threadPreviewCacheOrder.shift()];
    }
  }

  function getCachedUserSpBalance(now) {
    if (Number(spBalanceCache.expiresAt || 0) <= (now === undefined ? Date.now() : Number(now))) return null;
    return spBalanceCache.value === null ? null : Number(spBalanceCache.value);
  }

  function rememberUserSpBalance(balance, now) {
    var value = Number(balance);
    if (!isFinite(value)) return;
    spBalanceCache.value = value;
    spBalanceCache.expiresAt = (now === undefined ? Date.now() : Number(now)) + SP_BALANCE_CACHE_TTL;
  }

  function clearUserSpBalanceCache() {
    spBalanceCache = { value: null, expiresAt: 0 };
  }

  function getNavigationRefreshKey(urls) {
    return (urls || []).map(function normalizeRefreshUrl(url) {
      return String(url || '').replace(/#.*$/, '');
    }).join('|');
  }

  function shouldRefreshNavigationPool(urls, now) {
    var key = getNavigationRefreshKey(urls);
    var refreshedAt = Number(loadMap(NAVIGATION_REFRESH_KEY)[key] || 0);
    return !key || !refreshedAt || ((now === undefined ? Date.now() : Number(now)) - refreshedAt) >= NAVIGATION_REFRESH_TTL;
  }

  function rememberNavigationPoolRefresh(urls, now) {
    var key = getNavigationRefreshKey(urls);
    if (!key) return;
    var data = loadMap(NAVIGATION_REFRESH_KEY);
    data[key] = now === undefined ? Date.now() : Number(now);
    var keys = Object.keys(data).sort(function sortNavigationRefresh(left, right) {
      return Number(data[right] || 0) - Number(data[left] || 0);
    });
    keys.slice(20).forEach(function pruneNavigationRefresh(item) {
      delete data[item];
    });
    saveMap(NAVIGATION_REFRESH_KEY, data);
  }

  function safeJsonParse(value, fallback) {
    try {
      return JSON.parse(value);
    } catch (error) {
      return fallback;
    }
  }

  function isPlainObject(value) {
    return !!value && Object.prototype.toString.call(value) === '[object Object]';
  }

  function cloneJson(value, fallback) {
    if (value === undefined) return fallback;
    return safeJsonParse(JSON.stringify(value), fallback);
  }

  function copyStorageMap(value) {
    if (!isPlainObject(value)) return {};
    var result = {};
    Object.keys(value).forEach(function copyMapEntry(key) {
      var item = value[key];
      if (item === undefined || typeof item === 'function') return;
      result[key] = cloneJson(item, item);
    });
    return result;
  }

  function normalizeSettings(value) {
    var settings = Object.assign(copySettings(DEFAULT_SETTINGS), isPlainObject(value) ? value : {});
    Object.keys(DEFAULT_SETTINGS).forEach(function normalizeSetting(key) {
      var defaultValue = DEFAULT_SETTINGS[key];
      if (typeof defaultValue === 'boolean') {
        settings[key] = !!settings[key];
      }
    });
    settings.moduleNavDensity = normalizeModuleNavDensity(settings.moduleNavDensity);
    settings.immersiveFontSize = 16;
    settings.autoBuyMaxSp = Math.max(0, Number(settings.autoBuyMaxSp) || 0);
    settings.titleKeywords = normalizeListValue(settings.titleKeywords, parseLineList);
    settings.authorKeywords = normalizeListValue(settings.authorKeywords, parseLineList);
    settings.quickReplies = normalizeListValue(settings.quickReplies, parseQuickReplyList);
    return settings;
  }

  function createBackupPayload(data, timestamp) {
    var source = data || {};
    return {
      app: APP,
      version: 1,
      exportedAt: Number(timestamp) || Date.now(),
      data: {
        settings: normalizeSettings(source.settings),
        read: copyStorageMap(source.read),
        watch: copyStorageMap(source.watch),
        progress: pruneReadProgress(copyStorageMap(source.progress)),
        autoBuyAttempts: pruneAutoBuyAttempts(copyStorageMap(source.autoBuyAttempts)),
        resources: pruneResourceLibrary(copyStorageMap(source.resources)),
        navigation: normalizeNavigationPool(source.navigation),
        navigationPins: normalizeNavigationPinMap(source.navigationPins),
      },
    };
  }

  function normalizeBackupPayload(value) {
    var payload = typeof value === 'string' ? safeJsonParse(value, null) : value;
    if (!isPlainObject(payload)) return null;
    var source = isPlainObject(payload.data) ? payload.data : payload;
    return createBackupPayload({
      settings: source.settings,
      read: source.read,
      watch: source.watch,
      progress: source.progress,
      autoBuyAttempts: source.autoBuyAttempts || source.autoBuy,
      resources: source.resources,
      navigation: source.navigation,
      navigationPins: source.navigationPins,
    }, payload.exportedAt);
  }

  function collectBackupPayload(settings, state) {
    return createBackupPayload({
      settings: settings,
      read: state && state.read,
      watch: state && state.watch,
      progress: state && state.progress,
      autoBuyAttempts: loadAutoBuyAttempts(),
      resources: state && state.resources,
      navigation: loadNavigationPool(),
      navigationPins: loadNavigationPins(),
    });
  }

  function countMapItems(value) {
    return Object.keys(value || {}).length;
  }

  function estimateTextBytes(value) {
    var text = String(value == null ? '' : value);
    if (typeof TextEncoder !== 'undefined') {
      try {
        return new TextEncoder().encode(text).length;
      } catch (error) {
        // Fall back to a small UTF-8 byte counter below.
      }
    }
    var bytes = 0;
    for (var index = 0; index < text.length; index += 1) {
      var code = text.charCodeAt(index);
      if (code <= 0x7f) bytes += 1;
      else if (code <= 0x7ff) bytes += 2;
      else if (code >= 0xd800 && code <= 0xdbff) {
        bytes += 4;
        index += 1;
      } else {
        bytes += 3;
      }
    }
    return bytes;
  }

  function stringifyStorageValue(value) {
    try {
      return JSON.stringify(value == null ? {} : value);
    } catch (error) {
      return '';
    }
  }

  function formatStorageBytes(bytes) {
    var size = Math.max(0, Number(bytes) || 0);
    if (size < 1024) return size + ' B';
    if (size < 1024 * 1024) return (size / 1024).toFixed(size >= 10 * 1024 ? 0 : 1) + ' KB';
    return (size / 1024 / 1024).toFixed(1) + ' MB';
  }

  function createStorageUsageEntry(key, label, value, count, limit) {
    var text = stringifyStorageValue(value);
    return {
      key: key,
      label: label,
      bytes: estimateTextBytes(text),
      size: formatStorageBytes(estimateTextBytes(text)),
      count: Math.max(0, Number(count) || 0),
      limit: Math.max(0, Number(limit) || 0),
    };
  }

  function collectStorageUsageReport(data) {
    var backup = createBackupPayload(data || {});
    var source = backup.data || {};
    var entries = [
      createStorageUsageEntry(STORE_KEY, '设置', source.settings, 1, 0),
      createStorageUsageEntry(READ_KEY, '已读记录', source.read, countMapItems(source.read), 0),
      createStorageUsageEntry(WATCH_KEY, '稍后看', source.watch, countMapItems(source.watch), 0),
      createStorageUsageEntry(PROGRESS_KEY, '阅读进度', source.progress, countMapItems(source.progress), READ_PROGRESS_LIMIT),
      createStorageUsageEntry(AUTO_BUY_KEY, '自动购买记录', source.autoBuyAttempts, countMapItems(source.autoBuyAttempts), AUTO_BUY_ATTEMPT_LIMIT),
      createStorageUsageEntry(RESOURCE_KEY, '资源库', source.resources, countMapItems(source.resources), RESOURCE_LIMIT),
      createStorageUsageEntry(NAVIGATION_KEY, '导航池', source.navigation, countMapItems(source.navigation), NAVIGATION_POOL_LIMIT),
      createStorageUsageEntry(NAVIGATION_PIN_KEY, '导航置顶', source.navigationPins, countMapItems(source.navigationPins), 0),
    ];
    var totalBytes = entries.reduce(function sumBytes(total, entry) {
      return total + entry.bytes;
    }, 0);
    var largest = entries.slice().sort(function sortByBytes(left, right) {
      return right.bytes - left.bytes;
    })[0] || null;
    var suggestions = [];

    if (totalBytes >= LOCAL_STORAGE_WARNING_BYTES) {
      suggestions.push('本地存储接近浏览器常见上限,建议先导出备份再清理旧记录');
    }
    entries.forEach(function addLimitSuggestion(entry) {
      if (!entry.limit) return;
      if (entry.count >= Math.floor(entry.limit * 0.8)) {
        suggestions.push(entry.label + '接近 ' + entry.limit + ' 条上限,建议清理或导出归档');
      }
    });
    if (largest && largest.bytes >= 512 * 1024) {
      suggestions.push(largest.label + '占用超过 512 KB,优先检查这一项');
    }

    return {
      entries: entries,
      totalBytes: totalBytes,
      totalSize: formatStorageBytes(totalBytes),
      largest: largest,
      suggestions: suggestions,
    };
  }

  function formatStorageUsageSummary(report) {
    var data = report || collectStorageUsageReport({});
    var largest = data.largest;
    return '本地存储约 ' + data.totalSize + ' · ' + (data.entries || []).length + ' 项' +
      (largest ? ' · 最大:' + largest.label + ' ' + largest.size : '');
  }

  function formatStorageUsageWarnings(report) {
    var data = report || {};
    return data.suggestions && data.suggestions.length
      ? data.suggestions.join(' · ')
      : '当前体积正常,暂无额外清理建议';
  }

  function formatStorageUsageLimit(entry) {
    if (!entry) return '0 条';
    if (!entry.limit) return entry.count + ' 条';
    var ratio = Math.round(clampRatio(entry.count / entry.limit) * 100);
    return entry.count + ' / ' + entry.limit + ' 条(' + ratio + '%)';
  }

  function getStorageUsageLevel(entry) {
    if (!entry) return 'ok';
    if (entry.limit && entry.count >= Math.floor(entry.limit * 0.9)) return 'danger';
    if (entry.limit && entry.count >= Math.floor(entry.limit * 0.8)) return 'warning';
    if (entry.bytes >= 512 * 1024) return 'warning';
    return 'ok';
  }

  function formatStorageUsageEntry(entry) {
    if (!entry) return '';
    return entry.label + ':' + entry.size + ' / ' + entry.count + ' 条' +
      (entry.limit ? ' / 上限 ' + entry.limit : '');
  }

  function getDataRecordTimestamp(record) {
    if (typeof record === 'number') return Number(record) || 0;
    return Number(record && (record.updatedAt || record.savedAt || record.createdAt)) || 0;
  }

  function getDataRecordFingerprint(record) {
    if (!record || typeof record === 'number') return '';
    var rawUrl = String(record.url || record.progressUrl || '');
    if (rawUrl) {
      try {
        var parsed = new URL(rawUrl, 'https://south-plus.org/');
        parsed.hash = '';
        return 'url:' + parsed.href.toLowerCase();
      } catch (error) {
        return 'url:' + rawUrl.split('#')[0].toLowerCase();
      }
    }
    var title = compactText(record.title || record.subject || '');
    return title ? 'title:' + title.toLowerCase() : '';
  }

  function getDuplicateRecordKeys(map) {
    var source = map || {};
    var seen = {};
    var duplicates = [];
    Object.keys(source)
      .sort(function sortByFreshness(left, right) {
        return getDataRecordTimestamp(source[right]) - getDataRecordTimestamp(source[left]);
      })
      .forEach(function detectDuplicateRecord(key) {
        var fingerprint = getDataRecordFingerprint(source[key]);
        if (!fingerprint) return;
        if (seen[fingerprint]) {
          duplicates.push(key);
          return;
        }
        seen[fingerprint] = key;
      });
    return duplicates;
  }

  function getInvalidProgressKeys(progress) {
    var source = progress || {};
    return Object.keys(source).filter(function isInvalidProgress(key) {
      return !(source[key] && source[key].updatedAt);
    });
  }

  function getInvalidAutoBuyKeys(attempts) {
    var source = attempts || {};
    return Object.keys(source).filter(function isInvalidAutoBuy(key) {
      return !(source[key] && source[key].status);
    });
  }

  function getInvalidResourceKeys(resources) {
    var source = resources || {};
    return Object.keys(source).filter(function isInvalidResource(key) {
      return !normalizeResourceRecord(source[key], key);
    });
  }

  function getStaleProgressKeys(progress, now, maxAge) {
    var source = progress || {};
    var currentTime = Number(now) || Date.now();
    var threshold = Math.max(1, Number(maxAge) || STALE_PROGRESS_MAX_AGE);
    return Object.keys(source).filter(function isStaleProgress(key) {
      var updatedAt = Number(source[key] && source[key].updatedAt) || 0;
      return !!updatedAt && currentTime - updatedAt > threshold;
    });
  }

  function getOrphanProgressKeys(progress, read, watch) {
    var readMap = read || {};
    var watchMap = watch || {};
    return Object.keys(progress || {}).filter(function isOrphanProgress(key) {
      return !(readMap[key] || watchMap[key]);
    });
  }

  function collectDataHealthReport(data, now) {
    var source = data || {};
    var settings = normalizeSettings(source.settings);
    var read = copyStorageMap(source.read);
    var watch = copyStorageMap(source.watch);
    var progress = copyStorageMap(source.progress);
    var autoBuyAttempts = copyStorageMap(source.autoBuyAttempts || source.autoBuy);
    var resources = copyStorageMap(source.resources);
    var autoBuyStatusCounts = {};

    Object.keys(autoBuyAttempts).forEach(function countAutoBuyStatus(key) {
      var status = String(autoBuyAttempts[key] && autoBuyAttempts[key].status || '');
      if (!status) return;
      autoBuyStatusCounts[status] = (autoBuyStatusCounts[status] || 0) + 1;
    });

    var duplicateWatchKeys = getDuplicateRecordKeys(watch);
    var duplicateProgressKeys = getDuplicateRecordKeys(progress);
    var staleProgressKeys = getStaleProgressKeys(progress, now);
    var invalidProgressKeys = getInvalidProgressKeys(progress);
    var invalidAutoBuyKeys = getInvalidAutoBuyKeys(autoBuyAttempts);
    var invalidResourceKeys = getInvalidResourceKeys(resources);

    return {
      counts: {
        titleKeywords: settings.titleKeywords.length,
        authorKeywords: settings.authorKeywords.length,
        quickReplies: settings.quickReplies.length,
        read: countMapItems(read),
        watch: countMapItems(watch),
        progress: countMapItems(progress),
        autoBuyAttempts: countMapItems(autoBuyAttempts),
        resources: countMapItems(pruneResourceLibrary(resources)),
      },
      autoBuyStatusCounts: autoBuyStatusCounts,
      duplicateWatchKeys: duplicateWatchKeys,
      duplicateProgressKeys: duplicateProgressKeys,
      staleProgressKeys: staleProgressKeys,
      orphanProgressKeys: getOrphanProgressKeys(progress, read, watch),
      invalidProgressKeys: invalidProgressKeys,
      invalidAutoBuyKeys: invalidAutoBuyKeys,
      invalidResourceKeys: invalidResourceKeys,
      cleanupCount:
        duplicateWatchKeys.length +
        duplicateProgressKeys.length +
        staleProgressKeys.length +
        invalidProgressKeys.length +
        invalidAutoBuyKeys.length +
        invalidResourceKeys.length,
    };
  }

  function cleanupDataHealthPayload(data, now) {
    var source = data || {};
    var settings = normalizeSettings(source.settings);
    var read = copyStorageMap(source.read);
    var watch = copyStorageMap(source.watch);
    var progress = copyStorageMap(source.progress);
    var autoBuyAttempts = copyStorageMap(source.autoBuyAttempts || source.autoBuy);
    var resources = copyStorageMap(source.resources);
    var before = collectDataHealthReport({
      settings: settings,
      read: read,
      watch: watch,
      progress: progress,
      autoBuyAttempts: autoBuyAttempts,
      resources: resources,
    }, now);

    before.duplicateWatchKeys.forEach(function removeDuplicateWatch(key) {
      delete watch[key];
    });
    before.duplicateProgressKeys.concat(before.staleProgressKeys, before.invalidProgressKeys).forEach(function removeBadProgress(key) {
      delete progress[key];
    });
    before.invalidAutoBuyKeys.forEach(function removeBadAutoBuy(key) {
      delete autoBuyAttempts[key];
    });
    before.invalidResourceKeys.forEach(function removeBadResource(key) {
      delete resources[key];
    });

    var payload = createBackupPayload({
      settings: settings,
      read: read,
      watch: watch,
      progress: progress,
      autoBuyAttempts: autoBuyAttempts,
      resources: resources,
      navigation: source.navigation,
      navigationPins: source.navigationPins,
    }, now);

    return {
      payload: payload,
      before: before,
      after: collectDataHealthReport(payload.data, now),
    };
  }

  function formatDataHealthSummary(report) {
    var data = report || collectDataHealthReport({});
    var counts = data.counts || {};
    return [
      '已读 ' + (counts.read || 0),
      '稍后看 ' + (counts.watch || 0),
      '阅读进度 ' + (counts.progress || 0),
      '自动购买记录 ' + (counts.autoBuyAttempts || 0),
      '资源 ' + (counts.resources || 0),
      '屏蔽词 ' + ((counts.titleKeywords || 0) + (counts.authorKeywords || 0)),
      '快捷回复 ' + (counts.quickReplies || 0),
    ].join(' · ');
  }

  function formatDataHealthWarnings(report) {
    var data = report || {};
    var warnings = [];
    if (data.duplicateWatchKeys && data.duplicateWatchKeys.length) warnings.push('重复稍后看 ' + data.duplicateWatchKeys.length);
    if (data.duplicateProgressKeys && data.duplicateProgressKeys.length) warnings.push('重复进度 ' + data.duplicateProgressKeys.length);
    if (data.staleProgressKeys && data.staleProgressKeys.length) warnings.push('过期进度 ' + data.staleProgressKeys.length);
    if (data.orphanProgressKeys && data.orphanProgressKeys.length) warnings.push('孤立进度 ' + data.orphanProgressKeys.length);
    if (data.invalidProgressKeys && data.invalidProgressKeys.length) warnings.push('异常进度 ' + data.invalidProgressKeys.length);
    if (data.invalidAutoBuyKeys && data.invalidAutoBuyKeys.length) warnings.push('异常购买记录 ' + data.invalidAutoBuyKeys.length);
    if (data.invalidResourceKeys && data.invalidResourceKeys.length) warnings.push('异常资源 ' + data.invalidResourceKeys.length);
    return warnings.length ? warnings.join(' · ') : '未发现需要清理的重复或过期数据';
  }

  function formatBackupImportPreview(payload) {
    var backup = normalizeBackupPayload(payload);
    if (!backup) return '';
    var report = collectDataHealthReport(backup.data, backup.exportedAt);
    return [
      '即将导入 South Plus +++ 本地备份。',
      formatDataHealthSummary(report),
      formatDataHealthWarnings(report),
      '导入会覆盖当前浏览器内同类本地数据,确认继续?',
    ].join('\n');
  }

  function getStorage() {
    if (typeof window === 'undefined' || !window.localStorage) return null;
    return window.localStorage;
  }

  function loadSettings() {
    var storage = getStorage();
    if (!storage) return copySettings(DEFAULT_SETTINGS);
    var stored = safeJsonParse(storage.getItem(STORE_KEY), {});
    return normalizeSettings(stored);
  }

  function saveSettings(settings) {
    var storage = getStorage();
    if (!storage) return;
    storage.setItem(STORE_KEY, JSON.stringify(settings));
  }

  function copySettings(settings) {
    return JSON.parse(JSON.stringify(settings));
  }

  function loadMap(key) {
    var storage = getStorage();
    if (!storage) return {};
    return safeJsonParse(storage.getItem(key), {}) || {};
  }

  function saveMap(key, map) {
    var storage = getStorage();
    if (!storage) return;
    storage.setItem(key, JSON.stringify(map || {}));
  }

  function normalizeNavigationHref(href, baseUrl) {
    var value = String(href || '').trim();
    if (!value || /^javascript:/i.test(value)) return '';
    try {
      return new URL(value, baseUrl || (typeof location !== 'undefined' ? location.href : 'https://south-plus.org/')).href;
    } catch (error) {
      return value;
    }
  }

  function getNavigationItemKey(section, label, href) {
    var cleanHref = String(href || '').replace(/#.*$/, '').toLowerCase();
    return [String(section || '导航'), String(label || '').toLowerCase(), cleanHref].join('|');
  }

  function isForumGalleryModeUrl(url) {
    return /(?:^|\/)thread_new\.php\?fid[=-]?\d+/i.test(String(url || ''));
  }

  function shouldKeepNavigationLabel(label, options) {
    var normalized = normalizeNavigationLabel(label);
    if (!normalized) return false;
    if (/图墙模式/.test(normalized) && !(options && options.allowForumViewSwitch)) return false;
    return true;
  }

  function normalizePersistentNavigationConfig(config, baseUrl, fallbackOrder) {
    if (!config) return null;
    var label = normalizeNavigationLabel(config.label || config.title);
    var href = normalizeNavigationHref(config.href, baseUrl);
    if (!shouldKeepNavigationLabel(label) || !href || config.transient) return null;
    if (label.length > 32) label = label.slice(0, 31) + '…';
    return {
      section: config.persistSection || config.section || '子栏目',
      parentLabel: normalizeNavigationLabel(config.parentLabel || ''),
      label: label,
      href: href,
      title: normalizeNavigationLabel(config.title || label),
      className: config.className || '',
      order: Math.max(0, Number(config.order !== undefined ? config.order : fallbackOrder) || 0),
      updatedAt: Math.max(0, Number(config.updatedAt) || Date.now()),
    };
  }

  function normalizeNavigationPool(value) {
    var source = value || {};
    var result = {};
    var entries = Array.isArray(source)
      ? source
      : Object.keys(source).map(function mapNavigationEntry(key) {
        return source[key];
      });
    entries.forEach(function normalizeEntry(item, index) {
      var normalized = normalizePersistentNavigationConfig(item, item && item.baseUrl, index + 1);
      if (!normalized) return;
      var key = getNavigationItemKey(normalized.section, normalized.label, normalized.href);
      result[key] = normalized;
    });
    Object.keys(result)
      .sort(function sortNavigationPool(left, right) {
        var leftItem = result[left];
        var rightItem = result[right];
        return (rightItem.updatedAt || 0) - (leftItem.updatedAt || 0);
      })
      .slice(NAVIGATION_POOL_LIMIT)
      .forEach(function removeExtraNavigation(key) {
        delete result[key];
      });
    return result;
  }

  function loadNavigationPool() {
    return normalizeNavigationPool(loadMap(NAVIGATION_KEY));
  }

  function saveNavigationPool(pool) {
    saveMap(NAVIGATION_KEY, normalizeNavigationPool(pool));
  }

  function normalizeNavigationPinMap(value) {
    var source = isPlainObject(value) ? value : {};
    var result = {};
    Object.keys(source).forEach(function normalizePinKey(key) {
      var text = String(key || '').trim();
      if (text && source[key]) result[text] = true;
    });
    return result;
  }

  function loadNavigationPins() {
    return normalizeNavigationPinMap(loadMap(NAVIGATION_PIN_KEY));
  }

  function saveNavigationPins(pins) {
    saveMap(NAVIGATION_PIN_KEY, normalizeNavigationPinMap(pins));
  }

  function getModuleNavigationGroupKey(label) {
    return normalizeNavigationLabel(label).toLowerCase();
  }

  function normalizeNavigationCollapseState(value) {
    var source = isPlainObject(value) ? value : {};
    var result = {};
    Object.keys(source).forEach(function normalizeGroupState(key) {
      var groupKey = getModuleNavigationGroupKey(key);
      if (groupKey && source[key]) result[groupKey] = true;
    });
    return result;
  }

  function loadNavigationCollapseState() {
    return normalizeNavigationCollapseState(loadMap(NAVIGATION_COLLAPSE_KEY));
  }

  function saveNavigationCollapseState(state) {
    saveMap(NAVIGATION_COLLAPSE_KEY, normalizeNavigationCollapseState(state));
  }

  function isModuleNavigationGroupCollapsed(state, label) {
    return !!(state && state[getModuleNavigationGroupKey(label)]);
  }

  function loadReadProgress() {
    return pruneReadProgress(loadMap(PROGRESS_KEY));
  }

  function saveReadProgress(progress) {
    saveMap(PROGRESS_KEY, pruneReadProgress(progress));
  }

  function pruneAutoBuyAttempts(attempts) {
    var source = attempts || {};
    var keys = Object.keys(source)
      .filter(function hasRecord(key) {
        return source[key] && source[key].status;
      })
      .sort(function sortByUpdatedAt(left, right) {
        return (Number(source[right].updatedAt) || 0) - (Number(source[left].updatedAt) || 0);
      })
      .slice(0, AUTO_BUY_ATTEMPT_LIMIT);
    var result = {};
    keys.forEach(function keepAttempt(key) {
      result[key] = source[key];
    });
    return result;
  }

  function loadAutoBuyAttempts() {
    return pruneAutoBuyAttempts(loadMap(AUTO_BUY_KEY));
  }

  function saveAutoBuyAttempts(attempts) {
    saveMap(AUTO_BUY_KEY, pruneAutoBuyAttempts(attempts));
  }

  function loadResourceLibrary() {
    return pruneResourceLibrary(loadMap(RESOURCE_KEY));
  }

  function saveResourceLibrary(resources) {
    saveMap(RESOURCE_KEY, pruneResourceLibrary(resources));
  }

  function applyBackupPayload(payload, settings, state) {
    var backup = normalizeBackupPayload(payload);
    if (!backup) return false;
    var data = backup.data || {};
    var nextSettings = normalizeSettings(data.settings);
    Object.keys(settings || {}).forEach(function removeOldSetting(key) {
      delete settings[key];
    });
    Object.assign(settings, nextSettings);
    state.read = copyStorageMap(data.read);
    state.watch = copyStorageMap(data.watch);
    state.progress = pruneReadProgress(copyStorageMap(data.progress));
    state.resources = pruneResourceLibrary(copyStorageMap(data.resources));
    var navigation = normalizeNavigationPool(data.navigation);
    var navigationPins = normalizeNavigationPinMap(data.navigationPins);
    saveSettings(settings);
    saveMap(READ_KEY, state.read);
    saveMap(WATCH_KEY, state.watch);
    saveReadProgress(state.progress);
    saveAutoBuyAttempts(data.autoBuyAttempts);
    saveResourceLibrary(state.resources);
    saveNavigationPool(navigation);
    saveNavigationPins(navigationPins);
    clearReadProgressRestoreRequest(parseThreadId(location.href));
    refreshWatchCenter();
    refreshHistoryCenter();
    refreshAutoBuyCenter();
    refreshResourceCenter();
    refreshReadResourceRail();
    enhanceAll(settings, state);
    return true;
  }

  function formatBackupFileName(timestamp) {
    var date = new Date(Number(timestamp) || Date.now());
    function pad(number) {
      return String(number).padStart(2, '0');
    }
    return 'southplus-plus-backup-' +
      date.getFullYear() +
      pad(date.getMonth() + 1) +
      pad(date.getDate()) +
      '-' +
      pad(date.getHours()) +
      pad(date.getMinutes()) +
      '.json';
  }

  function downloadBackupPayload(payload) {
    if (
      typeof Blob === 'undefined' ||
      typeof URL === 'undefined' ||
      typeof URL.createObjectURL !== 'function' ||
      !document ||
      !document.body
    ) return false;
    var backup = normalizeBackupPayload(payload);
    if (!backup) return false;
    var blob = new Blob([JSON.stringify(backup, null, 2)], { type: 'application/json' });
    var url = URL.createObjectURL(blob);
    var link = createEl('a');
    link.href = url;
    link.download = formatBackupFileName(backup.exportedAt);
    document.body.appendChild(link);
    link.click();
    link.remove();
    window.setTimeout(function revokeBackupUrl() {
      if (typeof URL.revokeObjectURL === 'function') URL.revokeObjectURL(url);
    }, 1000);
    return true;
  }

  function importBackupFile(file, settings, state, callback) {
    if (!file || typeof FileReader === 'undefined') {
      callback(false, '无法读取备份文件');
      return;
    }
    var reader = new FileReader();
    reader.onload = function handleBackupLoaded() {
      var backup = normalizeBackupPayload(reader.result);
      if (!backup) {
        callback(false, '备份文件格式无效');
        return;
      }
      if (typeof window.confirm === 'function' && !window.confirm(formatBackupImportPreview(backup))) {
        callback(false, '已取消导入');
        return;
      }
      var ok = applyBackupPayload(backup, settings, state);
      callback(ok, ok ? '已导入本地备份' : '备份文件格式无效');
    };
    reader.onerror = function handleBackupError() {
      callback(false, '备份文件读取失败');
    };
    reader.readAsText(file);
  }

  function recordAutoBuyAttempt(key, status, message, details) {
    if (!key) return null;
    var record = {
      status: status,
      message: String(message || ''),
      updatedAt: Date.now(),
    };
    var extra = details || {};
    if (extra.url) record.url = String(extra.url);
    if (extra.price !== undefined) record.price = Number(extra.price);
    if (extra.balance !== undefined) record.balance = Number(extra.balance);

    var attempts = loadAutoBuyAttempts();
    attempts[key] = record;
    saveAutoBuyAttempts(attempts);
    refreshAutoBuyCenter();
    return record;
  }

  function currentPageNumber(url) {
    var text = String(url || '');
    var match = text.match(/-page-(\d+)/);
    return match ? Number(match[1]) : 1;
  }

  function qs(selector, root) {
    return (root || document).querySelector(selector);
  }

  function qsa(selector, root) {
    return Array.prototype.slice.call((root || document).querySelectorAll(selector));
  }

  function createEl(tag, className, text) {
    var node = document.createElement(tag);
    if (className) node.className = className;
    if (typeof text === 'string') node.textContent = text;
    return node;
  }

  function getInjectedStyleText() {
    return [
      ':root,.spx-theme-clean{--spx-bg:#f7f9fc;--spx-page-bg:#eef2f6;--spx-panel:#fff;--spx-panel-muted:#f7f9fc;--spx-line:#d7dee9;--spx-line-soft:#e5eaf2;--spx-text:#1f2937;--spx-strong:#182230;--spx-sub:#667085;--spx-muted:#98a2b3;--spx-link:#1d4ed8;--spx-accent:#2563eb;--spx-accent-soft:#dbeafe;--spx-accent-wash:#eff6ff;--spx-danger:#b91c1c;--spx-danger-soft:#fef2f2;--spx-warn:#b45309;--spx-nav-bg:#283241;--spx-nav-hover:#334155;--spx-nav-text:#dfe7f0;--spx-nav-current-bg:#fff;--spx-nav-current-text:#182230;--spx-input-bg:#fff;--spx-row-bg:#fff;--spx-row-hover:#f8fbff;--spx-module-width:260px;--spx-module-max-offset:52px;--spx-module-section-height:34px;--spx-module-parent-height:40px;--spx-module-item-height:44px;--spx-module-child-height:36px;--spx-module-item-padding:14px;--spx-module-section-padding:12px;--spx-module-item-radius:12px;--spx-radius-sm:6px;--spx-radius:8px;--spx-radius-lg:12px;--spx-radius-xl:16px;--spx-shadow-card:0 6px 18px rgba(15,23,42,.06);--spx-shadow-popover:0 18px 48px rgba(15,23,42,.24);--spx-shadow-strong:0 24px 80px rgba(15,23,42,.3);--spx-page-max:1680px;--spx-page-space:clamp(28px,5.2vw,72px);--spx-reader-line:clamp(760px,62vw,960px);--spx-panel-width:min(620px,calc(100vw - 96px));--spx-panel-max-height:min(78vh,760px);--spx-control-height:32px;}',
      ':root.spx-theme-night,.spx-theme-night{--spx-bg:#202720;--spx-page-bg:#111412;--spx-panel:#181d1a;--spx-panel-muted:#202720;--spx-line:#303a32;--spx-line-soft:#263029;--spx-text:#edf4ec;--spx-strong:#f8fff5;--spx-sub:#aebbae;--spx-muted:#7c887d;--spx-link:#86efac;--spx-accent:#4ade80;--spx-accent-soft:#173a25;--spx-accent-wash:#102318;--spx-danger:#f87171;--spx-danger-soft:#3a171c;--spx-warn:#fbbf24;--spx-nav-bg:#070908;--spx-nav-hover:#202720;--spx-nav-text:#aebbae;--spx-nav-current-bg:#202720;--spx-nav-current-text:#f8fff5;--spx-input-bg:#151a17;--spx-row-bg:#181d1a;--spx-row-hover:#202720;--spx-shadow-card:0 10px 30px rgba(0,0,0,.28);--spx-shadow-popover:0 18px 48px rgba(0,0,0,.46);--spx-shadow-strong:0 24px 80px rgba(0,0,0,.55);}',
      '@media(max-width:900px){:root,.spx-theme-clean,.spx-theme-night{--spx-page-max:100vw;--spx-page-space:16px;--spx-reader-line:calc(100vw - 28px);--spx-panel-width:calc(100vw - 16px);--spx-panel-max-height:calc(100vh - 80px);}}',
      '.spx-adblock .spx-ad-hidden{display:none!important;}',
      '.spx-adblock #banner a[href*="taobao"],.spx-adblock #banner a[href*="tmall"],.spx-adblock #banner a[href*="equity"]{display:none!important;}',
      '.spx-adblock a[href*="taobao"],.spx-adblock a[href*="tmall"],.spx-adblock a[href*="alimama"]{display:none!important;}',
      '.spx-adblock img[src*="taobao"],.spx-adblock img[src*="tmall"],.spx-adblock img[src*="alimama"]{display:none!important;}',
      '.spx-adblock #banner{min-height:0!important;}',
      '.spx-site-shell,.spx-site-shell body{width:100%!important;min-width:0!important;margin:0!important;background:var(--spx-page-bg)!important;color:var(--spx-text)!important;font:14px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif!important;}',
      '.spx-site-shell a{color:var(--spx-link)!important;text-decoration:none!important;}',
      '.spx-site-shell a:hover{text-decoration:underline!important;}',
      '.spx-site-shell #wrapA,.spx-site-shell #main{box-sizing:border-box!important;width:100%!important;max-width:none!important;background:var(--spx-page-bg)!important;}',
      '.spx-site-shell #header,.spx-site-shell #mainNav,.spx-site-shell #infobox,.spx-site-shell #notice,.spx-site-shell #content,.spx-site-shell #main{box-sizing:border-box!important;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)))!important;margin-left:auto!important;margin-right:auto!important;}',
      '.spx-site-shell #mainNav{display:block!important;height:30px!important;border-radius:8px!important;background:var(--spx-nav-bg)!important;box-shadow:0 3px 10px rgba(15,23,42,.12)!important;overflow:visible!important;}',
      '.spx-site-shell #mainNav a{color:var(--spx-nav-text)!important;text-decoration:none!important;text-shadow:none!important;font-size:14px!important;line-height:30px!important;}',
      '.spx-site-shell #mainNav a:hover{color:#fff!important;text-decoration:none!important;}',
      '.spx-site-shell #mainNav>div[style*="padding-left"]{box-sizing:border-box!important;width:100%!important;max-width:100%!important;height:30px!important;padding-left:250px!important;background:transparent!important;overflow:visible!important;}',
      '.spx-site-shell #mainNav>div[style*="padding-left"] table,.spx-site-shell #mainNav>div[style*="padding-left"] tbody,.spx-site-shell #mainNav>div[style*="padding-left"] tr,.spx-site-shell #mainNav>div[style*="padding-left"] td{display:block!important;box-sizing:border-box!important;width:auto!important;height:30px!important;margin:0!important;padding:0!important;border:0!important;background:transparent!important;}',
      '.spx-site-shell #guide{display:flex!important;float:none!important;align-items:center!important;justify-content:flex-start!important;flex-wrap:nowrap!important;width:max-content!important;max-width:100%!important;margin:0!important;padding:0!important;overflow:visible!important;white-space:nowrap!important;}',
      '.spx-site-shell #guide>li{display:block!important;float:none!important;width:auto!important;min-width:max-content!important;margin:0!important;white-space:nowrap!important;}',
      '.spx-site-shell #guide>#h_push,.spx-site-shell #guide>#h_hack{display:none!important;}',
      '.spx-site-shell #guide>li>a{box-sizing:border-box!important;display:flex!important;float:none!important;align-items:center!important;height:30px!important;line-height:30px!important;padding:0 12px!important;white-space:nowrap!important;color:var(--spx-nav-text)!important;font-size:14px!important;font-weight:800!important;letter-spacing:.1px!important;text-shadow:none!important;}',
      '.spx-site-shell #guide>li>a:hover{background:var(--spx-nav-hover)!important;color:#fff!important;text-decoration:none!important;}',
      '.spx-site-shell #guide>li.current>a,.spx-site-shell #guide>li.spx-nav-current>a,.spx-site-shell #guide>li>a.spx-nav-current{background:var(--spx-nav-current-bg)!important;color:var(--spx-nav-current-text)!important;text-decoration:none!important;box-shadow:inset 0 -3px 0 var(--spx-accent)!important;}',
      '.spx-site-shell #guide>li.spx-nav-active>a,.spx-site-shell #guide>li>a.spx-menu-open{background:var(--spx-nav-hover)!important;color:#fff!important;text-decoration:none!important;box-shadow:none!important;}',
      '.spx-site-shell #spx-nav-brand{display:inline-flex!important;flex:none!important;align-items:center!important;height:28px!important;padding:0 12px!important;color:#fff!important;font-size:13px!important;font-weight:900!important;line-height:28px!important;letter-spacing:.02em!important;white-space:nowrap!important;}',
      '.spx-favorite-nav{position:relative!important;display:flex!important;flex:none!important;align-items:center!important;margin-left:auto!important;align-self:center!important;height:38px!important;overflow:visible!important;z-index:10002!important;}',
      '.spx-favorite-nav-trigger{box-sizing:border-box!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;gap:6px!important;height:28px!important;min-width:max-content!important;margin:0!important;padding:0 10px!important;border:1px solid rgba(217,119,6,.28)!important;border-radius:8px!important;background:#ffedd5!important;color:#7c2d12!important;font:900 12px/28px -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif!important;white-space:nowrap!important;cursor:pointer!important;box-shadow:none!important;}',
      '.spx-favorite-nav-trigger:hover,.spx-favorite-nav-trigger[aria-expanded="true"]{background:#fff7ed!important;color:#7c2d12!important;text-decoration:none!important;box-shadow:inset 0 -2px 0 var(--spx-warn)!important;}',
      '.spx-favorite-nav-star{display:inline-flex!important;align-items:center!important;justify-content:center!important;width:18px!important;height:18px!important;border-radius:6px!important;background:var(--spx-warn)!important;color:#fff!important;font-size:12px!important;line-height:18px!important;}',
      '.spx-favorite-nav-count{display:inline-flex!important;align-items:center!important;justify-content:center!important;min-width:20px!important;height:18px!important;padding:0 6px!important;border-radius:999px!important;background:#fff!important;color:var(--spx-warn)!important;font-size:11px!important;line-height:18px!important;font-variant-numeric:tabular-nums!important;}',
      '.spx-favorite-nav-panel{box-sizing:border-box!important;position:absolute!important;top:46px!important;right:0!important;left:auto!important;z-index:10020!important;width:min(720px,calc(100vw - 72px))!important;max-height:min(78vh,720px)!important;overflow:hidden!important;border:1px solid #f3c27a!important;border-radius:12px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;box-shadow:0 22px 52px rgba(15,23,42,.24)!important;font:14px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif!important;}',
      '.spx-favorite-nav-panel[hidden]{display:none!important;}',
      '.spx-favorite-head{display:flex!important;align-items:flex-start!important;justify-content:space-between!important;gap:12px!important;padding:14px 14px 10px!important;border-bottom:1px solid #fde4bd!important;background:linear-gradient(180deg,#fff7ed 0%,var(--spx-panel) 100%)!important;}',
      '.spx-favorite-title h3{margin:0!important;color:var(--spx-strong)!important;font-size:15px!important;line-height:1.3!important;font-weight:900!important;}.spx-favorite-summary{margin-top:4px!important;color:#7c2d12!important;font-size:12px!important;font-weight:700!important;}',
      '.spx-favorite-open{display:inline-flex!important;align-items:center!important;min-height:30px!important;padding:0 10px!important;border-radius:8px!important;background:var(--spx-warn)!important;color:#fff!important;text-decoration:none!important;font-size:12px!important;font-weight:900!important;white-space:nowrap!important;}.spx-favorite-head-actions{display:flex!important;align-items:center!important;justify-content:flex-end!important;gap:8px!important;flex-wrap:wrap!important;}.spx-favorite-selected-count{color:#7c2d12!important;font-size:12px!important;font-weight:900!important;white-space:nowrap!important;}',
      '.spx-favorite-stats{display:grid!important;grid-template-columns:repeat(3,minmax(0,1fr))!important;gap:8px!important;padding:12px 14px 0!important;}.spx-favorite-stat{min-width:0!important;padding:9px 10px!important;border:1px solid #fde4bd!important;border-radius:10px!important;background:#fffaf3!important;}.spx-favorite-stat strong{display:block!important;color:#7c2d12!important;font-size:16px!important;line-height:1.2!important;font-variant-numeric:tabular-nums!important;}.spx-favorite-stat span{display:block!important;margin-top:3px!important;color:var(--spx-sub)!important;font-size:11px!important;font-weight:800!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}',
      '.spx-favorite-search{box-sizing:border-box!important;width:calc(100% - 28px)!important;min-height:34px!important;margin:12px 14px 8px!important;padding:0 11px!important;border:1px solid var(--spx-line)!important;border-radius:9px!important;background:var(--spx-panel-muted)!important;color:var(--spx-text)!important;font-size:13px!important;outline:none!important;}.spx-favorite-search:focus{border-color:var(--spx-warn)!important;box-shadow:0 0 0 3px rgba(217,119,6,.14)!important;}',
      '.spx-favorite-tabs,.spx-favorite-tools{display:flex!important;align-items:center!important;gap:8px!important;padding:0 14px 10px!important;}.spx-favorite-tools{justify-content:space-between!important;}.spx-favorite-chips{display:flex!important;gap:6px!important;min-width:0!important;overflow-x:auto!important;scrollbar-width:thin!important;}.spx-favorite-tab,.spx-favorite-chip{flex:none!important;min-height:28px!important;padding:0 10px!important;border:1px solid var(--spx-line)!important;border-radius:999px!important;background:var(--spx-panel)!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;cursor:pointer!important;}.spx-favorite-tab.spx-active,.spx-favorite-chip.spx-active{border-color:var(--spx-warn)!important;background:#ffedd5!important;color:#7c2d12!important;}.spx-favorite-sort{width:116px!important;height:30px!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:800!important;}',
      '.spx-favorite-list{display:grid!important;gap:8px!important;max-height:360px!important;overflow:auto!important;padding:0 14px 14px!important;}.spx-favorite-group{position:sticky!important;top:0!important;z-index:1!important;display:flex!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;min-height:30px!important;padding:4px 2px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:900!important;}.spx-favorite-group-main{display:flex!important;align-items:center!important;gap:8px!important;min-width:0!important;}.spx-favorite-group-actions{display:flex!important;align-items:center!important;justify-content:flex-end!important;gap:6px!important;flex-wrap:wrap!important;}.spx-favorite-group-actions button{min-height:26px!important;padding:0 8px!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:900!important;cursor:pointer!important;}.spx-favorite-group-actions .spx-danger{border-color:#fecaca!important;background:var(--spx-danger-soft)!important;color:var(--spx-danger)!important;}.spx-favorite-item{display:grid!important;grid-template-columns:minmax(0,1fr) auto!important;gap:12px!important;min-height:64px!important;padding:11px 12px!important;border:1px solid var(--spx-line)!important;border-radius:10px!important;background:var(--spx-panel)!important;}.spx-favorite-body-head{display:flex!important;align-items:flex-start!important;gap:8px!important;min-width:0!important;}.spx-favorite-select{flex:none!important;width:16px!important;height:16px!important;margin:2px 0 0!important;accent-color:var(--spx-warn)!important;}.spx-favorite-item-title{display:-webkit-box!important;min-width:0!important;color:var(--spx-strong)!important;font-size:14px!important;font-weight:900!important;line-height:1.38!important;white-space:normal!important;overflow:hidden!important;text-overflow:ellipsis!important;-webkit-line-clamp:2!important;-webkit-box-orient:vertical!important;}.spx-favorite-meta{margin-top:5px!important;color:var(--spx-text)!important;font-size:13px!important;line-height:1.42!important;white-space:normal!important;overflow:hidden!important;text-overflow:ellipsis!important;}.spx-favorite-actions{display:flex!important;gap:6px!important;align-items:center!important;}.spx-favorite-actions a,.spx-favorite-actions button,.spx-favorite-load-more{min-width:52px!important;height:28px!important;padding:0 8px!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:900!important;text-decoration:none!important;cursor:pointer!important;}.spx-favorite-actions .spx-primary{border-color:var(--spx-accent)!important;background:var(--spx-accent)!important;color:#fff!important;}.spx-favorite-actions .spx-danger{border-color:#fecaca!important;background:var(--spx-danger-soft)!important;color:var(--spx-danger)!important;}.spx-favorite-load{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:12px!important;min-height:42px!important;padding:8px 10px!important;border:1px dashed #f3c27a!important;border-radius:10px!important;background:#fffaf3!important;color:#7c2d12!important;font-size:12px!important;font-weight:900!important;}.spx-favorite-empty{padding:18px 14px 20px!important;color:var(--spx-sub)!important;font-size:13px!important;font-weight:700!important;text-align:center!important;}',
      '.spx-favorite-nav-panel a.spx-favorite-item-title,.spx-favorite-nav-panel a.spx-favorite-item-title:link,.spx-favorite-nav-panel a.spx-favorite-item-title:visited{color:var(--spx-strong)!important;text-decoration:none!important;text-shadow:none!important;opacity:1!important;filter:none!important;}.spx-favorite-nav-panel .spx-favorite-meta{color:var(--spx-text)!important;text-shadow:none!important;opacity:1!important;filter:none!important;}',
      '.spx-site-shell #mainNav .spx-favorite-nav-panel a.spx-favorite-item-title,.spx-site-shell #mainNav .spx-favorite-nav-panel a.spx-favorite-item-title:link,.spx-site-shell #mainNav .spx-favorite-nav-panel a.spx-favorite-item-title:visited{display:-webkit-box!important;color:#182230!important;text-decoration:none!important;text-shadow:none!important;font-size:14px!important;font-weight:900!important;line-height:1.38!important;opacity:1!important;filter:none!important;}.spx-site-shell #mainNav .spx-favorite-nav-panel .spx-favorite-meta{color:#1f2937!important;font-size:13px!important;line-height:1.42!important;opacity:1!important;filter:none!important;}.spx-site-shell #mainNav .spx-favorite-nav-panel .spx-favorite-open,.spx-site-shell #mainNav .spx-favorite-nav-panel .spx-favorite-actions a.spx-primary{color:#fff!important;line-height:28px!important;}.spx-site-shell #mainNav .spx-favorite-nav-panel .spx-favorite-actions a:not(.spx-primary){color:#1f2937!important;line-height:28px!important;}',
      '.spx-site-shell #guide .spx-peacemaker-nav{position:relative!important;width:auto!important;min-width:max-content!important;overflow:visible!important;}',
      '.spx-site-shell #peacemakerconfig{position:relative!important;overflow:visible!important;color:var(--spx-nav-text)!important;}',
      '.spx-site-shell #peacemakerconfig.spx-menu-open{color:#fff!important;}',
      '.spx-site-shell #peacemakerconfig>div[hidden]{display:none!important;}',
      '.spx-site-shell #peacemakerconfig>div:not([hidden]){box-sizing:border-box!important;display:block!important;position:absolute!important;top:30px!important;right:0!important;left:auto!important;z-index:10000!important;width:148px!important;min-width:148px!important;margin:0!important;padding:5px!important;border:1px solid #334155!important;border-radius:8px!important;background:#f8fafc!important;color:#0f172a!important;box-shadow:0 12px 28px rgba(15,23,42,.22)!important;font:700 13px/1.35 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif!important;}',
      '.spx-site-shell #peacemakerconfig>div:not([hidden])>div{box-sizing:border-box!important;display:block!important;min-height:28px!important;margin:0!important;padding:5px 8px!important;background:#f8fafc!important;color:#0f172a!important;text-align:left!important;font:inherit!important;line-height:18px!important;border-radius:5px!important;}',
      '.spx-site-shell #peacemakerconfig>div:not([hidden])>div:hover{background:#e0f2fe!important;}',
      '.spx-forum-dashboard-panel{box-sizing:border-box!important;grid-column:1/-1!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;padding:14px!important;border:1px solid var(--spx-line)!important;border-radius:12px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;box-shadow:var(--spx-shadow-card)!important;overflow:hidden!important;}',
      '.spx-forum-dashboard-head{display:flex!important;align-items:flex-end!important;justify-content:space-between!important;gap:14px!important;margin:0 0 12px!important;}.spx-forum-dashboard-head h2{margin:0!important;color:var(--spx-strong)!important;font-size:20px!important;line-height:1.25!important;font-weight:900!important;}.spx-forum-dashboard-head p{margin:4px 0 0!important;color:var(--spx-sub)!important;font-size:13px!important;line-height:1.45!important;}',
      '.spx-forum-dashboard-actions{display:flex!important;align-items:center!important;justify-content:flex-end!important;gap:8px!important;flex-wrap:wrap!important;}.spx-forum-dashboard-actions button{min-height:32px!important;padding:0 11px!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;background:var(--spx-panel-muted)!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:900!important;cursor:pointer!important;}.spx-forum-dashboard-actions button.spx-primary{border-color:var(--spx-accent)!important;background:var(--spx-accent)!important;color:#fff!important;}',
      '.spx-forum-dashboard-stats{display:grid!important;grid-template-columns:repeat(6,minmax(0,1fr))!important;gap:10px!important;margin:0 0 12px!important;}.spx-forum-dashboard-stat{box-sizing:border-box!important;min-width:0!important;padding:11px!important;border:1px solid var(--spx-line)!important;border-radius:10px!important;background:var(--spx-panel-muted)!important;}.spx-forum-dashboard-stat-label{display:block!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}.spx-forum-dashboard-stat strong{display:block!important;margin:7px 0 0!important;color:var(--spx-strong)!important;font-size:22px!important;line-height:1.1!important;font-weight:900!important;font-variant-numeric:tabular-nums!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}.spx-forum-dashboard-stat small{display:block!important;margin:5px 0 0!important;color:var(--spx-muted)!important;font-size:12px!important;font-weight:800!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}',
      '.spx-forum-dashboard-stat.spx-blue{border-color:#bfdbfe!important;background:#eff6ff!important;}.spx-forum-dashboard-stat.spx-amber{border-color:#fed7aa!important;background:#fff7ed!important;}.spx-forum-dashboard-stat.spx-rose{border-color:#fecdd3!important;background:#fff1f2!important;}.spx-forum-dashboard-stat.spx-green{border-color:#bbf7d0!important;background:#f0fdf4!important;}.spx-forum-dashboard-stat.spx-violet{border-color:#ddd6fe!important;background:#f5f3ff!important;}',
      '.spx-forum-dashboard-grid{display:grid!important;grid-template-columns:minmax(0,1.08fr) minmax(340px,.92fr)!important;gap:12px!important;align-items:start!important;}.spx-forum-dashboard-stack{display:grid!important;gap:12px!important;min-width:0!important;}.spx-forum-dashboard-split{display:grid!important;grid-template-columns:1fr 1fr!important;gap:12px!important;}.spx-forum-dashboard-card{box-sizing:border-box!important;min-width:0!important;padding:12px!important;border:1px solid var(--spx-line)!important;border-radius:10px!important;background:var(--spx-panel)!important;box-shadow:none!important;overflow:hidden!important;}.spx-forum-dashboard-card h3{margin:0 0 10px!important;color:var(--spx-strong)!important;font-size:14px!important;font-weight:900!important;line-height:1.3!important;}',
      '.spx-forum-dashboard-topic-list,.spx-forum-dashboard-resource-list,.spx-forum-dashboard-activity-list{display:grid!important;gap:8px!important;}.spx-forum-dashboard-topic,.spx-forum-dashboard-resource,.spx-forum-dashboard-activity{box-sizing:border-box!important;display:grid!important;grid-template-columns:minmax(0,1fr) 54px auto!important;gap:10px!important;align-items:center!important;min-height:54px!important;padding:9px 10px!important;border:1px solid var(--spx-line-soft)!important;border-radius:9px!important;background:var(--spx-panel-muted)!important;}.spx-forum-dashboard-resource{grid-template-columns:minmax(0,1fr) auto!important;}.spx-forum-dashboard-activity{grid-template-columns:auto minmax(0,1fr) auto!important;}',
      '.spx-forum-dashboard-topic-title,.spx-forum-dashboard-resource-title{display:block!important;min-width:0!important;color:var(--spx-strong)!important;font-size:13px!important;font-weight:900!important;line-height:1.35!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;text-decoration:none!important;}.spx-forum-dashboard-topic-meta{margin-top:3px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:700!important;line-height:1.35!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}.spx-forum-dashboard-score{color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;text-align:right!important;font-variant-numeric:tabular-nums!important;}',
      '.spx-forum-dashboard-chips{display:flex!important;align-items:center!important;justify-content:flex-end!important;gap:5px!important;flex-wrap:wrap!important;}.spx-forum-dashboard-chip{display:inline-flex!important;align-items:center!important;justify-content:center!important;height:22px!important;padding:0 7px!important;border-radius:999px!important;background:var(--spx-accent-soft)!important;color:var(--spx-accent)!important;font-size:11px!important;font-weight:900!important;white-space:nowrap!important;}.spx-forum-dashboard-chip.spx-green{background:#dcfce7!important;color:#047857!important;}.spx-forum-dashboard-chip.spx-amber{background:#ffedd5!important;color:#92400e!important;}.spx-forum-dashboard-chip.spx-violet{background:#ede9fe!important;color:#6d28d9!important;}',
      '.spx-forum-dashboard-ranks{display:grid!important;gap:7px!important;}.spx-forum-dashboard-rank{display:grid!important;grid-template-columns:70px minmax(0,1fr) 34px!important;gap:8px!important;align-items:center!important;min-height:28px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;}.spx-forum-dashboard-rank>b{display:block!important;height:8px!important;border-radius:999px!important;background:var(--spx-line-soft)!important;overflow:hidden!important;}.spx-forum-dashboard-rank>b>i{display:block!important;height:100%!important;border-radius:inherit!important;background:var(--spx-accent)!important;}.spx-forum-dashboard-rank>strong{text-align:right!important;color:var(--spx-strong)!important;font-variant-numeric:tabular-nums!important;}',
      '.spx-forum-dashboard-activity-mark{width:9px!important;height:36px!important;border-radius:999px!important;background:var(--spx-accent)!important;}.spx-forum-dashboard-activity.spx-resource .spx-forum-dashboard-activity-mark{background:#059669!important;}.spx-forum-dashboard-activity.spx-request .spx-forum-dashboard-activity-mark{background:#d97706!important;}.spx-forum-dashboard-activity time{color:var(--spx-muted)!important;font-size:12px!important;font-weight:900!important;white-space:nowrap!important;}.spx-forum-dashboard-empty{padding:12px!important;color:var(--spx-sub)!important;font-size:13px!important;font-weight:700!important;text-align:center!important;}',
      '@media(max-width:1180px){.spx-forum-dashboard-stats{grid-template-columns:repeat(3,minmax(0,1fr))!important}.spx-forum-dashboard-grid{grid-template-columns:minmax(0,1fr)!important}}',
      '@media(max-width:900px){.spx-forum-dashboard-panel{padding:10px!important}.spx-forum-dashboard-head{align-items:flex-start!important;flex-direction:column!important}.spx-forum-dashboard-actions{justify-content:flex-start!important}.spx-forum-dashboard-stats{grid-template-columns:repeat(2,minmax(0,1fr))!important}.spx-forum-dashboard-split{grid-template-columns:minmax(0,1fr)!important}.spx-forum-dashboard-topic,.spx-forum-dashboard-resource,.spx-forum-dashboard-activity{grid-template-columns:minmax(0,1fr)!important}.spx-forum-dashboard-score{text-align:left!important}.spx-forum-dashboard-chips{justify-content:flex-start!important}}',
      '.spx-search-page #wrapA,.spx-search-page #main{box-sizing:border-box!important;max-width:none!important;background:var(--spx-page-bg)!important;}',
      '.spx-search-page #main{box-sizing:border-box!important;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)))!important;margin:16px auto 42px!important;padding:0!important;display:block!important;}',
      '.spx-search-page .t{box-sizing:border-box!important;width:100%!important;margin:0 0 14px!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:0 6px 18px rgba(15,23,42,.06)!important;overflow:hidden!important;}',
      '.spx-search-page .tr1,.spx-search-page .tr2,.spx-search-page .tr3{font-size:14px!important;line-height:1.6!important;}',
      '.spx-search-page .tr3 td,.spx-search-page .tr1 td{padding:8px 10px!important;}',
      '.spx-search-page .spx-module-body input[type="text"],.spx-search-page .spx-module-body input[type="search"],.spx-search-page .spx-module-body input[name="keyword"],.spx-search-page .spx-module-body input[name="username"]{box-sizing:border-box!important;width:min(520px,100%)!important;min-width:0!important;max-width:100%!important;height:34px!important;padding:5px 10px!important;border:1px solid #cbd5e1!important;border-radius:5px!important;background:#fff!important;color:#172033!important;font-size:16px!important;line-height:22px!important;}',
      '.spx-search-page .spx-module-body input[type="text"]:focus,.spx-search-page .spx-module-body input[type="search"]:focus,.spx-search-page .spx-module-body input[name="keyword"]:focus,.spx-search-page .spx-module-body input[name="username"]:focus{border-color:#0f766e!important;box-shadow:0 0 0 2px rgba(15,118,110,.14)!important;outline:none!important;}',
      '.spx-profile-page #wrapA,.spx-profile-page #main{box-sizing:border-box!important;max-width:none!important;background:var(--spx-page-bg)!important;}',
      '.spx-profile-page #main,.spx-profile-page #content{box-sizing:border-box!important;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)))!important;margin:16px auto 42px!important;padding:0!important;display:block!important;}',
      '.spx-profile-page #main>*{box-sizing:border-box!important;width:100%!important;max-width:none!important;}',
      '.spx-profile-page #main>.bdbA,.spx-profile-page #main>.t{margin-left:0!important;margin-right:0!important;}',
      '.spx-account-tabs{box-sizing:border-box!important;display:flex!important;flex-wrap:wrap!important;align-items:center!important;justify-content:flex-start!important;gap:8px!important;width:100%!important;margin:0 0 14px!important;padding:12px 14px!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:0 6px 18px rgba(15,23,42,.06)!important;}',
      '.spx-account-tabs a{display:flex!important;align-items:center!important;justify-content:center!important;min-height:34px!important;padding:0 16px!important;border:1px solid #cbd5e1!important;border-radius:8px!important;background:#f8fafc!important;color:#075985!important;font-size:15px!important;font-weight:800!important;line-height:1.2!important;text-decoration:none!important;}',
      '.spx-account-tabs a.spx-account-tab-active,.spx-account-tabs a:hover{background:#e0f2fe!important;border-color:#7dd3fc!important;color:#0369a1!important;text-decoration:none!important;}',
      '.spx-profile-page #u-wrap,.spx-profile-page #u-wrap2{box-sizing:border-box!important;width:100%!important;max-width:none!important;background:#fff!important;overflow:visible!important;}',
      '.spx-profile-page #u-wrap2{display:grid!important;grid-template-columns:minmax(220px,300px) minmax(0,1fr)!important;gap:24px!important;padding:18px!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:0 6px 18px rgba(15,23,42,.06)!important;}',
      '.spx-profile-page #u-sidebar{box-sizing:border-box!important;float:none!important;width:auto!important;min-width:0!important;}',
      '.spx-profile-page #u-sidebar .bdbA,.spx-profile-page #u-sidebar .bgA{box-sizing:border-box!important;width:100%!important;max-width:100%!important;margin:0 0 12px!important;padding:8px 12px!important;background:#fff!important;border:0!important;border-bottom:1px solid #e2e8f0!important;border-radius:0!important;box-shadow:none!important;overflow:visible!important;}',
      '.spx-profile-page #u-sidebar .bdbA:last-child,.spx-profile-page #u-sidebar .bgA:last-child{margin-bottom:0!important;}',
      '.spx-profile-page #u-content{box-sizing:border-box!important;float:none!important;width:auto!important;min-width:0!important;display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr))!important;gap:18px 24px!important;}',
      '.spx-profile-page #u-contentmain{box-sizing:border-box!important;grid-column:1/-1!important;float:none!important;width:100%!important;min-width:0!important;max-width:none!important;}',
      '.spx-profile-page #u-contentside{display:none!important;}',
      '.spx-profile-page #u-top{box-sizing:border-box!important;grid-column:1/-1!important;width:100%!important;height:auto!important;min-height:0!important;padding:0!important;position:static!important;}',
      '.spx-profile-page #u-top-nav{position:static!important;display:block!important;box-sizing:border-box!important;width:100%!important;height:auto!important;margin:12px 0 0!important;padding:0!important;overflow:visible!important;}',
      '.spx-profile-page #u-top-nav .b{display:flex!important;float:none!important;align-items:center!important;justify-content:flex-start!important;flex-wrap:wrap!important;gap:8px!important;width:100%!important;height:auto!important;margin:0!important;padding:0!important;list-style:none!important;overflow:visible!important;}',
      '.spx-profile-page #u-top-nav .b>li{display:block!important;float:none!important;width:auto!important;height:auto!important;margin:0!important;padding:0!important;list-style:none!important;}',
      '.spx-profile-page #u-top-nav .b>li>a{display:flex!important;align-items:center!important;justify-content:center!important;min-height:34px!important;padding:0 16px!important;margin:0!important;border:1px solid #cbd5e1!important;border-radius:8px!important;background:#f8fafc!important;color:#075985!important;font-size:15px!important;font-weight:800!important;line-height:1.2!important;text-decoration:none!important;}',
      '.spx-profile-page #u-top-nav .b>li.current>a,.spx-profile-page #u-top-nav .b>li>a:hover{background:#e0f2fe!important;border-color:#7dd3fc!important;color:#0369a1!important;text-decoration:none!important;}',
      '.spx-profile-page.spx-account-tabs-ready #u-top-nav{display:none!important;}',
      '.spx-profile-page .u-content-66,.spx-profile-page .u-content-33{box-sizing:border-box!important;float:none!important;width:auto!important;min-width:0!important;}',
      '.spx-profile-page .u-profile,.spx-profile-page #u-content>.c{grid-column:1/-1!important;}',
      '.spx-site-shell:not(.spx-reader) .bdbA,.spx-site-shell:not(.spx-reader) .t,.spx-site-shell:not(.spx-reader) .t3,.spx-site-shell:not(.spx-reader) .t5{box-sizing:border-box!important;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)))!important;margin:16px auto 14px!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:var(--spx-radius)!important;box-shadow:var(--spx-shadow-card)!important;overflow:hidden!important;}',
      '.spx-site-shell:not(.spx-reader) .t,.spx-site-shell:not(.spx-reader) .t3,.spx-site-shell:not(.spx-reader) .t5{display:block!important;}',
      '.spx-site-shell:not(.spx-reader) .tr1,.spx-site-shell:not(.spx-reader) .tr2,.spx-site-shell:not(.spx-reader) .tr3{font-size:14px!important;line-height:1.6!important;}',
      '.spx-site-shell:not(.spx-reader) .tr3 td,.spx-site-shell:not(.spx-reader) .tr1 td,.spx-site-shell:not(.spx-reader) th,.spx-site-shell:not(.spx-reader) td{padding:8px 10px!important;}',
      '.spx-task-page #wrapA,.spx-task-page #main{box-sizing:border-box!important;max-width:none!important;background:var(--spx-page-bg)!important;}',
      '.spx-task-page #main{display:block!important;width:calc(100vw - 40px)!important;max-width:1480px!important;margin:16px auto 42px!important;padding:0!important;overflow:hidden!important;}',
      '.spx-task-page #main>.bdbA{box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:0 0 14px!important;}',
      '.spx-task-page #main>.t,.spx-task-page #main>.t3,.spx-task-page #main>.t5{box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:0 0 14px!important;}',
      '.spx-task-page #main>.t table,.spx-task-page #main>.t3 table,.spx-task-page #main>.t5 table{max-width:100%!important;}',
      '@media(max-width:900px){.spx-task-page #main{width:calc(100vw - 16px)!important;margin:10px 8px 34px!important}}',
      '.spx-site-shell #set-wrap{display:grid!important;grid-template-columns:minmax(190px,240px) minmax(0,1fr)!important;gap:16px!important;box-sizing:border-box!important;width:100%!important;max-width:none!important;margin:16px auto 42px!important;padding:0!important;background:transparent!important;line-height:1.6!important;}',
      '.spx-site-shell #set-side,.spx-site-shell #set-content{box-sizing:border-box!important;float:none!important;width:auto!important;min-width:0!important;margin:0!important;padding:0!important;background:transparent!important;}',
      '.spx-site-shell #set-side-wrap,.spx-site-shell #set-content-wrap{box-sizing:border-box!important;width:100%!important;min-width:0!important;margin:0!important;padding:14px!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:0 6px 18px rgba(15,23,42,.06)!important;overflow:visible!important;}',
      '.spx-site-shell #set-side .set-h2{box-sizing:border-box!important;margin:0 0 10px!important;padding:0 2px 10px!important;border-bottom:1px solid #e2e8f0!important;color:#0f172a!important;font-size:16px!important;line-height:1.35!important;font-weight:800!important;}',
      '.spx-site-shell #set-menu{box-sizing:border-box!important;display:flex!important;flex-direction:column!important;gap:6px!important;width:100%!important;margin:0!important;padding:0!important;list-style:none!important;background:transparent!important;}',
      '.spx-site-shell #set-menu>li{display:block!important;width:100%!important;margin:0!important;padding:0!important;list-style:none!important;background:transparent!important;}',
      '.spx-site-shell #set-menu>li>a{display:flex!important;align-items:center!important;min-height:34px!important;box-sizing:border-box!important;width:100%!important;padding:0 12px!important;margin:0!important;border:1px solid #e2e8f0!important;border-radius:8px!important;background:#f8fafc!important;color:#075985!important;font-size:14px!important;font-weight:700!important;line-height:1.25!important;text-decoration:none!important;}',
      '.spx-site-shell #set-menu>li.current>a,.spx-site-shell #set-menu>li>a:hover{background:#e0f2fe!important;border-color:#7dd3fc!important;color:#0369a1!important;text-decoration:none!important;}',
      '.spx-profile-page.spx-account-tabs-ready #set-side{display:none!important;}',
      '.spx-profile-page.spx-account-tabs-ready #set-wrap{grid-template-columns:minmax(0,1fr)!important;}',
      '.spx-site-shell #set-content{box-sizing:border-box!important;display:block!important;overflow:visible!important;}',
      '.spx-site-shell #set-content table{box-sizing:border-box!important;width:100%!important;max-width:100%!important;}',
      '.spx-site-shell #set-content .set-tab-table{box-sizing:border-box!important;width:100%!important;margin:0 0 12px!important;overflow:auto!important;}',
      '.spx-site-shell #set-content .set-tab-box{box-sizing:border-box!important;width:100%!important;overflow:visible!important;}',
    ].concat(
      getInjectedContentLayoutStyleRules(),
      getInjectedWidgetStyleRules(),
      getInjectedThemeOverrideStyleRules(),
      getInjectedPreviewParityStyleRules()
    ).join('\n');
  }

  function getInjectedContentLayoutStyleRules() {
    return [
      '.spx-reader,.spx-reader body{background:#f4f6f8!important;background-image:none!important;color:#263238!important;font:15px/1.7 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif!important;}',
      '.spx-reader #wrapA,.spx-reader #main,.spx-reader #content{background:#f4f6f8!important;background-image:none!important;}',
      '.spx-reader #main>br,.spx-reader #content>br{display:none!important;}',
      '.spx-reader a{color:#075985!important;text-decoration:none!important;}',
      '.spx-reader a:hover{text-decoration:underline!important;}',
      '.spx-reader #wrapA{box-sizing:border-box!important;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)))!important;max-width:none!important;margin:0 auto!important;}',
      '.spx-reader #main,.spx-reader #content{font-size:15px!important;line-height:1.65!important;}',
      '.spx-reader .t,.spx-reader .t3,.spx-reader .t5,.spx-reader .tr1,.spx-reader .tr2,.spx-reader .tr3{font-size:15px!important;}',
      '.spx-reader .tr3 td,.spx-reader .tr1 td{padding-top:8px!important;padding-bottom:8px!important;}',
      '.spx-reader td[id^="td_"]{font-size:15px!important;line-height:1.65!important;}',
      '.spx-reader td[id^="td_"] a[id^="a_ajax_"]{font-size:16px!important;line-height:1.65!important;font-weight:600!important;}',
      '.spx-reader td[id^="td_"] .s8{display:inline-block;margin-right:6px;padding:1px 6px;border-radius:4px;background:#e8f3ff;color:#075985!important;font-size:13px!important;}',
      '.spx-reader table.js-post{box-sizing:border-box!important;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)))!important;max-width:none!important;margin:14px auto!important;background:#fff!important;border:1px solid #d9e2ec!important;border-radius:var(--spx-radius)!important;box-shadow:0 3px 12px rgba(15,23,42,.06)!important;overflow:hidden!important;table-layout:fixed!important;}',
      '.spx-reader table.js-post>tbody>tr>th.r_two{box-sizing:border-box!important;width:128px!important;max-width:128px!important;min-width:128px!important;padding:18px 8px!important;overflow:hidden!important;vertical-align:top!important;text-align:center!important;}',
      '.spx-reader table.js-post>tbody>tr>th.r_two .user-pic{display:block!important;width:96px!important;height:96px!important;max-height:96px!important;margin:0 auto 10px!important;padding:0!important;text-align:center!important;overflow:hidden!important;}',
      '.spx-reader table.js-post>tbody>tr>th.r_two .user-pic img,.spx-reader table.js-post>tbody>tr>th.r_two>img{display:block!important;width:96px!important;max-width:96px!important;height:auto!important;margin:0 auto!important;}',
      '.spx-reader table.js-post>tbody>tr>th.r_two>a[href*="u.php"],.spx-reader table.js-post>tbody>tr>th.r_two .readprofile>a[href],.spx-reader table.js-post>tbody>tr>th.r_two .user-info>a[href]{display:block!important;margin:0 auto!important;padding:0!important;color:var(--spx-link)!important;font-size:16px!important;font-weight:800!important;line-height:1.35!important;text-align:center!important;word-break:break-word!important;}',
      '.spx-reader #content>table:not(.js-post):empty,.spx-reader #main>table:not(.js-post):empty,.spx-reader #content>table:not(.js-post) td:empty,.spx-reader #main>table:not(.js-post) td:empty{display:none!important;}',
      '.spx-reader #content>table:not(.js-post),.spx-reader #main>table:not(.js-post){border:0!important;box-shadow:none!important;background:transparent!important;}',
      '.spx-reader [data-spx-read-separator-hidden="1"]{display:none!important;}',
      '.spx-reader table.js-post>tbody>tr>th.r_one{box-sizing:border-box!important;width:auto!important;min-width:0!important;}',
      '.spx-reader.spx-compact-read table.js-post>tbody>tr>th.r_two,.spx-reader.spx-hide-profile table.js-post>tbody>tr>th.r_two{width:112px!important;max-width:112px!important;min-width:112px!important;}',
      '.spx-reader table.js-post td{font-size:15px!important;line-height:1.75!important;}',
      '.spx-reader .h1,.spx-reader [id^="subject_"]{font-size:18px!important;line-height:1.55!important;font-weight:700!important;color:#111827!important;}',
      '.spx-reader .tpc_content{box-sizing:border-box!important;max-width:var(--spx-reader-line)!important;margin:0 auto!important;padding:14px 18px 20px!important;font-size:16px!important;line-height:1.78!important;letter-spacing:0!important;color:#1f2937!important;word-break:break-word!important;}',
      '.spx-reader .tpc_content #read_tpc,.spx-reader .tpc_content>.f14{font-size:16px!important;line-height:1.78!important;}',
      '.spx-reader .tpc_content br{line-height:1.75!important;}',
      '.spx-reader .tpc_content img{max-width:100%!important;height:auto!important;border-radius:4px!important;}',
      '.spx-reader .tiptop,.spx-reader .readbot{max-width:var(--spx-reader-line)!important;margin-left:auto!important;margin-right:auto!important;color:#64748b!important;}',
      '.spx-reader .signature,.spx-reader .sigline{max-width:var(--spx-reader-line)!important;margin-left:auto!important;margin-right:auto!important;color:#64748b!important;font-size:13px!important;}',
      '.spx-home-dashboard,.spx-home-dashboard body{width:100%!important;min-width:0!important;overflow-x:hidden!important;background:var(--spx-page-bg)!important;color:#172033!important;}',
      '.spx-home-dashboard #wrapA,.spx-home-dashboard #main{box-sizing:border-box!important;width:100vw!important;max-width:none!important;margin:0!important;padding:0!important;background:var(--spx-page-bg)!important;border:0!important;}',
      '.spx-home-dashboard #content{box-sizing:border-box!important;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)))!important;margin:16px auto 42px!important;display:block!important;background:transparent!important;}',
      '.spx-home-dashboard #spx-home-grid{display:grid!important;grid-template-columns:repeat(12,minmax(0,1fr))!important;gap:14px!important;min-width:0!important;}',
      '.spx-home-dashboard #toptool,.spx-home-dashboard #footer,.spx-home-dashboard .footer,.spx-home-dashboard #cate_info{display:none!important;}',
      '.spx-home-dashboard #header,.spx-home-dashboard #mainNav,.spx-home-dashboard #infobox,.spx-home-dashboard #notice{box-sizing:border-box!important;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)))!important;margin-left:auto!important;margin-right:auto!important;}',
      '.spx-home-dashboard #header{margin-top:10px!important;}',
      '.spx-home-dashboard #mainNav{position:sticky!important;top:0!important;z-index:9990!important;border-radius:8px!important;box-shadow:0 4px 16px rgba(15,23,42,.08)!important;overflow:visible!important;}',
      '.spx-home-dashboard #notice{display:block!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;padding:10px 14px!important;box-shadow:0 4px 14px rgba(15,23,42,.05)!important;}',
      '.spx-home-dashboard #notice table,.spx-home-dashboard #notice tbody,.spx-home-dashboard #notice tr{display:block!important;width:100%!important;}',
      '.spx-home-dashboard #notice td{display:block!important;width:auto!important;padding:4px 0!important;}',
      '.spx-home-dashboard .spx-home-quick{display:none!important;}',
      '.spx-home-dashboard .spx-home-quick a{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;min-height:42px!important;padding:0 12px!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;color:#0f172a!important;text-decoration:none!important;font-weight:700!important;box-shadow:0 3px 12px rgba(15,23,42,.05)!important;}',
      '.spx-home-dashboard .spx-home-quick a span{font-size:12px!important;color:#64748b!important;font-weight:500!important;}',
      '.spx-home-dashboard .spx-home-module{grid-column:span 6!important;box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:0 6px 18px rgba(15,23,42,.06)!important;overflow:hidden!important;}',
      '.spx-home-dashboard .spx-home-module[data-spx-large="1"]{grid-column:span 12!important;}',
      '.spx-home-dashboard .spx-home-module>h2,.spx-home-dashboard .spx-home-module .h{display:flex!important;align-items:center!important;justify-content:space-between!important;min-height:38px!important;padding:0 14px!important;margin:0!important;background:#f8fafc!important;border-bottom:1px solid #e2e8f0!important;color:#0f172a!important;font-size:15px!important;font-weight:800!important;}',
      '.spx-home-dashboard .spx-home-module table,.spx-home-dashboard .spx-home-module tbody{display:block!important;width:100%!important;border:0!important;background:transparent!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr2{display:none!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr3{display:grid!important;grid-template-columns:minmax(220px,1.15fr) 120px minmax(260px,1fr)!important;gap:10px!important;align-items:center!important;margin:0!important;padding:10px 14px!important;border-bottom:1px solid #edf2f7!important;background:#fff!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr3:hover{background:#f8fbff!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr3:last-child{border-bottom:0!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr3>td{display:block!important;width:auto!important;padding:0!important;border:0!important;background:transparent!important;font-size:13px!important;line-height:1.45!important;color:#475569!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr3>td:first-child{display:none!important;}',
      '.spx-home-dashboard .spx-home-module [id^="fn_"] a,.spx-home-dashboard .spx-home-module [id^="fn_"]{font-size:15px!important;font-weight:800!important;color:#0f172a!important;line-height:1.4!important;}',
      '.spx-home-dashboard .spx-home-module [id^="desc_"]{margin-top:4px!important;color:#64748b!important;font-size:12px!important;}',
      '.spx-home-dashboard .spx-home-hot [id^="fn_"] a,.spx-home-dashboard .spx-home-hot [id^="fn_"]{color:#075985!important;}',
      '.spx-home-dashboard .spx-home-badge{display:inline-flex!important;align-items:center!important;justify-content:center!important;min-width:26px!important;height:20px!important;margin-left:6px!important;padding:0 7px!important;border-radius:999px!important;background:#e0f2fe!important;color:#0369a1!important;font-size:12px!important;font-weight:800!important;}',
      '.spx-home-dashboard .spx-home-collapse{border:0!important;background:transparent!important;color:#64748b!important;font-size:12px!important;cursor:pointer!important;}',
      '.spx-forum-dashboard,.spx-forum-dashboard body{width:100%!important;min-width:0!important;overflow-x:hidden!important;background:var(--spx-page-bg)!important;color:#172033!important;}',
      '.spx-forum-dashboard #wrapA,.spx-forum-dashboard #main{box-sizing:border-box!important;width:100vw!important;max-width:none!important;margin:0!important;padding:0!important;background:var(--spx-page-bg)!important;border:0!important;}',
      '.spx-forum-dashboard #content{box-sizing:border-box!important;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)))!important;margin:16px auto 42px!important;display:block!important;background:transparent!important;}',
      '.spx-forum-dashboard #content .t{box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:0 0 14px!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:0 6px 18px rgba(15,23,42,.06)!important;overflow:hidden!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table table,.spx-forum-dashboard #content .t.spx-thread-list-table tbody{display:block!important;width:100%!important;border:0!important;background:transparent!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr2{display:none!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3{display:grid!important;grid-template-columns:minmax(0,1fr) 96px 96px minmax(220px,.8fr)!important;gap:10px!important;align-items:center!important;margin:0!important;padding:10px 14px!important;border-bottom:1px solid #edf2f7!important;background:#fff!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3:hover{background:#f8fbff!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3:last-child{border-bottom:0!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3>td{display:block!important;width:auto!important;padding:0!important;border:0!important;background:transparent!important;font-size:13px!important;line-height:1.45!important;color:#475569!important;overflow:hidden!important;text-overflow:ellipsis!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3>td:first-child:not([id^="td_"]){display:none!important;}',
      '.spx-forum-dashboard td[id^="td_"]{font-size:14px!important;line-height:1.6!important;color:#475569!important;}',
      '.spx-forum-dashboard td[id^="td_"] a[id^="a_ajax_"],.spx-forum-dashboard [id^="td_"] a[href*="read.php"]{font-size:17px!important;font-weight:800!important;color:#075985!important;line-height:1.45!important;}',
      '.spx-forum-dashboard .gonggao{display:none!important;}',
      '.spx-forum-dashboard .spx-thread-tools{margin-left:8px!important;}',
      '.spx-immersive-read,.spx-immersive-read body{width:100%!important;min-width:0!important;overflow-x:hidden!important;background:var(--spx-page-bg)!important;}',
      '.spx-immersive-read #toptool,.spx-immersive-read #banner,.spx-immersive-read #footer,.spx-immersive-read .footer,.spx-immersive-read #bottom,.spx-immersive-read #music,.spx-immersive-read #readlog,.spx-immersive-read #threadlog{display:none!important;}',
      '.spx-immersive-read #header,.spx-immersive-read #mainNav,.spx-immersive-read #breadcrumbs,.spx-immersive-read .crumbs-item{box-sizing:border-box!important;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)))!important;margin-left:auto!important;margin-right:auto!important;}',
      '.spx-immersive-read #header{margin-top:10px!important;}',
      '.spx-immersive-read #mainNav{position:sticky!important;top:0!important;z-index:9990!important;border-radius:8px!important;box-shadow:0 4px 16px rgba(15,23,42,.08)!important;overflow:visible!important;}',
      '.spx-immersive-read .bdbA{display:block!important;box-sizing:border-box!important;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)))!important;margin:10px auto 0!important;padding:0!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:var(--spx-radius)!important;box-shadow:0 4px 14px rgba(15,23,42,.05)!important;overflow:visible!important;}',
      '.spx-immersive-read #breadcrumbs{display:flex!important;align-items:center!important;flex-wrap:wrap!important;gap:6px!important;width:100%!important;min-height:36px!important;margin:0!important;padding:7px 12px!important;overflow:visible!important;color:#64748b!important;font-size:13px!important;line-height:1.45!important;background:transparent!important;}',
      '.spx-immersive-read #breadcrumbs .crumbs-item{display:inline-flex!important;align-items:center!important;width:auto!important;max-width:100%!important;margin:0!important;overflow:visible!important;white-space:nowrap!important;color:#64748b!important;}',
      '.spx-immersive-read #breadcrumbs .crumbs-item.current{min-width:0!important;white-space:normal!important;color:#0f172a!important;font-weight:700!important;}',
      '.spx-immersive-read #wrapA,.spx-immersive-read #main,.spx-immersive-read #content{box-sizing:border-box!important;width:100vw!important;max-width:none!important;margin:0!important;padding:0!important;background:var(--spx-page-bg)!important;border:0!important;}',
      '.spx-immersive-read #content>table:not(.js-post),.spx-immersive-read #main>table:not(.js-post){width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)))!important;margin:12px auto!important;}',
      '.spx-immersive-read table.js-post{box-sizing:border-box!important;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)))!important;max-width:none!important;margin:18px auto!important;border:1px solid #d6dee8!important;border-radius:var(--spx-radius)!important;background:#fff!important;box-shadow:0 8px 26px rgba(15,23,42,.08)!important;}',
      '.spx-immersive-read table.js-post>tbody>tr>td:first-child{display:none!important;}',
      '.spx-immersive-read table.js-post>tbody>tr>td{display:block!important;box-sizing:border-box!important;width:100%!important;padding:0!important;border:0!important;background:#fff!important;}',
      '.spx-immersive-read .spx-post-tools{box-sizing:border-box!important;max-width:var(--spx-reader-line)!important;margin:0 auto!important;padding:8px 18px 0!important;color:#94a3b8!important;font-size:12px!important;opacity:.62!important;}',
      '.spx-immersive-read .spx-post-tools:hover{opacity:1!important;}',
      '.spx-immersive-read .spx-post-tools span{font-size:12px!important;font-weight:400!important;color:#94a3b8!important;}',
      '.spx-immersive-read .spx-post-tools button{font-size:12px!important;color:#94a3b8!important;border-color:#e2e8f0!important;background:#f8fafc!important;padding:1px 7px!important;}',
      '.spx-immersive-read .spx-preview-panel{box-sizing:border-box!important;max-width:100%!important;margin:0 auto 14px!important;padding:12px 18px 16px!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:0 6px 18px rgba(15,23,42,.06)!important;}',
      '.spx-immersive-read .spx-preview-header{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:0 0 10px;font-size:13px!important;color:#64748b!important;}',
      '.spx-immersive-read .spx-preview-header strong{font-size:14px!important;color:#0f172a!important;}',
      '.spx-immersive-read .spx-preview-summary{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}',
      '.spx-immersive-read .spx-preview-actions{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:6px;}',
      '.spx-immersive-read .spx-preview-actions button{height:26px;margin:0;padding:0 8px;border:1px solid #cbd5e1;border-radius:6px;background:#f8fafc;color:#334155;font-size:12px;line-height:1;cursor:pointer;}',
      '.spx-immersive-read .spx-preview-actions button:hover,.spx-immersive-read .spx-preview-actions button:focus-visible{border-color:#38bdf8;background:#e0f2fe;color:#075985;outline:none;}',
      '.spx-immersive-read .spx-preview-actions button[aria-pressed="true"]{border-color:#0284c7;background:#0284c7;color:#fff;}',
      '.spx-immersive-read .spx-preview-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px;}',
      '.spx-immersive-read .spx-preview-item{display:block;overflow:hidden;border:1px solid #e2e8f0;border-radius:8px;background:#f8fafc;text-decoration:none;}',
      '.spx-immersive-read .spx-preview-item img{display:block;width:100%;height:180px;object-fit:cover;background:#fff;}',
      '.spx-immersive-read .spx-preview-item span{display:block;padding:6px 8px;font-size:12px;line-height:1.35;color:#475569;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}',
      '.spx-immersive-read .spx-preview-load-more{display:block;width:100%;height:32px;margin:10px 0 0;border:1px solid #cbd5e1;border-radius:7px;background:#fff;color:#334155;font-size:12px;cursor:pointer;}',
      '.spx-immersive-read .spx-preview-load-more:hover,.spx-immersive-read .spx-preview-load-more:focus-visible{border-color:#38bdf8;background:#e0f2fe;color:#075985;outline:none;}',
      '.spx-immersive-read .spx-preview-load-more[hidden]{display:none!important;}',
      '.spx-immersive-read .spx-preview-empty{padding:10px 2px;color:#94a3b8;font-size:13px;}',
      '.spx-immersive-read .spx-preview-source{display:none!important;}',
      '.spx-immersive-read .h1,.spx-immersive-read [id^="subject_"]{display:block!important;box-sizing:border-box!important;max-width:var(--spx-reader-line)!important;margin:0 auto!important;padding:22px 18px 8px!important;font-size:21px!important;line-height:1.45!important;color:#111827!important;}',
      '.spx-immersive-read .tpc_content{box-sizing:border-box!important;max-width:var(--spx-reader-line)!important;margin:0 auto!important;padding:18px 34px 34px!important;font-size:16px!important;line-height:1.78!important;color:#172033!important;background:#fff!important;}',
      '.spx-immersive-read .tpc_content #read_tpc,.spx-immersive-read .tpc_content>.f14{font-size:16px!important;line-height:1.78!important;}',
      '.spx-immersive-read .tiptop,.spx-immersive-read .readbot,.spx-immersive-read .signature,.spx-immersive-read .sigline{max-width:var(--spx-reader-line)!important;margin-left:auto!important;margin-right:auto!important;padding-left:18px!important;padding-right:18px!important;color:#94a3b8!important;font-size:12px!important;opacity:.58!important;}',
      '.spx-immersive-read textarea,.spx-immersive-read input[type="text"]{font-size:16px!important;}',
      '.spx-reader .spx-post-body-split,.spx-immersive-read .spx-post-body-split{box-sizing:border-box!important;display:grid!important;grid-template-columns:minmax(320px,clamp(360px,34vw,560px)) minmax(0,1fr)!important;gap:clamp(14px,2vw,24px)!important;align-items:start!important;max-width:100%!important;margin:0 auto!important;padding:18px clamp(18px,3vw,36px) 36px!important;background:#fff!important;}',
      '.spx-reader .spx-post-body-split .tpc_content,.spx-immersive-read .spx-post-body-split .tpc_content{box-sizing:border-box!important;width:100%!important;max-width:var(--spx-reader-line)!important;margin:0 auto!important;padding:0!important;background:transparent!important;}',
      '.spx-reader .spx-preview-panel,.spx-immersive-read .spx-preview-panel{box-sizing:border-box!important;max-width:none!important;margin:0!important;padding:10px!important;background:#f8fafc!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:none!important;max-height:min(72vh,680px)!important;overflow:auto!important;}',
      '.spx-reader .spx-preview-header,.spx-immersive-read .spx-preview-header{position:sticky!important;top:0!important;z-index:3!important;display:flex!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;margin:0 0 10px!important;background:inherit!important;font-size:13px!important;color:#64748b!important;}',
      '.spx-reader .spx-preview-header strong,.spx-immersive-read .spx-preview-header strong{font-size:14px!important;color:#0f172a!important;}',
      '.spx-reader .spx-preview-summary,.spx-immersive-read .spx-preview-summary{min-width:0!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;}',
      '.spx-reader .spx-preview-actions,.spx-immersive-read .spx-preview-actions{display:flex!important;align-items:center!important;justify-content:flex-end!important;flex-wrap:wrap!important;gap:6px!important;}',
      '.spx-reader .spx-preview-actions button,.spx-immersive-read .spx-preview-actions button{box-sizing:border-box!important;height:26px!important;margin:0!important;padding:0 8px!important;border:1px solid #cbd5e1!important;border-radius:6px!important;background:#f8fafc!important;color:#334155!important;font-size:12px!important;line-height:1!important;cursor:pointer!important;}',
      '.spx-reader .spx-preview-actions button:hover,.spx-reader .spx-preview-actions button:focus-visible,.spx-immersive-read .spx-preview-actions button:hover,.spx-immersive-read .spx-preview-actions button:focus-visible{border-color:#38bdf8!important;background:#e0f2fe!important;color:#075985!important;outline:none!important;}',
      '.spx-reader .spx-preview-actions button[aria-pressed="true"],.spx-immersive-read .spx-preview-actions button[aria-pressed="true"]{border-color:#0284c7!important;background:#0284c7!important;color:#fff!important;}',
      '.spx-reader .spx-preview-grid,.spx-immersive-read .spx-preview-grid{display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr))!important;gap:8px!important;}',
      '.spx-reader .spx-preview-item,.spx-immersive-read .spx-preview-item{display:block!important;overflow:hidden!important;border:1px solid #e2e8f0!important;border-radius:8px!important;background:#fff!important;text-decoration:none!important;}',
      '.spx-reader .spx-preview-item img,.spx-immersive-read .spx-preview-item img{display:block!important;width:100%!important;height:190px!important;object-fit:cover!important;background:#fff!important;}',
      '.spx-reader .spx-preview-item img[data-spx-preview-ready="0"],.spx-immersive-read .spx-preview-item img[data-spx-preview-ready="0"],.spx-preview-images img[data-spx-preview-ready="0"],.spx-preview-lightbox-image[data-spx-preview-ready="0"],.spx-preview-lightbox-thumb img[data-spx-preview-ready="0"]{opacity:0!important;}',
      '.spx-reader .spx-preview-item img[data-spx-preview-ready="1"],.spx-immersive-read .spx-preview-item img[data-spx-preview-ready="1"],.spx-preview-images img[data-spx-preview-ready="1"],.spx-preview-lightbox-image[data-spx-preview-ready="1"],.spx-preview-lightbox-thumb img[data-spx-preview-ready="1"]{opacity:1!important;transition:opacity .16s ease!important;}',
      '.spx-reader .spx-preview-item .spx-preview-hover-image,.spx-immersive-read .spx-preview-item .spx-preview-hover-image{display:none!important;position:fixed!important;left:50%!important;top:50%!important;transform:translate(-50%,-50%)!important;z-index:100002!important;width:auto!important;height:auto!important;max-width:min(64vw,900px)!important;max-height:min(78vh,760px)!important;object-fit:contain!important;padding:6px!important;background:#fff!important;border:1px solid #cbd5e1!important;border-radius:10px!important;box-shadow:0 18px 52px rgba(15,23,42,.28)!important;pointer-events:none!important;transition:opacity .18s ease!important;}',
      '.spx-reader .spx-preview-item:hover .spx-preview-hover-image,.spx-immersive-read .spx-preview-item:hover .spx-preview-hover-image{display:block!important;opacity:.92!important;}',
      '.spx-reader .spx-preview-item:hover .spx-preview-hover-image[data-spx-preview-ready="0"],.spx-immersive-read .spx-preview-item:hover .spx-preview-hover-image[data-spx-preview-ready="0"]{opacity:0!important;}',
      '.spx-reader .spx-preview-item span,.spx-immersive-read .spx-preview-item span{display:block!important;padding:5px 7px!important;font-size:12px!important;line-height:1.35!important;color:#475569!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}',
      '.spx-reader .spx-preview-load-more,.spx-immersive-read .spx-preview-load-more{box-sizing:border-box!important;display:block!important;width:100%!important;height:32px!important;margin:10px 0 0!important;border:1px solid #cbd5e1!important;border-radius:7px!important;background:#fff!important;color:#334155!important;font-size:12px!important;cursor:pointer!important;}',
      '.spx-reader .spx-preview-load-more:hover,.spx-reader .spx-preview-load-more:focus-visible,.spx-immersive-read .spx-preview-load-more:hover,.spx-immersive-read .spx-preview-load-more:focus-visible{border-color:#38bdf8!important;background:#e0f2fe!important;color:#075985!important;outline:none!important;}',
      '.spx-reader .spx-preview-load-more[hidden],.spx-immersive-read .spx-preview-load-more[hidden]{display:none!important;}',
      '.spx-preview-download{box-sizing:border-box!important;display:grid!important;gap:7px!important;margin:0 0 10px!important;padding:8px 9px!important;border:1px solid var(--spx-line-soft)!important;border-radius:8px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;}',
      '.spx-preview-download[hidden]{display:none!important;}.spx-preview-download-top{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;color:var(--spx-strong)!important;font-size:12px!important;font-weight:900!important;}.spx-preview-download-percent{flex:none!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:850!important;}',
      '.spx-preview-download-track{height:8px!important;overflow:hidden!important;border-radius:999px!important;background:var(--spx-line-soft)!important;}.spx-preview-download-progress{display:block!important;width:0;height:100%!important;border-radius:inherit!important;background:linear-gradient(90deg,var(--spx-accent),var(--spx-link))!important;transition:width .16s ease!important;}',
      '.spx-preview-download-compact{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;}.spx-preview-download-badges{display:flex!important;flex-wrap:wrap!important;gap:5px!important;min-width:0!important;}.spx-preview-download-badge{display:inline-flex!important;align-items:center!important;justify-content:center!important;min-height:22px!important;padding:0 8px!important;border-radius:999px!important;background:#e0f2fe!important;color:#075985!important;font-size:11px!important;font-weight:900!important;white-space:nowrap!important;}.spx-preview-download-badge.spx-ok{background:var(--spx-ok-soft,#dcfce7)!important;color:var(--spx-ok,#15803d)!important;}.spx-preview-download-badge.spx-fail{background:var(--spx-danger-soft)!important;color:var(--spx-danger)!important;}.spx-preview-download-badge.spx-zip{background:#ffedd5!important;color:var(--spx-warn)!important;}',
      '.spx-preview-download-actions{display:flex!important;align-items:center!important;justify-content:flex-end!important;flex-wrap:wrap!important;gap:6px!important;}.spx-preview-download-actions button{height:24px!important;margin:0!important;padding:0 8px!important;border:1px solid var(--spx-line)!important;border-radius:7px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;font-size:11px!important;font-weight:850!important;line-height:1!important;cursor:pointer!important;}.spx-preview-download-actions button:hover,.spx-preview-download-actions button:focus-visible{border-color:var(--spx-accent)!important;color:var(--spx-accent)!important;outline:none!important;}.spx-preview-download-actions .spx-primary{border-color:var(--spx-accent)!important;background:var(--spx-accent)!important;color:#fff!important;}.spx-preview-download-actions button[disabled]{opacity:.58!important;cursor:not-allowed!important;}',
      '.spx-preview-download-detail{margin:0!important;border:1px solid var(--spx-line-soft)!important;border-radius:8px!important;background:var(--spx-panel)!important;}.spx-preview-download-detail summary{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;min-height:32px!important;padding:0 9px!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:900!important;cursor:pointer!important;list-style:none!important;}.spx-preview-download-detail summary::-webkit-details-marker{display:none!important;}.spx-preview-download-detail summary:after{content:"展开";flex:none!important;color:var(--spx-link)!important;font-size:11px!important;font-weight:900!important;}.spx-preview-download-detail[open] summary{border-bottom:1px solid var(--spx-line-soft)!important;}.spx-preview-download-detail[open] summary:after{content:"收起";}.spx-preview-download-detail-text{min-width:0!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;}',
      '.spx-preview-download-queue{display:grid!important;gap:7px!important;padding:8px!important;}.spx-preview-download-row{display:grid!important;grid-template-columns:minmax(0,1fr) auto!important;gap:8px!important;align-items:center!important;padding:7px!important;border:1px solid var(--spx-line-soft)!important;border-radius:8px!important;background:var(--spx-panel-muted)!important;}.spx-preview-download-name{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;margin:0 0 5px!important;color:var(--spx-strong)!important;font-size:12px!important;font-weight:900!important;}.spx-preview-download-name b{min-width:0!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;}.spx-preview-download-name em{flex:none!important;color:var(--spx-muted)!important;font-style:normal!important;font-size:11px!important;font-weight:850!important;}.spx-preview-download-meta{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;margin-top:5px!important;color:var(--spx-muted)!important;font-size:11px!important;font-weight:800!important;}.spx-preview-download-row button{height:24px!important;padding:0 8px!important;border:1px solid #fecaca!important;border-radius:7px!important;background:var(--spx-danger-soft)!important;color:var(--spx-danger)!important;font-size:11px!important;font-weight:850!important;cursor:pointer!important;}.spx-preview-download-row button[disabled]{color:var(--spx-muted)!important;background:var(--spx-panel-muted)!important;border-color:var(--spx-line-soft)!important;cursor:not-allowed!important;}',
      '.spx-preview-download-report{display:grid!important;gap:7px!important;padding:9px!important;border:1px solid #fed7aa!important;border-radius:8px!important;background:#fff7ed!important;color:var(--spx-warn)!important;font-size:12px!important;font-weight:850!important;}.spx-preview-download-report strong{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;color:#7c2d12!important;font-size:12px!important;font-weight:900!important;}',
      '.spx-reader .spx-preview-source,.spx-immersive-read .spx-preview-source{display:none!important;}',
      '.spx-preview-panel.spx-preview-drawer{position:fixed!important;right:14px!important;top:86px!important;bottom:18px!important;z-index:99970!important;box-sizing:border-box!important;width:min(clamp(360px,28vw,500px),calc(100vw - 28px))!important;max-width:none!important;max-height:none!important;margin:0!important;padding:10px!important;overflow:auto!important;background:var(--spx-panel-muted)!important;border:1px solid #cbd5e1!important;border-radius:var(--spx-radius-lg)!important;box-shadow:var(--spx-shadow-popover)!important;}',
      '.spx-preview-panel.spx-preview-drawer .spx-preview-grid,.spx-preview-panel.spx-preview-masonry .spx-preview-grid{display:block!important;column-width:150px!important;column-gap:8px!important;}',
      '.spx-preview-panel.spx-preview-drawer .spx-preview-item,.spx-preview-panel.spx-preview-masonry .spx-preview-item{display:block!important;break-inside:avoid!important;margin:0 0 8px!important;background:#fff!important;}',
      '.spx-preview-panel.spx-preview-drawer .spx-preview-item img:not(.spx-preview-hover-image),.spx-preview-panel.spx-preview-masonry .spx-preview-item img:not(.spx-preview-hover-image){width:100%!important;height:auto!important;max-height:none!important;object-fit:contain!important;}',
      '.spx-preview-panel.spx-preview-drawer .spx-preview-drawer-tab{display:none!important;}',
      '.spx-preview-panel.spx-preview-drawer.spx-preview-collapsed{top:120px!important;bottom:auto!important;width:48px!important;height:132px!important;padding:0!important;overflow:hidden!important;border-radius:10px 0 0 10px!important;}',
      '.spx-preview-panel.spx-preview-drawer.spx-preview-collapsed .spx-preview-header,.spx-preview-panel.spx-preview-drawer.spx-preview-collapsed .spx-preview-download,.spx-preview-panel.spx-preview-drawer.spx-preview-collapsed .spx-preview-grid,.spx-preview-panel.spx-preview-drawer.spx-preview-collapsed .spx-preview-load-more{display:none!important;}',
      '.spx-preview-panel.spx-preview-drawer.spx-preview-collapsed .spx-preview-drawer-tab{display:flex!important;align-items:center!important;justify-content:center!important;width:100%!important;height:100%!important;border:0!important;border-radius:0!important;background:var(--spx-accent)!important;color:#fff!important;font-weight:900!important;writing-mode:vertical-rl!important;letter-spacing:.08em!important;cursor:pointer!important;}',
      '.spx-preview-lightbox{position:fixed!important;inset:0!important;z-index:100010!important;box-sizing:border-box!important;display:flex!important;padding:18px!important;background:rgba(2,6,23,.9)!important;backdrop-filter:blur(5px)!important;color:#e2e8f0!important;font:13px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif!important;}',
      '.spx-preview-lightbox-shell{box-sizing:border-box!important;display:flex!important;flex:1!important;min-width:0!important;min-height:0!important;flex-direction:column!important;overflow:hidden!important;border:1px solid rgba(148,163,184,.34)!important;border-radius:12px!important;background:#020617!important;box-shadow:0 24px 80px rgba(0,0,0,.5)!important;}',
      '.spx-preview-lightbox-toolbar{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:12px!important;min-height:48px!important;padding:8px 10px 8px 14px!important;border-bottom:1px solid rgba(148,163,184,.22)!important;background:#0f172a!important;}',
      '.spx-preview-lightbox-counter{font-weight:700!important;color:#f8fafc!important;white-space:nowrap!important;}',
      '.spx-preview-lightbox-actions{display:flex!important;align-items:center!important;justify-content:flex-end!important;flex-wrap:wrap!important;gap:6px!important;}',
      '.spx-preview-lightbox button{box-sizing:border-box!important;min-width:34px!important;height:32px!important;margin:0!important;padding:0 10px!important;border:1px solid #334155!important;border-radius:7px!important;background:#1e293b!important;color:#e2e8f0!important;cursor:pointer!important;font:600 13px/1 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif!important;}',
      '.spx-preview-lightbox button:hover,.spx-preview-lightbox button:focus-visible{border-color:#38bdf8!important;background:#0c4a6e!important;color:#fff!important;outline:none!important;}',
      '.spx-preview-lightbox-zoom{display:inline-flex!important;min-width:58px!important;justify-content:center!important;color:#cbd5e1!important;font-variant-numeric:tabular-nums!important;}',
      '.spx-preview-lightbox-stage{position:relative!important;flex:1!important;min-height:0!important;overflow:hidden!important;background:#020617!important;}',
      '.spx-preview-lightbox-viewport{position:absolute!important;inset:0!important;overflow:auto!important;overscroll-behavior:contain!important;}',
      '.spx-preview-lightbox-canvas{box-sizing:border-box!important;display:flex!important;align-items:center!important;justify-content:center!important;min-width:100%!important;min-height:100%!important;padding:34px 78px!important;}',
      '.spx-preview-lightbox-image{display:block!important;flex:none!important;max-width:none!important;max-height:none!important;object-fit:contain!important;background:#fff!important;box-shadow:0 16px 50px rgba(0,0,0,.45)!important;}',
      '.spx-preview-lightbox-nav{position:absolute!important;top:50%!important;z-index:2!important;width:46px!important;height:64px!important;padding:0!important;transform:translateY(-50%)!important;border-color:rgba(148,163,184,.35)!important;background:rgba(15,23,42,.78)!important;font-size:30px!important;}',
      '.spx-preview-lightbox-prev{left:14px!important;}',
      '.spx-preview-lightbox-next{right:14px!important;}',
      '.spx-preview-lightbox-caption{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:12px!important;min-height:38px!important;padding:7px 14px!important;border-top:1px solid rgba(148,163,184,.22)!important;background:#0f172a!important;color:#94a3b8!important;}',
      '.spx-preview-lightbox-url{min-width:0!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;}',
      '.spx-preview-lightbox-help{flex:none!important;white-space:nowrap!important;font-size:12px!important;color:#64748b!important;}',
      '.spx-preview-lightbox-strip{display:flex!important;gap:7px!important;overflow-x:auto!important;padding:8px 10px!important;border-top:1px solid rgba(148,163,184,.18)!important;background:#020617!important;scrollbar-width:thin!important;}',
      '.spx-preview-lightbox-thumb{box-sizing:border-box!important;flex:none!important;width:58px!important;height:46px!important;padding:0!important;border:2px solid transparent!important;border-radius:7px!important;overflow:hidden!important;background:#111827!important;opacity:.62!important;cursor:pointer!important;}',
      '.spx-preview-lightbox-thumb.spx-active{border-color:#38bdf8!important;opacity:1!important;}',
      '.spx-preview-lightbox-thumb img{display:block!important;width:100%!important;height:100%!important;object-fit:cover!important;}',
      '.spx-clean #notice,.spx-clean #footer,.spx-clean .footer{display:none!important;}',
      '.spx-clean:not(.spx-site-shell) #wrapA{max-width:1180px!important;margin:0 auto!important;}',
      '.spx-clean #main{margin-top:8px!important;}',
      '.spx-clean table{border-collapse:collapse;}',
    ];
  }

  function getInjectedWidgetStyleRules() {
    return [
      '.spx-toolbar{position:fixed;right:14px;bottom:18px;z-index:99999;display:flex;flex-direction:column;gap:7px;box-sizing:border-box;padding:6px;border:1px solid rgba(203,213,225,.8);border-radius:16px;background:rgba(255,255,255,.92);box-shadow:0 18px 46px rgba(15,23,42,.18);font:12px/1.2 Arial,Helvetica,sans-serif;backdrop-filter:blur(10px);}',
      '.spx-toolbar button,.spx-toolbar a{width:52px;height:36px;border:1px solid transparent;border-radius:12px;background:#fff;color:var(--spx-text);box-shadow:0 4px 12px rgba(15,23,42,.08);cursor:pointer;text-align:center;text-decoration:none;display:flex;align-items:center;justify-content:center;padding:0;font-size:13px;font-weight:800;transition:background .16s ease,border-color .16s ease,color .16s ease,transform .16s ease,box-shadow .16s ease;}',
      '.spx-toolbar button:hover,.spx-toolbar a:hover,.spx-toolbar button:focus-visible,.spx-toolbar a:focus-visible{border-color:var(--spx-accent);color:var(--spx-accent);box-shadow:0 7px 18px rgba(15,118,110,.16);outline:none;transform:translateY(-1px);}',
      '.spx-toolbar .spx-active{background:var(--spx-accent-soft);border-color:var(--spx-accent);color:var(--spx-accent);font-weight:bold;box-shadow:inset 0 0 0 1px rgba(15,118,110,.12),0 6px 16px rgba(15,118,110,.14);}',
      '.spx-command-overlay{position:fixed!important;inset:0!important;z-index:100080!important;box-sizing:border-box!important;display:flex!important;align-items:flex-start!important;justify-content:center!important;padding:72px 24px 28px!important;background:rgba(15,23,42,.28)!important;backdrop-filter:blur(3px)!important;color:var(--spx-text)!important;font:13px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif!important;}',
      '.spx-command-overlay[hidden]{display:none!important;}.spx-command-wrap{box-sizing:border-box!important;display:grid!important;grid-template-columns:minmax(0,720px) minmax(220px,300px)!important;gap:12px!important;width:min(1032px,calc(100vw - 72px))!important;align-items:start!important;}.spx-command-palette,.spx-command-detail{box-sizing:border-box!important;overflow:hidden!important;border:1px solid var(--spx-line)!important;border-radius:14px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;box-shadow:var(--spx-shadow-popover)!important;}.spx-command-palette{display:flex!important;flex-direction:column!important;max-height:calc(100vh - 112px)!important;}',
      '.spx-command-head{display:grid!important;gap:10px!important;padding:14px!important;border-bottom:1px solid var(--spx-line-soft)!important;background:linear-gradient(180deg,#fff 0%,#f8fafc 100%)!important;}.spx-command-title{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:12px!important;}.spx-command-title strong{display:block!important;color:var(--spx-strong)!important;font-size:15px!important;line-height:1.25!important;font-weight:900!important;}.spx-command-title span{color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;white-space:nowrap!important;}.spx-command-close{flex:none!important;width:30px!important;height:30px!important;border:1px solid var(--spx-line)!important;border-radius:9px!important;background:var(--spx-panel)!important;color:var(--spx-sub)!important;font-size:16px!important;font-weight:900!important;cursor:pointer!important;}',
      '.spx-command-search-wrap{position:relative!important;}.spx-command-search{box-sizing:border-box!important;width:100%!important;height:42px!important;padding:0 42px 0 14px!important;border:1px solid var(--spx-line)!important;border-radius:9px!important;background:var(--spx-input-bg)!important;color:var(--spx-text)!important;font-size:15px!important;font-weight:850!important;outline:none!important;}.spx-command-search:focus{border-color:var(--spx-accent)!important;box-shadow:0 0 0 3px rgba(37,99,235,.12)!important;}.spx-command-search-mark{position:absolute!important;right:12px!important;top:50%!important;transform:translateY(-50%)!important;color:var(--spx-muted)!important;font-size:13px!important;font-weight:900!important;}',
      '.spx-command-tabs{display:flex!important;gap:7px!important;min-width:0!important;overflow-x:auto!important;scrollbar-width:thin!important;}.spx-command-tab{flex:none!important;min-height:28px!important;padding:0 10px!important;border:1px solid var(--spx-line)!important;border-radius:999px!important;background:var(--spx-panel)!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;cursor:pointer!important;}.spx-command-tab.spx-active{border-color:var(--spx-accent)!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;}',
      '.spx-command-list{display:grid!important;flex:1 1 auto!important;min-height:0!important;max-height:min(56vh,474px)!important;overflow:auto!important;padding:10px!important;scrollbar-width:thin!important;}.spx-command-item{box-sizing:border-box!important;display:grid!important;grid-template-columns:38px minmax(0,1fr) auto!important;gap:10px!important;align-items:center!important;min-height:64px!important;width:100%!important;padding:9px 10px!important;border:1px solid transparent!important;border-radius:12px!important;background:transparent!important;color:var(--spx-text)!important;text-align:left!important;cursor:pointer!important;}.spx-command-item:hover,.spx-command-item.spx-active{border-color:var(--spx-line)!important;background:var(--spx-panel-muted)!important;}.spx-command-item.spx-active{box-shadow:inset 3px 0 0 var(--spx-accent)!important;}',
      '.spx-command-icon{display:inline-flex!important;width:38px!important;height:38px!important;align-items:center!important;justify-content:center!important;border:1px solid var(--spx-line)!important;border-radius:10px!important;background:var(--spx-panel)!important;color:var(--spx-accent)!important;font-size:13px!important;font-weight:900!important;}.spx-command-main{min-width:0!important;}.spx-command-main strong{display:block!important;color:var(--spx-strong)!important;font-size:14px!important;line-height:1.3!important;font-weight:900!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}.spx-command-main span{display:block!important;margin-top:3px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:750!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}',
      '.spx-command-tail{display:flex!important;align-items:center!important;gap:7px!important;color:var(--spx-muted)!important;font-size:11px!important;font-weight:900!important;white-space:nowrap!important;}.spx-command-pill,.spx-command-kbd{display:inline-flex!important;align-items:center!important;justify-content:center!important;min-height:22px!important;padding:0 8px!important;border-radius:999px!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;font-size:11px!important;font-weight:900!important;}.spx-command-kbd{min-width:34px!important;height:20px!important;border:1px solid var(--spx-line)!important;border-radius:6px!important;background:var(--spx-panel)!important;color:var(--spx-sub)!important;box-shadow:inset 0 -1px 0 rgba(15,23,42,.08)!important;}.spx-command-pill.spx-warn{background:#ffedd5!important;color:#7c2d12!important;}.spx-command-pill.spx-ok{background:#dcfce7!important;color:#15803d!important;}',
      '.spx-command-empty{padding:22px 10px!important;color:var(--spx-sub)!important;text-align:center!important;font-size:13px!important;font-weight:800!important;}.spx-command-foot{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;padding:9px 14px!important;border-top:1px solid var(--spx-line-soft)!important;background:var(--spx-panel-muted)!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:850!important;}.spx-command-keys{display:flex!important;flex-wrap:wrap!important;gap:6px!important;justify-content:flex-end!important;}',
      '.spx-command-detail{padding:14px!important;}.spx-command-detail h3{margin:0!important;color:var(--spx-strong)!important;font-size:15px!important;line-height:1.3!important;font-weight:900!important;}.spx-command-detail p{margin:5px 0 12px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:750!important;}.spx-command-detail-card{display:grid!important;gap:9px!important;padding:11px!important;border:1px solid var(--spx-line-soft)!important;border-radius:12px!important;background:var(--spx-panel-muted)!important;}.spx-command-detail-row{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;min-height:26px!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:850!important;}.spx-command-detail-row span:first-child{flex:none!important;color:var(--spx-sub)!important;}.spx-command-detail-row strong,.spx-command-detail-row a{min-width:0!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;text-align:right!important;color:inherit!important;font-weight:900!important;text-decoration:none!important;}.spx-command-detail-row a:hover{text-decoration:underline!important;}.spx-command-detail-target{align-items:flex-start!important;}.spx-command-detail-target strong,.spx-command-detail-target a{white-space:normal!important;overflow:visible!important;overflow-wrap:anywhere!important;word-break:break-word!important;line-height:1.35!important;text-align:left!important;color:var(--spx-link)!important;}',
      '@media(max-width:980px){.spx-command-wrap{grid-template-columns:1fr!important;width:min(720px,calc(100vw - 52px))!important}.spx-command-detail{display:none!important}}',
      '@media(max-width:760px){.spx-command-overlay{padding:58px 8px 20px!important}.spx-command-wrap{width:calc(100vw - 16px)!important}.spx-command-palette{max-height:calc(100vh - 78px)!important}.spx-command-list{max-height:none!important}.spx-command-item{grid-template-columns:34px minmax(0,1fr)!important;min-height:62px!important}.spx-command-icon{width:34px!important;height:34px!important}.spx-command-tail{grid-column:2!important;justify-content:flex-start!important}.spx-command-foot{align-items:flex-start!important;flex-direction:column!important}}',
      '.spx-toolbox{position:fixed;right:82px;bottom:18px;width:min(440px,calc(100vw - 24px));max-height:min(72vh,680px);overflow:hidden;z-index:100000;box-sizing:border-box;background:rgba(255,255,255,.98);border:1px solid rgba(148,163,184,.48);box-shadow:var(--spx-shadow-strong);border-radius:var(--spx-radius-xl);padding:0;color:var(--spx-text);font:13px/1.45 Arial,Helvetica,sans-serif;backdrop-filter:blur(12px);}',
      '.spx-toolbox[hidden]{display:none!important;}',
      '.spx-toolbox-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border-bottom:1px solid var(--spx-line-soft);background:linear-gradient(135deg,#f8fafc 0%,#ecfeff 100%);}',
      '.spx-toolbox-eyebrow{margin:0 0 3px;color:#0f766e;font-size:11px;font-weight:900;letter-spacing:.08em;}',
      '.spx-toolbox-header h3{margin:0;color:var(--spx-strong);font-size:17px;line-height:1.25;}',
      '.spx-toolbox-header p{display:none;}',
      '.spx-toolbox-close{flex:none;width:32px;height:32px;border:1px solid #cbd5e1;border-radius:10px;background:#fff;color:#334155;cursor:pointer;font-size:18px;font-weight:800;line-height:1;}',
      '.spx-toolbox-close:hover,.spx-toolbox-close:focus-visible{border-color:#0f766e;color:#0f766e;outline:none;}',
      '.spx-toolbox-body{box-sizing:border-box;max-height:calc(min(72vh,680px) - 58px);overflow:auto;padding:12px 13px 13px;scrollbar-width:thin;}',
      '.spx-toolbox-section{margin-top:12px;}',
      '.spx-toolbox-section:first-of-type{margin-top:0;}',
      '.spx-toolbox-section-title{display:flex;align-items:center;gap:8px;margin:0 0 8px;color:#475569;font-size:12px;font-weight:900;letter-spacing:.02em;}',
      '.spx-toolbox-section-title:after{content:"";height:1px;flex:1;background:#e2e8f0;}',
      '.spx-toolbox-count{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;border-radius:999px;background:#e0f2fe;color:#075985;font-size:11px;font-weight:900;letter-spacing:0;}',
      '.spx-toolbox-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;}',
      '.spx-toolbox-action{box-sizing:border-box;min-width:0;min-height:56px;display:flex!important;align-items:center!important;justify-content:flex-start!important;gap:9px;position:relative;border:1px solid #dbe4ee!important;border-radius:var(--spx-radius-lg)!important;background:#fff!important;color:#1f2937!important;padding:9px 10px!important;text-align:left!important;text-decoration:none!important;cursor:pointer!important;box-shadow:0 5px 16px rgba(15,23,42,.06)!important;transition:background .16s ease,border-color .16s ease,color .16s ease,transform .16s ease,box-shadow .16s ease!important;}',
      '.spx-toolbox-action:hover,.spx-toolbox-action:focus-visible{border-color:var(--spx-accent)!important;background:#f0fdfa!important;color:#0f766e!important;text-decoration:none!important;outline:none!important;box-shadow:0 10px 24px rgba(15,118,110,.15)!important;transform:translateY(-1px)!important;}',
      '.spx-toolbox-action.spx-active{border-color:#0f766e!important;background:#ccfbf1!important;color:#0f766e!important;box-shadow:0 8px 22px rgba(15,118,110,.18)!important;}',
      '.spx-toolbox-action.spx-active:before{content:"";position:absolute;left:0;top:12px;bottom:12px;width:3px;border-radius:0 999px 999px 0;background:#0f766e;}',
      '.spx-toolbox-key{flex:none;width:32px;height:32px;border-radius:10px;display:flex;align-items:center;justify-content:center;background:#f8fafc;border:1px solid #cbd5e1;color:#075985;font-size:15px;font-weight:900;}',
      '.spx-toolbox-action.spx-active .spx-toolbox-key{border-color:#0f766e;background:#f0fdfa;color:#0f766e;}',
      '.spx-toolbox-copy{min-width:0;display:block;}',
      '.spx-toolbox-name{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:inherit;font-size:13px;font-weight:800;line-height:1.25;}',
      '.spx-toolbox-desc{display:-webkit-box;overflow:hidden;-webkit-line-clamp:1;-webkit-box-orient:vertical;margin-top:4px;color:#64748b;font-size:11px;line-height:1.3;}',
      '.spx-settings{position:fixed;right:66px;bottom:18px;width:min(520px,calc(100vw - 24px));max-height:var(--spx-panel-max-height);overflow:hidden;z-index:100000;display:flex;flex-direction:column;background:var(--spx-panel);border:1px solid var(--spx-line);box-shadow:var(--spx-shadow-popover);border-radius:var(--spx-radius-lg);padding:0;color:var(--spx-text);font:13px/1.45 Arial,Helvetica,sans-serif;}',
      '.spx-settings[hidden]{display:none!important;}',
      '.spx-settings-header{flex:none;padding:13px 16px;border-bottom:1px solid var(--spx-line-soft);background:linear-gradient(135deg,#fff 0%,#f8fafc 100%);}',
      '.spx-settings h3{margin:0;font-size:16px;color:var(--spx-strong);line-height:1.3;}',
      '.spx-settings-subtitle{margin:3px 0 0;color:var(--spx-sub);font-size:12px;}',
      '.spx-settings-body{box-sizing:border-box;overflow:auto;padding:12px;display:grid;gap:11px;scrollbar-width:thin;}',
      '.spx-settings-section{box-sizing:border-box;padding:11px 12px;border:1px solid var(--spx-line-soft);border-radius:var(--spx-radius-lg);background:var(--spx-panel-muted);}',
      '.spx-settings-section h4{margin:0 0 8px;color:var(--spx-strong);font-size:13px;font-weight:900;line-height:1.3;}',
      '.spx-settings-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px 12px;}',
      '.spx-settings label{display:flex;gap:8px;align-items:center;min-height:26px;margin:0;color:var(--spx-text);}',
      '.spx-settings label input[type="checkbox"]{flex:none;accent-color:var(--spx-accent);}',
      '.spx-settings .spx-number-setting{justify-content:space-between;gap:10px;}',
      '.spx-settings .spx-number-setting input{box-sizing:border-box;width:92px;height:var(--spx-control-height);border:1px solid var(--spx-line);border-radius:var(--spx-radius-sm);padding:0 8px;text-align:right;}',
      '.spx-settings .spx-choice-setting{justify-content:space-between;gap:10px;}',
      '.spx-settings .spx-choice-setting select{box-sizing:border-box;min-width:132px;height:var(--spx-control-height);border:1px solid var(--spx-line);border-radius:var(--spx-radius-sm);padding:0 8px;background:#fff;color:var(--spx-text);}',
      '.spx-settings textarea{box-sizing:border-box;width:100%;min-height:72px;border:1px solid var(--spx-line);border-radius:var(--spx-radius-sm);padding:7px;background:#fff;font:12px/1.4 monospace;}',
      '.spx-settings .spx-help{margin:4px 0 8px;color:var(--spx-sub);font-size:12px;}',
      '.spx-settings .spx-row{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px;}',
      '.spx-settings-footer{flex:none;display:flex;justify-content:flex-end;gap:8px;padding:10px 12px;border-top:1px solid var(--spx-line-soft);background:#fff;}',
      '.spx-settings button{border:1px solid var(--spx-line);border-radius:var(--spx-radius-sm);background:#fff;padding:6px 10px;cursor:pointer;color:var(--spx-text);}',
      '.spx-settings .spx-primary{background:var(--spx-accent);border-color:var(--spx-accent);color:#fff;}',
      '.spx-settings .spx-danger{border-color:#fecaca;background:var(--spx-danger-soft);color:var(--spx-danger);}',
      '.spx-data-health{box-sizing:border-box;margin-top:10px;padding:10px;border:1px solid #e2e8f0;border-radius:var(--spx-radius);background:#fff;}',
      '.spx-data-health[hidden]{display:none!important;}',
      '.spx-storage-usage{display:grid;gap:6px;margin:7px 0 9px;font-size:12px;color:#334155;}',
      '.spx-storage-usage-row{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:7px 8px;border-radius:7px;background:#fff;border:1px solid #e5e7eb;}',
      '.spx-storage-usage-row.spx-storage-warning{border-color:#fbbf24;background:#fffbeb;}',
      '.spx-storage-usage-row.spx-storage-danger{border-color:#f87171;background:#fef2f2;}',
      '.spx-storage-usage-main{min-width:0;display:block;}',
      '.spx-storage-usage-main b{display:block;color:#0f172a;font-size:12px;line-height:1.3;}',
      '.spx-storage-usage-main em{display:block;margin-top:2px;color:#94a3b8;font-style:normal;font-size:11px;line-height:1.25;word-break:break-all;}',
      '.spx-storage-usage-meta{flex:none;text-align:right;color:#334155;font-weight:800;line-height:1.45;white-space:nowrap;}',
      '.spx-storage-suggestions{margin:6px 0 8px;padding:7px 8px;border-radius:7px;background:#fff;border:1px solid #e5e7eb;color:#475569;font-size:12px;line-height:1.45;}',
      '.spx-watch-center{position:fixed;right:66px;bottom:18px;width:var(--spx-panel-width);max-height:var(--spx-panel-max-height);overflow:auto;z-index:100000;background:var(--spx-panel);border:1px solid var(--spx-line);box-shadow:var(--spx-shadow-popover);border-radius:var(--spx-radius-lg);padding:14px;color:var(--spx-text);font:13px/1.45 Arial,Helvetica,sans-serif;scrollbar-width:thin;}',
      '.spx-resource-panel,#spx-resource-center{width:min(720px,calc(100vw - 96px));}',
      '.spx-read-resource-rail{position:fixed!important;right:76px!important;top:72px!important;z-index:100015!important;box-sizing:border-box!important;display:flex!important;flex-direction:column!important;width:320px!important;max-height:calc(100vh - 112px)!important;padding:12px!important;border:1px solid var(--spx-line)!important;border-radius:16px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;box-shadow:var(--spx-shadow-popover)!important;font:13px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif!important;}',
      '.spx-read-resource-rail[hidden]{display:none!important;}',
      '.spx-read-resource-rail-head{display:flex!important;align-items:flex-start!important;justify-content:space-between!important;gap:10px!important;margin:-12px -12px 10px!important;padding:12px!important;border-bottom:1px solid var(--spx-line-soft)!important;border-radius:16px 16px 0 0!important;background:linear-gradient(180deg,#fff 0%,#f8fafc 100%)!important;}',
      '.spx-read-resource-rail-head strong{display:block!important;color:var(--spx-strong)!important;font-size:15px!important;font-weight:900!important;line-height:1.25!important;}',
      '.spx-read-resource-summary{display:block!important;margin-top:3px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:800!important;}',
      '.spx-read-resource-rail button,.spx-read-resource-launcher{box-sizing:border-box!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;background:#fff!important;color:var(--spx-text)!important;cursor:pointer!important;font:800 12px/1.2 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif!important;}',
      '.spx-read-resource-rail button{min-height:28px!important;padding:0 9px!important;}',
      '.spx-read-resource-rail button:hover,.spx-read-resource-launcher:hover{border-color:var(--spx-accent)!important;color:var(--spx-accent)!important;background:var(--spx-accent-wash)!important;}',
      '.spx-read-resource-rail button:disabled{cursor:not-allowed!important;opacity:.5!important;}',
      '.spx-read-resource-actions,.spx-read-resource-filters,.spx-read-resource-card-actions{display:flex!important;flex-wrap:wrap!important;gap:6px!important;align-items:center!important;}',
      '.spx-read-resource-actions{margin-bottom:8px!important;}',
      '.spx-read-resource-filters{margin-bottom:10px!important;padding:8px!important;border:1px solid var(--spx-line-soft)!important;border-radius:12px!important;background:var(--spx-panel-muted)!important;}',
      '.spx-read-resource-filters button.spx-active{border-color:var(--spx-accent)!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;}',
      '.spx-read-resource-list{display:flex!important;flex-direction:column!important;gap:8px!important;min-height:0!important;overflow:auto!important;padding-right:2px!important;scrollbar-width:thin!important;}',
      '.spx-read-resource-card{box-sizing:border-box!important;display:grid!important;gap:7px!important;padding:10px!important;border:1px solid var(--spx-line-soft)!important;border-radius:12px!important;background:var(--spx-panel-muted)!important;color:var(--spx-text)!important;}',
      '.spx-read-resource-card.spx-active{border-color:var(--spx-accent)!important;box-shadow:0 0 0 3px rgba(37,99,235,.12)!important;}',
      '.spx-read-resource-card-top{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;}',
      '.spx-read-resource-type{display:inline-flex!important;align-items:center!important;min-height:20px!important;padding:0 7px!important;border-radius:999px!important;background:#e0f2fe!important;color:#075985!important;font-size:11px!important;font-weight:900!important;}',
      '.spx-read-resource-url{display:block!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;color:var(--spx-link)!important;font-size:12px!important;font-weight:850!important;text-decoration:none!important;}',
      '.spx-read-resource-url:hover{text-decoration:underline!important;}',
      '.spx-read-resource-meta{color:var(--spx-sub)!important;font-size:12px!important;font-weight:750!important;line-height:1.38!important;word-break:break-word!important;}',
      '.spx-read-resource-card-actions button{min-height:26px!important;padding:0 8px!important;font-size:11px!important;}',
      '.spx-read-resource-card-actions .spx-action-secondary{color:var(--spx-sub)!important;background:var(--spx-panel)!important;}',
      '.spx-read-resource-launcher{position:fixed!important;right:16px!important;bottom:132px!important;z-index:100016!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;min-width:82px!important;height:38px!important;padding:0 12px!important;border-color:rgba(37,99,235,.34)!important;border-radius:14px!important;background:var(--spx-accent)!important;color:#fff!important;box-shadow:0 16px 42px rgba(37,99,235,.28)!important;}',
      '.spx-read-resource-launcher[hidden]{display:none!important;}',
      'table.js-post.spx-read-resource-floor-active{outline:3px solid rgba(37,99,235,.22)!important;outline-offset:3px!important;}',
      '.spx-watch-center[hidden]{display:none!important;}',
      '.spx-watch-center-header{position:sticky;top:-14px;z-index:1;display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin:-14px -14px 12px;padding:13px 14px 11px;border-bottom:1px solid var(--spx-line-soft);background:rgba(255,255,255,.96);backdrop-filter:blur(10px);}',
      '.spx-watch-center h3{margin:0;color:var(--spx-strong);font-size:16px;line-height:1.3;}',
      '.spx-watch-center .spx-watch-summary{color:var(--spx-sub);font-size:12px;}',
      '.spx-watch-controls{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin:0 0 12px;padding:9px;border:1px solid var(--spx-line-soft);border-radius:var(--spx-radius-lg);background:var(--spx-panel-muted);}',
      '.spx-watch-controls input,.spx-watch-controls select{box-sizing:border-box;height:var(--spx-control-height);border:1px solid var(--spx-line);border-radius:var(--spx-radius-sm);background:#fff;color:var(--spx-text);padding:0 10px;font-size:12px;}',
      '.spx-watch-controls input{flex:1 1 220px;min-width:184px;}',
      '.spx-watch-controls select{flex:1 0 132px;min-width:116px;max-width:190px;}',
      '.spx-watch-list{display:flex;flex-direction:column;gap:9px;}',
      '.spx-watch-item{box-sizing:border-box;padding:11px 12px;border:1px solid var(--spx-line-soft);border-radius:var(--spx-radius-lg);background:var(--spx-panel-muted);}',
      '.spx-watch-title{display:block;margin-bottom:5px;color:var(--spx-link)!important;font-size:14px;font-weight:800;line-height:1.38;text-decoration:none;word-break:break-word;}',
      '.spx-resource-title-row{display:flex;align-items:flex-start;gap:8px;margin-bottom:4px;}',
      '.spx-resource-title-row .spx-watch-title{min-width:0;margin-bottom:0;word-break:break-all;}',
      '.spx-resource-select{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--spx-accent);}',
      '.spx-resource-url{display:block;overflow:hidden;text-overflow:ellipsis;white-space:normal;word-break:break-all;color:var(--spx-link)!important;font-size:12px;line-height:1.35;text-decoration:none;}',
      '.spx-auto-resource-jump{box-sizing:border-box;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)));margin:10px auto;padding:10px 12px;border:1px solid #99f6e4;border-radius:var(--spx-radius);background:#f0fdfa;color:#0f766e;font:13px/1.45 Arial,Helvetica,sans-serif;}',
      '.spx-auto-resource-jump strong{display:block;margin-bottom:6px;color:#0f766e;font-size:14px;}',
      '.spx-auto-resource-actions{display:flex;flex-wrap:wrap;gap:6px;align-items:center;}',
      '.spx-auto-resource-actions a,.spx-auto-resource-actions button{box-sizing:border-box;max-width:220px;height:28px;padding:0 9px;border:1px solid #5eead4;border-radius:999px;background:#fff;color:#0f766e!important;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-decoration:none;font-size:12px;line-height:26px;cursor:pointer;}',
      '.spx-auto-resource-actions button:hover,.spx-auto-resource-actions a:hover{border-color:#0f766e;background:#ccfbf1;text-decoration:none!important;}',
      '.spx-watch-meta{margin-bottom:8px;color:var(--spx-sub);font-size:12px;}',
      '.spx-watch-actions{display:flex;flex-wrap:wrap;gap:6px;}',
      '.spx-watch-actions button,.spx-watch-actions a,.spx-watch-center-header button{border:1px solid var(--spx-line);border-radius:var(--spx-radius-sm);background:#fff;color:var(--spx-text);padding:4px 8px;cursor:pointer;text-decoration:none;font-size:12px;line-height:1.25;}',
      '.spx-watch-actions .spx-action-primary{border-color:#99f6e4;background:var(--spx-accent-wash);color:var(--spx-accent)!important;font-weight:700;}',
      '.spx-watch-actions .spx-action-secondary{color:var(--spx-sub)!important;}',
      '.spx-watch-actions .spx-danger{border-color:#fecaca!important;background:var(--spx-danger-soft)!important;color:var(--spx-danger)!important;}',
      '.spx-watch-actions button[data-action*="clear"],.spx-watch-actions button[data-action*="remove"],.spx-watch-actions button[data-action*="delete"],.spx-watch-actions button[data-action*="invalid"]{border-color:#fecaca;background:var(--spx-danger-soft);color:var(--spx-danger);}',
      '.spx-watch-actions button:hover,.spx-watch-actions a:hover,.spx-watch-center-header button:hover{border-color:var(--spx-accent);color:var(--spx-accent);}',
      '.spx-watch-empty{padding:14px 2px;color:var(--spx-sub);font-size:13px;}',
      '.spx-status-badge{display:inline-block;margin-right:6px;padding:1px 6px;border-radius:999px;background:#e0f2fe;color:#075985;font-weight:800;}',
      '.spx-status-badge.spx-status-saved{background:#e0f2fe;color:#075985;}',
      '.spx-status-badge.spx-status-todo{background:#fef3c7;color:#92400e;}',
      '.spx-status-badge.spx-status-failed{background:#fee2e2;color:#b91c1c;}',
      '.spx-status-badge.spx-status-done{background:#dcfce7;color:#15803d;}',
      '.spx-status-badge.spx-status-invalid{background:#fee2e2;color:#b91c1c;}',
      '.spx-quick-reply{position:fixed!important;right:82px!important;bottom:78px!important;z-index:100020!important;box-sizing:border-box!important;width:min(560px,calc(100vw - 108px))!important;max-height:min(78vh,720px)!important;overflow:auto!important;border:1px solid var(--spx-line)!important;border-radius:14px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;box-shadow:var(--spx-shadow-popover)!important;font:13px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif!important;}',
      '.spx-quick-reply.spx-quick-reply-collapsed{display:none!important;}',
      '.spx-quick-reply-launcher{position:fixed!important;right:16px!important;bottom:78px!important;z-index:100021!important;display:none!important;align-items:center!important;justify-content:center!important;min-width:118px!important;height:42px!important;padding:0 14px!important;border:1px solid rgba(37,99,235,.34)!important;border-radius:14px!important;background:var(--spx-accent)!important;color:#fff!important;font-size:13px!important;font-weight:900!important;box-shadow:0 18px 46px rgba(37,99,235,.3)!important;cursor:pointer!important;}',
      '.spx-quick-reply-launcher.spx-visible{display:inline-flex!important;}',
      '.spx-quick-reply-header{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;margin:0!important;padding:12px 14px!important;border-bottom:1px solid var(--spx-line-soft)!important;background:var(--spx-panel)!important;color:var(--spx-sub)!important;}',
      '.spx-quick-reply-header strong{display:block!important;color:var(--spx-text)!important;font-size:14px!important;font-weight:900!important;line-height:1.25!important;}',
      '.spx-quick-reply-status{display:block!important;margin-top:3px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:800!important;}',
      '.spx-quick-reply-status.spx-error{color:#b91c1c!important;font-weight:800!important;}',
      '.spx-quick-reply-actions{display:flex!important;gap:6px!important;flex:none!important;}',
      '.spx-quick-reply button{box-sizing:border-box!important;border:1px solid var(--spx-line)!important;background:#fff!important;color:var(--spx-text)!important;cursor:pointer!important;}',
      '.spx-quick-reply button:hover{border-color:var(--spx-accent)!important;color:var(--spx-accent)!important;background:var(--spx-accent-wash)!important;}',
      '.spx-quick-reply button:disabled{cursor:wait!important;opacity:.55!important;}',
      '.spx-quick-reply-icon{min-width:58px!important;height:30px!important;padding:0 10px!important;border-radius:8px!important;font-size:12px!important;font-weight:900!important;}',
      '.spx-quick-reply-context{display:none!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;min-height:36px!important;padding:8px 12px!important;border-bottom:1px solid #fde4bd!important;background:#fffbeb!important;color:#7c2d12!important;font-size:12px!important;font-weight:900!important;}',
      '.spx-quick-reply-context.spx-visible{display:flex!important;}',
      '.spx-quick-reply-context button{flex:none!important;width:24px!important;height:24px!important;padding:0!important;border:0!important;border-radius:7px!important;background:rgba(255,255,255,.8)!important;color:#7c2d12!important;font-size:14px!important;font-weight:900!important;}',
      '.spx-quick-reply-body{padding:12px!important;background:var(--spx-panel)!important;}',
      '.spx-quick-reply-list{display:flex!important;gap:7px!important;margin:0 0 9px!important;overflow-x:auto!important;scrollbar-width:thin!important;}',
      '.spx-quick-reply-chip{flex:none!important;min-height:28px!important;padding:0 9px!important;border-radius:999px!important;background:var(--spx-panel-muted)!important;font-size:13px!important;font-weight:800!important;line-height:1.2!important;}',
      '.spx-quick-reply-chip.spx-hot{border-color:#16a34a!important;background:#ecfdf5!important;color:#047857!important;}',
      '.spx-quick-reply-emotes{margin:-1px 0 10px!important;padding:8px!important;border:1px solid var(--spx-line-soft)!important;border-radius:9px!important;background:var(--spx-panel-muted)!important;}',
      '.spx-quick-reply-emote-head{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;margin-bottom:7px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;}',
      '.spx-quick-reply-emote-grid{display:grid!important;grid-template-columns:repeat(auto-fill,34px)!important;gap:5px!important;align-items:center!important;}',
      '.spx-quick-reply-emote{width:34px!important;height:34px!important;padding:0!important;border-radius:7px!important;background:#fff!important;line-height:1!important;}',
      '.spx-quick-reply-emote img{display:block!important;max-width:28px!important;max-height:28px!important;margin:auto!important;object-fit:contain!important;}',
      '.spx-quick-reply-tools{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;margin-bottom:8px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:850!important;}',
      '.spx-quick-reply-note{min-width:0!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;}',
      '.spx-quick-reply-counter{flex:none!important;font-weight:900!important;font-variant-numeric:tabular-nums!important;}',
      '.spx-quick-reply-editor{box-sizing:border-box!important;display:block!important;width:100%!important;min-height:128px!important;max-height:260px!important;resize:vertical!important;padding:11px 12px!important;border:1px solid var(--spx-line)!important;border-radius:9px!important;background:var(--spx-panel-muted)!important;color:var(--spx-text)!important;font-size:14px!important;line-height:1.6!important;outline:none!important;}',
      '.spx-quick-reply-editor:focus{border-color:var(--spx-accent)!important;box-shadow:0 0 0 3px rgba(37,99,235,.12)!important;}',
      '.spx-quick-reply-attach{display:grid!important;grid-template-columns:minmax(0,1fr) auto!important;gap:8px!important;align-items:center!important;margin-top:9px!important;padding:8px!important;border:1px solid var(--spx-line-soft)!important;border-radius:9px!important;background:var(--spx-panel-muted)!important;}',
      '.spx-quick-reply-desc{box-sizing:border-box!important;min-width:0!important;height:32px!important;padding:0 10px!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;background:#fff!important;color:var(--spx-text)!important;font-size:13px!important;outline:none!important;}',
      '.spx-quick-reply-picker{display:inline-flex!important;align-items:center!important;justify-content:center!important;min-height:32px!important;padding:0 11px!important;border:1px solid var(--spx-accent)!important;border-radius:8px!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;font-size:13px!important;font-weight:900!important;cursor:pointer!important;white-space:nowrap!important;}',
      '.spx-quick-reply-help{grid-column:1/-1!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:800!important;}',
      '.spx-quick-reply-attachments{display:none!important;grid-column:1/-1!important;grid-template-columns:repeat(auto-fit,minmax(140px,1fr))!important;gap:7px!important;margin-top:1px!important;}',
      '.spx-quick-reply-attachments.spx-visible{display:grid!important;}',
      '.spx-quick-reply-attachment{display:grid!important;grid-template-columns:38px minmax(0,1fr) auto!important;gap:8px!important;align-items:center!important;min-width:0!important;padding:6px!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;background:#fff!important;}',
      '.spx-quick-reply-thumb{width:38px!important;height:38px!important;border-radius:7px!important;background:#e5e7eb!important;object-fit:cover!important;}',
      '.spx-quick-reply-attachment-name{overflow:hidden!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:900!important;text-overflow:ellipsis!important;white-space:nowrap!important;}',
      '.spx-quick-reply-attachment-size{margin-top:2px!important;color:var(--spx-sub)!important;font-size:11px!important;font-weight:800!important;}',
      '.spx-quick-reply-remove{width:26px!important;height:26px!important;padding:0!important;border-radius:7px!important;color:#b91c1c!important;font-weight:900!important;}',
      '.spx-quick-reply-footer{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;margin-top:10px!important;}',
      '.spx-quick-reply-hint{min-width:0!important;overflow:hidden!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:800!important;text-overflow:ellipsis!important;white-space:nowrap!important;}',
      '.spx-quick-reply-submit-actions{display:flex!important;gap:7px!important;flex:none!important;}',
      '.spx-quick-reply-action{min-height:32px!important;padding:0 12px!important;border-radius:8px!important;font-weight:900!important;}',
      '.spx-quick-reply-action.spx-primary{border-color:var(--spx-accent)!important;background:var(--spx-accent)!important;color:#fff!important;}',
      '.spx-quick-reply-selection{position:fixed!important;left:52%!important;top:50%!important;z-index:100022!important;display:none!important;gap:6px!important;padding:6px!important;border:1px solid var(--spx-line)!important;border-radius:9px!important;background:var(--spx-panel)!important;box-shadow:var(--spx-shadow-popover)!important;}',
      '.spx-quick-reply-selection.spx-visible{display:flex!important;}',
      '.spx-quick-reply-selection button{min-height:28px!important;padding:0 10px!important;border-radius:8px!important;font-size:12px!important;font-weight:900!important;}',
      '@media(max-width:900px){.spx-quick-reply{right:8px!important;bottom:calc(78px + env(safe-area-inset-bottom,0px))!important;width:calc(100vw - 16px)!important;border-radius:12px!important}.spx-quick-reply-launcher{right:8px!important;bottom:calc(78px + env(safe-area-inset-bottom,0px))!important}.spx-quick-reply-footer{align-items:stretch!important;flex-direction:column!important}.spx-quick-reply-attach{grid-template-columns:minmax(0,1fr)!important}.spx-quick-reply-picker{width:100%!important}.spx-quick-reply-submit-actions{justify-content:flex-end!important}}',
      '.spx-author-hover-source{cursor:help!important;}',
      '.spx-author-popover{position:fixed!important;z-index:100003!important;box-sizing:border-box!important;width:min(320px,calc(100vw - 32px))!important;max-height:min(460px,calc(100vh - 32px))!important;overflow:auto!important;padding:12px!important;background:#fff!important;border:1px solid #cbd5e1!important;border-radius:10px!important;box-shadow:0 18px 46px rgba(15,23,42,.24)!important;color:#172033!important;font:13px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif!important;}',
      '.spx-author-popover-header{display:grid!important;grid-template-columns:48px minmax(0,1fr)!important;gap:10px!important;align-items:center!important;margin:0 0 10px!important;padding-bottom:10px!important;border-bottom:1px solid #e2e8f0!important;}',
      '.spx-author-popover-avatar{width:48px!important;height:48px!important;object-fit:cover!important;border-radius:8px!important;background:#f1f5f9!important;}',
      '.spx-author-popover-name{font-size:16px!important;font-weight:800!important;color:#075985!important;line-height:1.25!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;}',
      '.spx-author-popover-link{display:inline-block!important;margin-top:3px!important;color:#64748b!important;font-size:12px!important;text-decoration:none!important;}',
      '.spx-author-popover-lines{display:flex!important;flex-direction:column!important;gap:5px!important;color:#475569!important;}',
      '.spx-author-popover-line{display:block!important;word-break:break-word!important;}',
      '.spx-fold-box{border:1px dashed var(--spx-line);background:var(--spx-bg);padding:8px;margin:8px 0;border-radius:6px;color:var(--spx-sub);}',
      '.spx-fold-box button{margin-left:8px;border:1px solid var(--spx-line);background:#fff;border-radius:5px;padding:2px 8px;cursor:pointer;}',
      '.spx-read-thread{opacity:.48;}',
      '.spx-hidden-rule{display:none!important;}',
      '.spx-unread-hidden{display:none!important;}',
      '.spx-thread-tools{display:inline-flex;gap:4px;margin-left:8px;vertical-align:middle;}',
      '.spx-thread-tools button{border:1px solid var(--spx-line);background:#fff;border-radius:4px;color:var(--spx-sub);font-size:12px;line-height:16px;padding:0 5px;cursor:pointer;}',
      '.spx-thread-tools button:hover{color:var(--spx-accent);border-color:var(--spx-accent);}',
      '.spx-watch-badge{display:inline-block;margin-left:5px;color:var(--spx-warn);font-weight:bold;}',
      '.spx-forum-tools{box-sizing:border-box;display:flex;align-items:center;gap:8px;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)));margin:0 auto 10px;padding:10px 12px;background:#fff;border:1px solid #d7e1eb;border-radius:var(--spx-radius);box-shadow:0 4px 14px rgba(15,23,42,.05);}',
      '.spx-forum-tools input{box-sizing:border-box;flex:1;min-width:180px;height:30px;border:1px solid var(--spx-line);border-radius:6px;padding:0 9px;font-size:13px;}',
      '.spx-forum-tools button{height:30px;border:1px solid var(--spx-line);border-radius:6px;background:#fff;color:var(--spx-text);padding:0 9px;cursor:pointer;font-size:12px;}',
      '.spx-forum-tools button:hover{border-color:var(--spx-accent);color:var(--spx-accent);}',
      '.spx-forum-resource-filters{display:flex;align-items:center;gap:5px;max-width:46%;overflow-x:auto;scrollbar-width:thin;}',
      '.spx-forum-tools .spx-resource-filter-active{border-color:#93c5fd;background:#dbeafe;color:#1d4ed8;font-weight:900;}',
      '.spx-resource-badges{display:inline-flex;align-items:center;flex-wrap:wrap;gap:4px;margin-left:6px;vertical-align:middle;}',
      '.spx-resource-badge{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-height:21px;padding:0 7px;border:1px solid #bfdbfe;border-radius:6px;background:#eff6ff;color:#1d4ed8;font-size:11px;font-weight:900;line-height:1;white-space:nowrap;cursor:pointer;}',
      '.spx-resource-badge:hover{filter:brightness(.97);transform:translateY(-1px);}',
      '.spx-resource-badge-guess{border-style:dashed;background:#fff;color:#475467;}',
      '.spx-resource-badge-baidu{border-color:#bfdbfe;background:#eff6ff;color:#1d4ed8;}.spx-resource-badge-quark{border-color:#99f6e4;background:#f0fdfa;color:#0f766e;}.spx-resource-badge-pikpak{border-color:#ddd6fe;background:#faf5ff;color:#6d28d9;}.spx-resource-badge-magnet{border-color:#fed7aa;background:#fff7ed;color:#c2410c;}.spx-resource-badge-torrent{border-color:#bae6fd;background:#f0f9ff;color:#0369a1;}.spx-resource-badge-archive{border-color:#fbcfe8;background:#fdf2f8;color:#be185d;}.spx-resource-badge-ed2k{border-color:#cbd5e1;background:#f1f5f9;color:#334155;}.spx-resource-badge-external{border-color:#fde68a;background:#fffbeb;color:#92400e;}.spx-resource-badge-cloud{border-color:#cbd5e1;background:#f8fafc;color:#475569;}',
      '.spx-module-nav-ready.spx-home-dashboard #content,.spx-module-nav-ready.spx-forum-dashboard #content{display:grid!important;grid-template-columns:minmax(196px,var(--spx-module-width)) minmax(0,1fr)!important;gap:14px!important;align-items:start!important;}',
      '.spx-module-body{grid-column:2!important;min-width:0!important;display:block!important;}',
      '.spx-module-nav-ready.spx-home-dashboard #content>*:not(.spx-module-nav):not(.spx-module-body),.spx-module-nav-ready.spx-forum-dashboard #content>*:not(.spx-module-nav):not(.spx-module-body){grid-column:2!important;min-width:0!important;}',
      '.spx-module-nav-ready .spx-forum-tools{width:100%!important;margin:0 0 10px!important;}',
      '.spx-module-nav{grid-column:1!important;position:sticky!important;top:46px!important;z-index:20!important;box-sizing:border-box!important;display:flex!important;flex-direction:column!important;align-self:start!important;gap:8px!important;max-height:calc(100vh - var(--spx-module-max-offset))!important;padding:12px!important;overflow:auto!important;border:1px solid var(--spx-line)!important;border-radius:var(--spx-radius-lg)!important;background:var(--spx-panel)!important;box-shadow:var(--spx-shadow-card)!important;color:var(--spx-text)!important;scrollbar-width:thin!important;}',
      '.spx-module-nav-title{padding:0 2px 8px!important;border-bottom:1px solid var(--spx-line-soft)!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;line-height:1.25!important;white-space:nowrap!important;}',
      '.spx-module-nav-controls{display:flex!important;align-items:center!important;width:100%!important;min-width:0!important;}',
      'input.spx-module-nav-search{box-sizing:border-box!important;flex:1 1 auto!important;width:100%!important;min-width:0!important;max-width:100%!important;height:30px!important;padding:0 10px!important;border:1px solid var(--spx-line)!important;border-radius:9px!important;background:var(--spx-input-bg)!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:800!important;line-height:30px!important;outline:none!important;}',
      'input.spx-module-nav-search:focus{border-color:var(--spx-accent)!important;box-shadow:0 0 0 2px rgba(37,99,235,.12)!important;}',
      '.spx-module-nav-group{display:flex!important;flex-direction:column!important;gap:5px!important;}',
      '.spx-module-nav-section{appearance:none!important;box-sizing:border-box!important;display:flex!important;align-items:center!important;gap:7px!important;width:100%!important;min-height:var(--spx-module-section-height)!important;margin:5px 0 1px!important;padding:0 var(--spx-module-section-padding)!important;border:0!important;border-radius:calc(var(--spx-module-item-radius) - 2px)!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;font-size:11px!important;font-weight:900!important;line-height:1.2!important;text-align:left!important;white-space:nowrap!important;cursor:pointer!important;}',
      '.spx-module-nav-section:before{content:"▸";font-size:10px!important;color:var(--spx-accent)!important;}',
      '.spx-module-nav-section[aria-expanded="true"]:before{content:"▾";}',
      '.spx-module-nav-section[aria-expanded="false"]:before{content:"▸";}',
      '.spx-module-nav-section:after{content:"";height:1px!important;flex:1!important;background:rgba(37,99,235,.18)!important;}',
      '.spx-module-nav-group.spx-module-nav-collapsed>.spx-module-nav-node{display:none!important;}',
      '.spx-module-nav[data-spx-module-nav-searching="1"] .spx-module-nav-group.spx-module-nav-collapsed>.spx-module-nav-node{display:flex!important;}',
      '.spx-module-nav-node{display:flex!important;flex-direction:column!important;gap:4px!important;}',
      '.spx-module-nav-children{display:flex!important;flex-direction:column!important;gap:4px!important;margin:2px 0 3px 11px!important;padding-left:9px!important;border-left:2px solid var(--spx-line-soft)!important;}',
      '.spx-module-nav-parent-title{box-sizing:border-box!important;display:flex!important;align-items:center!important;min-height:var(--spx-module-parent-height)!important;padding:0 var(--spx-module-section-padding)!important;border-radius:var(--spx-module-item-radius)!important;background:var(--spx-panel-muted)!important;color:var(--spx-strong)!important;font-size:12px!important;font-weight:900!important;line-height:1.25!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;}',
      '.spx-module-nav-item{box-sizing:border-box!important;display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;min-height:var(--spx-module-item-height)!important;width:100%!important;padding:0 var(--spx-module-item-padding)!important;border:1px solid transparent!important;border-radius:var(--spx-module-item-radius)!important;background:var(--spx-panel-muted)!important;color:var(--spx-text)!important;text-decoration:none!important;font-size:12px!important;font-weight:900!important;line-height:1.2!important;white-space:nowrap!important;cursor:pointer!important;}',
      '.spx-module-nav-item.spx-module-nav-level-3{min-height:var(--spx-module-child-height)!important;padding:0 var(--spx-module-section-padding)!important;border-radius:calc(var(--spx-module-item-radius) - 2px)!important;background:transparent!important;color:var(--spx-link)!important;font-size:11px!important;font-weight:800!important;}',
      '.spx-module-nav-label{min-width:0!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;}',
      '.spx-module-nav-pin{flex:none;display:inline-flex!important;align-items:center!important;justify-content:center!important;width:20px!important;height:20px!important;border-radius:999px!important;color:var(--spx-muted)!important;font-size:13px!important;line-height:1!important;opacity:.72!important;cursor:pointer!important;}',
      '.spx-module-nav-pin:hover,.spx-module-nav-pin:focus-visible{background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;opacity:1!important;outline:none!important;}',
      '.spx-module-nav-item.spx-module-nav-pinned .spx-module-nav-pin{color:var(--spx-accent)!important;opacity:1!important;}',
      '.spx-module-nav-search-hidden{display:none!important;}',
      '.spx-module-nav-item:hover,.spx-module-nav-item:focus-visible{border-color:var(--spx-accent)!important;color:var(--spx-accent)!important;text-decoration:none!important;outline:none!important;}',
      '.spx-module-nav-item.spx-active{border-color:var(--spx-accent)!important;background:var(--spx-accent)!important;color:#fff!important;box-shadow:0 7px 16px rgba(37,99,235,.2)!important;}',
      '.spx-module-nav-count{display:inline-flex!important;align-items:center!important;justify-content:center!important;min-width:22px!important;height:20px!important;padding:0 6px!important;border-radius:999px!important;background:var(--spx-panel)!important;color:var(--spx-sub)!important;font-size:11px!important;font-weight:900!important;font-variant-numeric:tabular-nums!important;}',
      '.spx-module-nav-item.spx-active .spx-module-nav-count{background:rgba(255,255,255,.22)!important;color:#fff!important;}',
      '.spx-filter-hidden{display:none!important;}',
      '.spx-resource-filter-hidden{display:none!important;}',
      '.spx-module-filter-hidden{display:none!important;}',
      '.spx-forum-prelude-hidden{display:none!important;}',
      '.spx-thread-row-hidden{display:none!important;}',
      '.spx-forum-section-title{box-sizing:border-box!important;display:flex!important;align-items:center!important;justify-content:space-between!important;gap:12px!important;min-height:38px!important;margin:0 0 10px!important;color:var(--spx-strong)!important;}',
      '.spx-forum-section-main{display:flex!important;align-items:center!important;gap:10px!important;min-width:0!important;}',
      '.spx-forum-section-title strong{display:block!important;min-width:0!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;font-size:16px!important;font-weight:900!important;}',
      '.spx-forum-section-title span{flex:none!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:800!important;}',
      '.spx-forum-gallery-link{flex:none!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;min-height:30px!important;padding:0 11px!important;border:1px solid #fecaca!important;border-radius:8px!important;background:#fef2f2!important;color:#b91c1c!important;font-size:12px!important;font-weight:900!important;text-decoration:none!important;}',
      '.spx-forum-gallery-link:hover,.spx-forum-gallery-link:focus-visible{border-color:#f87171!important;background:#fee2e2!important;color:#991b1b!important;text-decoration:none!important;outline:none!important;}',
      '.spx-forum-gallery-link.spx-forum-list-link{border-color:#bfdbfe!important;background:#eff6ff!important;color:#1d4ed8!important;}',
      '.spx-forum-gallery-link.spx-forum-list-link:hover,.spx-forum-gallery-link.spx-forum-list-link:focus-visible{border-color:#60a5fa!important;background:#dbeafe!important;color:#1e40af!important;}',
      '.spx-forum-gallery-page #wall{box-sizing:border-box!important;width:100%!important;max-width:100%!important;margin:0!important;padding:0!important;overflow:visible!important;background:transparent!important;}',
      '.spx-forum-gallery-page #wall .stream{position:static!important;display:grid!important;grid-template-columns:repeat(auto-fill,minmax(224px,1fr))!important;gap:14px!important;width:100%!important;height:auto!important;margin:0!important;padding:0!important;list-style:none!important;}',
      '.spx-forum-gallery-page #wall .stream>li{position:static!important;left:auto!important;top:auto!important;right:auto!important;bottom:auto!important;box-sizing:border-box!important;display:block!important;width:auto!important;height:auto!important;margin:0!important;padding:0!important;list-style:none!important;}',
      '.spx-forum-gallery-page #wall .stream>li.spx-filter-hidden,.spx-forum-gallery-page #wall .stream>li.spx-resource-filter-hidden,.spx-forum-gallery-page #wall .stream>li.spx-hidden-rule,.spx-forum-gallery-page #wall .stream>li.spx-unread-hidden,.spx-forum-gallery-page #wall .stream>li.spx-thread-row-hidden{display:none!important;}',
      '.spx-forum-gallery-page #wall .inner,.spx-forum-gallery-page #wall .spx-gallery-card-inner{box-sizing:border-box!important;display:flex!important;flex-direction:column!important;gap:0!important;min-height:100%!important;margin:0!important;padding:0!important;border:1px solid var(--spx-line)!important;border-radius:12px!important;background:var(--spx-panel)!important;box-shadow:0 8px 20px rgba(15,23,42,.07)!important;overflow:hidden!important;color:var(--spx-text)!important;}',
      '.spx-forum-gallery-page #wall .inner:hover{border-color:#bfdbfe!important;box-shadow:0 14px 30px rgba(15,23,42,.11)!important;}',
      '.spx-forum-gallery-page #wall .section-title{box-sizing:border-box!important;display:block!important;width:100%!important;max-width:100%!important;height:auto!important;min-height:0!important;margin:0!important;padding:12px 12px 7px!important;border:0!important;background:transparent!important;font-size:14px!important;line-height:1.45!important;overflow:visible!important;}',
      '.spx-forum-gallery-page #wall .section-title a{display:block!important;color:var(--spx-strong)!important;font-size:14px!important;font-weight:900!important;line-height:1.45!important;text-decoration:none!important;word-break:break-word!important;}',
      '.spx-forum-gallery-page #wall .section-title a:hover{color:var(--spx-accent)!important;text-decoration:none!important;}',
      '.spx-forum-gallery-page #wall .section-text{box-sizing:border-box!important;display:block!important;width:100%!important;max-width:100%!important;margin:0!important;padding:0 12px 12px!important;clear:both!important;color:var(--spx-sub)!important;font-size:12px!important;line-height:1.55!important;overflow:visible!important;}',
      '.spx-forum-gallery-page #wall .section-text>span{float:none!important;display:block!important;width:100%!important;max-width:100%!important;margin:0 0 8px!important;padding:0!important;clear:both!important;color:var(--spx-sub)!important;font-size:12px!important;line-height:1.45!important;}',
      '.spx-forum-gallery-page #wall .section-text>span:first-child{display:none!important;}',
      '.spx-forum-gallery-page #wall .section-text>div{display:block!important;width:100%!important;max-width:100%!important;margin:0!important;clear:both!important;}',
      '.spx-forum-gallery-page #wall .section-text img{display:block!important;width:100%!important;max-width:100%!important;height:176px!important;object-fit:cover!important;border:1px solid var(--spx-line-soft)!important;border-radius:10px!important;background:var(--spx-panel-muted)!important;}',
      '.spx-forum-gallery-page #wall .section-text a#favor{display:none!important;}',
      '.spx-forum-gallery-page #wall .section-intro{box-sizing:border-box!important;position:static!important;left:auto!important;right:auto!important;top:auto!important;bottom:auto!important;z-index:auto!important;display:block!important;width:100%!important;max-width:100%!important;flex:none!important;margin:auto 0 0!important;padding:9px 12px!important;border-top:1px solid var(--spx-line-soft)!important;background:var(--spx-panel-muted)!important;color:var(--spx-sub)!important;font-size:12px!important;line-height:1.45!important;overflow:hidden!important;}',
      '.spx-forum-gallery-page #wall .section-intro table,.spx-forum-gallery-page #wall .section-intro tbody,.spx-forum-gallery-page #wall .section-intro tr,.spx-forum-gallery-page #wall .section-intro td{display:block!important;width:auto!important;margin:0!important;padding:0!important;border:0!important;background:transparent!important;}',
      '.spx-forum-gallery-page #wall .section-intro a{color:var(--spx-link)!important;font-weight:800!important;text-decoration:none!important;}',
      '.spx-forum-gallery-page #wall .clear{display:none!important;}',
      '.spx-gallery-card-tools{box-sizing:border-box!important;display:flex!important;align-items:center!important;gap:6px!important;width:100%!important;max-width:100%!important;flex:none!important;margin:0!important;padding:9px 12px 12px!important;clear:both!important;border-top:1px solid var(--spx-line-soft)!important;background:var(--spx-panel)!important;}',
      '.spx-gallery-card-tools button{box-sizing:border-box!important;min-height:28px!important;padding:0 10px!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:900!important;line-height:26px!important;cursor:pointer!important;}',
      '.spx-gallery-card-tools button:hover,.spx-gallery-card-tools button:focus-visible{border-color:var(--spx-accent)!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;outline:none!important;}',
      '.spx-gallery-card-tools button:disabled{cursor:default!important;opacity:.62!important;}',
      '@media(max-width:900px){.spx-forum-gallery-page #wall .stream{grid-template-columns:repeat(auto-fill,minmax(156px,1fr))!important;gap:10px!important}.spx-forum-gallery-page #wall .section-title{padding:10px 10px 6px!important}.spx-forum-gallery-page #wall .section-text{padding:0 10px 10px!important}.spx-forum-gallery-page #wall .section-text img{height:138px!important}.spx-forum-gallery-page #wall .section-intro{padding:8px 10px!important}.spx-gallery-card-tools{gap:5px!important;padding:8px 10px 10px!important}.spx-gallery-card-tools button{flex:1 1 0!important;min-width:0!important;padding:0 6px!important}}',
      '.spx-post-tools{display:flex;gap:6px;justify-content:flex-end;margin:4px 0;}',
      '.spx-post-tools button{border:1px solid var(--spx-line);background:#fff;border-radius:5px;padding:2px 8px;cursor:pointer;color:var(--spx-sub);}',
      '.spx-auto-buy-status{box-sizing:border-box;margin:8px 0;padding:8px 10px;border:1px solid #99f6e4;border-radius:6px;background:#f0fdfa;color:#0f766e;font-size:13px;line-height:1.45;}',
      '.spx-auto-buy-status.spx-error{border-color:#fecaca;background:#fef2f2;color:#b91c1c;}',
      '.spx-post-hidden{display:none!important;}',
      '.spx-site-shell .spx-post-shell-hidden,.spx-site-shell:not(.spx-reader) .spx-post-shell-hidden,.spx-reader .spx-post-shell-hidden,.spx-immersive-read .spx-post-shell-hidden{display:none!important;border:0!important;margin:0!important;padding:0!important;height:0!important;min-height:0!important;overflow:hidden!important;}',
      '.spx-preview-popover{position:fixed;z-index:100001;width:min(520px,calc(100vw - 28px));max-height:min(74vh,620px);overflow:auto;padding:12px;background:#fff;border:1px solid #cbd5e1;border-radius:10px;box-shadow:0 18px 48px rgba(15,23,42,.28);color:#172033;font:13px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif;}',
      '.spx-preview-popover h4{display:block;margin:0 0 6px;font-size:15px;line-height:1.35;color:#0f172a;}',
      '.spx-preview-meta{margin-bottom:8px;color:#64748b;font-size:12px;}',
      '.spx-preview-popover-actions{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 10px;}',
      '.spx-preview-popover-actions a,.spx-preview-popover-actions button{box-sizing:border-box;min-height:28px;border:1px solid #cbd5e1;border-radius:7px;background:#fff;color:#1f2937;padding:4px 9px;font-size:12px;line-height:18px;text-decoration:none;cursor:pointer;}',
      '.spx-preview-popover-actions a:hover,.spx-preview-popover-actions button:hover{border-color:#2563eb;color:#2563eb;}',
      '.spx-preview-popover-actions button:disabled{cursor:default;opacity:.62;}',
      '.spx-preview-text{max-height:120px;overflow:hidden;margin-bottom:10px;color:#334155;}',
      '.spx-preview-images{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:7px;}',
      '.spx-preview-images a{display:block;overflow:hidden;border:1px solid #e2e8f0;border-radius:7px;background:#f8fafc;}',
      '.spx-preview-images img{display:block;width:100%;height:118px;object-fit:cover;}',
      '.spx-preview-images img[data-spx-preview-lazy-src]{background:linear-gradient(135deg,#f1f5f9,#e2e8f0);opacity:.68;}',
      '.spx-preview-images img[data-spx-preview-loaded="1"]{opacity:1;transition:opacity .16s ease;}',
      '.spx-preview-status{color:#94a3b8;font-size:12px;}',
      '.spx-compact-read .user-info,.spx-compact-read .readprofile,.spx-hide-profile .user-pic,.spx-hide-profile .user-info,.spx-hide-profile .readprofile{display:none!important;}',
      '.spx-compact-read:not(.spx-reader) .tpc_content{font-size:15px;line-height:1.7;max-width:var(--spx-reader-line);}',
      '.spx-folded-quote{max-height:110px;overflow:hidden;position:relative;border-bottom:1px dashed var(--spx-line);}',
      '.spx-folded-quote:after{content:"";position:absolute;left:0;right:0;bottom:0;height:30px;background:linear-gradient(transparent,var(--spx-panel));}',
      '@media(max-width:900px){.spx-home-dashboard #content,.spx-forum-dashboard #content{width:calc(100vw - 16px)!important;margin:10px 8px 34px!important}.spx-module-nav-ready.spx-home-dashboard #content,.spx-module-nav-ready.spx-forum-dashboard #content{display:grid!important;grid-template-columns:minmax(0,1fr)!important;gap:10px!important}.spx-module-nav-ready.spx-home-dashboard #content>*:not(.spx-module-nav),.spx-module-nav-ready.spx-forum-dashboard #content>*:not(.spx-module-nav),.spx-module-nav{grid-column:1!important}.spx-module-nav{position:static!important;display:flex!important;flex-direction:row!important;align-items:center!important;max-height:none!important;overflow-x:auto!important;overflow-y:hidden!important;padding:7px!important}.spx-module-nav-title{flex:none!important;padding:0 8px!important;border-bottom:0!important}.spx-module-nav-controls{flex:none!important;width:180px!important}.spx-module-nav-group,.spx-module-nav-node,.spx-module-nav-children{flex:none!important;display:flex!important;flex-direction:row!important;align-items:center!important;gap:6px!important;margin:0!important;padding:0!important;border-left:0!important}.spx-module-nav-section,.spx-module-nav-parent-title{flex:none!important}.spx-module-nav-parent-title{min-height:30px!important;border-radius:999px!important}.spx-module-nav-item{flex:none!important;width:auto!important;min-height:30px!important;border-radius:999px!important}.spx-home-dashboard #spx-home-grid{grid-template-columns:1fr!important}.spx-home-dashboard .spx-home-module,.spx-home-dashboard .spx-home-module[data-spx-large="1"]{grid-column:1!important}.spx-home-dashboard #header,.spx-home-dashboard #mainNav,.spx-home-dashboard #infobox,.spx-home-dashboard #notice,.spx-home-dashboard .spx-home-quick,.spx-forum-dashboard #header,.spx-forum-dashboard #mainNav,.spx-forum-dashboard #infobox,.spx-forum-dashboard #notice{width:calc(100vw - 16px)!important}.spx-home-dashboard .spx-home-module tr.tr3,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3{grid-template-columns:1fr!important;gap:4px!important}.spx-home-dashboard .spx-home-module tr.tr3>td:first-child,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3>td:first-child:not([id^="td_"]){display:none!important}.spx-forum-tools{width:100%!important;flex-wrap:wrap}.spx-forum-tools input{flex-basis:100%!important}}',
      '@media(max-width:760px){.spx-read-resource-rail{left:8px!important;right:8px!important;top:auto!important;bottom:calc(104px + env(safe-area-inset-bottom,0px))!important;width:auto!important;max-height:46vh!important;border-radius:18px!important}.spx-read-resource-rail-head{border-radius:18px 18px 0 0!important}.spx-read-resource-list{max-height:calc(46vh - 156px)!important}.spx-read-resource-launcher{right:8px!important;bottom:calc(104px + env(safe-area-inset-bottom,0px))!important;min-width:88px!important;height:40px!important}.spx-read-resource-actions button,.spx-read-resource-filters button{flex:1 1 auto!important}.spx-read-resource-card-actions{display:grid!important;grid-template-columns:repeat(3,minmax(0,1fr))!important}.spx-read-resource-card-actions button{min-width:0!important;padding:0 5px!important}}',
      '@media(max-width:760px){.spx-preview-lightbox{padding:0!important}.spx-preview-lightbox-shell{border:0!important;border-radius:0!important}.spx-preview-lightbox-toolbar{align-items:flex-start!important;min-height:0!important;padding:8px!important}.spx-preview-lightbox-actions{gap:4px!important}.spx-preview-lightbox button{height:30px!important;padding:0 8px!important}.spx-preview-lightbox-canvas{padding:22px 50px!important}.spx-preview-lightbox-nav{width:38px!important;height:54px!important;font-size:26px!important}.spx-preview-lightbox-prev{left:6px!important}.spx-preview-lightbox-next{right:6px!important}.spx-preview-lightbox-caption{padding:6px 9px!important}.spx-preview-lightbox-help{display:none!important}.spx-reader body{font-size:16px!important}.spx-reader #wrapA{width:calc(100vw - 14px)!important;margin:0 7px!important}.spx-reader .tpc_content{font-size:16px!important;line-height:1.76!important;padding:12px!important}.spx-reader .tpc_content #read_tpc,.spx-reader .tpc_content>.f14{font-size:16px!important;line-height:1.76!important}.spx-reader .spx-post-body-split,.spx-immersive-read .spx-post-body-split{display:flex!important;flex-direction:column!important;gap:12px!important;padding:14px!important}.spx-reader .spx-post-body-split .tpc_content,.spx-immersive-read .spx-post-body-split .tpc_content{padding:0!important}.spx-reader .spx-preview-panel,.spx-immersive-read .spx-preview-panel{width:auto!important;max-height:360px!important;margin:0!important;padding:10px!important}.spx-reader .spx-preview-grid,.spx-immersive-read .spx-preview-grid{grid-template-columns:repeat(2,minmax(0,1fr))!important;gap:8px!important}.spx-reader .spx-preview-item img,.spx-immersive-read .spx-preview-item img{height:132px!important}.spx-preview-panel.spx-preview-drawer{left:8px!important;right:8px!important;top:auto!important;bottom:calc(62px + env(safe-area-inset-bottom,0px))!important;width:auto!important;max-height:58vh!important;border-radius:16px!important}.spx-preview-panel.spx-preview-drawer.spx-preview-collapsed{left:auto!important;right:8px!important;bottom:calc(62px + env(safe-area-inset-bottom,0px))!important;top:auto!important;width:46px!important;height:126px!important}.spx-immersive-read #wrapA,.spx-immersive-read #main,.spx-immersive-read #content{width:100vw!important;margin:0!important}.spx-immersive-read table.js-post{width:calc(100vw - 14px)!important;margin:10px 7px!important}.spx-immersive-read .h1,.spx-immersive-read [id^="subject_"]{font-size:19px!important;padding:16px 14px 6px!important}.spx-immersive-read .tpc_content{font-size:16px!important;line-height:1.76!important;padding:14px!important}.spx-immersive-read .tpc_content #read_tpc,.spx-immersive-read .tpc_content>.f14{font-size:16px!important;line-height:1.76!important}.spx-toolbar{left:8px!important;right:8px!important;bottom:calc(8px + env(safe-area-inset-bottom,0px))!important;width:auto!important;flex-direction:row!important;justify-content:space-between!important;padding:6px!important;border-radius:16px!important}.spx-toolbar button,.spx-toolbar a{flex:1 1 0!important;width:auto!important;height:34px!important;font-size:12px}.spx-settings,.spx-watch-center,.spx-toolbox{left:8px!important;right:8px!important;bottom:calc(58px + env(safe-area-inset-bottom,0px))!important;width:auto!important;max-height:calc(100vh - 92px - env(safe-area-inset-bottom,0px))!important;border-radius:18px!important}.spx-settings-grid,.spx-toolbox-grid{grid-template-columns:1fr!important}.spx-toolbox-body{max-height:calc(100vh - 150px - env(safe-area-inset-bottom,0px));padding:11px}.spx-toolbox-action{min-height:54px!important}.spx-watch-controls input,.spx-watch-controls select{flex:1 1 100%!important;max-width:none!important}.spx-resource-panel,#spx-resource-center{width:auto!important}.spx-watch-center-header{top:-14px}.spx-toolbox-desc{-webkit-line-clamp:1}}',
    ];
  }

  function getInjectedThemeOverrideStyleRules() {
    return [
      '.spx-theme-night,.spx-theme-night body{background:var(--spx-page-bg)!important;color:var(--spx-text)!important;}',
      '.spx-theme-night #wrapA,.spx-theme-night #main,.spx-theme-night #content,.spx-theme-night.spx-reader #wrapA,.spx-theme-night.spx-reader #main,.spx-theme-night.spx-reader #content,.spx-theme-night.spx-immersive-read #wrapA,.spx-theme-night.spx-immersive-read #main,.spx-theme-night.spx-immersive-read #content{background:var(--spx-page-bg)!important;background-image:none!important;color:var(--spx-text)!important;}',
      '.spx-theme-night a{color:var(--spx-link)!important;}',
      '.spx-theme-night input,.spx-theme-night textarea,.spx-theme-night select{background:var(--spx-input-bg)!important;color:var(--spx-text)!important;border-color:var(--spx-line)!important;}',
      '.spx-theme-night .t,.spx-theme-night .t3,.spx-theme-night .t5,.spx-theme-night .bdbA,.spx-theme-night table.js-post,.spx-theme-night .spx-home-module,.spx-theme-night .spx-watch-center,.spx-theme-night .spx-settings,.spx-theme-night .spx-toolbox,.spx-theme-night .spx-command-palette,.spx-theme-night .spx-command-detail,.spx-theme-night .spx-preview-panel,.spx-theme-night .spx-preview-popover,.spx-theme-night .spx-author-popover,.spx-theme-night .spx-quick-reply,.spx-theme-night .spx-forum-tools,.spx-theme-night .spx-read-resource-rail,.spx-theme-night .spx-read-resource-card,.spx-theme-night .spx-read-resource-filters,.spx-theme-night .spx-data-health,.spx-theme-night .spx-storage-usage-row,.spx-theme-night .spx-storage-suggestions{background:var(--spx-panel)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;box-shadow:var(--spx-shadow-card)!important;}',
      '.spx-theme-night .tr1,.spx-theme-night .tr2,.spx-theme-night .tr3,.spx-theme-night td,.spx-theme-night th,.spx-theme-night .spx-home-module tr.tr3,.spx-theme-night.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3{background:var(--spx-row-bg)!important;border-color:var(--spx-line-soft)!important;color:var(--spx-text)!important;}',
      '.spx-theme-night .spx-home-module tr.tr3:hover,.spx-theme-night.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3:hover{background:var(--spx-row-hover)!important;}',
      '.spx-theme-night .spx-home-module>h2,.spx-theme-night .spx-home-module .h,.spx-theme-night .spx-settings-header,.spx-theme-night .spx-settings-footer,.spx-theme-night .spx-toolbox-header,.spx-theme-night .spx-watch-center-header,.spx-theme-night .spx-read-resource-rail-head,.spx-theme-night .spx-preview-lightbox-toolbar,.spx-theme-night .spx-preview-lightbox-caption{background:var(--spx-panel-muted)!important;border-color:var(--spx-line-soft)!important;color:var(--spx-strong)!important;}',
      '.spx-theme-night .spx-read-resource-rail button{background:var(--spx-panel)!important;color:var(--spx-text)!important;border-color:var(--spx-line)!important;}',
      '.spx-theme-night .spx-toolbox-action,.spx-theme-night .spx-watch-item,.spx-theme-night .spx-watch-controls,.spx-theme-night .spx-settings-section,.spx-theme-night .spx-preview-item,.spx-theme-night .spx-preview-images a,.spx-theme-night .spx-fold-box,.spx-theme-night .spx-auto-resource-actions a,.spx-theme-night .spx-auto-resource-actions button{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;}',
      '.spx-theme-night button,.spx-theme-night .spx-toolbar button,.spx-theme-night .spx-toolbar a,.spx-theme-night .spx-thread-tools button,.spx-theme-night .spx-post-tools button,.spx-theme-night .spx-watch-actions button,.spx-theme-night .spx-watch-actions a{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;}',
      '.spx-theme-night .spx-toolbox-action:hover,.spx-theme-night .spx-toolbox-action:focus-visible,.spx-theme-night button:hover,.spx-theme-night .spx-toolbar button:hover,.spx-theme-night .spx-toolbar a:hover{border-color:var(--spx-accent)!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;}',
      '.spx-theme-night .spx-toolbox-action.spx-active,.spx-theme-night .spx-toolbar .spx-active,.spx-theme-night .spx-settings .spx-primary{border-color:var(--spx-accent)!important;background:var(--spx-accent-soft)!important;color:var(--spx-accent)!important;}',
      '.spx-theme-night .spx-toolbox-key,.spx-theme-night .spx-status-badge,.spx-theme-night .spx-home-badge{background:var(--spx-accent-soft)!important;border-color:var(--spx-line)!important;color:var(--spx-accent)!important;}',
      '.spx-theme-night .spx-preview-lightbox{background:rgba(0,0,0,.92)!important;color:var(--spx-text)!important;}',
      '.spx-theme-night .spx-preview-lightbox-shell,.spx-theme-night .spx-preview-lightbox-stage,.spx-theme-night .spx-preview-lightbox-strip{background:#070908!important;border-color:var(--spx-line)!important;}',
      '.spx-theme-night .spx-preview-lightbox-image{background:#0b0f0c!important;}',
      '.spx-theme-night .spx-danger,.spx-theme-night .spx-watch-actions .spx-danger,.spx-theme-night .spx-settings .spx-danger{border-color:var(--spx-danger)!important;background:var(--spx-danger-soft)!important;color:var(--spx-danger)!important;}',
      '.spx-theme-night #peacemakerconfig>div:not([hidden]),.spx-theme-night #peacemakerconfig>div:not([hidden])>div,.spx-theme-night.spx-home-dashboard #notice,.spx-theme-night.spx-home-dashboard .spx-home-quick a,.spx-theme-night.spx-search-page .t,.spx-theme-night.spx-profile-page .spx-account-tabs,.spx-theme-night.spx-profile-page #u-wrap,.spx-theme-night.spx-profile-page #u-wrap2,.spx-theme-night.spx-profile-page #set-side-wrap,.spx-theme-night.spx-profile-page #set-content-wrap{background:var(--spx-panel)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;box-shadow:var(--spx-shadow-card)!important;}',
      '.spx-theme-night.spx-home-dashboard #notice td,.spx-theme-night.spx-home-dashboard .spx-home-quick a span,.spx-theme-night.spx-home-dashboard .spx-home-module tr.tr3>td,.spx-theme-night.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3>td,.spx-theme-night.spx-forum-dashboard td[id^="td_"],.spx-theme-night.spx-search-page .tr1,.spx-theme-night.spx-search-page .tr2,.spx-theme-night.spx-search-page .tr3,.spx-theme-night.spx-profile-page #set-side .set-h2{background:transparent!important;border-color:var(--spx-line-soft)!important;color:var(--spx-sub)!important;}',
      '.spx-theme-night.spx-home-dashboard .spx-home-module [id^="fn_"],.spx-theme-night.spx-home-dashboard .spx-home-module [id^="fn_"] a,.spx-theme-night.spx-forum-dashboard td[id^="td_"] a[id^="a_ajax_"],.spx-theme-night.spx-forum-dashboard [id^="td_"] a[href*="read.php"],.spx-theme-night.spx-profile-page #set-menu>li>a,.spx-theme-night.spx-profile-page #u-top-nav .b>li>a,.spx-theme-night.spx-profile-page .spx-account-tabs a{color:var(--spx-link)!important;}',
      '.spx-theme-night.spx-profile-page #set-menu>li>a,.spx-theme-night.spx-profile-page #u-top-nav .b>li>a,.spx-theme-night.spx-profile-page .spx-account-tabs a,.spx-theme-night.spx-search-page .spx-module-body input[type="text"],.spx-theme-night.spx-search-page .spx-module-body input[type="search"],.spx-theme-night.spx-search-page .spx-module-body input[name="keyword"],.spx-theme-night.spx-search-page .spx-module-body input[name="username"]{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;}',
      '.spx-theme-night .spx-preview-popover h4{color:var(--spx-strong)!important;}',
      '.spx-theme-night .spx-preview-popover .spx-preview-meta,.spx-theme-night .spx-preview-popover .spx-preview-status{color:var(--spx-muted)!important;}',
      '.spx-theme-night .spx-preview-popover-actions a,.spx-theme-night .spx-preview-popover-actions button{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;}',
      '.spx-theme-night .spx-preview-popover-actions a:hover,.spx-theme-night .spx-preview-popover-actions button:hover{border-color:var(--spx-accent)!important;color:var(--spx-accent)!important;}',
      '.spx-theme-night .spx-preview-popover .spx-preview-text{color:var(--spx-text)!important;}',
      '.spx-theme-night .spx-preview-popover .spx-preview-images a{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;}',
    ];
  }

  function getInjectedPreviewParityStyleRules() {
    return [
      ':root,.spx-theme-clean{--spx-page-max:1480px;--spx-page-space:40px;--spx-radius:10px;--spx-radius-lg:12px;--spx-shadow-card:0 8px 18px rgba(15,23,42,.07);}',
      ':root.spx-theme-night,.spx-theme-night{--spx-page-max:1480px;}',
      '.spx-site-shell #mainNav,.spx-home-dashboard #mainNav,.spx-forum-dashboard #mainNav,.spx-immersive-read #mainNav{height:38px!important;padding:0 8px!important;border-radius:10px!important;box-shadow:0 6px 18px rgba(15,23,42,.12)!important;}',
      '.spx-site-shell #mainNav>div:not([style*="padding-left"]){display:none!important;}',
      '.spx-site-shell #mainNav>div[style*="padding-left"]{display:flex!important;align-items:center!important;gap:6px!important;height:38px!important;padding-left:8px!important;}',
      '.spx-site-shell #mainNav>div[style*="padding-left"] table,.spx-site-shell #mainNav>div[style*="padding-left"] tbody,.spx-site-shell #mainNav>div[style*="padding-left"] tr,.spx-site-shell #mainNav>div[style*="padding-left"] td{flex:none!important;height:38px!important;}',
      '.spx-site-shell #guide{height:38px!important;align-items:center!important;gap:6px!important;}',
      '.spx-site-shell #guide>li>a{height:28px!important;line-height:28px!important;padding:0 10px!important;border-radius:8px!important;font-size:12px!important;font-weight:800!important;}',
      '.spx-site-shell #guide>li.current>a,.spx-site-shell #guide>li.spx-nav-current>a,.spx-site-shell #guide>li>a.spx-nav-current{box-shadow:inset 0 -2px 0 var(--spx-accent)!important;}',
      '.spx-site-shell #peacemakerconfig>div:not([hidden]){top:38px!important;border-radius:10px!important;}',
      '.spx-site-shell #header,.spx-site-shell #mainNav,.spx-site-shell #infobox,.spx-site-shell #notice,.spx-site-shell #content,.spx-home-dashboard #header,.spx-home-dashboard #mainNav,.spx-home-dashboard #infobox,.spx-home-dashboard #notice,.spx-home-dashboard #content,.spx-forum-dashboard #header,.spx-forum-dashboard #mainNav,.spx-forum-dashboard #infobox,.spx-forum-dashboard #notice,.spx-forum-dashboard #content,.spx-immersive-read #header,.spx-immersive-read #mainNav,.spx-immersive-read #breadcrumbs{width:calc(100vw - 40px)!important;max-width:1480px!important;margin-left:auto!important;margin-right:auto!important;}',
      '.spx-home-dashboard #header,.spx-forum-dashboard #header,.spx-immersive-read #header{margin-top:18px!important;}',
      '.spx-home-dashboard #content,.spx-forum-dashboard #content{margin:14px auto 56px!important;}',
      '.spx-module-nav-ready.spx-home-dashboard #content,.spx-module-nav-ready.spx-forum-dashboard #content{grid-template-columns:minmax(196px,var(--spx-module-width)) minmax(0,1fr)!important;gap:14px!important;}',
      '.spx-module-nav-ready .spx-module-nav-host{display:grid!important;grid-template-columns:minmax(196px,var(--spx-module-width)) minmax(0,1fr)!important;gap:14px!important;align-items:start!important;width:calc(100vw - 40px)!important;max-width:1480px!important;margin:14px auto 56px!important;}',
      '.spx-module-nav-ready .spx-module-body{grid-column:2!important;min-width:0!important;display:block!important;}',
      '.spx-module-nav-ready .spx-module-nav-host>*:not(.spx-module-nav):not(.spx-module-body){grid-column:2!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-search-page #main.spx-module-nav-host,.spx-module-nav-ready.spx-search-page #content.spx-module-nav-host,.spx-module-nav-ready.spx-profile-page #main.spx-module-nav-host,.spx-module-nav-ready.spx-profile-page #content.spx-module-nav-host{display:grid!important;grid-template-columns:minmax(196px,var(--spx-module-width)) minmax(0,1fr)!important;gap:14px!important;align-items:start!important;width:calc(100vw - 40px)!important;max-width:1480px!important;margin:14px auto 56px!important;}',
      '.spx-module-nav-ready.spx-search-page #main.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-search-page #content.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-profile-page #main.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-profile-page #content.spx-module-nav-host>.spx-module-nav{grid-column:1!important;width:auto!important;max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-search-page #main.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-search-page #content.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-profile-page #main.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-profile-page #content.spx-module-nav-host>.spx-module-body{grid-column:2!important;width:auto!important;max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-search-page .spx-module-body>.bdbA,.spx-module-nav-ready.spx-search-page .spx-module-body>.t,.spx-module-nav-ready.spx-search-page .spx-module-body>.t3,.spx-module-nav-ready.spx-search-page .spx-module-body>.t5,.spx-module-nav-ready.spx-profile-page .spx-module-body>.bdbA,.spx-module-nav-ready.spx-profile-page .spx-module-body>.t,.spx-module-nav-ready.spx-profile-page .spx-module-body>.t3,.spx-module-nav-ready.spx-profile-page .spx-module-body>.t5{box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:0 0 14px!important;}',
      '.spx-module-nav-ready.spx-search-page .spx-module-body form{display:block!important;box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-search-page .spx-module-body form>.t,.spx-module-nav-ready.spx-search-page .spx-module-body form .t{box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;margin-left:0!important;margin-right:0!important;}',
      '.spx-module-nav-ready.spx-search-page .spx-module-body table,.spx-module-nav-ready.spx-profile-page .spx-module-body table{box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;table-layout:fixed!important;}',
      '.spx-module-nav-ready.spx-search-page .spx-module-body form table{box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;table-layout:fixed!important;}',
      '.spx-module-nav-ready.spx-search-page .spx-module-body td,.spx-module-nav-ready.spx-search-page .spx-module-body th{box-sizing:border-box!important;max-width:100%!important;word-break:break-word!important;}',
      '.spx-module-nav-ready.spx-profile-page .spx-module-body>#spx-account-tabs,.spx-module-nav-ready.spx-profile-page .spx-module-body>#u-wrap,.spx-module-nav-ready.spx-profile-page .spx-module-body>#u-wrap2,.spx-module-nav-ready.spx-profile-page .spx-module-body>#set-wrap,.spx-module-nav-ready.spx-profile-page .spx-module-body>#set-side-wrap,.spx-module-nav-ready.spx-profile-page .spx-module-body>#set-content-wrap{box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-reader #main.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-reader #content.spx-module-nav-host>.spx-module-body{grid-column:2!important;width:auto!important;max-width:100%!important;min-width:0!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body>.bdbA{box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:0 0 10px!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body #breadcrumbs{box-sizing:border-box!important;display:flex!important;flex-wrap:wrap!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body #breadcrumbs .crumbs-item{min-width:0!important;max-width:100%!important;white-space:normal!important;word-break:break-word!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body table.js-post{box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:14px 0!important;table-layout:fixed!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body table.js-post>tbody,.spx-module-nav-ready.spx-reader .spx-module-body table.js-post>tbody>tr{max-width:100%!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body table.js-post>tbody>tr>th.r_one{box-sizing:border-box!important;width:auto!important;max-width:100%!important;min-width:0!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body table.js-post>tbody>tr>th.r_two{box-sizing:border-box!important;max-width:128px!important;min-width:0!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body .spx-post-tools,.spx-module-nav-ready.spx-reader .spx-module-body .tiptop,.spx-module-nav-ready.spx-reader .spx-module-body .tipad,.spx-module-nav-ready.spx-reader .spx-module-body .readbot,.spx-module-nav-ready.spx-reader .spx-module-body .spx-post-body-split,.spx-module-nav-ready.spx-reader .spx-module-body .tpc_content,.spx-module-nav-ready.spx-reader .spx-module-body .signature,.spx-module-nav-ready.spx-reader .spx-module-body .sigline{box-sizing:border-box!important;max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host{display:grid!important;grid-template-columns:minmax(196px,var(--spx-module-width)) minmax(0,1fr)!important;gap:14px!important;align-items:start!important;width:calc(100vw - 40px)!important;max-width:1480px!important;margin:14px auto 56px!important;}',
      '.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host>.spx-module-nav{grid-column:1!important;width:auto!important;max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host>.spx-module-body{grid-column:2!important;display:block!important;width:auto!important;max-width:100%!important;min-width:0!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-module-body>.bdbA{box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:0 0 14px!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-module-body>.t,.spx-module-nav-ready.spx-task-page .spx-module-body>.t3,.spx-module-nav-ready.spx-task-page .spx-module-body>.t5{box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:0 0 14px!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-module-body table{box-sizing:border-box!important;width:100%!important;max-width:100%!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-module-body td,.spx-module-nav-ready.spx-task-page .spx-module-body th{max-width:100%!important;word-break:break-word!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-module-body img{max-width:100%!important;height:auto!important;}',
      '.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host>.spx-module-body.spx-task-layout-body,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host>.spx-module-body.spx-task-layout-body{display:grid!important;grid-template-columns:minmax(240px,320px) minmax(0,1fr)!important;gap:18px!important;align-items:start!important;overflow:visible!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-layout-body>.bdbA{grid-column:1/-1!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-side-stack{grid-column:1!important;display:flex!important;flex-direction:column!important;gap:14px!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-side-stack:empty{display:none!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-main-stack{grid-column:2!important;min-width:0!important;max-width:100%!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-side-stack>.t,.spx-module-nav-ready.spx-task-page .spx-task-side-stack>.t3,.spx-module-nav-ready.spx-task-page .spx-task-side-stack>.t5,.spx-module-nav-ready.spx-task-page .spx-task-main-stack>.t,.spx-module-nav-ready.spx-task-page .spx-task-main-stack>.t3,.spx-module-nav-ready.spx-task-page .spx-task-main-stack>.t5{box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-side-stack>.spx-task-side-block,.spx-module-nav-ready.spx-task-page .spx-task-main-stack>.spx-task-main-block{float:none!important;clear:none!important;box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-main-stack table,.spx-module-nav-ready.spx-task-page .spx-task-side-stack table{box-sizing:border-box!important;width:100%!important;max-width:100%!important;}',
      '.spx-module-nav-ready.spx-home-dashboard .spx-module-body #spx-home-grid{grid-template-columns:minmax(0,1fr)!important;width:100%!important;max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-home-dashboard .spx-module-body .spx-home-module,.spx-module-nav-ready.spx-home-dashboard .spx-module-body .spx-home-module[data-spx-large="1"]{grid-column:1!important;width:100%!important;max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav{top:52px!important;gap:9px!important;padding:12px!important;border-radius:12px!important;box-shadow:0 5px 14px rgba(15,23,42,.05)!important;}',
      '.spx-module-nav-title{padding:0 2px 9px!important;border-bottom:1px solid var(--spx-line)!important;}',
      '.spx-module-nav-section{margin:6px 0 1px!important;border:0!important;background:var(--spx-accent-wash)!important;cursor:pointer!important;}',
      '.spx-module-nav-group.spx-module-nav-collapsed>.spx-module-nav-node{display:none!important;}',
      '.spx-module-nav-item{min-height:var(--spx-module-item-height)!important;padding:0 var(--spx-module-item-padding)!important;border-radius:var(--spx-module-item-radius)!important;background:var(--spx-panel-muted)!important;}',
      '.spx-module-nav-count{min-width:22px!important;height:20px!important;}',
      '.spx-home-dashboard .spx-home-quick{display:none!important;}',
      '.spx-home-dashboard #notice,.spx-home-dashboard .spx-home-module,.spx-forum-dashboard #content .t,.spx-forum-dashboard .spx-module-nav-host .t{box-sizing:border-box!important;width:100%!important;max-width:100%!important;min-width:0!important;border-radius:12px!important;background:var(--spx-panel)!important;border-color:var(--spx-line)!important;box-shadow:0 8px 18px rgba(15,23,42,.06)!important;}',
      '.spx-home-dashboard .spx-home-module>h2,.spx-home-dashboard .spx-home-module .h{min-height:38px!important;padding:0 12px!important;background:var(--spx-panel)!important;border:0!important;border-bottom:1px solid var(--spx-line)!important;color:var(--spx-strong)!important;font-size:13px!important;font-weight:900!important;}',
      '.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table table,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tbody{display:block!important;width:100%!important;border:0!important;background:transparent!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr2,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr2,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr2{display:grid!important;gap:8px!important;padding:10px 12px!important;border-bottom:1px solid var(--spx-line)!important;background:var(--spx-panel-muted)!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr2{grid-template-columns:minmax(360px,620px) 96px minmax(180px,260px)!important;justify-content:start!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr2,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr2{grid-template-columns:minmax(0,1fr) 92px 82px minmax(160px,.7fr)!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr2>td,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr2>td,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr2>td{display:block!important;width:auto!important;padding:0!important;border:0!important;background:transparent!important;color:var(--spx-sub)!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr2>td:last-child,.spx-home-dashboard .spx-home-module tr.tr3>td:last-child{display:none!important;}',
      '.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr2>td:first-child{display:none!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr3,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr3{min-height:44px!important;padding:9px 12px!important;border-bottom:1px solid var(--spx-line)!important;background:var(--spx-panel)!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr3{grid-template-columns:minmax(360px,620px) 96px minmax(180px,260px)!important;justify-content:start!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr3{grid-template-columns:minmax(0,1fr) 92px 82px minmax(160px,.7fr)!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr3>td,.spx-home-dashboard .spx-home-module tr.tr3>th,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3>td,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr3>td,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr3>th{display:block!important;width:auto!important;padding:0!important;border:0!important;background:transparent!important;font-size:12px!important;line-height:1.45!important;color:var(--spx-sub)!important;overflow:hidden!important;text-overflow:ellipsis!important;}',
      '.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr3>td:first-child:not([id^="td_"]){display:none!important;}',
      '.spx-home-dashboard .spx-home-module [id^="fn_"] a,.spx-home-dashboard .spx-home-module [id^="fn_"],.spx-forum-dashboard td[id^="td_"] a[id^="a_ajax_"],.spx-forum-dashboard [id^="td_"] a[href*="read.php"]{color:var(--spx-strong)!important;font-size:14px!important;font-weight:800!important;}',
      '.spx-home-dashboard .spx-home-hot [id^="fn_"] a,.spx-home-dashboard .spx-home-hot [id^="fn_"]{color:var(--spx-accent)!important;}',
      '.spx-home-dashboard .spx-home-badge,.spx-toolbox-count,.spx-status-badge,.spx-toolbox-key{background:var(--spx-accent-soft)!important;color:var(--spx-accent)!important;}',
      '.spx-forum-tools{min-height:42px!important;padding:8px 10px!important;border-radius:12px!important;background:var(--spx-panel)!important;border-color:var(--spx-line)!important;box-shadow:0 8px 18px rgba(15,23,42,.06)!important;}',
      '.spx-forum-tools input{height:30px!important;border-radius:8px!important;background:var(--spx-panel-muted)!important;}',
      '.spx-forum-tools button,.spx-thread-tools button,.spx-post-tools button{border-radius:8px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;}',
      '.spx-forum-dashboard .spx-thread-list-table tr:not(.tr2):not(.tr3){display:none!important;}',
      '.spx-forum-dashboard .spx-thread-list-table tr.spx-filter-hidden,.spx-forum-dashboard .spx-thread-list-table tr.spx-hidden-rule,.spx-forum-dashboard .spx-thread-list-table tr.spx-unread-hidden,.spx-forum-dashboard .spx-thread-list-table tr.spx-thread-row-hidden{display:none!important;}',
      '.spx-forum-dashboard .spx-thread-list-table tr.tr3:not(:hover) .spx-thread-tools{display:none!important;}',
      '.spx-theme-night.spx-forum-gallery-page #wall .inner,.spx-theme-night.spx-forum-gallery-page #wall .spx-gallery-card-inner{background:var(--spx-panel)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;box-shadow:0 10px 30px rgba(0,0,0,.28)!important;}',
      '.spx-theme-night.spx-forum-gallery-page #wall .section-intro{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;color:var(--spx-sub)!important;}',
      '.spx-theme-night.spx-forum-gallery-page #wall .section-text img{border-color:var(--spx-line)!important;background:var(--spx-panel-muted)!important;}',
      '.spx-toolbar{right:16px!important;bottom:18px!important;display:flex!important;flex-direction:row!important;gap:7px!important;padding:6px!important;border-radius:16px!important;background:rgba(255,255,255,.92)!important;box-shadow:0 18px 46px rgba(15,23,42,.18)!important;}',
      '.spx-toolbar button,.spx-toolbar a{width:52px!important;height:36px!important;border-radius:12px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;}',
      '.spx-toolbar button:hover,.spx-toolbar a:hover,.spx-toolbar button:focus-visible,.spx-toolbar a:focus-visible{border-color:var(--spx-accent)!important;color:var(--spx-accent)!important;box-shadow:0 7px 18px rgba(37,99,235,.16)!important;}',
      '.spx-toolbar .spx-active{border-color:var(--spx-accent)!important;background:var(--spx-accent-soft)!important;color:var(--spx-accent)!important;box-shadow:inset 0 0 0 1px rgba(37,99,235,.12),0 6px 16px rgba(37,99,235,.14)!important;}',
      '.spx-toolbox,.spx-settings,.spx-watch-center{right:16px!important;bottom:72px!important;border-radius:12px!important;}',
      '.spx-toolbox{width:min(460px,calc(100vw - 32px))!important;}',
      '.spx-toolbox-header,.spx-settings-header,.spx-watch-center-header{background:var(--spx-panel)!important;border-color:var(--spx-line)!important;}',
      '.spx-toolbox-eyebrow{color:var(--spx-accent)!important;}',
      '.spx-toolbox-action{min-height:42px!important;border-radius:10px!important;background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;box-shadow:none!important;}',
      '.spx-toolbox-action:hover,.spx-toolbox-action:focus-visible{border-color:var(--spx-accent)!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;box-shadow:0 10px 24px rgba(37,99,235,.15)!important;}',
      '.spx-toolbox-action.spx-active{border-color:var(--spx-accent)!important;background:var(--spx-accent-soft)!important;color:var(--spx-accent)!important;box-shadow:0 8px 22px rgba(37,99,235,.18)!important;}',
      '.spx-toolbox-action.spx-active:before{background:var(--spx-accent)!important;}',
      '.spx-theme-night .spx-toolbar{background:rgba(24,29,26,.92)!important;box-shadow:0 18px 46px rgba(0,0,0,.42)!important;}',
      '.spx-theme-night.spx-home-dashboard .spx-home-module,.spx-theme-night.spx-home-dashboard #notice,.spx-theme-night.spx-home-dashboard .spx-home-quick a,.spx-theme-night.spx-forum-dashboard #content .t,.spx-theme-night.spx-forum-dashboard .spx-module-nav-host .t{background:#181d1a!important;border-color:#303a32!important;color:#edf4ec!important;box-shadow:0 10px 30px rgba(0,0,0,.28)!important;}',
      '.spx-theme-night .spx-forum-dashboard-panel,.spx-theme-night .spx-forum-dashboard-card{background:var(--spx-panel)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;box-shadow:0 10px 30px rgba(0,0,0,.28)!important;}.spx-theme-night .spx-forum-dashboard-stat,.spx-theme-night .spx-forum-dashboard-topic,.spx-theme-night .spx-forum-dashboard-resource,.spx-theme-night .spx-forum-dashboard-activity{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;}.spx-theme-night .spx-forum-dashboard-stat.spx-blue,.spx-theme-night .spx-forum-dashboard-stat.spx-amber,.spx-theme-night .spx-forum-dashboard-stat.spx-rose,.spx-theme-night .spx-forum-dashboard-stat.spx-green,.spx-theme-night .spx-forum-dashboard-stat.spx-violet{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;}',
      '.spx-theme-night.spx-home-dashboard .spx-home-module tr.tr2,.spx-theme-night.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr2,.spx-theme-night.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr2{background:#202720!important;border-color:#303a32!important;color:#b7c8b7!important;}',
      '.spx-theme-night.spx-home-dashboard .spx-home-module tr.tr3,.spx-theme-night.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3,.spx-theme-night.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr3{background:#181d1a!important;border-color:#303a32!important;}',
      '.spx-theme-night.spx-home-dashboard .spx-home-module>h2,.spx-theme-night.spx-home-dashboard .spx-home-module .h,.spx-theme-night .spx-toolbox-header,.spx-theme-night .spx-settings-header,.spx-theme-night .spx-watch-center-header{background:var(--spx-panel)!important;}',
      '@media(max-width:900px){:root,.spx-theme-clean,.spx-theme-night{--spx-page-space:16px}.spx-site-shell #mainNav,.spx-home-dashboard #mainNav,.spx-forum-dashboard #mainNav,.spx-immersive-read #mainNav{height:34px!important}.spx-site-shell #guide{height:34px!important;overflow-x:auto!important}.spx-site-shell #guide>li>a{height:26px!important}.spx-site-shell #spx-nav-brand{height:26px!important;line-height:26px!important}.spx-module-nav-ready.spx-home-dashboard #content,.spx-module-nav-ready.spx-forum-dashboard #content,.spx-module-nav-ready .spx-module-nav-host{grid-template-columns:minmax(0,1fr)!important;gap:10px!important;width:calc(100vw - 16px)!important;margin:10px 8px 34px!important}.spx-module-nav-ready .spx-module-nav-host>*:not(.spx-module-nav),.spx-module-nav-ready .spx-module-body,.spx-module-nav{grid-column:1!important;width:auto!important;max-width:100%!important}.spx-module-nav{top:auto!important;position:static!important}.spx-module-nav-section{flex:none!important;margin:0 2px!important}.spx-home-dashboard .spx-home-module tr.tr2,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr2,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr2{display:none!important}.spx-home-dashboard .spx-home-module tr.tr3,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr3{grid-template-columns:1fr!important}}',
      '@media(max-width:900px){.spx-favorite-nav{margin-left:0!important;height:34px!important}.spx-favorite-nav-trigger{height:26px!important;line-height:26px!important}.spx-favorite-nav-panel{top:38px!important;right:auto!important;left:0!important;width:min(720px,calc(100vw - 24px))!important;max-height:calc(100vh - 84px)!important}.spx-favorite-stats{grid-template-columns:repeat(3,minmax(96px,1fr))!important;overflow-x:auto!important}.spx-favorite-item{grid-template-columns:minmax(0,1fr)!important}.spx-favorite-actions{justify-content:flex-start!important}.spx-favorite-tools{align-items:flex-start!important;flex-direction:column!important}.spx-favorite-sort{width:100%!important}}',
      '@media(max-width:900px){.spx-module-nav-ready.spx-search-page #main.spx-module-nav-host,.spx-module-nav-ready.spx-search-page #content.spx-module-nav-host,.spx-module-nav-ready.spx-profile-page #main.spx-module-nav-host,.spx-module-nav-ready.spx-profile-page #content.spx-module-nav-host{grid-template-columns:minmax(0,1fr)!important;width:calc(100vw - 16px)!important;margin:10px 8px 34px!important}.spx-module-nav-ready.spx-search-page #main.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-search-page #content.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-profile-page #main.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-profile-page #content.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-search-page #main.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-search-page #content.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-profile-page #main.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-profile-page #content.spx-module-nav-host>.spx-module-body{grid-column:1!important;width:auto!important;max-width:100%!important}}',
      '@media(max-width:900px){.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host{grid-template-columns:minmax(0,1fr)!important;width:calc(100vw - 16px)!important;margin:10px 8px 34px!important}.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host>.spx-module-body{grid-column:1!important;width:auto!important;max-width:100%!important}.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host>.spx-module-body.spx-task-layout-body,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host>.spx-module-body.spx-task-layout-body{grid-template-columns:minmax(0,1fr)!important}.spx-module-nav-ready.spx-task-page .spx-task-side-stack,.spx-module-nav-ready.spx-task-page .spx-task-main-stack{grid-column:1!important}}',
    ];
  }

  function injectStyles() {
    if (qs('#sp-enhancer-style')) return;
    var style = createEl('style');
    style.id = 'sp-enhancer-style';
    style.textContent = getInjectedStyleText();
    document.head.appendChild(style);
  }

  function setBodyClasses(settings) {
    var navDensity = getModuleNavigationDensityConfig(settings && settings.moduleNavDensity);
    document.documentElement.classList.toggle('spx-theme-clean', !settings.nightMode);
    document.documentElement.classList.toggle('spx-theme-night', !!settings.nightMode);
    document.documentElement.classList.toggle('spx-site-shell', shouldUseSiteShell(location.href));
    document.documentElement.classList.toggle('spx-adblock', !!settings.adBlock);
    document.documentElement.classList.toggle('spx-clean', !!settings.cleanMode);
    document.documentElement.classList.toggle('spx-reader', shouldUseReaderMode(settings, location.href));
    document.documentElement.classList.toggle('spx-immersive-read', shouldUseImmersiveRead(settings, location.href));
    document.documentElement.classList.toggle('spx-home-dashboard', shouldUseHomeDashboard(settings, location.href));
    document.documentElement.classList.toggle('spx-forum-dashboard', shouldUseForumDashboard(location.href, document));
    document.documentElement.classList.toggle('spx-forum-gallery-page', isForumGalleryModeUrl(location.href));
    document.documentElement.classList.toggle('spx-search-page', shouldUseSearchPage(location.href));
    document.documentElement.classList.toggle('spx-profile-page', shouldUseProfilePage(location.href));
    document.documentElement.classList.toggle('spx-task-page', shouldUseTaskPage(location.href));
    document.documentElement.classList.toggle('spx-compact-read', !!settings.compactRead);
    document.documentElement.classList.toggle('spx-hide-profile', !!settings.hideUserProfile);
    document.documentElement.style.setProperty('--spx-immersive-font-size', '16px');
    document.documentElement.style.setProperty('--spx-module-width', navDensity.width + 'px');
    document.documentElement.style.setProperty('--spx-module-max-offset', navDensity.maxOffset + 'px');
    document.documentElement.style.setProperty('--spx-module-section-height', navDensity.sectionHeight + 'px');
    document.documentElement.style.setProperty('--spx-module-parent-height', navDensity.parentHeight + 'px');
    document.documentElement.style.setProperty('--spx-module-item-height', navDensity.itemHeight + 'px');
    document.documentElement.style.setProperty('--spx-module-child-height', navDensity.childHeight + 'px');
    document.documentElement.style.setProperty('--spx-module-item-padding', navDensity.itemPadding + 'px');
    document.documentElement.style.setProperty('--spx-module-section-padding', navDensity.sectionPadding + 'px');
    document.documentElement.style.setProperty('--spx-module-item-radius', navDensity.radius + 'px');
  }

  function extractAccountUserId(root, url) {
    var scope = root || document;
    var text = String(url || location.href || '');
    var urlMatch = text.match(/[?&]uid[=-](\d+)/) || text.match(/uid-(\d+)/);
    if (urlMatch) return urlMatch[1];

    var ownProfileLink = qsa('a[href*="u.php?action-show-uid"]', scope).filter(function likelyOwnProfile(link) {
      return /查看个人资料|个人资料|资料/.test((link.textContent || '').trim());
    })[0] || qs('#user-login a[href*="u.php"]', scope);

    var href = ownProfileLink ? ownProfileLink.getAttribute('href') || ownProfileLink.href : '';
    var linkMatch = String(href || '').match(/uid[=-](\d+)/) || String(href || '').match(/uid-(\d+)/);
    return linkMatch ? linkMatch[1] : '';
  }

  function getAccountTabItems(userId, origin) {
    var uid = String(userId || '');
    var base = String(origin || location.origin || '');
    return [
      { key: 'home', label: '个人首页', href: base + '/u.php?uid-' + uid + '.html' },
      { key: 'profile', label: '资料', href: base + '/u.php?action-show-uid-' + uid + '.html' },
      { key: 'topic', label: '主题', href: base + '/u.php?action-topic-uid-' + uid + '.html' },
      { key: 'post', label: '回复', href: base + '/u.php?action-post-uid-' + uid + '.html' },
      { key: 'favor', label: '收藏', href: base + '/u.php?action-favor-uid-' + uid + '.html' },
      { key: 'friend', label: '好友', href: base + '/u.php?action-friend-uid-' + uid + '.html' },
      { key: 'trade', label: '商品', href: base + '/u.php?action-trade-uid-' + uid + '.html' },
      { key: 'message', label: '消息', href: base + '/message.php' },
      { key: 'settings', label: '设置', href: base + '/profile.php' },
    ];
  }

  function getAccountActiveKey(url) {
    var text = String(url || '');
    if (/\/message\.php/.test(text)) return 'message';
    if (/\/profile\.php/.test(text)) return 'settings';
    if (/action-show/.test(text)) return 'profile';
    if (/action-topic/.test(text)) return 'topic';
    if (/action-post/.test(text)) return 'post';
    if (/action-favor/.test(text)) return 'favor';
    if (/action-friend/.test(text)) return 'friend';
    if (/action-trade/.test(text)) return 'trade';
    if (/\/u\.php/.test(text)) return 'home';
    return '';
  }

  function getFavoriteNavUrl(userId, origin) {
    var uid = String(userId || '').replace(/\D+/g, '');
    if (!uid) return '';
    var base = String(origin || location.origin || '').replace(/\/+$/, '');
    return base + '/u.php?action-favor-uid-' + uid + '.html';
  }

  function extractSiteVerifyHashFromText(text) {
    var value = String(text || '');
    var markerIndex = value.indexOf('verifyhash');
    if (markerIndex < 0) return '';
    var assignIndex = value.indexOf('=', markerIndex);
    if (assignIndex < 0) return '';
    var rest = value.slice(assignIndex + 1).replace(/^\s+/, '');
    var quote = rest.charAt(0);
    if (quote !== "'" && quote !== '"') return '';
    var endIndex = rest.indexOf(quote, 1);
    return endIndex > 0 ? rest.slice(1, endIndex) : '';
  }

  function getSiteVerifyHash(root) {
    if (typeof verifyhash !== 'undefined' && verifyhash) return String(verifyhash);
    if (typeof window !== 'undefined' && window.verifyhash) return String(window.verifyhash);
    var scope = root || (typeof document !== 'undefined' ? document : null);
    var scripts = scope && scope.querySelectorAll ? qsa('script', scope) : [];
    for (var i = 0; i < scripts.length; i += 1) {
      var scriptMatch = extractSiteVerifyHashFromText(scripts[i].textContent || '');
      if (scriptMatch) return scriptMatch;
    }
    var html = scope && scope.documentElement ? scope.documentElement.outerHTML : '';
    return extractSiteVerifyHashFromText(html);
  }

  function getThreadFavoriteUrl(threadId, origin, verify, now) {
    var tid = parseThreadId(threadId);
    if (!tid) return '';
    var base = String(origin || location.origin || '').replace(/\/+$/, '');
    var url = base + '/pw_ajax.php?action=favor&tid=' + encodeURIComponent(tid);
    var timestamp = now === undefined ? Date.now() : Number(now);
    url += '&nowtime=' + encodeURIComponent(isFinite(timestamp) ? timestamp : Date.now());
    if (verify) url += '&verify=' + encodeURIComponent(verify);
    return url;
  }

  function getThreadFavoriteResultText(text) {
    var value = compactText(String(text || '').replace(/^<\?xml[^>]*>/i, '').replace(/<ajax><!\[CDATA\[|\]\]><\/ajax>/gi, ''));
    if (!value) return '已收藏';
    if (/登录|权限|失败|错误|非法|重试/.test(value)) return '收藏失败';
    if (/已经|已收藏|重复/.test(value)) return '已收藏';
    if (/success|成功|收藏/.test(value)) return '已收藏';
    return '已提交';
  }

  function isNewThreadFavoriteResult(text) {
    var value = compactText(String(text || '').replace(/^<\?xml[^>]*>/i, '').replace(/<ajax><!\[CDATA\[|\]\]><\/ajax>/gi, ''));
    if (!value || /已经|已收藏|重复/.test(value)) return false;
    return /success|成功|收藏/.test(value);
  }

  function getSiteFavoriteDeleteResultText(text) {
    var value = compactText(String(text || '').replace(/^<\?xml[^>]*>/i, '').replace(/<ajax><!\[CDATA\[|\]\]><\/ajax>/gi, ''));
    if (/登录|权限|失败|错误|非法|重试/.test(value)) return '删除失败';
    if (/删除|取消|移除|success|成功/.test(value)) return '已删除';
    return value ? '已提交' : '已删除';
  }

  function buildFavoriteDeleteRequest(action, method, fields, pageUrl) {
    var targetUrl = normalizeNavigationHref(action || '', pageUrl || location.href);
    if (!targetUrl) return null;
    var requestMethod = String(method || 'GET').toUpperCase() === 'POST' ? 'POST' : 'GET';
    var params = new URLSearchParams();
    (fields || []).forEach(function appendFavoriteDeleteField(field) {
      if (!field || !field.name) return;
      params.append(field.name, field.value == null ? '' : String(field.value));
    });
    var body = params.toString();
    if (requestMethod === 'GET') {
      if (body) targetUrl += (targetUrl.indexOf('?') === -1 ? '?' : '&') + body;
      return { url: targetUrl, method: 'GET', body: '' };
    }
    return {
      url: targetUrl,
      method: 'POST',
      body: body,
      contentType: 'application/x-www-form-urlencoded;charset=UTF-8',
    };
  }

  function normalizeFavoriteDeleteFields(fields) {
    var result = [];
    var hasJob = false;
    (fields || []).forEach(function keepFavoriteDeleteField(field) {
      if (!field || !field.name) return;
      if (field.name === 'job') {
        if (!hasJob) {
          result.push({ name: 'job', value: 'clear' });
          hasJob = true;
        }
        return;
      }
      if (field.name === 'type') return;
      result.push(field);
    });
    if (!hasJob) result.push({ name: 'job', value: 'clear' });
    return result;
  }

  function isFavoriteDeleteControl(node) {
    if (!node) return false;
    var text = [
      node.textContent,
      node.value,
      node.title,
      node.getAttribute && node.getAttribute('href'),
      node.getAttribute && node.getAttribute('onclick'),
    ].join(' ');
    return /删除|取消收藏|移除|delete|del/i.test(text) && /收藏|favor|selid|delete|del/i.test(text);
  }

  function getFavoriteDeleteUrlFromLink(node, pageUrl) {
    if (!isFavoriteDeleteControl(node)) return '';
    var values = [
      node.getAttribute && node.getAttribute('href'),
      node.getAttribute && node.getAttribute('onclick'),
    ];
    var candidates = [];
    values.forEach(function collectFavoriteDeleteCandidate(value) {
      var text = String(value || '').replace(/&amp;/g, '&').trim();
      if (!text) return;
      if (!/^javascript:/i.test(text)) candidates.push(text);
      var match;
      var quotedUrlRe = /['"]([^'"]*(?:u\.php|pw_ajax\.php|action[=-]favor|selid|delete|del)[^'"]*)['"]/ig;
      while ((match = quotedUrlRe.exec(text))) {
        candidates.push(match[1].replace(/&amp;/g, '&'));
      }
    });
    for (var index = 0; index < candidates.length; index += 1) {
      var candidate = candidates[index];
      if (!/(?:favor|selid|delete|del)/i.test(candidate)) continue;
      var url = normalizeNavigationHref(candidate, pageUrl || location.href);
      if (url) return url;
    }
    return '';
  }

  function collectFavoriteDeleteFormFields(form, targetControl, submitControl) {
    var fields = [];
    qsa('input,textarea,select', form).forEach(function collectFavoriteDeleteField(control) {
      if (!control || control.disabled || !control.name) return;
      var tag = String(control.tagName || '').toLowerCase();
      var type = String(control.type || '').toLowerCase();
      if (type === 'file' || type === 'reset' || type === 'button' || type === 'image') return;
      if (type === 'submit') {
        if (control === submitControl) fields.push({ name: control.name, value: control.value || '' });
        return;
      }
      if (type === 'checkbox') {
        if (control === targetControl) fields.push({ name: control.name, value: control.value || 'on' });
        return;
      }
      if (type === 'radio') {
        if (control.name === 'job' && control.value === 'clear') fields.push({ name: control.name, value: control.value });
        else if (control === targetControl) fields.push({ name: control.name, value: control.value || 'on' });
        return;
      }
      if (tag === 'select' && control.name !== 'type') {
        qsa('option', control).forEach(function collectFavoriteSelectOption(option) {
          if (option.selected) fields.push({ name: control.name, value: option.value });
        });
        return;
      }
      if (tag === 'select') return;
      fields.push({ name: control.name, value: control.value || '' });
    });
    if (submitControl && submitControl.name && submitControl.type !== 'submit') {
      fields.push({ name: submitControl.name, value: submitControl.value || submitControl.textContent || '' });
    }
    return fields;
  }

  function findFavoriteDeleteRequestFromForm(row, pageUrl) {
    if (!row || !row.closest) return null;
    var form = row.closest('form');
    if (!form) return null;
    var controls = qsa('input,button', form);
    var targetControl = qsa('input[type="checkbox"],input[type="radio"]', row).filter(function keepFavoriteCheckbox(control) {
      return /sel|id|favor|tid/i.test(control.name || '') || /\d+/.test(control.value || '');
    })[0];
    if (!targetControl) return null;
    var submitControl = controls.filter(isFavoriteDeleteControl)[0] || null;
    var fields = normalizeFavoriteDeleteFields(collectFavoriteDeleteFormFields(form, targetControl, submitControl));
    return buildFavoriteDeleteRequest(form.getAttribute('action') || form.action || pageUrl, form.getAttribute('method') || form.method || 'GET', fields, pageUrl);
  }

  function findFavoriteDeleteRequestFromLinks(row, pageUrl) {
    if (!row) return null;
    var deleteUrl = '';
    qsa('a[href],a[onclick],button[onclick],input[onclick]', row).some(function findFavoriteDeleteLink(node) {
      deleteUrl = getFavoriteDeleteUrlFromLink(node, pageUrl);
      return !!deleteUrl;
    });
    return deleteUrl ? buildFavoriteDeleteRequest(deleteUrl, 'GET', [], pageUrl) : null;
  }

  function getFavoriteNavDeleteKey(entry) {
    var item = entry || {};
    return [item.source || '', item.id || '', item.url || '', item.index == null ? '' : item.index].join('|');
  }

  function getSelectedSiteFavoriteEntries(entries, selectedMap) {
    var selected = selectedMap || {};
    return (entries || []).filter(function keepSelectedFavorite(entry) {
      return !!(entry && entry.deleteRequest && selected[getFavoriteNavDeleteKey(entry)]);
    });
  }

  function createSiteFavoriteDeleteRequest(row, pageUrl) {
    return findFavoriteDeleteRequestFromLinks(row, pageUrl) || findFavoriteDeleteRequestFromForm(row, pageUrl);
  }

  function markThreadFavoriteSeen(info) {
    var tid = parseThreadId(info && info.id);
    if (!tid) return false;
    var seenMap = loadMap(FAVORITE_NAV_SEEN_KEY);
    if (seenMap[tid]) return false;
    seenMap[tid] = Date.now();
    saveMap(FAVORITE_NAV_SEEN_KEY, seenMap);
    return true;
  }

  function isThreadFavoriteSeen(threadId) {
    var tid = parseThreadId(threadId);
    if (!tid) return false;
    return !!loadMap(FAVORITE_NAV_SEEN_KEY)[tid];
  }

  function saveThreadSiteFavorite(info, settings) {
    var verify = getSiteVerifyHash(document);
    var url = getThreadFavoriteUrl(info && info.id, location.origin, verify, Date.now());
    if (!url) return Promise.reject(new Error('缺少帖子ID'));
    if (!verify) return Promise.reject(new Error('缺少站点校验参数'));
    var policy = {
      mode: 'action',
      label: '收藏帖子',
      networkFriendly: isNetworkFriendlyMode(settings),
    };
    return requestWithPolicy(url, { credentials: 'include', cache: 'no-store' }, policy)
      .then(function readFavoriteResponse(response) {
        if (!response.ok) throw new Error('收藏失败');
        return readScriptResponseText(response, policy);
      })
      .then(function resolveFavoriteText(html) {
        var text = getThreadFavoriteResultText(html);
        if (text === '收藏失败') throw new Error('收藏失败');
        return {
          text: text,
          added: isNewThreadFavoriteResult(html) && markThreadFavoriteSeen(info),
        };
      });
  }

  function runThreadFavoriteAction(info, settings, state, button, doneText) {
    if (!button) return;
    var originalText = button.dataset.spxFavoriteOriginalText || button.textContent || '收藏';
    button.dataset.spxFavoriteOriginalText = originalText;
    button.disabled = true;
    button.textContent = '收藏中';
    saveThreadSiteFavorite(info, settings).then(
      function markFavoriteDone(result) {
        delete button.dataset.spxFavoriteOriginalText;
        button.textContent = doneText || result.text || '已收藏';
        syncFavoriteNavAfterSiteFavorite(info, settings, state, !!(result && result.added));
      },
      function markFavoriteFailed(error) {
        var message = error && error.message ? error.message : '收藏失败';
        button.dataset.spxFavoriteError = message;
        button.title = message;
        button.textContent = '收藏失败';
        if (typeof window === 'undefined' || typeof window.setTimeout !== 'function') {
          button.disabled = false;
          button.textContent = originalText;
          delete button.dataset.spxFavoriteOriginalText;
          return;
        }
        window.setTimeout(function restoreFavoriteButton() {
          if ('isConnected' in button && !button.isConnected) return;
          button.disabled = false;
          button.textContent = originalText;
          delete button.dataset.spxFavoriteOriginalText;
        }, 1400);
      }
    );
  }

  function normalizeFavoriteNavSource(value) {
    return value === 'watch' ? 'watch' : 'site';
  }

  function normalizeFavoriteNavFilter(value) {
    var filter = String(value || 'all');
    return /^(all|recent|unread|resource|image|ai)$/.test(filter) ? filter : 'all';
  }

  function normalizeFavoriteNavSort(value) {
    var sort = String(value || 'recent');
    return /^(recent|read|reply)$/.test(sort) ? sort : 'recent';
  }

  function formatFavoriteNavCount(count, loading) {
    if (loading) return '...';
    var value = Number(count);
    if (!isFinite(value) || value < 0) return '-';
    if (value >= 1000) return '999+';
    return String(value);
  }

  function getFavoriteNavCountCacheKey(favoriteUrl) {
    return String(favoriteUrl || '').replace(/#.*$/, '');
  }

  function normalizeFavoriteNavCountCacheEntry(entry, now) {
    var source = entry || {};
    var count = Number(source.count);
    var updatedAt = Number(source.updatedAt);
    var nextRefreshAt = Number(source.nextRefreshAt || 0);
    var currentTime = now === undefined ? Date.now() : Number(now);
    if (!isFinite(count) || count < 0 || !isFinite(updatedAt) || updatedAt <= 0) return null;
    return {
      count: Math.floor(count),
      updatedAt: updatedAt,
      expiresAt: updatedAt + FAVORITE_NAV_COUNT_CACHE_TTL,
      fresh: currentTime - updatedAt < FAVORITE_NAV_COUNT_CACHE_TTL,
      nextRefreshAt: isFinite(nextRefreshAt) && nextRefreshAt > 0 ? nextRefreshAt : 0,
      failedAt: Number(source.failedAt || 0) || 0,
    };
  }

  function shouldRefreshFavoriteNavCountCache(entry, now) {
    var cache = normalizeFavoriteNavCountCacheEntry(entry, now);
    var currentTime = now === undefined ? Date.now() : Number(now);
    if (!cache) {
      var nextRefreshAt = Number(entry && entry.nextRefreshAt || 0);
      return !(isFinite(nextRefreshAt) && nextRefreshAt > currentTime);
    }
    if (cache.fresh) return false;
    return !(cache.nextRefreshAt > currentTime);
  }

  function loadFavoriteNavCountCache(favoriteUrl, now) {
    var key = getFavoriteNavCountCacheKey(favoriteUrl);
    if (!key) return null;
    return normalizeFavoriteNavCountCacheEntry(loadMap(FAVORITE_NAV_COUNT_CACHE_KEY)[key], now);
  }

  function saveFavoriteNavCountCache(favoriteUrl, count, now) {
    var key = getFavoriteNavCountCacheKey(favoriteUrl);
    var value = Number(count);
    if (!key || !isFinite(value) || value < 0) return;
    var data = loadMap(FAVORITE_NAV_COUNT_CACHE_KEY);
    data[key] = {
      count: Math.floor(value),
      updatedAt: now === undefined ? Date.now() : Number(now),
    };
    var keys = Object.keys(data).sort(function sortFavoriteCountCache(left, right) {
      return Number(data[right] && data[right].updatedAt || 0) - Number(data[left] && data[left].updatedAt || 0);
    });
    keys.slice(12).forEach(function pruneFavoriteCountCache(item) {
      delete data[item];
    });
    saveMap(FAVORITE_NAV_COUNT_CACHE_KEY, data);
  }

  function delayFavoriteNavCountCacheRefresh(favoriteUrl, now) {
    var key = getFavoriteNavCountCacheKey(favoriteUrl);
    if (!key) return;
    var currentTime = now === undefined ? Date.now() : Number(now);
    var data = loadMap(FAVORITE_NAV_COUNT_CACHE_KEY);
    var entry = data[key] || {};
    entry.failedAt = currentTime;
    entry.nextRefreshAt = currentTime + FAVORITE_NAV_COUNT_RETRY_TTL;
    data[key] = entry;
    saveMap(FAVORITE_NAV_COUNT_CACHE_KEY, data);
  }

  function inferFavoriteNavTags(title, meta) {
    var text = compactText([title, meta].join(' '));
    var tags = [];
    if (/资源|磁力|网盘|种子|下载|合集|ed2k|torrent|magnet/i.test(text)) tags.push('资源');
    if (/图片|图集|预览|写真|画廊|gallery/i.test(text)) tags.push('图片');
    if (/\bAI\b|人工智能|模型|ChatGPT|Claude|Gemini/i.test(text)) tags.push('AI');
    if (/阅读|长帖|续读|楼层/i.test(text)) tags.push('阅读');
    return parseTagList(tags.join('\n'));
  }

  function getFavoriteNavEntrySearchText(entry) {
    return compactText([
      entry && entry.title,
      entry && entry.author,
      entry && entry.meta,
      entry && entry.progressText,
      entry && entry.tagText,
      entry && entry.tags && entry.tags.join ? entry.tags.join(' ') : entry && entry.tags,
    ].join(' ')).toLowerCase();
  }

  function matchesFavoriteNavFilter(entry, filter) {
    var normalized = normalizeFavoriteNavFilter(filter);
    var text = getFavoriteNavEntrySearchText(entry);
    if (normalized === 'all') return true;
    if (normalized === 'recent') return !entry || !entry.savedAt || Date.now() - entry.savedAt <= 14 * 24 * 60 * 60 * 1000;
    if (normalized === 'unread') return !entry || !entry.read;
    if (normalized === 'resource') return /资源|磁力|网盘|种子|下载|合集|ed2k|torrent|magnet/i.test(text);
    if (normalized === 'image') return /图片|图集|预览|写真|画廊|gallery/i.test(text);
    if (normalized === 'ai') return /\bai\b|人工智能|模型|chatgpt|claude|gemini/i.test(text);
    return true;
  }

  function filterFavoriteNavEntries(entries, options) {
    var query = normalizeCenterSearchQuery(options && options.query);
    var filter = normalizeFavoriteNavFilter(options && options.filter);
    return (entries || []).filter(function matchFavoriteEntry(entry) {
      if (query && getFavoriteNavEntrySearchText(entry).indexOf(query) === -1) return false;
      return matchesFavoriteNavFilter(entry, filter);
    });
  }

  function sortFavoriteNavEntries(entries, sort) {
    var normalized = normalizeFavoriteNavSort(sort);
    return (entries || []).slice().sort(function sortFavoriteEntry(left, right) {
      if (normalized === 'reply') return (Number(right.replies) || 0) - (Number(left.replies) || 0);
      if (normalized === 'read') return (Number(right.progressAt) || 0) - (Number(left.progressAt) || 0);
      return (Number(right.savedAt) || 0) - (Number(left.savedAt) || 0) || (Number(left.index) || 0) - (Number(right.index) || 0);
    });
  }

  function parseFavoriteReplyCount(text) {
    var match = String(text || '').match(/(?:回复|回帖|Replies?)\D{0,4}(\d+)/i);
    return match ? Number(match[1]) || 0 : 0;
  }

  function parseFavoriteSavedAt(text, now) {
    var value = String(text || '').replace(/\s+/g, ' ');
    var base = new Date(now || Date.now());
    var fullMatch = value.match(/(20\d{2})[-/.年](\d{1,2})[-/.月](\d{1,2})日?\s+(\d{1,2}):(\d{2})/);
    if (fullMatch) {
      return new Date(
        Number(fullMatch[1]),
        Number(fullMatch[2]) - 1,
        Number(fullMatch[3]),
        Number(fullMatch[4]),
        Number(fullMatch[5])
      ).getTime() || 0;
    }
    var shortMatch = value.match(/(?:收藏|时间|保存)?\s*(\d{1,2})[-/.月](\d{1,2})日?\s+(\d{1,2}):(\d{2})/);
    if (shortMatch) {
      return new Date(
        base.getFullYear(),
        Number(shortMatch[1]) - 1,
        Number(shortMatch[2]),
        Number(shortMatch[3]),
        Number(shortMatch[4])
      ).getTime() || 0;
    }
    var todayMatch = value.match(/今天\s*(\d{1,2}):(\d{2})/);
    if (todayMatch) {
      return new Date(base.getFullYear(), base.getMonth(), base.getDate(), Number(todayMatch[1]), Number(todayMatch[2])).getTime() || 0;
    }
    var yesterdayMatch = value.match(/昨天\s*(\d{1,2}):(\d{2})/);
    if (yesterdayMatch) {
      return new Date(base.getFullYear(), base.getMonth(), base.getDate() - 1, Number(yesterdayMatch[1]), Number(yesterdayMatch[2])).getTime() || 0;
    }
    var relativeMatch = value.match(/(\d+)\s*(分钟|小时|天)前/);
    if (relativeMatch) {
      var amount = Number(relativeMatch[1]) || 0;
      var unit = relativeMatch[2];
      var duration = unit === '分钟' ? amount * 60 * 1000 : (unit === '小时' ? amount * 60 * 60 * 1000 : amount * 24 * 60 * 60 * 1000);
      return Date.now() - duration;
    }
    return 0;
  }

  function normalizeFavoriteNavSeenMap(map) {
    var result = {};
    Object.keys(map || {}).forEach(function normalizeFavoriteSeenKey(key) {
      var time = Number(map[key]) || 0;
      if (key && time > 0) result[key] = time;
    });
    return result;
  }

  function applyFavoriteNavSeenTimes(entries, seenMap, now) {
    var map = normalizeFavoriteNavSeenMap(seenMap);
    var changed = false;
    var currentTime = now || Date.now();
    (entries || []).forEach(function applyFavoriteSeen(entry) {
      if (!entry || entry.source !== 'site' || !entry.id) return;
      if (entry.savedAt) {
        entry.savedAtLabel = '收藏';
        if (!map[entry.id] || entry.savedAt < map[entry.id]) {
          map[entry.id] = entry.savedAt;
          changed = true;
        }
        return;
      }
      if (!map[entry.id]) {
        map[entry.id] = currentTime;
        changed = true;
      }
      entry.savedAt = map[entry.id];
      entry.savedAtLabel = '记录';
    });
    return { map: map, changed: changed };
  }

  function createSiteFavoriteEntryFromLink(link, pageUrl, index, readMap, now) {
    if (!link) return null;
    var href = normalizeNavigationHref(link.getAttribute('href') || link.href, pageUrl || location.href);
    var id = parseThreadId(href) || href;
    var title = compactText(link.textContent || link.title || '');
    if (!href || !title || !/read\.php/i.test(href)) return null;
    var row = link.closest ? (link.closest('tr') || link.closest('.tr3') || link.parentNode) : link.parentNode;
    var rowText = compactText(row ? row.textContent : '');
    var authorLink = row ? qsa('a[href*="u.php"]', row).filter(function keepAuthorLink(candidate) {
      return candidate !== link && !/read\.php/i.test(candidate.getAttribute('href') || candidate.href || '');
    })[0] : null;
    var author = compactText(authorLink ? authorLink.textContent : '');
    var tags = inferFavoriteNavTags(title, rowText);
    var savedAt = parseFavoriteSavedAt(rowText, now);
    var deleteRequest = createSiteFavoriteDeleteRequest(row, pageUrl);
    return {
      source: 'site',
      id: String(id),
      title: title,
      url: href,
      author: author,
      meta: rowText,
      savedAt: savedAt,
      savedAtLabel: savedAt ? '收藏' : '',
      progressAt: 0,
      replies: parseFavoriteReplyCount(rowText),
      read: !!(readMap && readMap[id]),
      tags: tags,
      tagText: formatTags(tags),
      index: index,
      deleteRequest: deleteRequest,
    };
  }

  function parseFavoriteNavEntriesFromDocument(root, pageUrl, readMap, limit, seenMap, now) {
    var seen = {};
    var entries = [];
    var maxItems = Math.max(1, Number(limit) || FAVORITE_NAV_MAX_FETCHED_ITEMS);
    qsa('a[href*="read.php"]', root).some(function collectFavoriteLink(link) {
      var entry = createSiteFavoriteEntryFromLink(link, pageUrl, entries.length, readMap, now);
      if (!entry || seen[entry.url]) return false;
      seen[entry.url] = true;
      entries.push(entry);
      return entries.length >= maxItems;
    });
    return entries;
  }

  function getFavoriteNavWatchEntries(state) {
    var source = state || {};
    return getWatchCenterEntries(source.watch || {}, source.progress || {}).map(function mapWatchFavorite(entry, index) {
      return {
        source: 'watch',
        id: entry.id,
        title: entry.title,
        url: entry.progressUrl || entry.url,
        author: '',
        meta: entry.progressText || '',
        savedAt: entry.savedAt,
        progressAt: entry.progressAt,
        progressText: entry.progressText,
        floorLabel: entry.floorLabel,
        nextFloorLabel: entry.nextFloorLabel,
        replies: 0,
        read: !!entry.isCompleted,
        tags: entry.tags,
        tagText: entry.tagText,
        index: index,
      };
    });
  }

  function createFavoriteNavEntryFromThreadInfo(info, now) {
    if (!info) return null;
    var tid = parseThreadId(info.id);
    var url = info.titleLink && info.titleLink.href ? info.titleLink.href : getThreadPreviewUrl(info);
    var title = compactText(info.title || (info.titleLink && info.titleLink.textContent) || '');
    if (!tid || !url || !title) return null;
    var timestamp = now === undefined ? Date.now() : Number(now);
    var savedAt = isFinite(timestamp) ? timestamp : Date.now();
    var rowText = compactText([title, info.author].filter(Boolean).join(' '));
    var tags = inferFavoriteNavTags(title, rowText);
    return {
      source: 'site',
      id: tid,
      title: title,
      url: url,
      author: info.author || '',
      meta: rowText,
      savedAt: savedAt,
      savedAtLabel: '收藏',
      progressAt: 0,
      replies: parseFavoriteReplyCount(rowText),
      read: false,
      tags: tags,
      tagText: formatTags(tags),
      index: 0,
    };
  }

  function createReadPageFavoriteInfo(tid, title, author, url) {
    var id = parseThreadId(tid);
    if (!id) return null;
    return {
      id: id,
      title: compactText(title) || '未命名帖子',
      author: compactText(author),
      url: String(url || location.href || '').split('#')[0],
    };
  }

  function bumpFavoriteNavTriggerCount(wrapper, panelState, state) {
    var countNode = qs('.spx-favorite-nav-count', wrapper);
    if (!countNode) return;
    var current = panelState && isFinite(Number(panelState.lastKnownCount))
      ? Number(panelState.lastKnownCount)
      : parseInt(countNode.textContent, 10);
    if (!isFinite(current)) current = Object.keys((state && state.watch) || {}).length;
    if (!isFinite(current)) return;
    var next = current + 1;
    if (panelState) {
      var watchCount = Object.keys((state && state.watch) || {}).length;
      var nextSiteCount = Math.max(0, next - watchCount);
      panelState.cachedSiteCount = nextSiteCount;
      panelState.cachedSiteCountUpdatedAt = Date.now();
      panelState.cachedSiteCountFresh = true;
      panelState.lastKnownCount = next;
      saveFavoriteNavCountCache(panelState.favoriteUrl, nextSiteCount, panelState.cachedSiteCountUpdatedAt);
    }
    countNode.textContent = formatFavoriteNavCount(next);
    countNode.title = '我的收藏已更新 ' + next + ' 条';
  }

  function syncFavoriteNavAfterSiteFavorite(info, settings, state, added) {
    if (!added) return;
    var entry = createFavoriteNavEntryFromThreadInfo(info, Date.now());
    if (!entry) return;
    qsa('#spx-favorite-nav').forEach(function syncFavoriteWrapper(wrapper) {
      var panel = qs('.spx-favorite-nav-panel', wrapper);
      if (!panel) return;
      var panelState = ensureFavoriteNavState(panel, panel.dataset.favoriteUrl || wrapper.dataset.favoriteUrl);
      var siteEntries = panelState.siteEntries || [];
      var exists = siteEntries.some(function matchFavoriteEntry(item) {
        return String(item.id || '') === entry.id || (item.url && item.url === entry.url);
      });
      if (!exists) {
        panelState.siteEntries = [entry].concat(siteEntries).map(function reindexFavoriteEntry(item, index) {
          item.index = index;
          return item;
        });
        if (panelState.siteLoaded) {
          panelState.cachedSiteCount = panelState.siteEntries.length;
          panelState.cachedSiteCountUpdatedAt = Date.now();
          panelState.cachedSiteCountFresh = true;
          saveFavoriteNavCountCache(panelState.favoriteUrl, panelState.siteEntries.length, panelState.cachedSiteCountUpdatedAt);
        }
      }
      if (panelState.siteLoaded) {
        updateFavoriteNavTrigger(wrapper, panelState, state);
        if (!panel.hidden) renderFavoriteNavPanel(panel, settings, state, wrapper);
      } else {
        bumpFavoriteNavTriggerCount(wrapper, panelState, state);
      }
    });
  }

  function deleteSiteFavoriteEntry(entry, settings) {
    var request = entry && entry.deleteRequest;
    if (!request || !request.url) return Promise.reject(new Error('缺少删除入口'));
    var policy = {
      mode: 'action',
      label: '删除收藏',
      networkFriendly: isNetworkFriendlyMode(settings),
    };
    var options = {
      method: request.method || 'GET',
      credentials: 'include',
      cache: 'no-store',
    };
    if (request.method === 'POST') {
      options.body = request.body || '';
      options.headers = { 'Content-Type': request.contentType || 'application/x-www-form-urlencoded;charset=UTF-8' };
    }
    return requestWithPolicy(request.url, options, policy)
      .then(function readFavoriteDeleteResponse(response) {
        if (!response.ok) throw new Error('删除收藏失败');
        return readScriptResponseText(response, policy);
      })
      .then(function resolveFavoriteDeleteText(html) {
        var text = getSiteFavoriteDeleteResultText(html);
        if (text === '删除失败') throw new Error('删除收藏失败');
        return entry;
      });
  }

  function deleteSiteFavoriteEntries(entries, settings) {
    var result = { deleted: [], errors: [] };
    return (entries || []).reduce(function queueFavoriteDelete(promise, entry) {
      return promise.then(function deleteNextFavorite() {
        return deleteSiteFavoriteEntry(entry, settings).then(
          function markFavoriteDeleted(deletedEntry) {
            result.deleted.push(deletedEntry);
          },
          function markFavoriteDeleteFailed(error) {
            result.errors.push({ entry: entry, error: error });
          }
        );
      });
    }, Promise.resolve()).then(function returnFavoriteDeleteResult() {
      return result;
    });
  }

  function removeDeletedSiteFavoriteEntries(panelState, entries) {
    if (!panelState || !entries || !entries.length) return;
    var deleted = {};
    entries.forEach(function collectDeletedFavorite(entry) {
      deleted[getFavoriteNavDeleteKey(entry)] = true;
    });
    panelState.siteEntries = (panelState.siteEntries || []).filter(function keepSiteFavorite(entry) {
      return !deleted[getFavoriteNavDeleteKey(entry)];
    }).map(function reindexFavoriteEntry(entry, index) {
      entry.index = index;
      return entry;
    });
    panelState.cachedSiteCount = panelState.siteEntries.length;
    panelState.cachedSiteCountUpdatedAt = Date.now();
    panelState.cachedSiteCountFresh = true;
    saveFavoriteNavCountCache(panelState.favoriteUrl, panelState.siteEntries.length, panelState.cachedSiteCountUpdatedAt);
    panelState.lastKnownCount = null;
    var seenMap = loadMap(FAVORITE_NAV_SEEN_KEY);
    var changed = false;
    entries.forEach(function removeDeletedFavoriteSeen(entry) {
      if (entry && entry.id && seenMap[entry.id]) {
        delete seenMap[entry.id];
        changed = true;
      }
    });
    if (changed) saveMap(FAVORITE_NAV_SEEN_KEY, seenMap);
    if (panelState.selectedSiteFavorites) {
      entries.forEach(function removeDeletedFavoriteSelection(entry) {
        delete panelState.selectedSiteFavorites[getFavoriteNavDeleteKey(entry)];
      });
    }
  }

  function ensureFavoriteNavState(panel, favoriteUrl) {
    if (!panel.spxFavoriteNavState) {
      panel.spxFavoriteNavState = {
        source: 'site',
        query: '',
        filter: 'all',
        sort: 'recent',
        limit: FAVORITE_NAV_BATCH_SIZE,
        favoriteUrl: favoriteUrl || '',
        siteEntries: [],
        siteLoaded: false,
        siteLoading: false,
        siteError: '',
        lastKnownCount: null,
        cachedSiteCount: null,
        cachedSiteCountUpdatedAt: 0,
        cachedSiteCountFresh: false,
        selectedSiteFavorites: {},
      };
    }
    if (!panel.spxFavoriteNavState.selectedSiteFavorites) panel.spxFavoriteNavState.selectedSiteFavorites = {};
    if (favoriteUrl) panel.spxFavoriteNavState.favoriteUrl = favoriteUrl;
    return panel.spxFavoriteNavState;
  }

  function updateFavoriteNavTrigger(wrapper, panelState, state) {
    var countNode = qs('.spx-favorite-nav-count', wrapper);
    if (!countNode) return;
    var siteCount = panelState && panelState.siteLoaded ? (panelState.siteEntries || []).length : 0;
    var cachedSiteCount = Number(panelState && panelState.cachedSiteCount);
    var hasCachedSiteCount = isFinite(cachedSiteCount) && cachedSiteCount >= 0;
    var watchCount = Object.keys((state && state.watch) || {}).length;
    var totalCount = siteCount + watchCount;
    var loading = !!(panelState && panelState.siteLoading);
    var hasSiteCount = !!(panelState && panelState.siteLoaded);
    var hasError = !!(panelState && panelState.siteError);
    if (hasSiteCount) {
      countNode.textContent = formatFavoriteNavCount(totalCount, false);
      countNode.title = '我的收藏已读取 ' + totalCount + ' 条(站内 ' + siteCount + ',稍后看 ' + watchCount + ')';
      panelState.lastKnownCount = totalCount;
    } else if (hasCachedSiteCount) {
      var cachedTotal = cachedSiteCount + watchCount;
      countNode.textContent = formatFavoriteNavCount(cachedTotal, false);
      if (loading) {
        countNode.title = '显示短期缓存 ' + cachedTotal + ' 条(站内 ' + cachedSiteCount + ',稍后看 ' + watchCount + '),正在刷新站内收藏。';
      } else if (hasError) {
        countNode.title = '显示短期缓存 ' + cachedTotal + ' 条;站内收藏刷新失败:' + panelState.siteError;
      } else {
        countNode.title = '显示短期缓存 ' + cachedTotal + ' 条(站内 ' + cachedSiteCount + ',稍后看 ' + watchCount + ')。';
      }
      panelState.lastKnownCount = cachedTotal;
    } else if (hasError) {
      countNode.textContent = formatFavoriteNavCount(-1, false);
      countNode.title = '站内收藏读取失败:' + panelState.siteError;
    } else if (loading) {
      countNode.textContent = formatFavoriteNavCount(0, true);
      countNode.title = '正在读取站内收藏数量';
    } else {
      countNode.textContent = formatFavoriteNavCount(watchCount, false);
      countNode.title = '仅显示本地稍后看 ' + watchCount + ' 条;打开我的收藏面板后读取站内收藏。';
    }
  }

  function createFavoriteNavAction(text, action, value, primary) {
    var button = createEl('button', primary ? 'spx-primary' : '', text);
    button.type = 'button';
    button.dataset.action = action;
    if (value !== undefined) button.dataset.value = String(value);
    if (/delete|remove|clear/.test(String(action || ''))) button.classList.add('spx-danger');
    return button;
  }

  function appendFavoriteNavEntry(list, entry, panelState) {
    var item = createEl('div', 'spx-favorite-item');
    item.dataset.source = entry.source || 'site';
    item.dataset.id = String(entry.id || '');
    var body = createEl('div');
    var bodyHead = createEl('div', entry.source === 'site' && entry.deleteRequest ? 'spx-favorite-body-head' : '');
    if (entry.source === 'site' && entry.deleteRequest) {
      var select = createEl('input', 'spx-favorite-select');
      select.type = 'checkbox';
      select.title = '选择后可批量删除';
      select.dataset.spxFavoriteSelect = '1';
      select.dataset.value = getFavoriteNavDeleteKey(entry);
      select.checked = !!(panelState && panelState.selectedSiteFavorites && panelState.selectedSiteFavorites[select.dataset.value]);
      bodyHead.appendChild(select);
    }
    var title = createEl('a', 'spx-favorite-item-title', entry.title || '未命名帖子');
    title.href = entry.url || '#';
    title.target = '_blank';
    title.rel = 'noreferrer';
    bodyHead.appendChild(title);
    body.appendChild(bodyHead);
    var metaParts = [];
    if (entry.author) metaParts.push(entry.author);
    if (entry.savedAt) metaParts.push((entry.savedAtLabel || '保存') + ' ' + formatShortTime(entry.savedAt));
    if (entry.progressText) metaParts.push('进度 ' + entry.progressText);
    if (entry.nextFloorLabel) metaParts.push('续读 ' + entry.nextFloorLabel);
    if (entry.replies) metaParts.push('回复 ' + entry.replies);
    if (entry.tagText) metaParts.push('标签 ' + entry.tagText);
    body.appendChild(createEl('div', 'spx-favorite-meta', metaParts.join(' · ') || (entry.source === 'site' ? '站内收藏' : '本地稍后看')));
    item.appendChild(body);
    var actions = createEl('div', 'spx-favorite-actions');
    var open = createEl('a', 'spx-primary', entry.source === 'watch' && entry.progressAt ? '续读' : '打开');
    open.href = entry.url || '#';
    open.target = '_blank';
    open.rel = 'noreferrer';
    actions.appendChild(open);
    actions.appendChild(createFavoriteNavAction('复制', 'copy-favorite', entry.url || '', false));
    if (entry.source === 'site' && entry.deleteRequest) {
      actions.appendChild(createFavoriteNavAction('删除', 'delete-favorite-site', getFavoriteNavDeleteKey(entry), false));
    }
    if (entry.source === 'watch') actions.appendChild(createFavoriteNavAction('移除', 'remove-favorite-watch', entry.id, false));
    item.appendChild(actions);
    list.appendChild(item);
  }

  function renderFavoriteNavPanel(panel, settings, state, wrapper) {
    var panelState = ensureFavoriteNavState(panel, panel && panel.dataset.favoriteUrl);
    var watchEntries = getFavoriteNavWatchEntries(state);
    var siteEntries = panelState.siteEntries || [];
    var cachedSiteCount = Number(panelState.cachedSiteCount);
    var hasCachedSiteCount = isFinite(cachedSiteCount) && cachedSiteCount >= 0;
    var displaySiteCount = panelState.siteLoaded ? siteEntries.length : hasCachedSiteCount ? cachedSiteCount : siteEntries.length;
    var source = normalizeFavoriteNavSource(panelState.source);
    var sourceEntries = source === 'watch' ? watchEntries : siteEntries;
    var filtered = sortFavoriteNavEntries(filterFavoriteNavEntries(sourceEntries, panelState), panelState.sort);
    var visible = filtered.slice(0, Math.max(1, Number(panelState.limit) || FAVORITE_NAV_BATCH_SIZE));
    var isFiltered = !!(panelState.query || normalizeFavoriteNavFilter(panelState.filter) !== 'all');
    var deletableVisible = source === 'site' ? visible.filter(function keepDeletableFavorite(entry) {
      return !!(entry && entry.deleteRequest);
    }) : [];
    var selectedEntries = getSelectedSiteFavoriteEntries(siteEntries, panelState.selectedSiteFavorites);
    var selectedVisibleCount = getSelectedSiteFavoriteEntries(visible, panelState.selectedSiteFavorites).length;
    var allVisibleSelected = !!(deletableVisible.length && selectedVisibleCount === deletableVisible.length);

    panel.textContent = '';

    var header = createEl('div', 'spx-favorite-head');
    var title = createEl('div', 'spx-favorite-title');
    title.appendChild(createEl('h3', '', '我的收藏'));
    title.appendChild(createEl('div', 'spx-favorite-summary', panelState.siteLoading ? '正在读取站内收藏页...' : '主导航一键打开,站内收藏和稍后看分开管理'));
    header.appendChild(title);
    var headerButtons = createEl('div', 'spx-favorite-head-actions');
    var openLink = createEl('a', 'spx-favorite-open', '打开收藏页');
    openLink.href = panelState.favoriteUrl || '#';
    openLink.target = '_blank';
    openLink.rel = 'noreferrer';
    headerButtons.appendChild(openLink);
    header.appendChild(headerButtons);
    panel.appendChild(header);

    var stats = createEl('div', 'spx-favorite-stats');
    [['站内收藏', panelState.siteLoading && !hasCachedSiteCount ? '...' : displaySiteCount], ['本次显示', visible.length + ' / ' + filtered.length], ['稍后看', watchEntries.length]].forEach(function appendStat(item) {
      var stat = createEl('div', 'spx-favorite-stat');
      stat.appendChild(createEl('strong', '', String(item[1])));
      stat.appendChild(createEl('span', '', item[0]));
      stats.appendChild(stat);
    });
    panel.appendChild(stats);

    var query = createEl('input', 'spx-favorite-search');
    query.type = 'search';
    query.placeholder = '筛选收藏标题、作者或标签';
    query.value = panelState.query || '';
    query.dataset.spxFavoriteQuery = '1';
    panel.appendChild(query);

    var tabs = createEl('div', 'spx-favorite-tabs');
    [
      { value: 'site', label: '站内收藏 ' + (panelState.siteLoading && !hasCachedSiteCount ? '...' : displaySiteCount) },
      { value: 'watch', label: '稍后看 ' + watchEntries.length },
    ].forEach(function appendTab(tab) {
      var button = createFavoriteNavAction(tab.label, 'favorite-source', tab.value, false);
      button.className = 'spx-favorite-tab' + (source === tab.value ? ' spx-active' : '');
      tabs.appendChild(button);
    });
    panel.appendChild(tabs);

    var tools = createEl('div', 'spx-favorite-tools');
    var chips = createEl('div', 'spx-favorite-chips');
    [
      { value: 'all', label: '全部' },
      { value: 'recent', label: '最近' },
      { value: 'unread', label: '未读' },
      { value: 'resource', label: '资源' },
      { value: 'image', label: '图片' },
      { value: 'ai', label: 'AI' },
    ].forEach(function appendChip(chip) {
      var button = createFavoriteNavAction(chip.label, 'favorite-filter', chip.value, false);
      button.className = 'spx-favorite-chip' + (normalizeFavoriteNavFilter(panelState.filter) === chip.value ? ' spx-active' : '');
      chips.appendChild(button);
    });
    tools.appendChild(chips);
    var sort = createEl('select', 'spx-favorite-sort');
    sort.dataset.spxFavoriteSort = '1';
    [
      { value: 'recent', label: '最近收藏' },
      { value: 'read', label: '最近阅读' },
      { value: 'reply', label: '回复最多' },
    ].forEach(function appendSortOption(item) {
      var option = createEl('option');
      option.value = item.value;
      option.textContent = item.label;
      option.selected = normalizeFavoriteNavSort(panelState.sort) === item.value;
      sort.appendChild(option);
    });
    tools.appendChild(sort);
    panel.appendChild(tools);

    var list = createEl('div', 'spx-favorite-list');
    if (source === 'site' && panelState.siteLoading) {
      list.appendChild(createEl('div', 'spx-favorite-empty', '正在读取站内收藏页...'));
    } else if (source === 'site' && panelState.siteError) {
      list.appendChild(createEl('div', 'spx-favorite-empty', panelState.siteError + ',可先打开收藏页查看。'));
    } else if (!sourceEntries.length) {
      list.appendChild(createEl('div', 'spx-favorite-empty', source === 'site' ? '暂无可展示的站内收藏。' : '还没有本地稍后看。'));
    } else if (!visible.length) {
      list.appendChild(createEl('div', 'spx-favorite-empty', '没有匹配的收藏。'));
    } else {
      var group = createEl('div', 'spx-favorite-group');
      var groupMain = createEl('div', 'spx-favorite-group-main');
      groupMain.appendChild(createEl('span', '', source === 'site' ? '站内收藏' : '本地稍后看'));
      groupMain.appendChild(createEl('span', '', (isFiltered ? filtered.length + ' / ' : '') + sourceEntries.length + ' 条'));
      group.appendChild(groupMain);
      var groupActions = createEl('div', 'spx-favorite-group-actions');
      if (source === 'site' && deletableVisible.length) {
        groupActions.appendChild(createFavoriteNavAction(allVisibleSelected ? '取消本页' : '全选本页', 'toggle-visible-favorite-site', allVisibleSelected ? 'off' : 'on', false));
      }
      if (source === 'site' && selectedEntries.length) {
        groupActions.appendChild(createEl('span', 'spx-favorite-selected-count', '已选 ' + selectedEntries.length));
        groupActions.appendChild(createFavoriteNavAction('删除所选', 'delete-selected-favorite-site', '', false));
      }
      group.appendChild(groupActions);
      list.appendChild(group);
      visible.forEach(function appendVisibleEntry(entry) {
        appendFavoriteNavEntry(list, entry, panelState);
      });
      if (filtered.length > visible.length) {
        var load = createEl('div', 'spx-favorite-load');
        load.appendChild(createEl('span', '', '已显示 ' + visible.length + ' 条,继续加载下一批。'));
        load.appendChild(createFavoriteNavAction('加载更多', 'favorite-load-more'));
        list.appendChild(load);
      }
    }
    panel.appendChild(list);
    updateFavoriteNavTrigger(wrapper, panelState, state);
  }

  function loadFavoriteNavSiteEntries(panel, settings, state, wrapper) {
    var panelState = ensureFavoriteNavState(panel, panel && panel.dataset.favoriteUrl);
    if (panelState.siteLoaded || panelState.siteLoading || !panelState.favoriteUrl) return;
    if (typeof DOMParser === 'undefined') return;
    panelState.siteLoading = true;
    panelState.siteError = '';
    renderFavoriteNavPanel(panel, settings, state, wrapper);
    var policy = {
      mode: 'interactive',
      label: '我的收藏',
      networkFriendly: isNetworkFriendlyMode(settings),
    };
    requestWithPolicy(panelState.favoriteUrl, { credentials: 'include', cache: 'no-store' }, policy)
      .then(function readFavoriteResponse(response) {
        if (!response.ok) throw new Error('读取收藏页失败');
        return readScriptResponseText(response, policy);
      })
      .then(function parseFavoriteHtml(html) {
        var doc = new DOMParser().parseFromString(html, 'text/html');
        var seenMap = loadMap(FAVORITE_NAV_SEEN_KEY);
        panelState.siteEntries = parseFavoriteNavEntriesFromDocument(
          doc,
          panelState.favoriteUrl,
          state && state.read,
          FAVORITE_NAV_MAX_FETCHED_ITEMS,
          seenMap,
          Date.now()
        );
        var seenResult = applyFavoriteNavSeenTimes(panelState.siteEntries, seenMap, Date.now());
        if (seenResult.changed) saveMap(FAVORITE_NAV_SEEN_KEY, seenResult.map);
        panelState.cachedSiteCount = panelState.siteEntries.length;
        panelState.cachedSiteCountUpdatedAt = Date.now();
        panelState.cachedSiteCountFresh = true;
        saveFavoriteNavCountCache(panelState.favoriteUrl, panelState.siteEntries.length, panelState.cachedSiteCountUpdatedAt);
        panelState.siteLoaded = true;
      })
      .catch(function handleFavoriteLoadError(error) {
        panelState.siteError = error && error.message ? error.message : '读取收藏页失败';
        delayFavoriteNavCountCacheRefresh(panelState.favoriteUrl);
      })
      .then(function finishFavoriteLoad() {
        panelState.siteLoading = false;
        if (panel && !panel.hidden) renderFavoriteNavPanel(panel, settings, state, wrapper);
        else updateFavoriteNavTrigger(wrapper, panelState, state);
      });
  }

  function closeFavoriteNavPanel(wrapper) {
    var panel = qs('.spx-favorite-nav-panel', wrapper);
    var trigger = qs('.spx-favorite-nav-trigger', wrapper);
    if (panel) panel.hidden = true;
    if (trigger) trigger.setAttribute('aria-expanded', 'false');
  }

  function setFavoriteNavPanelHidden(wrapper, hidden, settings, state) {
    var panel = qs('.spx-favorite-nav-panel', wrapper);
    var trigger = qs('.spx-favorite-nav-trigger', wrapper);
    if (!panel || !trigger) return;
    panel.hidden = !!hidden;
    trigger.setAttribute('aria-expanded', String(!panel.hidden));
    if (!panel.hidden) {
      renderFavoriteNavPanel(panel, settings, state, wrapper);
      loadFavoriteNavSiteEntries(panel, settings, state, wrapper);
      var input = qs('.spx-favorite-search', panel);
      if (input && typeof input.focus === 'function') input.focus();
    }
  }

  function getFavoriteNavMountHost(scope) {
    return qs('#mainNav>div[style*="padding-left"]', scope) || qs('#mainNav', scope);
  }

  function enhanceFavoriteNavigation(settings, state, root) {
    var scope = root || document;
    var host = getFavoriteNavMountHost(scope);
    if (!host) return;
    var uid = extractAccountUserId(scope, location.href);
    var favoriteUrl = getFavoriteNavUrl(uid, location.origin);
    if (!favoriteUrl) return;

    var wrapper = qs('#spx-favorite-nav', host) || createEl('div', 'spx-favorite-nav');
    wrapper.id = 'spx-favorite-nav';
    wrapper.dataset.favoriteUrl = favoriteUrl;
    var panel = qs('.spx-favorite-nav-panel', wrapper);

    if (!panel) {
      var trigger = createEl('button', 'spx-favorite-nav-trigger');
      trigger.type = 'button';
      trigger.setAttribute('aria-haspopup', 'dialog');
      trigger.setAttribute('aria-expanded', 'false');
      trigger.setAttribute('aria-controls', 'spx-favorite-nav-panel');
      trigger.title = '打开我的收藏';
      trigger.appendChild(createEl('span', 'spx-favorite-nav-star', '★'));
      trigger.appendChild(createEl('span', '', '我的收藏'));
      trigger.appendChild(createEl('span', 'spx-favorite-nav-count', formatFavoriteNavCount(Object.keys((state && state.watch) || {}).length, false)));
      wrapper.appendChild(trigger);

      panel = createEl('section', 'spx-favorite-nav-panel');
      panel.id = 'spx-favorite-nav-panel';
      panel.hidden = true;
      panel.dataset.favoriteUrl = favoriteUrl;
      wrapper.appendChild(panel);

      trigger.addEventListener('click', function toggleFavoriteNav(event) {
        event.preventDefault();
        event.stopPropagation();
        setFavoriteNavPanelHidden(wrapper, !panel.hidden, settings, state);
      });
      panel.addEventListener('click', function stopFavoritePanelClick(event) {
        event.stopPropagation();
      });
      panel.addEventListener('input', function handleFavoriteInput(event) {
        var target = event.target;
        if (!target || !target.dataset || target.dataset.spxFavoriteQuery !== '1') return;
        var panelState = ensureFavoriteNavState(panel, favoriteUrl);
        panelState.query = target.value;
        panelState.limit = FAVORITE_NAV_BATCH_SIZE;
        renderFavoriteNavPanel(panel, settings, state, wrapper);
        var nextInput = qs('.spx-favorite-search', panel);
        if (nextInput) nextInput.focus();
      });
      panel.addEventListener('change', function handleFavoriteChange(event) {
        var target = event.target;
        var panelState = ensureFavoriteNavState(panel, favoriteUrl);
        if (!target || !target.dataset) return;
        if (target.dataset.spxFavoriteSelect === '1') {
          var selectionKey = target.dataset.value;
          if (!selectionKey) return;
          if (target.checked) panelState.selectedSiteFavorites[selectionKey] = true;
          else delete panelState.selectedSiteFavorites[selectionKey];
          renderFavoriteNavPanel(panel, settings, state, wrapper);
          return;
        }
        if (target.dataset.spxFavoriteSort !== '1') return;
        panelState.sort = normalizeFavoriteNavSort(target.value);
        panelState.limit = FAVORITE_NAV_BATCH_SIZE;
        renderFavoriteNavPanel(panel, settings, state, wrapper);
      });
      panel.addEventListener('click', function handleFavoriteAction(event) {
        var target = event.target;
        var action = target && target.dataset && target.dataset.action;
        if (!action) return;
        var panelState = ensureFavoriteNavState(panel, favoriteUrl);
        if (action === 'favorite-source') {
          panelState.source = normalizeFavoriteNavSource(target.dataset.value);
          panelState.limit = FAVORITE_NAV_BATCH_SIZE;
          renderFavoriteNavPanel(panel, settings, state, wrapper);
          return;
        }
        if (action === 'favorite-filter') {
          panelState.filter = normalizeFavoriteNavFilter(target.dataset.value);
          panelState.limit = FAVORITE_NAV_BATCH_SIZE;
          renderFavoriteNavPanel(panel, settings, state, wrapper);
          return;
        }
        if (action === 'favorite-load-more') {
          panelState.limit += FAVORITE_NAV_BATCH_SIZE;
          renderFavoriteNavPanel(panel, settings, state, wrapper);
          return;
        }
        if (action === 'copy-favorite') {
          copyTextToClipboard(target.dataset.value).then(
            function showFavoriteCopySuccess() { setTemporaryText(target, '已复制', '复制'); },
            function showFavoriteCopyFailure() { setTemporaryText(target, '失败', '复制'); }
          );
          return;
        }
        if (action === 'toggle-visible-favorite-site') {
          var shouldSelectVisible = target.dataset.value !== 'off';
          var visibleForSelection = sortFavoriteNavEntries(
            filterFavoriteNavEntries(panelState.siteEntries || [], panelState),
            panelState.sort
          ).slice(0, Math.max(1, Number(panelState.limit) || FAVORITE_NAV_BATCH_SIZE));
          visibleForSelection.forEach(function toggleVisibleFavoriteSelection(entry) {
            if (!entry || !entry.deleteRequest) return;
            var key = getFavoriteNavDeleteKey(entry);
            if (shouldSelectVisible) panelState.selectedSiteFavorites[key] = true;
            else delete panelState.selectedSiteFavorites[key];
          });
          renderFavoriteNavPanel(panel, settings, state, wrapper);
          return;
        }
        if (action === 'delete-favorite-site' || action === 'delete-selected-favorite-site') {
          var entriesToDelete;
          if (action === 'delete-selected-favorite-site') {
            entriesToDelete = getSelectedSiteFavoriteEntries(panelState.siteEntries || [], panelState.selectedSiteFavorites);
          } else {
            entriesToDelete = (panelState.siteEntries || []).filter(function matchSiteFavorite(entry) {
              return getFavoriteNavDeleteKey(entry) === target.dataset.value;
            });
          }
          entriesToDelete = (entriesToDelete || []).filter(function keepDeletableSiteFavorite(entry) {
            return !!(entry && entry.deleteRequest);
          });
          if (!entriesToDelete.length) return;
          var confirmText = entriesToDelete.length > 1
            ? '确定删除选中的 ' + entriesToDelete.length + ' 条站内收藏?此操作会同步到原站收藏夹。'
            : '删除这个站内收藏?此操作会同步到原站收藏夹。';
          if (typeof window.confirm === 'function' && !window.confirm(confirmText)) return;
          var originalText = target.textContent || '删除';
          target.disabled = true;
          target.textContent = '删除中';
          deleteSiteFavoriteEntries(entriesToDelete, settings).then(function handleFavoriteDeleteResult(result) {
            if (result.deleted.length) {
              removeDeletedSiteFavoriteEntries(panelState, result.deleted);
              updateFavoriteNavTrigger(wrapper, panelState, state);
              renderFavoriteNavPanel(panel, settings, state, wrapper);
            }
            if (result.errors.length && !result.deleted.length) {
              target.disabled = false;
              setTemporaryText(target, '删除失败', originalText);
              return;
            }
            if (result.errors.length && typeof window.alert === 'function') {
              window.alert('部分收藏删除失败:' + result.errors.length + ' 条');
            }
          });
          return;
        }
        if (action === 'remove-favorite-watch') {
          var id = target.dataset.value;
          if (!id || !state || !state.watch) return;
          delete state.watch[id];
          saveMap(WATCH_KEY, state.watch);
          refreshWatchCenter();
          renderFavoriteNavPanel(panel, settings, state, wrapper);
        }
      });
      document.addEventListener('click', function closeFavoriteNavOnOutside(event) {
        if (!wrapper.contains(event.target)) closeFavoriteNavPanel(wrapper);
      });
      document.addEventListener('keydown', function closeFavoriteNavOnEscape(event) {
        if (event.key === 'Escape') closeFavoriteNavPanel(wrapper);
      });
    } else {
      panel.dataset.favoriteUrl = favoriteUrl;
    }

    if (!wrapper.parentNode) host.appendChild(wrapper);
    var panelState = ensureFavoriteNavState(panel, favoriteUrl);
    var countCacheKey = getFavoriteNavCountCacheKey(favoriteUrl);
    var countCacheSource = countCacheKey ? loadMap(FAVORITE_NAV_COUNT_CACHE_KEY)[countCacheKey] : null;
    var countCache = loadFavoriteNavCountCache(favoriteUrl);
    if (countCache) {
      panelState.cachedSiteCount = countCache.count;
      panelState.cachedSiteCountUpdatedAt = countCache.updatedAt;
      panelState.cachedSiteCountFresh = countCache.fresh;
    }
    updateFavoriteNavTrigger(wrapper, panel.spxFavoriteNavState, state);
    if (shouldRefreshFavoriteNavCountCache(countCacheSource)) {
      loadFavoriteNavSiteEntries(panel, settings, state, wrapper);
    }
  }

  function enhanceAccountNavigation(root) {
    if (!shouldUseProfilePage(location.href)) return;
    var scope = root || document;
    var uid = extractAccountUserId(scope, location.href);
    if (!uid) return;

    var tabs = qs('#spx-account-tabs', scope);
    if (!tabs) {
      tabs = createEl('nav', 'spx-account-tabs');
      tabs.id = 'spx-account-tabs';
    }

    var activeKey = getAccountActiveKey(location.href);
    tabs.textContent = '';
    getAccountTabItems(uid, location.origin).forEach(function appendAccountTab(item) {
      var link = createEl('a', item.key === activeKey ? 'spx-account-tab-active' : '', item.label);
      link.href = item.href;
      tabs.appendChild(link);
    });

    var mountBefore = qs('#u-wrap', scope) || qs('#set-wrap', scope) || qs('.bdbA', scope);
    var main = qs('#main', scope) || document.body;
    if (mountBefore && mountBefore.parentNode) {
      mountBefore.parentNode.insertBefore(tabs, mountBefore);
    } else if (tabs.parentNode !== main) {
      main.insertBefore(tabs, main.firstChild);
    }

    document.documentElement.classList.add('spx-account-tabs-ready');
  }

  function extractThreadCellInfo(cell) {
    var row = cell.closest('tr') || cell;
    var id = parseThreadId(cell.id);
    var titleLink = qs('#a_ajax_' + id, row) || qs('a[href*="read.php?tid"]', row);
    var authorLink = qs('a.bl[href*="u.php"]', row);
    return {
      id: id,
      cell: cell,
      row: row,
      titleLink: titleLink,
      title: titleLink ? titleLink.textContent.trim() : cell.textContent.trim(),
      author: authorLink ? authorLink.textContent.trim() : '',
    };
  }

  function extractForumGalleryCardInfo(card) {
    var inner = qs('.inner', card) || card;
    var titleLink = qs('.section-title a[href*="read.php?tid"]', inner) || qs('a[href*="read.php?tid"]', inner);
    var id = parseThreadId(titleLink ? (titleLink.getAttribute('href') || titleLink.href) : '');
    var authorLink = qs('.section-intro a.bl[href*="u.php"]', inner) || qs('.section-intro a[href*="u.php"]', inner);
    return {
      id: id,
      cell: inner,
      row: card,
      titleLink: titleLink,
      title: titleLink ? titleLink.textContent.trim() : inner.textContent.trim(),
      author: authorLink ? authorLink.textContent.trim() : '',
    };
  }

  function dispatchResourceBadgeFilter(type) {
    if (!type || typeof document === 'undefined') return;
    var detail = { type: type };
    try {
      document.dispatchEvent(new CustomEvent('spx-resource-filter', { detail: detail }));
    } catch (error) {
      var event = document.createEvent && document.createEvent('CustomEvent');
      if (!event) return;
      event.initCustomEvent('spx-resource-filter', true, false, detail);
      document.dispatchEvent(event);
    }
  }

  function renderThreadResourceBadges(info, badges) {
    if (!info || !info.cell || !info.titleLink) return;
    var previous = qs('.spx-resource-badges', info.cell);
    if (previous) previous.remove();
    var list = mergeResourceBadges(badges || []);
    info.resourceBadges = list;
    info.resourceBadgeTypes = getResourceBadgeTypes(list);
    if (info.row && info.row.dataset) info.row.dataset.spxResourceTypes = info.resourceBadgeTypes.join(' ');
    if (!list.length) return;

    var wrap = createEl('span', 'spx-resource-badges');
    wrap.setAttribute('aria-label', '资源类型角标');
    list.forEach(function appendResourceBadge(badge) {
      var button = createEl('button', 'spx-resource-badge spx-resource-badge-' + badge.type + (badge.guessed ? ' spx-resource-badge-guess' : ''), badge.label);
      button.type = 'button';
      button.dataset.spxResourceType = badge.type;
      button.title = (badge.guessed ? '标题关键词推测:' : '已识别资源:') + badge.label + ',点击筛选同类主题';
      button.addEventListener('click', function filterByResourceBadge(event) {
        event.preventDefault();
        event.stopPropagation();
        dispatchResourceBadgeFilter(badge.type);
      });
      wrap.appendChild(button);
    });

    var anchor = qs('.spx-thread-tools', info.cell) || qs('.spx-watch-badge', info.cell) || info.titleLink;
    anchor.insertAdjacentElement('afterend', wrap);
  }

  function updateThreadResourceBadges(info, state, previewPayload) {
    if (!info) return;
    var badges = getThreadResourceBadges(info, getThreadResourceBadgeIndex(state && state.resources), previewPayload);
    renderThreadResourceBadges(info, badges);
  }

  function isStickyCell(cell) {
    var text = (cell.textContent || '').trim();
    if (!cell.id) return false;
    var tid = Number(parseThreadId(cell.id));
    if (!tid) return false;
    if (/\[公告\]|版规|指南|长期招人|Q&A|新人报道/.test(text)) return true;
    return tid < 1000000;
  }

  function hideStickyThreads(cells) {
    (cells || qsa('td[id^="td_"]')).filter(isStickyCell).forEach(function hideCell(cell) {
      var row = cell.closest('tr') || cell;
      if (row && row.style && typeof row.style.setProperty === 'function') {
        row.style.setProperty('display', 'none', 'important');
        return;
      }
      if (row && row.style) row.style.display = 'none';
    });
  }

  function hideForumAnnouncementPanels(nodes) {
    (nodes || qsa('.gonggao')).forEach(function hidePanel(panel) {
      if (panel && panel.style && typeof panel.style.setProperty === 'function') {
        panel.style.setProperty('display', 'none', 'important');
        return;
      }
      if (panel && panel.style) panel.style.display = 'none';
    });
  }

  function hideForumAnnouncements() {
    if (detectPageType(location.href) !== 'forum') {
      restoreStickyThreads();
      return;
    }
    var stickyCells = qsa('td[id^="td_"]').filter(isStickyCell);
    hideForumAnnouncementPanels();
    if (!stickyCells.length) return;
    hideStickyThreads(stickyCells);
  }

  function restoreStickyThreads() {
    var foldBox = qs('#spx-sticky-fold-box');
    if (foldBox) {
      var foldRow = foldBox.closest('tr');
      if (foldRow) foldRow.remove();
    }
    qsa('td[id^="td_"]').filter(isStickyCell).forEach(function showCell(cell) {
      var row = cell.closest('tr') || cell;
      row.style.display = '';
    });
  }

  function boxRow(content) {
    var tr = createEl('tr');
    var td = createEl('td');
    td.colSpan = 8;
    td.appendChild(content);
    tr.appendChild(td);
    return tr;
  }

  function createWatchBadge(id) {
    var badge = createEl('span', 'spx-watch-badge', '★');
    badge.dataset.spxWatchId = String(id || '');
    return badge;
  }

  function syncThreadWatchState(info, state, button) {
    if (!info || !info.id || !info.cell || !info.titleLink) return;
    var watch = (state && state.watch) || {};
    var saved = !!watch[info.id];
    if (button) button.textContent = saved ? '已存' : '稍后';
    var badge = qs('.spx-watch-badge', info.cell);
    if (saved && !badge) {
      info.titleLink.insertAdjacentElement('afterend', createWatchBadge(info.id));
    }
    if (!saved && badge) badge.remove();
  }

  function toggleThreadWatch(info, state, button) {
    if (!info || !info.id || !info.titleLink) return;
    state.watch = state.watch || {};
    if (state.watch[info.id]) {
      delete state.watch[info.id];
    } else {
      state.watch[info.id] = {
        title: info.title,
        url: info.titleLink.href,
        savedAt: Date.now(),
      };
    }
    syncThreadWatchState(info, state, button);
    saveMap(WATCH_KEY, state.watch);
    refreshWatchCenter();
  }

  function ensureForumGalleryCardTools(info, settings, state) {
    if (!info || !info.id || !info.cell || !info.titleLink) return null;
    var tools = qs('.spx-gallery-card-tools', info.cell);
    if (!tools) {
      tools = createEl('div', 'spx-gallery-card-tools');
    }

    var watchButton = qs('[data-spx-gallery-action="watch"]', tools);
    if (!watchButton) {
      watchButton = createEl('button', '', state.watch && state.watch[info.id] ? '已存' : '稍后');
      watchButton.type = 'button';
      watchButton.dataset.spxGalleryAction = 'watch';
      watchButton.title = '切换本地稍后看';
      tools.appendChild(watchButton);
    }
    if (watchButton.dataset.spxGalleryBound !== '1') {
      watchButton.dataset.spxGalleryBound = '1';
      watchButton.addEventListener('click', function toggleGalleryWatch(event) {
        event.preventDefault();
        event.stopPropagation();
        toggleThreadWatch(info, state, watchButton);
      });
    }

    var favoriteButton = qs('[data-spx-gallery-action="favorite"]', tools);
    if (!favoriteButton) {
      favoriteButton = createEl('button', '', isThreadFavoriteSeen(info.id) ? '已收藏' : '收藏');
      favoriteButton.type = 'button';
      favoriteButton.dataset.spxGalleryAction = 'favorite';
      favoriteButton.title = '收藏到站内收藏夹';
      tools.appendChild(favoriteButton);
    }
    if (favoriteButton.dataset.spxGalleryBound !== '1') {
      favoriteButton.dataset.spxGalleryBound = '1';
      favoriteButton.addEventListener('click', function favoriteGalleryThread(event) {
        event.preventDefault();
        event.stopPropagation();
        runThreadFavoriteAction(info, settings, state, favoriteButton, '已收藏');
      });
    }

    syncThreadWatchState(info, state, watchButton);
    favoriteButton.disabled = isThreadFavoriteSeen(info.id);
    favoriteButton.textContent = favoriteButton.disabled ? '已收藏' : '收藏';

    var sectionIntro = qs('.section-intro', info.cell);
    var introTail = sectionIntro;
    if (introTail && introTail.nextElementSibling && introTail.nextElementSibling.classList && introTail.nextElementSibling.classList.contains('clear')) {
      introTail = introTail.nextElementSibling;
    }
    if (introTail && introTail.parentNode === info.cell && tools.previousElementSibling !== introTail) {
      introTail.insertAdjacentElement('afterend', tools);
    } else if (tools.parentNode !== info.cell) {
      info.cell.appendChild(tools);
    }
    return tools;
  }

  function clearForumGalleryResourceBadges(info) {
    if (!info || !info.cell) return;
    var badges = qs('.spx-resource-badges', info.cell);
    if (badges) badges.remove();
  }

  function watchForumGalleryStream(wall, settings, state) {
    if (!wall || wall.dataset.spxGalleryObserver === '1' || typeof MutationObserver === 'undefined') return;
    var stream = qs('.stream', wall);
    if (!stream) return;
    wall.dataset.spxGalleryObserver = '1';
    var timer = null;
    var observer = new MutationObserver(function refreshGalleryCards(mutations) {
      var hasCardMutation = (mutations || []).some(function hasAddedOrRemovedCard(mutation) {
        return mutation && (mutation.addedNodes.length || mutation.removedNodes.length);
      });
      if (!hasCardMutation) return;
      if (timer) window.clearTimeout(timer);
      timer = window.setTimeout(function runDeferredGalleryEnhance() {
        timer = null;
        enhanceForumGallery(settings, state);
      }, 120);
    });
    observer.observe(stream, { childList: true });
  }

  function scheduleForumGalleryToolRepair(wall, settings, state) {
    if (!wall || wall.dataset.spxGalleryRepairScheduled === '1' || typeof window === 'undefined' || typeof window.setTimeout !== 'function') return;
    wall.dataset.spxGalleryRepairScheduled = '1';
    [300, 900, 1800].forEach(function scheduleRepair(delayMs) {
      window.setTimeout(function repairGalleryTools() {
        enhanceForumGallery(settings, state);
      }, delayMs);
    });
  }

  function formatShortTime(timestamp) {
    var value = Number(timestamp) || 0;
    if (!value) return '';
    var date = new Date(value);
    if (isNaN(date.getTime())) return '';
    function pad(number) {
      return String(number).padStart(2, '0');
    }
    return pad(date.getMonth() + 1) + '-' + pad(date.getDate()) + ' ' +
      pad(date.getHours()) + ':' + pad(date.getMinutes());
  }

  function createCenterActionElement(action) {
    if (!action) return null;
    var node = action.href ? createEl('a', '', action.text || '') : createEl('button', '', action.text || '');
    var actionText = String(action.text || '');
    if (/^(打开|复制|继续阅读|上次楼层|全选筛选)$/.test(actionText)) {
      node.classList.add('spx-action-primary');
    } else {
      node.classList.add('spx-action-secondary');
    }
    if (/删除|移除|清空|失效/.test(actionText)) {
      node.classList.add('spx-danger');
    }
    if (action.href) {
      node.href = action.href;
      node.target = action.target || '_blank';
      node.rel = action.rel || 'noreferrer';
    } else {
      node.type = 'button';
      if (action.dataset) {
        Object.keys(action.dataset).forEach(function setActionDataset(key) {
          node.dataset[key] = action.dataset[key];
        });
      }
    }
    return node;
  }

  function createCenterTitleElement(options) {
    var config = options || {};
    var node = createEl(config.href ? 'a' : 'div', 'spx-watch-title');
    if (config.href) {
      node.href = config.href;
      node.target = config.target || '_blank';
      node.rel = config.rel || 'noreferrer';
    }
    if (config.title) node.title = config.title;
    if (config.badgeText) {
      var badge = createEl('span', 'spx-status-badge' + (config.badgeClass ? ' ' + config.badgeClass : ''), config.badgeText);
      node.appendChild(badge);
    }
    node.appendChild(document.createTextNode(config.text || ''));
    return node;
  }

  function ensureCenterPanelState(panel, defaults) {
    if (!panel) return {};
    if (!panel.spxState) panel.spxState = {};
    Object.keys(defaults || {}).forEach(function keepCenterDefault(key) {
      if (panel.spxState[key] === undefined) panel.spxState[key] = defaults[key];
    });
    return panel.spxState;
  }

  function isCenterFiltered(panelState) {
    return !!(
      panelState &&
      (
        panelState.query ||
        (panelState.filter && panelState.filter !== 'all') ||
        (panelState.tag && panelState.tag !== 'all') ||
        (panelState.provider && panelState.provider !== 'all')
      )
    );
  }

  function createCenterControls(panelState, options) {
    var config = options || {};
    var controls = createEl('div', 'spx-watch-controls');
    if (config.searchPlaceholder !== false) {
      var queryInput = createEl('input');
      queryInput.type = 'search';
      queryInput.placeholder = config.searchPlaceholder || '搜索';
      queryInput.value = String(panelState.query || '');
      queryInput.dataset.spxCenterQuery = '1';
      controls.appendChild(queryInput);
    }
    if ((config.filters || []).length) {
      var filterSelect = createEl('select');
      filterSelect.dataset.spxCenterFilter = '1';
      (config.filters || []).forEach(function appendFilterOption(item) {
        var option = createEl('option');
        option.value = item.value;
        option.textContent = item.label;
        option.selected = String(panelState.filter || 'all') === String(item.value);
        filterSelect.appendChild(option);
      });
      controls.appendChild(filterSelect);
    }
    if ((config.providers || []).length) {
      var providerSelect = createEl('select');
      providerSelect.dataset.spxCenterProvider = '1';
      var allProviderOption = createEl('option');
      allProviderOption.value = 'all';
      allProviderOption.textContent = config.providerAllLabel || '全部来源';
      providerSelect.appendChild(allProviderOption);
      (config.providers || []).forEach(function appendProviderOption(item) {
        var option = createEl('option');
        option.value = item.value;
        option.textContent = item.label;
        option.selected = String(panelState.provider || 'all') === String(item.value);
        providerSelect.appendChild(option);
      });
      controls.appendChild(providerSelect);
    }
    if ((config.views || []).length) {
      var viewSelect = createEl('select');
      viewSelect.dataset.spxCenterView = '1';
      (config.views || []).forEach(function appendViewOption(item) {
        var option = createEl('option');
        option.value = item.value;
        option.textContent = item.label;
        option.selected = String(panelState.view || 'list') === String(item.value);
        viewSelect.appendChild(option);
      });
      controls.appendChild(viewSelect);
    }
    if ((config.tags || []).length) {
      var tagSelect = createEl('select');
      tagSelect.dataset.spxCenterTag = '1';
      var allTagOption = createEl('option');
      allTagOption.value = 'all';
      allTagOption.textContent = '全部标签';
      tagSelect.appendChild(allTagOption);
      (config.tags || []).forEach(function appendTagOption(item) {
        var option = createEl('option');
        option.value = item.value;
        option.textContent = item.label;
        option.selected = String(panelState.tag || 'all') === String(item.value);
        tagSelect.appendChild(option);
      });
      controls.appendChild(tagSelect);
    }
    return controls.children.length ? controls : null;
  }

  function renderCenterPanel(panel, options) {
    if (!panel || !options) return;
    var scrollTop = panel.scrollTop || 0;
    var panelState = ensureCenterPanelState(panel, options.stateDefaults || { query: '', filter: 'all', tag: 'all', provider: 'all' });
    var entries = options.entries || [];
    var visibleEntries = typeof options.filterEntries === 'function'
      ? options.filterEntries(entries, panelState)
      : entries.slice();
    var displayEntries = typeof options.transformVisibleEntries === 'function'
      ? options.transformVisibleEntries(visibleEntries, entries, panelState)
      : visibleEntries;
    panel.textContent = '';
    var header = createEl('div', 'spx-watch-center-header');
    var title = createEl('div');
    title.appendChild(createEl('h3', '', options.title || ''));
    title.appendChild(createEl(
      'div',
      'spx-watch-summary',
      (typeof options.summary === 'function' ? options.summary(visibleEntries, entries, panelState) : options.summary) || ''
    ));
    header.appendChild(title);

    var headerActions = createEl('div', 'spx-watch-actions');
    (typeof options.headerActions === 'function'
      ? options.headerActions(visibleEntries, entries, panelState)
      : (options.headerActions || []))
      .forEach(function appendHeaderAction(action) {
        var actionNode = createCenterActionElement(action);
        if (actionNode) headerActions.appendChild(actionNode);
      });
    header.appendChild(headerActions);
    panel.appendChild(header);

    var controls = createCenterControls(panelState, options.controls);
    if (controls) panel.appendChild(controls);

    if (!entries.length) {
      panel.appendChild(createEl('div', 'spx-watch-empty', options.emptyText || '暂无内容'));
      panel.scrollTop = scrollTop;
      return;
    }

    if (!displayEntries.length) {
      panel.appendChild(createEl('div', 'spx-watch-empty', options.emptyFilteredText || '没有匹配结果。'));
      panel.scrollTop = scrollTop;
      return;
    }

    var list = createEl('div', 'spx-watch-list');
    displayEntries.forEach(function appendEntry(entry) {
      var item = createEl('div', 'spx-watch-item');
      var itemData = options.getItemData ? options.getItemData(entry) : null;
      if (itemData) {
        Object.keys(itemData).forEach(function setItemData(key) {
          item.dataset[key] = itemData[key];
        });
      }
      item.appendChild(options.createTitle(entry));
      item.appendChild(createEl('div', 'spx-watch-meta', options.createMeta(entry)));
      var actions = createEl('div', 'spx-watch-actions');
      (options.createActions(entry) || []).forEach(function appendAction(action) {
        var actionNode = createCenterActionElement(action);
        if (actionNode) actions.appendChild(actionNode);
      });
      item.appendChild(actions);
      list.appendChild(item);
    });
    panel.appendChild(list);
    panel.scrollTop = scrollTop;
  }

  function setCenterPanelHidden(panel, hidden, buttonSelector) {
    if (!panel) return;
    panel.hidden = !!hidden;
    if (buttonSelector) {
      qsa(buttonSelector).forEach(function toggleCenterButton(button) {
        button.classList.toggle('spx-active', !panel.hidden);
      });
    }
  }

  function refreshCenterPanel(panelId) {
    var panel = qs(panelId);
    if (!panel || panel.hidden || typeof panel.spxRender !== 'function') return;
    panel.spxRender();
  }

  function commitCenterQueryInput(panel, config, target) {
    if (!target || !target.dataset || target.dataset.spxCenterQuery !== '1') return;
    var state = ensureCenterPanelState(panel, config.stateDefaults || { query: '', filter: 'all', tag: 'all', provider: 'all' });
    if (state.query === target.value) return;
    state.query = target.value;
    var selectionStart = typeof target.selectionStart === 'number' ? target.selectionStart : null;
    var selectionEnd = typeof target.selectionEnd === 'number' ? target.selectionEnd : null;
    panel.spxRender();
    var nextInput = qs('input[data-spx-center-query="1"]', panel);
    if (nextInput) {
      nextInput.focus();
      if (
        selectionStart !== null &&
        selectionEnd !== null &&
        typeof nextInput.setSelectionRange === 'function'
      ) {
        nextInput.setSelectionRange(selectionStart, selectionEnd);
      }
    }
  }

  function createCenterPanel(options) {
    var config = options || {};
    var panel = qs('#' + config.id);
    if (panel) return panel;

    panel = createEl('div', 'spx-watch-center');
    panel.id = config.id;
    panel.hidden = true;
    document.body.appendChild(panel);

    panel.addEventListener('compositionstart', function handleCenterCompositionStart(event) {
      var target = event.target;
      if (!target || !target.dataset || target.dataset.spxCenterQuery !== '1') return;
      panel.spxCenterQueryComposing = true;
    });

    panel.addEventListener('compositionend', function handleCenterCompositionEnd(event) {
      var target = event.target;
      if (!target || !target.dataset || target.dataset.spxCenterQuery !== '1') return;
      panel.spxCenterQueryComposing = false;
      commitCenterQueryInput(panel, config, target);
    });

    panel.addEventListener('input', function handleCenterInput(event) {
      var target = event.target;
      if (!target || !target.dataset || target.dataset.spxCenterQuery !== '1') return;
      if (event.isComposing || panel.spxCenterQueryComposing) return;
      commitCenterQueryInput(panel, config, target);
    });

    panel.addEventListener('change', function handleCenterChange(event) {
      var target = event.target;
      if (!target || !target.dataset) return;
      var state = ensureCenterPanelState(panel, config.stateDefaults || { query: '', filter: 'all', tag: 'all', provider: 'all' });
      if (target.dataset.spxCenterFilter === '1') {
        if (state.filter === target.value) return;
        state.filter = target.value;
      } else if (target.dataset.spxCenterTag === '1') {
        if (state.tag === target.value) return;
        state.tag = target.value;
      } else if (target.dataset.spxCenterProvider === '1') {
        if (state.provider === target.value) return;
        state.provider = target.value;
      } else if (target.dataset.spxCenterView === '1') {
        if (state.view === target.value) return;
        state.view = target.value;
      } else {
        return;
      }
      panel.spxRender();
    });

    panel.addEventListener('click', function handleCenterClick(event) {
      var target = event.target;
      var action = target && target.dataset && target.dataset.action;
      if (!action) return;
      config.onAction(action, target, panel);
    });

    panel.spxRender = function renderPanel() {
      config.render(panel);
    };
    panel.spxRender();
    return panel;
  }

  function openProgressEntry(state, id, url, mode) {
    if (!id) return;
    requestReadProgressRestore(id, mode);
    if (detectPageType(location.href) === 'read' && parseThreadId(location.href) === id) {
      restoreReadProgress(state, id, mode);
      return;
    }
    if (url) location.href = url;
  }

  function renderWatchCenter(panel, state) {
    state.watch = state.watch || {};
    state.progress = state.progress || {};
    var entries = getWatchCenterEntries(state.watch, state.progress);
    renderCenterPanel(panel, {
      title: '稍后看中心',
      summary: function summary(visibleEntries, entries, panelState) {
        return (
          (isCenterFiltered(panelState) ? (visibleEntries.length + ' / ') : '') +
          entries.length +
          ' 条已保存主题'
        );
      },
      headerActions: function headerActions(visibleEntries, entries, panelState) {
        var actions = [];
        if (visibleEntries.length && isCenterFiltered(panelState)) {
          actions.push({ text: '移除筛选', dataset: { action: 'remove-visible-watch' } });
        }
        if (entries.length) actions.push({ text: '清空', dataset: { action: 'clear-watch' } });
        actions.push({ text: '关闭', dataset: { action: 'close-watch' } });
        return actions;
      },
      controls: {
        searchPlaceholder: '搜索标题或楼层',
        filters: [
          { value: 'all', label: '全部' },
          { value: 'todo', label: '未读完' },
          { value: 'progress', label: '有进度' },
          { value: 'done', label: '已读完' },
        ],
        tags: getCenterTagOptions(entries),
      },
      emptyText: '还没有保存的主题。可在帖子列表点击“稍后”加入。',
      emptyFilteredText: '没有匹配的已保存主题。',
      entries: entries,
      filterEntries: filterWatchCenterEntries,
      getItemData: function getItemData(entry) {
        return { id: entry.id };
      },
      createTitle: function createTitle(entry) {
        return createCenterTitleElement({
          text: entry.title,
          href: entry.url || entry.progressUrl || '#',
        });
      },
      createMeta: function createMeta(entry) {
        var metaParts = [];
        if (entry.savedAt) metaParts.push('保存 ' + formatShortTime(entry.savedAt));
        if (entry.progressText) metaParts.push('进度 ' + entry.progressText);
        if (entry.nextFloorLabel) metaParts.push('续读 ' + entry.nextFloorLabel);
        if (entry.floorLabel && entry.floorLabel !== entry.nextFloorLabel) metaParts.push('上次 ' + entry.floorLabel);
        if (entry.tagText) metaParts.push('标签 ' + entry.tagText);
        return metaParts.join(' · ') || '暂无进度';
      },
      createActions: function createActions(entry) {
        var actions = [];
        if (entry.progressAt && entry.progressUrl) {
          actions.push({ text: '继续阅读', dataset: { action: 'continue-watch', id: entry.id } });
          if (entry.floorLabel) actions.push({ text: '上次楼层', dataset: { action: 'continue-last-watch', id: entry.id } });
        }
        actions.push({ text: '标签', dataset: { action: 'tag-watch', id: entry.id } });
        actions.push({ text: '打开', href: entry.url || entry.progressUrl || '#' });
        actions.push({ text: '移除', dataset: { action: 'remove-watch', id: entry.id } });
        return actions;
      },
    });
  }

  function refreshWatchCenter() {
    refreshCenterPanel('#spx-watch-center');
  }

  function createWatchCenterPanel(settings, state) {
    return createCenterPanel({
      id: 'spx-watch-center',
      render: function render(panel) {
        renderWatchCenter(panel, state);
      },
      onAction: function onAction(action, target, panel) {
        if (action === 'close-watch') {
          setCenterPanelHidden(panel, true, '[data-spx-watch-center-button="1"]');
          return;
        }
        if (action === 'remove-visible-watch') {
          var visibleEntries = filterWatchCenterEntries(
            getWatchCenterEntries(state.watch, state.progress),
            ensureCenterPanelState(panel, { query: '', filter: 'all', tag: 'all' })
          );
          if (!visibleEntries.length) return;
          if (typeof window.confirm === 'function' && !window.confirm('移除当前筛选结果中的稍后看主题?')) return;
          visibleEntries.forEach(function removeVisibleEntry(entry) {
            delete state.watch[entry.id];
          });
          saveMap(WATCH_KEY, state.watch);
          qsa('.spx-watch-badge').forEach(function removeMatchingBadge(badge) {
            if (!state.watch[badge.dataset.spxWatchId]) badge.remove();
          });
          renderWatchCenter(panel, state);
          return;
        }
        if (action === 'clear-watch') {
          if (typeof window.confirm === 'function' && !window.confirm('清空全部稍后看主题?')) return;
          state.watch = {};
          saveMap(WATCH_KEY, state.watch);
          qsa('.spx-watch-badge').forEach(function removeBadge(badge) {
            badge.remove();
          });
          renderWatchCenter(panel, state);
          return;
        }
        var id = target.dataset.id;
        if (!id) return;
        if (action === 'tag-watch') {
          var currentTags = parseTagList(state.watch[id] && state.watch[id].tags);
          var nextTags = typeof window.prompt === 'function'
            ? window.prompt('编辑标签,多个标签用逗号或换行分隔', currentTags.join(','))
            : null;
          if (nextTags === null) return;
          state.watch[id] = state.watch[id] || {};
          state.watch[id].tags = parseTagList(nextTags);
          saveMap(WATCH_KEY, state.watch);
          renderWatchCenter(panel, state);
          return;
        }
        if (action === 'remove-watch') {
          delete state.watch[id];
          saveMap(WATCH_KEY, state.watch);
          qsa('.spx-watch-badge').forEach(function removeMatchingBadge(badge) {
            if (badge.dataset.spxWatchId === id) badge.remove();
          });
          renderWatchCenter(panel, state);
          return;
        }
        if (action === 'continue-watch') {
          var entry = getWatchCenterEntries(state.watch, state.progress).filter(function matchEntry(item) {
            return item.id === id;
          })[0];
          if (!entry) return;
          openProgressEntry(state, id, entry.progressUrl || entry.url, 'next');
          return;
        }
        if (action === 'continue-last-watch') {
          var lastEntry = getWatchCenterEntries(state.watch, state.progress).filter(function matchLastEntry(item) {
            return item.id === id;
          })[0];
          if (!lastEntry) return;
          openProgressEntry(state, id, lastEntry.progressUrl || lastEntry.url, 'last');
        }
      },
    });
  }

  function renderHistoryCenter(panel, state) {
    state.progress = state.progress || {};
    var entries = getHistoryCenterEntries(state.progress);
    renderCenterPanel(panel, {
      title: '最近浏览',
      summary: function summary(visibleEntries, entries, panelState) {
        return (
          (isCenterFiltered(panelState) ? (visibleEntries.length + ' / ') : '') +
          entries.length +
          ' 条阅读记录'
        );
      },
      headerActions: function headerActions(visibleEntries, entries, panelState) {
        var actions = [];
        if (visibleEntries.length && isCenterFiltered(panelState)) {
          actions.push({ text: '移除筛选', dataset: { action: 'remove-visible-history' } });
        }
        if (entries.length) actions.push({ text: '清空', dataset: { action: 'clear-history' } });
        actions.push({ text: '关闭', dataset: { action: 'close-history' } });
        return actions;
      },
      controls: {
        searchPlaceholder: '搜索标题或楼层',
        filters: [
          { value: 'all', label: '全部' },
          { value: 'todo', label: '未读完' },
          { value: 'done', label: '已读完' },
        ],
        tags: getCenterTagOptions(entries),
      },
      emptyText: '还没有阅读记录。打开帖子后会自动记录进度。',
      emptyFilteredText: '没有匹配的阅读记录。',
      entries: entries,
      filterEntries: filterHistoryCenterEntries,
      getItemData: function getItemData(entry) {
        return { id: entry.id };
      },
      createTitle: function createTitle(entry) {
        return createCenterTitleElement({
          text: entry.title,
          href: entry.url || '#',
        });
      },
      createMeta: function createMeta(entry) {
        var metaParts = [];
        if (entry.progressAt) metaParts.push('浏览 ' + formatShortTime(entry.progressAt));
        if (entry.progressText) metaParts.push('进度 ' + entry.progressText);
        if (entry.nextFloorLabel) metaParts.push('续读 ' + entry.nextFloorLabel);
        if (entry.floorLabel && entry.floorLabel !== entry.nextFloorLabel) metaParts.push('上次 ' + entry.floorLabel);
        if (entry.tagText) metaParts.push('标签 ' + entry.tagText);
        return metaParts.join(' · ') || '暂无进度';
      },
      createActions: function createActions(entry) {
        var actions = [
          { text: '继续阅读', dataset: { action: 'continue-history', id: entry.id } },
          { text: '标签', dataset: { action: 'tag-history', id: entry.id } },
          { text: '打开', href: entry.url || '#' },
          { text: '移除', dataset: { action: 'remove-history', id: entry.id } },
        ];
        if (entry.floorLabel) actions.splice(1, 0, { text: '上次楼层', dataset: { action: 'continue-last-history', id: entry.id } });
        return actions;
      },
    });
  }

  function refreshHistoryCenter() {
    refreshCenterPanel('#spx-history-center');
  }

  function createHistoryCenterPanel(settings, state) {
    return createCenterPanel({
      id: 'spx-history-center',
      render: function render(panel) {
        renderHistoryCenter(panel, state);
      },
      onAction: function onAction(action, target, panel) {
        if (action === 'close-history') {
          setCenterPanelHidden(panel, true, '[data-spx-history-center-button="1"]');
          return;
        }
        if (action === 'remove-visible-history') {
          var visibleEntries = filterHistoryCenterEntries(
            getHistoryCenterEntries(state.progress),
            ensureCenterPanelState(panel, { query: '', filter: 'all', tag: 'all' })
          );
          if (!visibleEntries.length) return;
          if (typeof window.confirm === 'function' && !window.confirm('移除当前筛选结果中的阅读记录?')) return;
          visibleEntries.forEach(function removeVisibleHistory(entry) {
            delete state.progress[entry.id];
          });
          saveReadProgress(state.progress);
          refreshWatchCenter();
          renderHistoryCenter(panel, state);
          return;
        }
        if (action === 'clear-history') {
          if (typeof window.confirm === 'function' && !window.confirm('清空全部阅读记录?')) return;
          state.progress = {};
          saveReadProgress(state.progress);
          clearReadProgressRestoreRequest(parseThreadId(location.href));
          refreshWatchCenter();
          renderHistoryCenter(panel, state);
          return;
        }
        var id = target.dataset.id;
        if (!id) return;
        if (action === 'tag-history') {
          var currentTags = parseTagList(state.progress[id] && state.progress[id].tags);
          var nextTags = typeof window.prompt === 'function'
            ? window.prompt('编辑标签,多个标签用逗号或换行分隔', currentTags.join(','))
            : null;
          if (nextTags === null) return;
          state.progress[id] = state.progress[id] || {};
          state.progress[id].tags = parseTagList(nextTags);
          saveReadProgress(state.progress);
          refreshWatchCenter();
          renderHistoryCenter(panel, state);
          return;
        }
        if (action === 'remove-history') {
          delete state.progress[id];
          saveReadProgress(state.progress);
          refreshWatchCenter();
          renderHistoryCenter(panel, state);
          return;
        }
        if (action === 'continue-history') {
          var entry = getHistoryCenterEntries(state.progress).filter(function matchEntry(item) {
            return item.id === id;
          })[0];
          if (!entry) return;
          openProgressEntry(state, id, entry.url, 'next');
          return;
        }
        if (action === 'continue-last-history') {
          var lastEntry = getHistoryCenterEntries(state.progress).filter(function matchLastEntry(item) {
            return item.id === id;
          })[0];
          if (!lastEntry) return;
          openProgressEntry(state, id, lastEntry.url, 'last');
        }
      },
    });
  }

  function renderAutoBuyCenter(panel) {
    renderCenterPanel(panel, {
      title: '自动购买记录',
      summary: function summary(visibleEntries, entries, panelState) {
        return (
          ((panelState.query || panelState.filter !== 'all') ? (visibleEntries.length + ' / ') : '') +
          entries.length +
          ' 条执行记录'
        );
      },
      headerActions: function headerActions(visibleEntries, entries, panelState) {
        var actions = [];
        if (visibleEntries.length && (panelState.query || panelState.filter !== 'all')) {
          actions.push({ text: '删除筛选', dataset: { action: 'remove-visible-auto-buy-records' } });
        }
        if (entries.length) actions.push({ text: '清空', dataset: { action: 'clear-auto-buy-records' } });
        actions.push({ text: '关闭', dataset: { action: 'close-auto-buy-center' } });
        return actions;
      },
      controls: {
        searchPlaceholder: '搜索帖子或结果',
        filters: [
          { value: 'all', label: '全部' },
          { value: 'checking', label: '检查中' },
          { value: 'skipped', label: '已跳过' },
          { value: 'buying', label: '购买中' },
          { value: 'done', label: '已完成' },
          { value: 'failed', label: '失败' },
        ],
      },
      emptyText: '还没有自动购买记录。开启自动购买并遇到付费帖后会记录执行状态。',
      emptyFilteredText: '没有匹配的自动购买记录。',
      entries: getAutoBuyCenterEntries(loadAutoBuyAttempts()),
      filterEntries: filterAutoBuyCenterEntries,
      getItemData: function getItemData(entry) {
        return { key: entry.key };
      },
      createTitle: function createTitle(entry) {
        return createCenterTitleElement({
          text: entry.key,
          href: entry.url,
          badgeText: entry.statusLabel,
          badgeClass: 'spx-status-' + entry.status,
        });
      },
      createMeta: function createMeta(entry) {
        var metaParts = [];
        if (entry.updatedAt) metaParts.push('记录 ' + formatShortTime(entry.updatedAt));
        if (entry.price !== null && isFinite(entry.price)) metaParts.push('价格 ' + entry.price + ' SP');
        if (entry.balance !== null && isFinite(entry.balance)) metaParts.push('余额 ' + entry.balance + ' SP');
        if (entry.message) metaParts.push(entry.message);
        return metaParts.join(' · ') || '暂无详情';
      },
      createActions: function createActions(entry) {
        var actions = [];
        if (entry.url) actions.push({ text: '打开', href: entry.url });
        actions.push({ text: '删除记录', dataset: { action: 'remove-auto-buy-record', key: entry.key } });
        return actions;
      },
    });
  }

  function refreshAutoBuyCenter() {
    refreshCenterPanel('#spx-auto-buy-center');
  }

  function createAutoBuyCenterPanel(settings, state) {
    return createCenterPanel({
      id: 'spx-auto-buy-center',
      render: function render(panel) {
        renderAutoBuyCenter(panel);
      },
      onAction: function onAction(action, target, panel) {
        if (action === 'close-auto-buy-center') {
          setCenterPanelHidden(panel, true, '[data-spx-auto-buy-center-button="1"]');
          return;
        }
        if (action === 'remove-visible-auto-buy-records') {
          var attempts = loadAutoBuyAttempts();
          var visibleEntries = filterAutoBuyCenterEntries(
            getAutoBuyCenterEntries(attempts),
            ensureCenterPanelState(panel, { query: '', filter: 'all' })
          );
          if (!visibleEntries.length) return;
          if (typeof window.confirm === 'function' && !window.confirm('删除当前筛选结果中的自动购买记录?')) return;
          visibleEntries.forEach(function removeVisibleAutoBuyEntry(entry) {
            delete attempts[entry.key];
          });
          saveAutoBuyAttempts(attempts);
          renderAutoBuyCenter(panel);
          return;
        }
        if (action === 'clear-auto-buy-records') {
          if (typeof window.confirm === 'function' && !window.confirm('清空全部自动购买记录?')) return;
          saveAutoBuyAttempts({});
          delete document.documentElement.dataset.spxAutoBuyStatus;
          var status = qs('#spx-auto-buy-status');
          if (status) status.remove();
          renderAutoBuyCenter(panel);
          return;
        }
        if (action === 'remove-auto-buy-record') {
          var key = target.dataset.key;
          if (!key) return;
          var attempts = loadAutoBuyAttempts();
          delete attempts[key];
          saveAutoBuyAttempts(attempts);
          renderAutoBuyCenter(panel);
        }
      },
    });
  }

  function renderResourceCenter(panel, state) {
    state.resources = pruneResourceLibrary(state.resources || {});
    var entries = getResourceCenterEntries(state.resources);
    renderCenterPanel(panel, {
      title: '资源工作台',
      stateDefaults: { query: '', filter: 'all', provider: 'all', tag: 'all', view: 'list', selectedResources: {} },
      summary: function summary(visibleEntries, entries, panelState) {
        var queueCount = getResourceDownloadQueueEntries(entries).length;
        var selectedCount = getSelectedResourceKeys(entries, panelState).length;
        return (
          (isCenterFiltered(panelState) ? (visibleEntries.length + ' / ') : '') +
          entries.length +
          ' 条资源' +
          (queueCount ? (' · 待下载 ' + queueCount + ' 条') : '') +
          (selectedCount ? (' · 已选 ' + selectedCount + ' 条') : '') +
          (panelState.view === 'source' ? ' · 来源分组' : '')
        );
      },
      headerActions: function headerActions(visibleEntries, entries, panelState) {
        var actions = [];
        var selectedKeys = getSelectedResourceKeys(entries, panelState);
        var visibleQueue = getResourceDownloadQueueEntries(visibleEntries);
        if (visibleEntries.length) actions.push({ text: '全选筛选', dataset: { action: 'select-visible-resources' } });
        if (selectedKeys.length) {
          actions.push({ text: '取消选择', dataset: { action: 'clear-resource-selection' } });
          actions.push({ text: '复制选中', dataset: { action: 'copy-selected-resources' } });
          actions.push({ text: 'Markdown', dataset: { action: 'copy-selected-resource-markdown' } });
          actions.push({ text: '选中待下载', dataset: { action: 'mark-selected-resources', status: 'todo' } });
          actions.push({ text: '选中已处理', dataset: { action: 'mark-selected-resources', status: 'done' } });
          actions.push({ text: '选中失效', dataset: { action: 'mark-selected-resources', status: 'invalid' } });
          actions.push({ text: '选中标签', dataset: { action: 'tag-selected-resources' } });
          actions.push({ text: '选中备注', dataset: { action: 'note-selected-resources' } });
        }
        if (visibleEntries.length) actions.push({ text: '复制筛选', dataset: { action: 'copy-visible-resources' } });
        if (visibleQueue.length) actions.push({ text: '复制待下载', dataset: { action: 'copy-download-queue' } });
        if (visibleEntries.length) actions.push({ text: '导出清单', dataset: { action: 'export-visible-resources' } });
        if (visibleEntries.length && isCenterFiltered(panelState)) {
          actions.push({ text: '删除筛选', dataset: { action: 'remove-visible-resources' } });
        }
        if (entries.length) actions.push({ text: '清空', dataset: { action: 'clear-resources' } });
        actions.push({ text: '关闭', dataset: { action: 'close-resource-center' } });
        return actions;
      },
      controls: {
        searchPlaceholder: '搜索资源、帖子、作者、备注或标签',
        filters: [
          { value: 'all', label: '全部状态' },
          { value: 'saved', label: RESOURCE_STATUSES.saved },
          { value: 'todo', label: RESOURCE_STATUSES.todo },
          { value: 'done', label: RESOURCE_STATUSES.done },
          { value: 'invalid', label: RESOURCE_STATUSES.invalid },
        ],
        providers: getResourceProviderOptions(entries),
        providerAllLabel: '全部类型',
        tags: getCenterTagOptions(entries),
        views: [
          { value: 'list', label: '列表视图' },
          { value: 'source', label: '按来源帖' },
        ],
      },
      emptyText: '还没有保存的资源。可在阅读页资源面板或自动购买成功后存入。',
      emptyFilteredText: '没有匹配的资源。',
      entries: entries,
      filterEntries: filterResourceCenterEntries,
      transformVisibleEntries: function transformVisibleEntries(visibleEntries, entries, panelState) {
        return panelState.view === 'source' ? groupResourceCenterEntries(visibleEntries) : visibleEntries;
      },
      getItemData: function getItemData(entry) {
        return entry.entries ? { resourceGroupKey: entry.key } : { key: entry.key };
      },
      createTitle: function createTitle(entry) {
        if (entry.entries) {
          return createCenterTitleElement({
            text: entry.label + ' · ' + entry.entries.length + ' 条资源',
            href: entry.sourceUrl,
            badgeText: '来源分组',
            badgeClass: 'spx-status-saved',
            title: entry.label,
          });
        }
        var titleRow = createEl('div', 'spx-resource-title-row');
        var select = createEl('input', 'spx-resource-select');
        select.type = 'checkbox';
        select.dataset.action = 'toggle-resource-selection';
        select.dataset.key = entry.key;
        select.checked = !!ensureResourceSelection(ensureCenterPanelState(panel, { selectedResources: {} }))[entry.key];
        select.title = '选择资源';
        titleRow.appendChild(select);
        titleRow.appendChild(createCenterTitleElement({
          text: entry.label + ' · ' + entry.url,
          href: entry.url,
          badgeText: entry.statusLabel,
          badgeClass: 'spx-status-' + entry.status,
          title: entry.url,
        }));
        return titleRow;
      },
      createMeta: function createMeta(entry) {
        if (entry.entries) {
          var typeCounts = {};
          entry.entries.forEach(function countGroupResource(item) {
            var label = item.label || getResourceDisplayLabel(item);
            typeCounts[label] = (typeCounts[label] || 0) + 1;
          });
          var typeText = Object.keys(typeCounts).map(function formatTypeCount(label) {
            return label + ' ' + typeCounts[label];
          }).join(' / ');
          return [
            typeText,
            entry.updatedAt ? ('最新 ' + formatShortTime(entry.updatedAt)) : '',
            entry.sourceUrl ? entry.sourceUrl : '',
          ].filter(Boolean).join(' · ') || '暂无来源详情';
        }
        var metaParts = [];
        if (entry.savedAt) metaParts.push('保存 ' + formatShortTime(entry.savedAt));
        if (entry.provider) metaParts.push(entry.provider);
        if (entry.sourceText) metaParts.push(entry.sourceText);
        if (entry.accessCode) metaParts.push('提取码 ' + entry.accessCode);
        if (entry.tagText) metaParts.push('标签 ' + entry.tagText);
        if (entry.note) metaParts.push('备注 ' + entry.note);
        return metaParts.join(' · ') || '暂无来源';
      },
      createActions: function createActions(entry) {
        if (entry.entries) {
          var groupActions = [
            { text: '选择分组', dataset: { action: 'select-resource-group', sourceKey: entry.key } },
            { text: '复制分组', dataset: { action: 'copy-resource-group', sourceKey: entry.key } },
            { text: '分组 Markdown', dataset: { action: 'copy-resource-group-markdown', sourceKey: entry.key } },
            { text: '待下载', dataset: { action: 'mark-resource-group', sourceKey: entry.key, status: 'todo' } },
            { text: '已处理', dataset: { action: 'mark-resource-group', sourceKey: entry.key, status: 'done' } },
          ];
          if (entry.sourceUrl) groupActions.push({ text: '来源帖', href: entry.sourceUrl });
          return groupActions;
        }
        var actions = [
          { text: '复制', dataset: { action: 'copy-resource', key: entry.key } },
          { text: '备注', dataset: { action: 'note-resource', key: entry.key } },
          { text: '标签', dataset: { action: 'tag-resource', key: entry.key } },
          { text: '待下载', dataset: { action: 'mark-resource-todo', key: entry.key } },
          { text: '已处理', dataset: { action: 'mark-resource-done', key: entry.key } },
          { text: '失效', dataset: { action: 'mark-resource-invalid', key: entry.key } },
        ];
        if (entry.status !== 'saved') actions.push({ text: '已保存', dataset: { action: 'mark-resource-saved', key: entry.key } });
        if (entry.sourceUrl) actions.push({ text: '来源帖', href: entry.sourceUrl });
        actions.push({ text: '删除', dataset: { action: 'remove-resource', key: entry.key } });
        return actions;
      },
    });
  }

  function getVisibleResourceCenterEntries(panel, state) {
    return filterResourceCenterEntries(
      getResourceCenterEntries(state.resources),
      ensureCenterPanelState(panel, { query: '', filter: 'all', provider: 'all', tag: 'all', view: 'list', selectedResources: {} })
    );
  }

  function getResourceGroupEntriesFromPanel(panel, state, sourceKey) {
    var groups = groupResourceCenterEntries(getVisibleResourceCenterEntries(panel, state));
    var group = groups.filter(function matchResourceGroup(item) {
      return item.key === sourceKey;
    })[0];
    return group ? group.entries : [];
  }

  function updateResourceRecords(state, keys, updater) {
    var now = Date.now();
    (keys || []).forEach(function updateResourceKey(key) {
      if (!state.resources[key]) return;
      var record = normalizeResourceRecord(state.resources[key], key);
      if (!record) return;
      updater(record);
      record.updatedAt = now;
      state.resources[key] = record;
    });
  }

  function promptResourceTags(currentTags) {
    return typeof window.prompt === 'function'
      ? window.prompt('编辑标签,多个标签用逗号或换行分隔', normalizeResourceTags(currentTags).join(','))
      : null;
  }

  function promptResourceNote(currentNote) {
    return typeof window.prompt === 'function'
      ? window.prompt('编辑备注', String(currentNote || ''))
      : null;
  }

  function getResourceEntryKeys(entries) {
    return (entries || []).map(function mapResourceEntryKey(entry) {
      return entry && entry.key;
    }).filter(Boolean);
  }

  function copyResourceEntriesToClipboard(entries, formatter, target, successText, restoreText) {
    copyTextToClipboard(formatter(entries)).then(
      function showCopySuccess() {
        setTemporaryText(target, successText, restoreText);
      },
      function showCopyFailure() {
        setTemporaryText(target, '复制失败', restoreText);
      }
    );
  }

  function refreshResourceCenter() {
    refreshCenterPanel('#spx-resource-center');
  }

  function saveResourceCenterState(state) {
    state.resources = pruneResourceLibrary(state.resources || {});
    saveResourceLibrary(state.resources);
    refreshResourceCenter();
    refreshReadResourceRail();
  }

  function createResourceCenterPanel(settings, state) {
    return createCenterPanel({
      id: 'spx-resource-center',
      stateDefaults: { query: '', filter: 'all', provider: 'all', tag: 'all', view: 'list', selectedResources: {} },
      render: function render(panel) {
        renderResourceCenter(panel, state);
      },
      onAction: function onAction(action, target, panel) {
        state.resources = pruneResourceLibrary(state.resources || {});
        if (action === 'close-resource-center') {
          setCenterPanelHidden(panel, true, '[data-spx-resource-center-button="1"]');
          return;
        }
        var panelState = ensureCenterPanelState(panel, { query: '', filter: 'all', provider: 'all', tag: 'all', view: 'list', selectedResources: {} });
        var allEntries = getResourceCenterEntries(state.resources);
        var visibleEntriesNow = getVisibleResourceCenterEntries(panel, state);
        var selectedKeys = getSelectedResourceKeys(allEntries, panelState);
        var selectedEntries = getResourceEntriesByKeys(allEntries, selectedKeys);
        if (action === 'toggle-resource-selection') {
          var toggleKey = target.dataset.key;
          if (!toggleKey) return;
          var selection = ensureResourceSelection(panelState);
          if (target.checked) selection[toggleKey] = true;
          else delete selection[toggleKey];
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'select-visible-resources') {
          setResourceSelection(visibleEntriesNow, panelState, true);
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'clear-resource-selection') {
          panelState.selectedResources = {};
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'select-resource-group') {
          setResourceSelection(getResourceGroupEntriesFromPanel(panel, state, target.dataset.sourceKey), panelState, true);
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'copy-selected-resources') {
          copyResourceEntriesToClipboard(selectedEntries, formatResourceLinks, target, '已复制 ' + selectedEntries.length + ' 条', '复制选中');
          return;
        }
        if (action === 'copy-selected-resource-markdown') {
          copyResourceEntriesToClipboard(selectedEntries, formatResourceMarkdownList, target, '已复制 Markdown', 'Markdown');
          return;
        }
        if (action === 'copy-resource-group' || action === 'copy-resource-group-markdown') {
          var groupEntriesForCopy = getResourceGroupEntriesFromPanel(panel, state, target.dataset.sourceKey);
          copyResourceEntriesToClipboard(
            groupEntriesForCopy,
            action === 'copy-resource-group-markdown' ? formatResourceMarkdownList : formatResourceLinks,
            target,
            action === 'copy-resource-group-markdown' ? '已复制 Markdown' : ('已复制 ' + groupEntriesForCopy.length + ' 条'),
            action === 'copy-resource-group-markdown' ? '分组 Markdown' : '复制分组'
          );
          return;
        }
        if (action === 'mark-selected-resources' || action === 'mark-resource-group') {
          var statusKeys = action === 'mark-resource-group'
            ? getResourceEntryKeys(getResourceGroupEntriesFromPanel(panel, state, target.dataset.sourceKey))
            : selectedKeys;
          updateResourceRecords(state, statusKeys, function markSelectedResource(record) {
            record.status = normalizeResourceStatus(target.dataset.status);
          });
          saveResourceCenterState(state);
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'tag-selected-resources') {
          var selectedTagText = promptResourceTags([]);
          if (selectedTagText === null) return;
          updateResourceRecords(state, selectedKeys, function tagSelectedResource(record) {
            record.tags = parseTagList(selectedTagText);
          });
          saveResourceCenterState(state);
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'note-selected-resources') {
          var selectedNoteText = promptResourceNote('');
          if (selectedNoteText === null) return;
          updateResourceRecords(state, selectedKeys, function noteSelectedResource(record) {
            record.note = String(selectedNoteText || '').replace(/\s+/g, ' ').trim().slice(0, 160);
          });
          saveResourceCenterState(state);
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'copy-visible-resources') {
          var visibleForCopy = visibleEntriesNow;
          copyTextToClipboard(formatResourceLinks(visibleForCopy)).then(
            function showCopySuccess() {
              setTemporaryText(target, '已复制 ' + visibleForCopy.length + ' 条', '复制筛选');
            },
            function showCopyFailure() {
              setTemporaryText(target, '复制失败', '复制筛选');
            }
          );
          return;
        }
        if (action === 'copy-download-queue') {
          var visibleQueueForCopy = getResourceDownloadQueueEntries(visibleEntriesNow);
          copyTextToClipboard(formatResourceDownloadList(visibleQueueForCopy)).then(
            function showQueueCopySuccess() {
              setTemporaryText(target, '已复制 ' + visibleQueueForCopy.length + ' 条', '复制待下载');
            },
            function showQueueCopyFailure() {
              setTemporaryText(target, '复制失败', '复制待下载');
            }
          );
          return;
        }
        if (action === 'export-visible-resources') {
          var visibleForExport = visibleEntriesNow;
          if (!visibleForExport.length) return;
          if (exportResourceDownloadList(visibleForExport)) {
            setTemporaryText(target, '已导出 ' + visibleForExport.length + ' 条', '导出清单');
          } else {
            copyTextToClipboard(formatResourceDownloadList(visibleForExport)).then(
              function showExportCopySuccess() {
                setTemporaryText(target, '已复制清单', '导出清单');
              },
              function showExportFailure() {
                setTemporaryText(target, '导出失败', '导出清单');
              }
            );
          }
          return;
        }
        if (action === 'remove-visible-resources') {
          var visibleEntries = visibleEntriesNow;
          if (!visibleEntries.length) return;
          if (typeof window.confirm === 'function' && !window.confirm('删除当前筛选结果中的资源?')) return;
          visibleEntries.forEach(function removeVisibleResource(entry) {
            delete state.resources[entry.key];
            delete ensureResourceSelection(panelState)[entry.key];
          });
          saveResourceCenterState(state);
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'clear-resources') {
          if (typeof window.confirm === 'function' && !window.confirm('清空全部资源库记录?')) return;
          state.resources = {};
          panelState.selectedResources = {};
          saveResourceCenterState(state);
          renderResourceCenter(panel, state);
          return;
        }

        var key = target.dataset.key;
        if (!key) return;
        if (action === 'copy-resource') {
          var entry = state.resources[key];
          copyTextToClipboard(formatResourceLinks(entry ? [entry] : [])).catch(function noop() {});
          return;
        }
        if (action === 'remove-resource') {
          delete state.resources[key];
          delete ensureResourceSelection(panelState)[key];
          saveResourceCenterState(state);
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'tag-resource' && state.resources[key]) {
          var tagText = promptResourceTags(state.resources[key].tags);
          if (tagText === null) return;
          updateResourceRecords(state, [key], function tagResource(record) {
            record.tags = parseTagList(tagText);
          });
          saveResourceCenterState(state);
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'note-resource' && state.resources[key]) {
          var noteText = promptResourceNote(state.resources[key].note);
          if (noteText === null) return;
          updateResourceRecords(state, [key], function noteResource(record) {
            record.note = String(noteText || '').replace(/\s+/g, ' ').trim().slice(0, 160);
          });
          saveResourceCenterState(state);
          renderResourceCenter(panel, state);
          return;
        }
        if (/^mark-resource-/.test(action) && state.resources[key]) {
          var status = action.replace('mark-resource-', '');
          updateResourceRecords(state, [key], function markResource(record) {
            record.status = normalizeResourceStatus(status);
          });
          saveResourceCenterState(state);
          renderResourceCenter(panel, state);
        }
      },
    });
  }

  function createForumQuickTools(settings, state, items) {
    if (qs('#spx-forum-tools')) return;
    var content = qs('#content') || qs('#main') || document.body;
    if (!content) return;

    var tools = createEl('div', 'spx-forum-tools');
    tools.id = 'spx-forum-tools';
    var activeResourceFilter = 'all';
    var input = createEl('input');
    input.type = 'search';
    input.placeholder = '快速过滤:关键词、!排除、作者:用户名';
    var resourceFilters = createEl('div', 'spx-forum-resource-filters');
    var markVisibleButton = createEl('button', '', '可见已读');
    var watchVisibleButton = createEl('button', '', '可见稍后');
    var preloadNextButton = createEl('button', '', '预载下页');
    var clearButton = createEl('button', '', '清空过滤');
    tools.appendChild(input);
    tools.appendChild(resourceFilters);
    tools.appendChild(markVisibleButton);
    tools.appendChild(watchVisibleButton);
    tools.appendChild(preloadNextButton);
    tools.appendChild(clearButton);

    function getAvailableResourceFilterTypes() {
      var seen = {};
      (items || []).forEach(function collectItemResourceTypes(item) {
        (item.resourceBadgeTypes || []).forEach(function collectType(type) {
          if (getResourceBadgeDefinition(type)) seen[type] = true;
        });
      });
      return RESOURCE_BADGE_ORDER.filter(function keepAvailableType(type) {
        return !!seen[type];
      });
    }

    function syncResourceFilterButtons() {
      qsa('button[data-spx-resource-filter]', resourceFilters).forEach(function syncFilterButton(button) {
        button.classList.toggle('spx-resource-filter-active', button.dataset.spxResourceFilter === activeResourceFilter);
      });
    }

    function setResourceFilter(type) {
      activeResourceFilter = getResourceBadgeDefinition(type) ? type : 'all';
      syncResourceFilterButtons();
      applyFilter();
    }

    function createResourceFilterButton(type, label) {
      var button = createEl('button', type === activeResourceFilter ? 'spx-resource-filter-active' : '', label);
      button.type = 'button';
      button.dataset.spxResourceFilter = type;
      button.title = type === 'all' ? '显示全部资源类型' : '只看' + label + '资源主题';
      button.addEventListener('click', function applyResourceFilter(event) {
        event.preventDefault();
        setResourceFilter(type);
      });
      return button;
    }

    function renderResourceFilters() {
      var types = getAvailableResourceFilterTypes();
      resourceFilters.textContent = '';
      if (!types.length) return;
      resourceFilters.appendChild(createResourceFilterButton('all', '全部资源'));
      types.forEach(function appendResourceFilter(type) {
        resourceFilters.appendChild(createResourceFilterButton(type, getResourceBadgeDefinition(type).label));
      });
    }

    function applyFilter() {
      var parsed = parseForumFilterQuery(input.value);
      items.forEach(function toggleItem(item) {
        var textHidden = !!input.value.trim() && !matchesForumFilter(item, parsed);
        var resourceHidden = activeResourceFilter !== 'all' && (item.resourceBadgeTypes || []).indexOf(activeResourceFilter) === -1;
        setThreadRowHiddenClass(item.row, 'spx-filter-hidden', textHidden);
        setThreadRowHiddenClass(item.row, 'spx-resource-filter-hidden', resourceHidden);
      });
    }

    renderResourceFilters();
    document.addEventListener('spx-resource-filter', function handleResourceBadgeFilter(event) {
      if (!tools.isConnected) return;
      setResourceFilter(event && event.detail && event.detail.type);
    });

    input.addEventListener('input', applyFilter);
    clearButton.addEventListener('click', function clearFilter() {
      input.value = '';
      activeResourceFilter = 'all';
      syncResourceFilterButtons();
      applyFilter();
      input.focus();
    });
    markVisibleButton.addEventListener('click', function markVisibleRead() {
      markThreadsRead(items, state);
      saveMap(READ_KEY, state.read);
      items.forEach(function syncRead(item) {
        if (item.row && state.read[item.id]) item.row.classList.add('spx-read-thread');
      });
    });
    watchVisibleButton.addEventListener('click', function watchVisibleThreads() {
      var count = 0;
      state.watch = state.watch || {};
      items.forEach(function saveVisibleWatch(item) {
        if (!item || !item.id || !item.titleLink || !isVisibleThreadRow(item.row)) return;
        if (state.watch[item.id]) return;
        state.watch[item.id] = {
          title: item.title,
          url: item.titleLink.href,
          savedAt: Date.now(),
        };
        count += 1;
        var button = qs('.spx-thread-tools button', item.cell);
        if (button && button.textContent === '稍后') button.textContent = '已存';
        if (!qs('.spx-watch-badge', item.cell)) {
          item.titleLink.insertAdjacentElement('afterend', createWatchBadge(item.id));
        }
      });
      saveMap(WATCH_KEY, state.watch);
      refreshWatchCenter();
      watchVisibleButton.textContent = count ? '已存 ' + count + ' 条' : '无新增';
      window.setTimeout(function resetWatchVisibleText() {
        watchVisibleButton.textContent = '可见稍后';
      }, 1600);
    });
    preloadNextButton.addEventListener('click', function preloadNextPage() {
      if (typeof window.fetch !== 'function') return;
      var nextUrl = buildPageUrl(location.href, currentPageNumber(location.href) + 1);
      var preloadPolicy = {
        mode: 'interactive',
        label: '预载下页',
        networkFriendly: isNetworkFriendlyMode(settings),
      };
      preloadNextButton.disabled = true;
      preloadNextButton.textContent = '预载中';
      requestWithPolicy(nextUrl, {
        credentials: 'include',
        cache: 'force-cache',
      }, preloadPolicy)
        .then(function readNextPage(response) {
          if (!response.ok) throw new Error('下一页加载失败');
          return readScriptResponseText(response, preloadPolicy);
        })
        .then(function showNextPageCount(html) {
          var doc = new DOMParser().parseFromString(html, 'text/html');
          var count = qsa('td[id^="td_"]', doc).length || qsa('#wall .stream li a[href*="read.php?tid"]', doc).length;
          preloadNextButton.textContent = count ? '已预载 ' + count + ' 条' : '已预载';
        })
        .catch(function showPreloadFailure() {
          preloadNextButton.textContent = '预载失败';
        })
        .then(function resetPreloadButton() {
          window.setTimeout(function resetPreloadText() {
            preloadNextButton.disabled = false;
            preloadNextButton.textContent = '预载下页';
          }, 1800);
        });
    });

    content.insertBefore(tools, content.firstChild);
  }

  function restoreModuleNavigation() {
    var nav = qs('#spx-module-nav');
    if (nav) nav.remove();
    document.documentElement.classList.remove('spx-module-nav-ready');
    qsa('.spx-module-body').forEach(function unwrapModuleBody(body) {
      var parent = body.parentNode;
      if (!parent) return;
      while (body.firstChild) {
        parent.insertBefore(body.firstChild, body);
      }
      body.remove();
    });
    qsa('.spx-module-nav-host').forEach(function restoreNavHost(host) {
      host.classList.remove('spx-module-nav-host');
    });
    qsa('.spx-module-filter-hidden').forEach(function restoreFilteredRow(row) {
      row.classList.remove('spx-module-filter-hidden');
    });
  }

  function getCompactNodeText(node, fallback) {
    if (!node) return fallback || '';
    var clone = node.cloneNode(true);
    qsa('button,input,select,textarea', clone).forEach(function removeControl(control) {
      control.remove();
    });
    return (clone.textContent || '').replace(/\s+/g, ' ').trim() || fallback || '';
  }

  function ensureModuleTargetId(node, prefix, index) {
    if (!node) return '';
    if (!node.id) node.id = 'spx-module-target-' + prefix + '-' + (index + 1);
    return node.id;
  }

  function setModuleNavActive(nav, activeItem) {
    qsa('.spx-module-nav-item', nav).forEach(function toggleActive(item) {
      item.classList.toggle('spx-active', item === activeItem);
    });
  }

  function getModuleNavigationConfigPinKey(config) {
    if (!config || !config.href) return '';
    var label = normalizeNavigationLabel(config.label || config.title);
    var baseUrl = typeof location !== 'undefined' ? location.href : 'https://south-plus.org/';
    var href = normalizeNavigationHref(config.href, baseUrl);
    if (!label || !href) return '';
    return getNavigationItemKey(config.section || '导航', label, href);
  }

  function toggleModuleNavigationPin(pinKey) {
    if (!pinKey) return;
    var pins = loadNavigationPins();
    if (pins[pinKey]) {
      delete pins[pinKey];
    } else {
      pins[pinKey] = true;
    }
    saveNavigationPins(pins);
    remountGlobalModuleNavigation();
  }

  function createModuleNavigationPin(config) {
    var pin = createEl('span', 'spx-module-nav-pin', config.pinned ? '★' : '☆');
    pin.setAttribute('role', 'button');
    pin.setAttribute('tabindex', '0');
    pin.setAttribute('aria-label', config.pinned ? '取消置顶' : '置顶导航');
    pin.title = config.pinned ? '取消置顶' : '置顶导航';
    pin.addEventListener('click', function handleModuleNavigationPinClick(event) {
      event.preventDefault();
      event.stopPropagation();
      toggleModuleNavigationPin(config.pinKey);
    });
    pin.addEventListener('keydown', function handleModuleNavigationPinKeydown(event) {
      if (event.key !== 'Enter' && event.key !== ' ') return;
      event.preventDefault();
      event.stopPropagation();
      toggleModuleNavigationPin(config.pinKey);
    });
    return pin;
  }

  function getModuleNavigationSearchText(config) {
    return compactText([
      config && config.section,
      config && config.parentLabel,
      config && config.label,
      config && config.title,
    ].join(' '));
  }

  function appendModuleNavigationItem(container, config, active, level) {
    var item = createEl(config.href ? 'a' : 'button', 'spx-module-nav-item');
    item.classList.add('spx-module-nav-level-' + (level || 2));
    if (config.className) {
      String(config.className).split(/\s+/).filter(Boolean).forEach(function addNavigationClass(className) {
        item.classList.add(className);
      });
    }
    item.classList.toggle('spx-module-nav-pinned', !!config.pinned);
    if (config.href) {
      item.href = config.href;
    } else {
      item.type = 'button';
    }
    item.title = config.title || config.label || '';
    item.dataset.spxModuleNavText = getModuleNavigationSearchText(config);
    item.appendChild(createEl('span', 'spx-module-nav-label', config.label));
    if (!config.navigationOnly && (config.countText !== undefined || config.count !== undefined)) {
      item.appendChild(createEl('span', 'spx-module-nav-count', String(config.countText !== undefined ? config.countText : config.count)));
    }
    if (config.pinKey) item.appendChild(createModuleNavigationPin(config));
    item.classList.toggle('spx-active', !!active);
    item.addEventListener('click', function activateModuleNav(event) {
      var nav = item.closest ? item.closest('.spx-module-nav') : null;
      if (typeof config.onClick === 'function') {
        event.preventDefault();
        config.onClick(event);
      }
      if (nav) setModuleNavActive(nav, item);
      if (config.target && config.target.scrollIntoView) {
        config.target.scrollIntoView({ behavior: 'smooth', block: 'start' });
      }
    });
    container.appendChild(item);
  }

  function filterModuleNavigation(nav, query) {
    if (!nav) return;
    var term = compactText(query).toLowerCase();
    var hiddenClass = 'spx-module-nav-search-hidden';
    qsa('.' + hiddenClass, nav).forEach(function clearPreviousSearch(node) {
      node.classList.remove(hiddenClass);
    });
    if (!term) {
      delete nav.dataset.spxModuleNavSearching;
      return;
    }
    nav.dataset.spxModuleNavSearching = '1';
    qsa('.spx-module-nav-item,.spx-module-nav-parent-title', nav).forEach(function toggleSearchItem(item) {
      var text = compactText(item.dataset.spxModuleNavText || item.textContent).toLowerCase();
      item.classList.toggle(hiddenClass, text.indexOf(term) === -1);
    });
    qsa('.spx-module-nav-node', nav).reverse().forEach(function toggleSearchNode(node) {
      var own = node.firstElementChild;
      var hasVisibleItem = qsa('.spx-module-nav-item:not(.' + hiddenClass + '),.spx-module-nav-parent-title:not(.' + hiddenClass + ')', node).length > 0;
      if (hasVisibleItem && own && own.classList && own.classList.contains(hiddenClass)) {
        own.classList.remove(hiddenClass);
      }
      node.classList.toggle(hiddenClass, !hasVisibleItem);
    });
    qsa('.spx-module-nav-group', nav).forEach(function toggleSearchGroup(group) {
      var hasVisibleNode = qsa('.spx-module-nav-node:not(.' + hiddenClass + ')', group).length > 0;
      group.classList.toggle(hiddenClass, !hasVisibleNode);
    });
  }

  function createModuleNavigationSearch(nav) {
    var controls = createEl('div', 'spx-module-nav-controls');
    var search = createEl('input', 'spx-module-nav-search');
    search.type = 'search';
    search.placeholder = '搜索导航';
    search.setAttribute('aria-label', '搜索导航');
    search.addEventListener('input', function handleModuleNavigationSearchInput() {
      filterModuleNavigation(nav, search.value);
    });
    controls.appendChild(search);
    return controls;
  }

  function getModuleNavigationHost() {
    return qs('#content') || qs('#main') || qs('#wrapA');
  }

  function createModuleNavigationNode(config) {
    return {
      key: getNavigationItemKey(config.section, config.label, config.href || config.parentLabel || ''),
      label: config.label,
      config: config,
      children: [],
      childKeys: {},
    };
  }

  function dedupeModuleNavigationTree(groups) {
    var topLevelLabels = {};
    var childLabels = {};
    (groups || []).forEach(function collectTopLevelLabels(group) {
      (group.nodes || []).forEach(function collectNodeLabel(node) {
        var label = normalizeNavigationLabel(node && node.label).toLowerCase();
        if (label) topLevelLabels[label] = true;
      });
    });
    (groups || []).forEach(function pruneGroupChildren(group) {
      (group.nodes || []).forEach(function pruneNodeChildren(node) {
        node.children = (node.children || []).filter(function keepUniqueChild(child) {
          var label = normalizeNavigationLabel(child && child.label).toLowerCase();
          if (!label || topLevelLabels[label] || childLabels[label]) return false;
          childLabels[label] = true;
          return true;
        });
      });
    });
    return groups;
  }

  function buildModuleNavigationTree(configs) {
    var groups = [];
    var groupMap = {};
    (configs || []).forEach(function appendTreeConfig(config) {
      var section = config.section || '导航';
      if (!groupMap[section]) {
        groupMap[section] = {
          label: section,
          nodes: [],
          nodeMap: {},
          parentMap: {},
        };
        groups.push(groupMap[section]);
      }
      var group = groupMap[section];
      var parentLabel = normalizeNavigationLabel(config.parentLabel);
      var label = normalizeNavigationLabel(config.label);
      var labelKey = label.toLowerCase();
      if (parentLabel && parentLabel !== label) {
        var parentMapKey = parentLabel.toLowerCase();
        var parentKey = 'parent|' + parentMapKey;
        var parentNode = group.parentMap[parentMapKey] || group.nodeMap[parentKey];
        if (!parentNode) {
          parentNode = {
            key: parentKey,
            label: parentLabel,
            config: null,
            children: [],
            childKeys: {},
          };
          group.nodeMap[parentKey] = parentNode;
          group.parentMap[parentMapKey] = parentNode;
          group.nodes.push(parentNode);
        }
        var childKey = label.toLowerCase();
        if (parentNode.childKeys[childKey] === undefined) {
          parentNode.childKeys[childKey] = parentNode.children.length;
          parentNode.children.push(createModuleNavigationNode(config));
        } else {
          var childIndex = parentNode.childKeys[childKey];
          parentNode.children[childIndex] = createModuleNavigationNode(
            Object.assign({}, parentNode.children[childIndex].config || {}, config)
          );
        }
        return;
      }

      var key = getNavigationItemKey(section, label, config.href || label);
      var existingParentNode = group.parentMap[labelKey];
      if (existingParentNode) {
        existingParentNode.config = Object.assign({}, existingParentNode.config || {}, config);
        existingParentNode.label = label;
        group.nodeMap[key] = existingParentNode;
        return;
      }
      if (group.nodeMap[key]) {
        group.nodeMap[key].config = Object.assign({}, group.nodeMap[key].config || {}, config);
        if (!group.parentMap[labelKey]) group.parentMap[labelKey] = group.nodeMap[key];
        return;
      }
      group.nodeMap[key] = createModuleNavigationNode(config);
      group.parentMap[labelKey] = group.nodeMap[key];
      group.nodes.push(group.nodeMap[key]);
    });
    return dedupeModuleNavigationTree(groups);
  }

  function appendModuleNavigationNode(nav, parent, node, hasActive, state) {
    var wrapper = createEl('div', 'spx-module-nav-node');
    var active = !!(node.config && node.config.active);
    if (node.config) {
      appendModuleNavigationItem(wrapper, node.config, active || (!hasActive && state.index === 0), node.children.length ? 2 : 2);
      state.index += 1;
    } else {
      var parentTitle = createEl('div', 'spx-module-nav-parent-title', node.label);
      parentTitle.dataset.spxModuleNavText = compactText(node.label);
      wrapper.appendChild(parentTitle);
    }
    if (node.children.length) {
      var children = createEl('div', 'spx-module-nav-children');
      node.children.forEach(function appendChildNavigation(childNode) {
        appendModuleNavigationItem(children, childNode.config, !!childNode.config.active, 3);
        state.index += 1;
      });
      wrapper.appendChild(children);
    }
    parent.appendChild(wrapper);
  }

  function setModuleNavigationGroupCollapsed(groupNode, button, label, collapsed) {
    if (!groupNode || !button) return;
    groupNode.classList.toggle('spx-module-nav-collapsed', !!collapsed);
    button.setAttribute('aria-expanded', String(!collapsed));
    button.title = (collapsed ? '展开 ' : '折叠 ') + label;
  }

  function appendModuleNavigationSection(groupNode, label, collapseState) {
    var button = createEl('button', 'spx-module-nav-section', label);
    var key = getModuleNavigationGroupKey(label);
    var collapsed = isModuleNavigationGroupCollapsed(collapseState, label);
    button.type = 'button';
    button.dataset.spxModuleNavGroup = key;
    setModuleNavigationGroupCollapsed(groupNode, button, label, collapsed);
    button.addEventListener('click', function toggleModuleNavigationGroup() {
      var nextCollapsed = !isModuleNavigationGroupCollapsed(collapseState, label);
      if (nextCollapsed) {
        collapseState[key] = true;
      } else {
        delete collapseState[key];
      }
      saveNavigationCollapseState(collapseState);
      setModuleNavigationGroupCollapsed(groupNode, button, label, nextCollapsed);
    });
    groupNode.appendChild(button);
  }

  function mountModuleNavigation(title, configs) {
    restoreModuleNavigation();
    var content = getModuleNavigationHost();
    var visibleConfigs = (configs || []).filter(function keepModuleConfig(config) {
      return config && config.label && (
        config.count ||
        config.count === 0 ||
        config.countText !== undefined ||
        config.alwaysShow ||
        config.href ||
        config.onClick ||
        config.target
      );
    });
    if (!content || !visibleConfigs.length) return null;

    var nav = createEl('nav', 'spx-module-nav');
    nav.id = 'spx-module-nav';
    nav.setAttribute('aria-label', title || '导航中心');
    nav.appendChild(createEl('div', 'spx-module-nav-title', title || '导航中心'));
    nav.appendChild(createModuleNavigationSearch(nav));
    var hasActive = visibleConfigs.some(function hasActiveConfig(config) {
      return !!config.active;
    });
    var state = { index: 0 };
    var collapseState = loadNavigationCollapseState();
    buildModuleNavigationTree(visibleConfigs).forEach(function appendGroup(group) {
      var groupNode = createEl('div', 'spx-module-nav-group');
      appendModuleNavigationSection(groupNode, group.label, collapseState);
      group.nodes.forEach(function appendNode(node) {
        appendModuleNavigationNode(nav, groupNode, node, hasActive, state);
      });
      nav.appendChild(groupNode);
    });
    var body = createEl('div', 'spx-module-body');
    while (content.firstChild) {
      body.appendChild(content.firstChild);
    }
    content.classList.add('spx-module-nav-host');
    content.appendChild(nav);
    content.appendChild(body);
    document.documentElement.classList.add('spx-module-nav-ready');
    return nav;
  }

  function queueModuleNavigationConfigs(configs) {
    var nextConfigs = configs || [];
    pendingModuleNavigationConfigs = pendingModuleNavigationConfigs.concat(nextConfigs);
    rememberModuleNavigationConfigs(nextConfigs);
  }

  function getHomeNavigationBaseUrl(origin) {
    return String(origin || location.origin || '') + '/index.php';
  }

  function getSeedModuleNavigationConfigs(origin) {
    var homeUrl = getHomeNavigationBaseUrl(origin);
    return [
      { label: '漫区特设', href: homeUrl + '#spx-module-target-home-1', order: 10 },
      { label: '蜜柑计划', href: homeUrl + '#spx-module-target-home-2', order: 20 },
      { label: '综合交流', href: homeUrl + '#spx-module-target-home-3', order: 30 },
      { label: '人民囧府', href: homeUrl + '#spx-module-target-home-4', order: 40 },
    ].map(function mapSeedNavigation(item) {
      return {
        section: '子栏目',
        label: item.label,
        href: item.href,
        title: item.label,
        alwaysShow: true,
        navigationOnly: true,
        order: item.order,
      };
    });
  }

  function rememberModuleNavigationConfigs(configs) {
    var source = configs || [];
    if (!source.length) return false;
    var pool = loadNavigationPool();
    var changed = false;
    var now = Date.now();
    var baseUrl = typeof location !== 'undefined' ? location.href : '';
    var maxOrder = Object.keys(pool).reduce(function getMaxOrder(max, key) {
      return Math.max(max, Number(pool[key] && pool[key].order) || 0);
    }, 0);
    source.forEach(function rememberConfig(config, index) {
      var normalized = normalizePersistentNavigationConfig(config, baseUrl, maxOrder + index + 1);
      if (!normalized) return;
      if (/^(当前位置|主题导航)$/.test(normalized.section)) return;
      normalized.updatedAt = now;
      var key = getNavigationItemKey(normalized.section, normalized.label, normalized.href);
      var existing = pool[key];
      if (existing) {
        normalized.order = existing.order || normalized.order;
      }
      if (!existing || JSON.stringify(existing) !== JSON.stringify(normalized)) {
        pool[key] = normalized;
        changed = true;
      }
    });
    if (changed) saveNavigationPool(pool);
    return changed;
  }

  function getPersistentModuleNavigationConfigs() {
    var sectionOrder = {
      子栏目: 1,
      版块导航: 2,
    };
    var pool = loadNavigationPool();
    var poolItems = Object.keys(pool).map(function mapPoolItem(key) {
      return pool[key];
    });
    return getSeedModuleNavigationConfigs(location.origin)
      .concat(poolItems)
      .map(function mapPersistentNavigation(item) {
        return {
          section: item.section || '子栏目',
          parentLabel: item.parentLabel || '',
          label: item.label,
          href: item.href,
          title: item.title || item.label,
          className: item.className || 'spx-module-nav-persisted',
          active: isModuleNavigationConfigActive(item, location.href),
          alwaysShow: true,
          navigationOnly: true,
          order: Number(item.order) || 0,
          updatedAt: Number(item.updatedAt) || 0,
        };
      })
      .sort(function sortPersistentNavigation(left, right) {
        var leftSection = sectionOrder[left.section] || 99;
        var rightSection = sectionOrder[right.section] || 99;
        if (leftSection !== rightSection) return leftSection - rightSection;
        return (left.order || 0) - (right.order || 0) || String(left.label).localeCompare(String(right.label));
      });
  }

  function mergeModuleNavigationConfigs(configs) {
    var result = [];
    var positions = {};
    (configs || []).forEach(function mergeNavigationConfig(config) {
      if (!config || !config.label) return;
      var label = normalizeNavigationLabel(config.label);
      if (!shouldKeepNavigationLabel(label, { allowForumViewSwitch: !!config.allowForumViewSwitch })) return;
      var href = normalizeNavigationHref(config.href || '', location.href);
      var section = config.section || '';
      var parentLabel = normalizeNavigationLabel(config.parentLabel || '');
      var key = [section, parentLabel, label, (href || '').replace(/#.*$/, '') || config.target || config.onClick || result.length].join('|');
      var nextConfig = Object.assign({}, config, {
        parentLabel: parentLabel,
        label: label,
        href: href || config.href,
        active: !!config.active || isModuleNavigationConfigActive(Object.assign({}, config, { href: href || config.href }), location.href),
        alwaysShow: config.alwaysShow !== false,
        navigationOnly: config.navigationOnly !== false,
      });
      if (positions[key] !== undefined) {
        result[positions[key]] = Object.assign({}, result[positions[key]], nextConfig);
        return;
      }
      positions[key] = result.length;
      result.push(nextConfig);
    });
    return result;
  }

  function getPinnedModuleNavigationClassName(config) {
    var classNames = String(config && config.className || '').split(/\s+/).filter(Boolean);
    if (classNames.indexOf('spx-module-nav-pinned') === -1) classNames.push('spx-module-nav-pinned');
    return classNames.join(' ');
  }

  function withPinnedModuleNavigationConfigs(configs) {
    var pins = loadNavigationPins();
    var pinnedConfigs = [];
    var sourceConfigs = (configs || []).map(function markPinnedNavigationConfig(config) {
      var pinKey = getModuleNavigationConfigPinKey(config);
      var pinned = !!(pinKey && pins[pinKey]);
      var nextConfig = Object.assign({}, config, {
        pinKey: pinKey,
        pinned: pinned,
      });
      if (pinned && nextConfig.href) {
        pinnedConfigs.push(Object.assign({}, nextConfig, {
          section: '置顶导航',
          parentLabel: '',
          title: (nextConfig.title || nextConfig.label) + ' · 置顶',
          className: getPinnedModuleNavigationClassName(nextConfig),
          active: !!nextConfig.active,
          alwaysShow: true,
          navigationOnly: true,
        }));
      }
      return nextConfig;
    });
    return pinnedConfigs.concat(sourceConfigs);
  }

  function collectHomeNavigationConfigsFromDocument(root, pageUrl) {
    var baseUrl = normalizeNavigationHref(pageUrl || getHomeNavigationBaseUrl(location.origin), location.href);
    return qsa('#content .t[id^="t_"]', root).map(function mapHomeDocumentModule(module, index) {
      var header = qs('.h', module) || qs('h2', module);
      var moduleId = module.id || 'spx-module-target-home-' + (index + 1);
      return {
        section: '子栏目',
        label: getCompactNodeText(header, '模块 ' + (index + 1)).slice(0, 18),
        href: baseUrl.replace(/#.*$/, '') + '#' + moduleId,
        title: getCompactNodeText(header, '模块 ' + (index + 1)).slice(0, 18),
        alwaysShow: true,
        navigationOnly: true,
      };
    });
  }

  function collectForumNavigationConfigsFromDocument(root, pageUrl) {
    var currentFid = getCurrentForumId(pageUrl || location.href);
    var seen = {};
    var configs = [];
    qsa('a[href*="thread.php?fid"]', root).forEach(function collectFetchedForumLink(link) {
      if (!isUsefulForumNavigationLink(link, currentFid)) return;
      var href = normalizeNavigationHref(link.getAttribute('href') || link.href, pageUrl || location.href);
      var fid = getCurrentForumId(href);
      var label = normalizeNavigationLabel(link.textContent);
      var parentLabel = getForumNavigationParentLabel(link);
      var key = [parentLabel, fid || href].join('|');
      if (!label || !href || seen[key]) return;
      seen[key] = true;
      configs.push({
        section: '子栏目',
        parentLabel: parentLabel,
        label: label,
        href: href,
        title: label,
        alwaysShow: true,
        navigationOnly: true,
      });
    });
    return configs;
  }

  function collectRemoteNavigationConfigsFromHtml(html, pageUrl) {
    if (!html || typeof DOMParser === 'undefined') return [];
    var doc = new DOMParser().parseFromString(html, 'text/html');
    return collectHomeNavigationConfigsFromDocument(doc, pageUrl)
      .concat(collectForumNavigationConfigsFromDocument(doc, pageUrl));
  }

  function getNavigationDiscoveryUrls() {
    var seen = {};
    function append(url) {
      var href = normalizeNavigationHref(url, location.href).replace(/#.*$/, '');
      try {
        if (!href || seen[href] || new URL(href, location.href).origin !== location.origin) return;
      } catch (error) {
        return;
      }
      seen[href] = true;
    }
    append(getHomeNavigationBaseUrl(location.origin));
    if (detectPageType(location.href) === 'forum') append(location.href);
    qsa('.bdbA a[href*="thread.php?fid"], #breadcrumbs a[href*="thread.php?fid"], .crumbs-item[href*="thread.php?fid"], .crumbs-item a[href*="thread.php?fid"]').forEach(function collectCrumbDiscoveryUrl(link) {
      append(link.href || link.getAttribute('href'));
    });
    return Object.keys(seen).slice(0, 4);
  }

  function remountGlobalModuleNavigation() {
    mountModuleNavigation('导航中心', getAllModuleNavigationConfigs());
  }

  function scheduleNavigationPoolRefresh(settings) {
    if (testMode || typeof window === 'undefined' || typeof window.fetch !== 'function') return;
    var urls = getNavigationDiscoveryUrls();
    if (isNetworkFriendlyMode(settings)) {
      urls = urls.slice(0, 2);
      if (!shouldRefreshNavigationPool(urls)) return;
    }
    var refreshKey = urls.join('|');
    if (!urls.length || document.documentElement.dataset.spxNavigationRefreshKey === refreshKey) return;
    document.documentElement.dataset.spxNavigationRefreshKey = refreshKey;
    var navigationPolicy = {
      mode: 'background',
      label: '导航中心后台刷新',
      networkFriendly: isNetworkFriendlyMode(settings),
    };
    var navigationRateLimited = false;
    urls.reduce(function chainNavigationFetch(chain, url) {
      return chain.then(function fetchNavigationUrl(results) {
        return requestWithPolicy(url, {
          credentials: 'include',
          cache: 'force-cache',
        }, navigationPolicy)
          .then(function readNavigationResponse(response) {
            if (!response.ok) return '';
            return readScriptResponseText(response, navigationPolicy);
          })
          .then(function collectFetchedNavigation(html) {
            results.push(collectRemoteNavigationConfigsFromHtml(html, url));
            return results;
          })
          .catch(function ignoreNavigationFetchFailure(error) {
            if (error && error.spxRateLimited) navigationRateLimited = true;
            results.push([]);
            return results;
          });
      });
    }, Promise.resolve([])).then(function applyFetchedNavigation(results) {
      var configs = [];
      results.forEach(function concatNavigationItems(items) {
        configs = configs.concat(items || []);
      });
      if (!navigationRateLimited) rememberNavigationPoolRefresh(urls);
      if (rememberModuleNavigationConfigs(configs)) remountGlobalModuleNavigation();
    });
  }

  function createHomeModuleNavigation(modules) {
    var configs = (modules || []).map(function mapHomeModule(module, index) {
      var header = qs('.h', module) || qs('h2', module);
      var id = ensureModuleTargetId(module, 'home', index);
      var label = getCompactNodeText(header, '模块 ' + (index + 1)).slice(0, 18);
      return {
        section: '子栏目',
        label: label,
        href: getHomeNavigationBaseUrl(location.origin) + '#' + id,
        title: label,
        target: module,
        alwaysShow: true,
        navigationOnly: true,
      };
    });
    queueModuleNavigationConfigs(configs);
  }

  function getForumBoardTitle() {
    var currentCrumb = qs('.bdbA .crumbs-item.current');
    var title = getCompactNodeText(currentCrumb, '').replace(/\[.*?\]/g, '').trim();
    if (title) return title.slice(0, 24);
    var documentTitle = (document.title || '').replace(/\s*[-|].*$/, '').trim();
    return documentTitle || '全部主题';
  }

  function createForumSectionTitle(items, target) {
    var table = target && target.closest ? target.closest('.spx-thread-list-table') || target : target;
    if (!table || !table.parentNode) return null;
    qsa('.spx-forum-section-title').forEach(function removeOldTitle(node) {
      node.remove();
    });
    var title = createEl('div', 'spx-forum-section-title');
    var main = createEl('div', 'spx-forum-section-main');
    var galleryConfig = collectForumViewNavigationConfigs(table ? [table, document] : [document], getCurrentForumId(location.href))[0];
    main.appendChild(createEl('strong', '', getForumBoardTitle()));
    main.appendChild(createEl('span', '', '当前定位 · ' + (items ? items.length : 0) + ' 个帖子'));
    title.appendChild(main);
    if (galleryConfig && galleryConfig.href) {
      var galleryLink = createEl('a', 'spx-forum-gallery-link', galleryConfig.label || '图墙模式');
      galleryLink.href = galleryConfig.href;
      if (galleryConfig.label === '列表模式') galleryLink.classList.add('spx-forum-list-link');
      galleryLink.title = galleryConfig.title || (galleryConfig.label === '列表模式' ? '切换回列表模式' : '点击进入图墙模式');
      title.appendChild(galleryLink);
    }
    table.parentNode.insertBefore(title, table);
    return title;
  }

  function compactForumPrelude(threadTable) {
    var table = threadTable && threadTable.closest ? threadTable.closest('.spx-thread-list-table') || threadTable : threadTable;
    var content = qs('#content') || qs('#main');
    if (!content || !table) return;
    var node = content.firstElementChild;
    while (node && node !== table) {
      var next = node.nextElementSibling;
      if (
        node.id !== 'spx-forum-tools' &&
        !node.classList.contains('spx-forum-section-title') &&
        !node.classList.contains('spx-module-nav')
      ) {
        node.classList.add('spx-forum-prelude-hidden');
      }
      node = next;
    }
  }

  function getForumNavigationScopeNodes(threadTable) {
    var content = qs('#content') || qs('#main');
    if (!content) return [];
    if (!threadTable) return [content];
    var nodes = [];
    var node = content.firstElementChild;
    while (node && node !== threadTable) {
      if (
        node.id !== 'spx-forum-tools' &&
        !node.classList.contains('spx-forum-section-title') &&
        !node.classList.contains('spx-module-nav')
      ) {
        nodes.push(node);
      }
      node = node.nextElementSibling;
    }
    return nodes;
  }

  function normalizeNavigationLabel(text) {
    return String(text || '')
      .replace(/\s+/g, ' ')
      .replace(/\s*\(\d+\)\s*$/, '')
      .trim();
  }

  function getCommonForumNavigationLabelByFid() {
    return {
      9: '茶馆',
      48: '询问&求物',
      13: '免空资源区',
      128: 'GALGAME汉化区',
      208: 'AI交流',
    };
  }

  function isGlobalSiteNavigationTarget(label, href) {
    var normalized = normalizeNavigationLabel(label);
    var fid = getCurrentForumId(href);
    var fidMap = getCommonForumNavigationLabelByFid();
    if (fid && fidMap[fid] && normalizeNavigationLabel(fidMap[fid]) === normalized) return true;
    return normalized === '最新帖子' && /search2?\.php/.test(String(href || '')) && /newatc/.test(String(href || ''));
  }

  function getHomeNavigationParentLabel(link) {
    var module = link && link.closest ? link.closest('.spx-home-module,.t[id^="t_"]') : null;
    if (!module) return '';
    var header = qs('.h', module) || qs('h2', module);
    return normalizeNavigationLabel(getCompactNodeText(header, '')).slice(0, 32);
  }

  function getForumNavigationParentLabel(link) {
    var ownLabel = normalizeNavigationLabel(link && link.textContent);
    var row = link && link.closest ? link.closest('tr') : null;
    var isRowTitle = false;
    var selectors = [
      'a[id^="a_ajax_"]',
      '[id^="fn_"] h3 a',
      '[id^="fn_"] > a',
      '[id^="fn_"] a[href*="thread.php?fid"]',
    ];
    if (row) {
      for (var index = 0; index < selectors.length; index += 1) {
        var candidate = qs(selectors[index], row);
        var label = normalizeNavigationLabel(candidate && candidate.textContent);
        if (label && label === ownLabel) isRowTitle = true;
        if (label && label !== ownLabel) return label.slice(0, 32);
      }
    }
    if (isRowTitle) return '';
    var homeParent = getHomeNavigationParentLabel(link);
    if (homeParent && homeParent !== ownLabel) return homeParent;
    return '';
  }

  function isUsefulForumNavigationLink(link, currentFid) {
    if (!link) return false;
    var href = String(link.getAttribute('href') || link.href || '');
    var label = normalizeNavigationLabel(link.textContent);
    if (!href || !/thread\.php\?fid[=-]?\d+/.test(href)) return false;
    if (/skinco|page-\d+|search|rss/i.test(href)) return false;
    if (!shouldKeepNavigationLabel(label) || label.length > 28) return false;
    if (/^(折叠|展开|版块|最后发表|主题\/文章|版主|上一页|下一页|首页)$/.test(label)) return false;
    if (isGlobalSiteNavigationTarget(label, href)) return false;
    return getCurrentForumId(href) !== String(currentFid || '');
  }

  function getForumViewNavigationLabel(label) {
    var normalized = normalizeNavigationLabel(label);
    return /图墙模式/.test(normalized) ? '图墙模式' : normalized;
  }

  function appendForumViewNavigationConfig(configs, seen, label, href, title, active) {
    var normalizedLabel = normalizeNavigationLabel(label);
    var normalizedHref = normalizeNavigationHref(href, location.href);
    var key = normalizedHref.replace(/#.*$/, '');
    if (!normalizedLabel || !normalizedHref || seen[key]) return;
    seen[key] = true;
    configs.push({
      section: '版块导航',
      parentLabel: getForumBoardTitle(),
      label: normalizedLabel,
      href: normalizedHref,
      title: normalizeNavigationLabel(title) || normalizedLabel,
      className: 'spx-module-nav-forum-view',
      active: !!active,
      alwaysShow: true,
      navigationOnly: true,
      transient: true,
      allowForumViewSwitch: true,
    });
  }

  function isForumGalleryModeLink(link, currentFid) {
    if (!link) return false;
    var href = String(link.getAttribute('href') || link.href || '');
    var label = normalizeNavigationLabel(link.textContent);
    var fid = getCurrentForumId(href);
    if (!href || !isForumGalleryModeUrl(href) || !/图墙模式/.test(label)) return false;
    return !currentFid || !fid || fid === String(currentFid);
  }

  function collectForumViewNavigationConfigs(scopeNodes, currentFid) {
    var configs = [];
    var seen = {};
    if (isForumGalleryModeUrl(location.href)) {
      appendForumViewNavigationConfig(configs, seen, '列表模式', getForumListModeUrl(location.href), '切换回列表模式', false);
    }
    (scopeNodes || []).forEach(function collectForumViewLinks(scope) {
      qsa('a[href*="thread_new.php"]', scope).forEach(function appendForumViewLink(link) {
        if (!isForumGalleryModeLink(link, currentFid)) return;
        var href = normalizeNavigationHref(link.getAttribute('href') || link.href, location.href);
        var label = getForumViewNavigationLabel(link.textContent);
        appendForumViewNavigationConfig(configs, seen, label, href, normalizeNavigationLabel(link.textContent) || label, isForumGalleryModeUrl(location.href));
      });
    });
    return configs;
  }

  function getCurrentForumViewNavigationConfigs() {
    if (detectPageType(location.href) !== 'forum') return [];
    return collectForumViewNavigationConfigs([document], getCurrentForumId(location.href));
  }

  function createForumModuleNavigation(items) {
    if (!items || !items.length) return;
    var listRoot = items[0].row && items[0].row.closest ? items[0].row.closest('.spx-thread-list-table') || items[0].row.closest('#wall') : null;
    var currentFid = getCurrentForumId(location.href);
    var seen = {};
    var configs = [];
    var scopeNodes = getForumNavigationScopeNodes(listRoot);
    scopeNodes.forEach(function collectForumNavLinks(scope) {
      qsa('a[href*="thread.php?fid"]', scope).forEach(function appendForumNavLink(link) {
        if (!isUsefulForumNavigationLink(link, currentFid)) return;
        var href = link.href || link.getAttribute('href');
        var fid = getCurrentForumId(href);
        var label = normalizeNavigationLabel(link.textContent);
        var parentLabel = getForumNavigationParentLabel(link);
        var key = [parentLabel, fid || href].join('|');
        if (seen[key]) return;
        seen[key] = true;
        configs.push({
          section: '子栏目',
          parentLabel: parentLabel,
          label: label,
          href: href,
          title: label,
          alwaysShow: true,
          navigationOnly: true,
        });
      });
    });
    configs = configs.concat(collectForumViewNavigationConfigs(scopeNodes.concat(listRoot ? [listRoot] : []), currentFid));
    queueModuleNavigationConfigs(configs);
  }

  function getCommonForumNavigationItems(origin) {
    var base = String(origin || location.origin || '');
    return [
      { label: '首页', href: base + '/index.php' },
      { label: '茶馆', fid: '9' },
      { label: '询问&求物', fid: '48' },
      { label: '免空资源区', fid: '13' },
      { label: 'GALGAME汉化区', fid: '128' },
      { label: 'AI交流', fid: '208' },
      { label: '最新帖子', href: base + '/search2.php?orderway-postdate-asc-desc-newatc-1.html' },
    ].map(function buildForumNavigationItem(item) {
      if (item.href) return item;
      return {
        label: item.label,
        fid: item.fid,
        href: base + '/thread.php?fid-' + item.fid + '.html',
      };
    });
  }

  function getCurrentForumId(url) {
    var text = String(url || '');
    var match = text.match(/fid[=-](\d+)/) || text.match(/fid-(\d+)/);
    return match ? match[1] : '';
  }

  function getNavigationComparableUrl(href, baseUrl, includeHash) {
    if (!href || /^javascript:/i.test(String(href))) return '';
    try {
      var parsed = new URL(String(href), baseUrl || location.href);
      return parsed.origin + parsed.pathname + parsed.search + (includeHash ? parsed.hash : '');
    } catch (error) {
      return '';
    }
  }

  function isModuleNavigationConfigActive(config, url) {
    var href = config && config.href;
    if (!href) return false;
    var currentUrl = String(url || (typeof location !== 'undefined' ? location.href : ''));
    var includeHash = String(href).indexOf('#') !== -1;
    var targetKey = getNavigationComparableUrl(href, currentUrl, includeHash);
    var currentKey = getNavigationComparableUrl(currentUrl, currentUrl, includeHash);
    if (targetKey && currentKey && targetKey === currentKey) return true;
    if (includeHash) return false;
    var currentFid = getCurrentForumId(currentUrl);
    var targetFid = getCurrentForumId(href);
    if (isForumGalleryModeUrl(currentUrl) || isForumGalleryModeUrl(href)) {
      return !!(currentFid && targetFid && currentFid === targetFid && isForumGalleryModeUrl(currentUrl) === isForumGalleryModeUrl(href));
    }
    return !!(currentFid && targetFid && currentFid === targetFid);
  }

  function isCommonNavigationActive(item, url) {
    var text = String(url || '');
    if (item.fid) return getCurrentForumId(text) === String(item.fid);
    return item.label === '最新帖子' && isModuleNavigationConfigActive(item, url);
  }

  function getGlobalSiteNavigationConfigs(url) {
    return getCommonForumNavigationItems(location.origin).map(function mapSiteNavigation(item) {
      return {
        section: '站点导航',
        label: item.label,
        href: item.href,
        title: item.label,
        className: 'spx-module-nav-site',
        active: isCommonNavigationActive(item, url),
        alwaysShow: true,
        navigationOnly: true,
      };
    });
  }

  function getReadPageNavigationConfigs() {
    if (detectPageType(location.href) !== 'read') return [];
    var configs = [];
    var seen = {};
    function appendConfig(section, label, href, options) {
      var normalized = normalizeNavigationLabel(label);
      var url = String(href || '');
      if (!normalized || !url || /^javascript:/i.test(url)) return;
      var key = section + '|' + normalized + '|' + url.replace(/#.*$/, '');
      if (seen[key]) return;
      seen[key] = true;
      var config = {
        section: section,
        label: normalized,
        href: url,
        title: normalized,
        alwaysShow: true,
        navigationOnly: true,
      };
      if (options && options.active) config.active = true;
      if (options && options.className) config.className = options.className;
      configs.push(config);
    }

    var crumbSeen = {};
    var crumbLinks = [];
    qsa('.bdbA a[href*="thread.php?fid"], #breadcrumbs a[href*="thread.php?fid"], .crumbs-item[href*="thread.php?fid"], .crumbs-item a[href*="thread.php?fid"], .breadcrumbs a[href*="thread.php?fid"], #breadCrumb a[href*="thread.php?fid"]').forEach(function collectReadCrumb(link) {
      var label = normalizeNavigationLabel(link.textContent);
      var href = link.href || link.getAttribute('href');
      var fid = getCurrentForumId(href);
      var key = fid || href;
      if (!label || !href || !key || crumbSeen[key]) return;
      if (/^(南\+ South Plus|首页)$/.test(label)) return;
      crumbSeen[key] = true;
      crumbLinks.push({ label: label, href: href });
    });
    crumbLinks.forEach(function appendReadCrumb(item, index) {
      appendConfig('当前位置', item.label, item.href, {
        active: index === crumbLinks.length - 1,
        className: 'spx-module-nav-current',
      });
    });
    rememberModuleNavigationConfigs(crumbLinks.map(function mapReadCrumbToPersistentNav(item) {
      return {
        section: '子栏目',
        label: item.label,
        href: item.href,
        title: item.label,
        alwaysShow: true,
        navigationOnly: true,
      };
    }));

    var topicSeen = {};
    qsa('a[href]').forEach(function collectTopicNavigation(link) {
      var rawLabel = normalizeNavigationLabel(link.textContent);
      var label = '';
      if (/上一主题/.test(rawLabel)) label = '上一主题';
      if (/下一主题/.test(rawLabel)) label = '下一主题';
      if (!label || topicSeen[label]) return;
      var href = link.href || link.getAttribute('href');
      if (!href || /^javascript:/i.test(href)) return;
      topicSeen[label] = true;
      appendConfig('主题导航', label, href, {
        className: 'spx-module-nav-topic',
      });
    });
    return configs;
  }

  function getSearchPageNavigationConfigs() {
    if (detectPageType(location.href) !== 'search') return [];
    var isLatest = /search2?\.php/.test(String(location.href || '')) && /newatc/.test(String(location.href || ''));
    return [{
      section: '站点导航',
      label: '搜索',
      href: location.origin + '/search.php',
      title: '搜索',
      className: 'spx-module-nav-search-page',
      active: !isLatest,
      alwaysShow: true,
      navigationOnly: true,
    }];
  }

  function getProfilePageNavigationConfigs() {
    if (!shouldUseProfilePage(location.href)) return [];
    var uid = extractAccountUserId(document, location.href);
    if (!uid) return [];
    var activeKey = getAccountActiveKey(location.href);
    return getAccountTabItems(uid, location.origin).map(function mapProfileListNav(item) {
      var label = item.key === 'topic' ? '我的主题' : (item.key === 'post' ? '我的回复' : item.label);
      return {
        section: '个人导航',
        label: label,
        href: item.href,
        title: label,
        className: 'spx-module-nav-profile',
        active: item.key === activeKey,
        alwaysShow: true,
        navigationOnly: true,
      };
    });
  }

  function getTaskPageNavigationConfigs() {
    if (!shouldUseTaskPage(location.href)) return [];
    return [{
      section: '站点导航',
      label: '社区论坛任务',
      href: location.origin + '/plugin.php?H_name-tasks.html',
      title: '社区论坛任务',
      className: 'spx-module-nav-task',
      active: true,
      alwaysShow: true,
      navigationOnly: true,
    }];
  }

  function getAllModuleNavigationConfigs() {
    return withPinnedModuleNavigationConfigs(mergeModuleNavigationConfigs(
      getGlobalSiteNavigationConfigs(location.href)
        .concat(getPersistentModuleNavigationConfigs())
        .concat(getSearchPageNavigationConfigs())
        .concat(getProfilePageNavigationConfigs())
        .concat(getTaskPageNavigationConfigs())
        .concat(getCurrentForumViewNavigationConfigs())
        .concat(getReadPageNavigationConfigs())
        .concat(pendingModuleNavigationConfigs)
    ));
  }

  function createGlobalModuleNavigation(settings, state) {
    if (!shouldUseModuleNavigation(settings, location.href, document)) return;
    mountModuleNavigation('导航中心', getAllModuleNavigationConfigs());
    scheduleNavigationPoolRefresh(settings);
  }

  function positionPreviewPanel(panel, event) {
    var margin = 14;
    var left = Math.min((event && event.clientX ? event.clientX + 16 : 24), window.innerWidth - panel.offsetWidth - margin);
    var top = Math.min((event && event.clientY ? event.clientY + 16 : 24), window.innerHeight - panel.offsetHeight - margin);
    panel.style.left = Math.max(margin, left) + 'px';
    panel.style.top = Math.max(margin, top) + 'px';
  }

  function removeThreadPreview() {
    var panel = qs('#spx-preview-popover');
    if (!panel) return;
    if (panel.spxPreviewImageTimer && typeof window !== 'undefined' && typeof window.clearTimeout === 'function') {
      window.clearTimeout(panel.spxPreviewImageTimer);
      panel.spxPreviewImageTimer = null;
    }
    panel.remove();
  }

  function getThreadPreviewUrl(info) {
    return String(info && info.titleLink && info.titleLink.href || info && info.url || '');
  }

  function getThreadPreviewMetaText(info, payload) {
    var status = payload && payload.status
      ? String(payload.status)
      : (payload && payload.cached ? '已缓存' : '悬停预览');
    return (info && info.author ? '作者:' + info.author + ' · ' : '') + status;
  }

  function getThreadPreviewImageUrls(payload) {
    return (payload && payload.images || []).slice(0, THREAD_PREVIEW_IMAGE_LIMIT);
  }

  function getThreadPreviewImageSummary(payload) {
    var count = payload && payload.images ? payload.images.length : 0;
    if (!count) return '没有可预览图片';
    if (count > THREAD_PREVIEW_IMAGE_LIMIT) return '已显示前 ' + THREAD_PREVIEW_IMAGE_LIMIT + ' 张,打开帖子查看其余 ' + (count - THREAD_PREVIEW_IMAGE_LIMIT) + ' 张';
    return '共 ' + count + ' 张预览图';
  }

  function getThreadPreviewHoverDelay(settings) {
    return isNetworkFriendlyMode(settings) ? THREAD_PREVIEW_HOVER_DELAY : THREAD_PREVIEW_FAST_HOVER_DELAY;
  }

  function loadThreadPreviewPanelImage(image) {
    if (!image || image.src) return false;
    var src = image.dataset && image.dataset.spxPreviewLazySrc;
    if (!src) return false;
    preparePreviewImageReveal(image);
    image.src = src;
    delete image.dataset.spxPreviewLazySrc;
    if (image.complete && image.naturalWidth) markPreviewImageLoaded(image);
    return true;
  }

  function loadThreadPreviewPanelImages(panel, batchSize) {
    if (!panel || !panel.isConnected) return 0;
    var limit = Math.max(1, Number(batchSize) || THREAD_PREVIEW_IMAGE_BATCH_SIZE);
    var loaded = 0;
    qsa('img[data-spx-preview-lazy-src]', panel).some(function loadPreviewImage(image) {
      if (loaded >= limit) return true;
      if (loadThreadPreviewPanelImage(image)) loaded += 1;
      return false;
    });
    return loaded;
  }

  function scheduleThreadPreviewPanelImages(panel) {
    if (!panel || typeof window === 'undefined' || typeof window.setTimeout !== 'function') return;
    var loadNextBatch = function loadNextThreadPreviewImageBatch() {
      if (!panel.isConnected) return;
      panel.spxPreviewImageTimer = null;
      loadThreadPreviewPanelImages(panel, THREAD_PREVIEW_IMAGE_BATCH_SIZE);
      if (qs('img[data-spx-preview-lazy-src]', panel)) {
        panel.spxPreviewImageTimer = window.setTimeout(loadNextBatch, THREAD_PREVIEW_IMAGE_LOAD_DELAY);
      }
    };
    panel.spxPreviewImageTimer = window.setTimeout(loadNextBatch, THREAD_PREVIEW_IMAGE_LOAD_DELAY);
    panel.addEventListener('mouseenter', function loadVisibleThreadPreviewImages() {
      loadThreadPreviewPanelImages(panel, THREAD_PREVIEW_IMAGE_BATCH_SIZE);
    }, { once: true });
    panel.addEventListener('focusin', function loadFocusedThreadPreviewImages() {
      loadThreadPreviewPanelImages(panel, THREAD_PREVIEW_IMAGE_BATCH_SIZE);
    }, { once: true });
  }

  function saveThreadPreviewWatch(info, state) {
    if (!info || !info.id || !state) return false;
    state.watch = state.watch || {};
    if (state.watch[info.id]) return false;
    state.watch[info.id] = {
      title: info.title,
      url: getThreadPreviewUrl(info),
      savedAt: Date.now(),
    };
    saveMap(WATCH_KEY, state.watch);
    if (info.cell && info.titleLink && !qs('.spx-watch-badge', info.cell)) {
      info.titleLink.insertAdjacentElement('afterend', createWatchBadge(info.id));
    }
    refreshWatchCenter();
    return true;
  }

  function setPreviewActionText(button, text) {
    if (!button) return;
    var previous = button.dataset.spxPreviewOriginalText || button.textContent;
    button.dataset.spxPreviewOriginalText = previous;
    button.textContent = text;
    if (typeof window === 'undefined' || typeof window.setTimeout !== 'function') return;
    window.setTimeout(function restorePreviewActionText() {
      if ('isConnected' in button && !button.isConnected) return;
      button.textContent = button.dataset.spxPreviewOriginalText || previous;
      delete button.dataset.spxPreviewOriginalText;
    }, 1400);
  }

  function appendPreviewActions(panel, info, state, settings) {
    var url = getThreadPreviewUrl(info);
    if (!url) return;
    var actions = createEl('div', 'spx-preview-popover-actions');
    var openLink = createEl('a', '', '打开帖子');
    openLink.href = url;
    openLink.target = '_blank';
    openLink.rel = 'noopener noreferrer';
    actions.appendChild(openLink);

    var watchButton = createEl('button', '', state && state.watch && state.watch[info.id] ? '已稍后' : '加入稍后');
    watchButton.type = 'button';
    watchButton.disabled = !!(state && state.watch && state.watch[info.id]);
    watchButton.addEventListener('click', function addPreviewWatch(event) {
      event.preventDefault();
      event.stopPropagation();
      if (saveThreadPreviewWatch(info, state)) {
        watchButton.textContent = '已加入稍后';
        watchButton.disabled = true;
      } else {
        setPreviewActionText(watchButton, state ? '已稍后' : '无法保存');
      }
    });
    actions.appendChild(watchButton);

    var favoriteButton = createEl('button', '', isThreadFavoriteSeen(info && info.id) ? '已收藏' : '收藏');
    favoriteButton.type = 'button';
    favoriteButton.title = '收藏到站内收藏夹';
    favoriteButton.disabled = isThreadFavoriteSeen(info && info.id);
    favoriteButton.addEventListener('click', function addPreviewFavorite(event) {
      event.preventDefault();
      event.stopPropagation();
      runThreadFavoriteAction(info, settings, state, favoriteButton, '已收藏');
    });
    actions.appendChild(favoriteButton);

    var copyButton = createEl('button', '', '复制链接');
    copyButton.type = 'button';
    copyButton.addEventListener('click', function copyPreviewLink(event) {
      event.preventDefault();
      event.stopPropagation();
      copyTextToClipboard(url).then(
        function markCopied() { setPreviewActionText(copyButton, '已复制'); },
        function markCopyFailed() { setPreviewActionText(copyButton, '复制失败'); }
      );
    });
    actions.appendChild(copyButton);
    panel.appendChild(actions);
  }

  function renderPreviewPanel(info, payload, event, state, settings) {
    removeThreadPreview();
    var panel = createEl('div', 'spx-preview-popover');
    panel.id = 'spx-preview-popover';
    var title = createEl('h4', '', info.title || '帖子预览');
    var meta = createEl('div', 'spx-preview-meta', getThreadPreviewMetaText(info, payload));
    var text = createEl('div', 'spx-preview-text', payload.text || '未提取到文字预览');
    panel.appendChild(title);
    panel.appendChild(meta);
    appendPreviewActions(panel, info, state, settings);
    panel.appendChild(text);

    var previewImages = getThreadPreviewImageUrls(payload);
    if (previewImages.length) {
      var grid = createEl('div', 'spx-preview-images');
      previewImages.forEach(function appendPreviewImage(url) {
        var link = createEl('a');
        link.href = url;
        link.target = '_blank';
        link.rel = 'noopener noreferrer';
        var image = createEl('img');
        image.loading = 'lazy';
        image.decoding = 'async';
        image.alt = '预览图';
        preparePreviewImageReveal(image);
        image.dataset.spxPreviewLazySrc = url;
        link.appendChild(image);
        grid.appendChild(link);
      });
      panel.appendChild(grid);
    }
    panel.appendChild(createEl('div', 'spx-preview-status', getThreadPreviewImageSummary(payload)));

    panel.addEventListener('mouseleave', removeThreadPreview);
    document.body.appendChild(panel);
    positionPreviewPanel(panel, event || {});
    scheduleThreadPreviewPanelImages(panel);
  }

  function extractPreviewPayloadFromDocument(doc, url) {
    var content = doc && doc.querySelector ? doc.querySelector('.tpc_content') : null;
    var images = content ? Array.prototype.slice.call(content.querySelectorAll('img')) : [];
    var resourceLinks = doc && doc.querySelectorAll ? getJumpResourceLinks(extractReadPageResourceLinks(qsa('table.js-post', doc), url)) : [];
    return {
      text: content ? content.textContent.replace(/\s+/g, ' ').trim().slice(0, 260) : '',
      images: extractPreviewImageUrls(
        images.map(function mapPreviewImage(image) {
          return {
            src: image.getAttribute('src') || image.src,
            naturalWidth: image.naturalWidth || image.width || 0,
            naturalHeight: image.naturalHeight || image.height || 0,
            postIndex: 0,
          };
        }),
        url
      ),
      resourceBadges: getResourceBadgesFromItems(resourceLinks, 'preview'),
    };
  }

  function attachThreadHoverPreview(info, settings, state) {
    if (!info || !info.titleLink || info.titleLink.dataset.spxPreviewReady) return;
    info.titleLink.dataset.spxPreviewReady = '1';
    var timer = null;
    var previewToken = 0;
    var lastPreviewEvent = null;
    var previewAbortController = null;
    info.titleLink.addEventListener('mouseenter', function schedulePreview(event) {
      lastPreviewEvent = event;
      previewToken += 1;
      var currentToken = previewToken;
      var url = info.titleLink.href;
      var cached = getCachedThreadPreview(url);
      if (timer) window.clearTimeout(timer);
      if (previewAbortController) {
        previewAbortController.abort();
        previewAbortController = null;
      }
      timer = window.setTimeout(function loadPreview() {
        timer = null;
        if (currentToken !== previewToken) return;
        if (cached) {
          cached.cached = true;
          updateThreadResourceBadges(info, state, cached);
          renderPreviewPanel(info, cached, lastPreviewEvent || event, state, settings);
          return;
        }
        if (isScriptRequestCoolingDown()) {
          renderPreviewPanel(info, { text: '站点提示操作频繁,悬停预览已临时暂停。', images: [], status: '请求冷却中' }, lastPreviewEvent || event, state, settings);
          return;
        }
        var previewPolicy = {
          mode: 'preview',
          label: '悬停预览',
          networkFriendly: isNetworkFriendlyMode(settings),
        };
        var requestOptions = { credentials: 'include' };
        var requestController = null;
        if (typeof AbortController === 'function') {
          requestController = new AbortController();
          previewAbortController = requestController;
          requestOptions.signal = requestController.signal;
        }
        renderPreviewPanel(info, { text: '正在加载预览...', images: [], status: '加载中' }, lastPreviewEvent || event, state, settings);
        requestWithPolicy(url, requestOptions, previewPolicy)
          .then(function parseResponse(response) {
            return readScriptResponseText(response, previewPolicy);
          })
          .then(function renderHtml(html) {
            if (previewAbortController === requestController) previewAbortController = null;
            if (currentToken !== previewToken) return;
            var doc = new DOMParser().parseFromString(html, 'text/html');
            var payload = extractPreviewPayloadFromDocument(doc, url);
            rememberThreadPreview(url, payload);
            updateThreadResourceBadges(info, state, payload);
            renderPreviewPanel(info, payload, lastPreviewEvent || event, state, settings);
          })
          .catch(function showPreviewError(error) {
            if (previewAbortController === requestController) previewAbortController = null;
            if (error && error.name === 'AbortError') return;
            if (currentToken !== previewToken) return;
            var failurePayload = { text: '预览加载失败,已短暂缓存失败状态,避免重复请求同一主题。', images: [], status: '加载失败' };
            rememberThreadPreview(url, failurePayload, undefined, THREAD_PREVIEW_FAILURE_TTL);
            renderPreviewPanel(info, failurePayload, lastPreviewEvent || event, state, settings);
          });
      }, cached ? Math.min(120, getThreadPreviewHoverDelay(settings)) : getThreadPreviewHoverDelay(settings));
    });
    info.titleLink.addEventListener('mousemove', function movePreview(event) {
      lastPreviewEvent = event;
      var panel = qs('#spx-preview-popover');
      if (panel) positionPreviewPanel(panel, event);
    });
    info.titleLink.addEventListener('mouseleave', function cancelPreview() {
      previewToken += 1;
      if (timer) window.clearTimeout(timer);
      timer = null;
      if (previewAbortController) {
        previewAbortController.abort();
        previewAbortController = null;
      }
      window.setTimeout(function removePreviewIfPanelInactive() {
        var panel = qs('#spx-preview-popover');
        if (panel && panel.matches && panel.matches(':hover')) return;
        removeThreadPreview();
      }, 160);
    });
  }

  function enhanceForumGallery(settings, state) {
    if (!isForumGalleryModeUrl(location.href)) return false;
    var wall = qs('#wall');
    if (!wall) return false;
    var cards = qsa('.stream li', wall).filter(function keepGalleryThreadCard(card) {
      var info = extractForumGalleryCardInfo(card);
      return !!(info.id && info.titleLink);
    });
    if (!cards.length) return false;

    wall.classList.add('spx-forum-gallery-wall');
    watchForumGalleryStream(wall, settings, state);
    scheduleForumGalleryToolRepair(wall, settings, state);
    var items = [];
    var resourceBadgeIndex = getThreadResourceBadgeIndex(state.resources);

    cards.forEach(function enhanceGalleryCard(card) {
      var info = extractForumGalleryCardInfo(card);
      if (!info.id || !info.titleLink) return;
      items.push(info);
      card.classList.add('spx-gallery-thread-card');
      info.cell.classList.add('spx-gallery-card-inner');
      if (card.dataset) card.dataset.spxGalleryThread = '1';

      var isRead = !!state.read[info.id];
      card.classList.toggle('spx-read-thread', isRead);
      setThreadRowHiddenClass(card, 'spx-unread-hidden', !!settings.unreadOnly && isRead);
      setThreadRowHiddenClass(card, 'spx-hidden-rule', matchesBlockRules(info, settings));

      var resourceBadges = getThreadResourceBadges(info, resourceBadgeIndex);
      info.resourceBadges = resourceBadges;
      info.resourceBadgeTypes = getResourceBadgeTypes(resourceBadges);
      if (card.dataset) card.dataset.spxResourceTypes = info.resourceBadgeTypes.join(' ');

      ensureForumGalleryCardTools(info, settings, state);
      clearForumGalleryResourceBadges(info);

      if (info.titleLink.dataset && info.titleLink.dataset.spxGalleryReadBound !== '1') {
        info.titleLink.dataset.spxGalleryReadBound = '1';
        info.titleLink.addEventListener('click', function markGalleryThreadRead() {
          state.read[info.id] = Date.now();
          saveMap(READ_KEY, state.read);
        }, { capture: true });
      }
    });

    createForumQuickTools(settings, state, items);
    createForumSectionTitle(items, wall);
    compactForumPrelude(wall);
    createForumModuleNavigation(items, state);
    hideForumAnnouncements();
    return true;
  }

  function enhanceThreadList(settings, state) {
    if (detectPageType(location.href) !== 'forum') return;
    if (enhanceForumGallery(settings, state)) return;
    qsa('.spx-thread-list-table').forEach(function clearThreadListTable(table) {
      table.classList.remove('spx-thread-list-table');
      delete table.dataset.spxThreadListLayout;
    });
    var cells = qsa('td[id^="td_"]').filter(function realThreadCell(cell) {
      return parseThreadId(cell.id) && qs('a[id^="a_ajax_"]', cell);
    });
    var items = [];
    var resourceBadgeIndex = getThreadResourceBadgeIndex(state.resources);

    cells.forEach(function enhanceCell(cell) {
      var table = cell.closest ? cell.closest('.t') : null;
      if (table) {
        table.classList.add('spx-thread-list-table');
        if (table.dataset.spxThreadListLayout !== '1') {
          table.dataset.spxThreadListLayout = '1';
          qsa('table,tbody', table).forEach(function blockThreadTable(node) {
            setImportantStyle(node, 'display', 'block');
            setImportantStyle(node, 'width', '100%');
          });
          qsa('tr.tr2,tr.tr3', table).forEach(function gridThreadRow(row) {
            var isDecorativeThreadRow = row.classList.contains('tr3') && !qs('td[id^="td_"]', row);
            if (row.dataset) row.dataset.spxThreadListRow = '1';
            setThreadRowHiddenClass(row, 'spx-thread-row-hidden', isDecorativeThreadRow);
          });
        }
      }
      var info = extractThreadCellInfo(cell);
      if (!info.id || !info.titleLink) return;
      items.push(info);

      var isRead = !!state.read[info.id];
      info.row.classList.toggle('spx-read-thread', isRead);
      if (info.row.dataset) info.row.dataset.spxThreadListRow = '1';
      setThreadRowHiddenClass(info.row, 'spx-unread-hidden', !!settings.unreadOnly && isRead);
      setThreadRowHiddenClass(info.row, 'spx-hidden-rule', matchesBlockRules(info, settings));
      var resourceBadges = getThreadResourceBadges(info, resourceBadgeIndex);
      info.resourceBadges = resourceBadges;
      info.resourceBadgeTypes = getResourceBadgeTypes(resourceBadges);

      if (state.watch[info.id] && !qs('.spx-watch-badge', info.cell)) {
        info.titleLink.insertAdjacentElement('afterend', createWatchBadge(info.id));
      }

      if (qs('.spx-thread-tools', info.cell)) {
        renderThreadResourceBadges(info, resourceBadges);
        attachThreadHoverPreview(info, settings, state);
        return;
      }
      var tools = createEl('span', 'spx-thread-tools');
      var watchButton = createEl('button', '', state.watch[info.id] ? '已存' : '稍后');
      var titleBlockButton = createEl('button', '', '屏题');
      var authorBlockButton = createEl('button', '', '屏人');
      var hideRowButton = createEl('button', '', '隐藏');
      var favoriteButton = createEl('button', '', isThreadFavoriteSeen(info.id) ? '已收藏' : '收藏');

      watchButton.title = '切换本地稍后看';
      titleBlockButton.title = '把标题加入本地屏蔽关键词';
      authorBlockButton.title = '把作者加入本地屏蔽关键词';
      hideRowButton.title = '临时隐藏当前行';
      favoriteButton.title = '收藏到站内收藏夹';
      favoriteButton.disabled = isThreadFavoriteSeen(info.id);

      watchButton.addEventListener('click', function toggleWatch(event) {
        event.preventDefault();
        event.stopPropagation();
        toggleThreadWatch(info, state, watchButton);
      });

      titleBlockButton.addEventListener('click', function blockTitle(event) {
        event.preventDefault();
        event.stopPropagation();
        var keyword = window.prompt('添加标题屏蔽关键词', info.title.slice(0, 30));
        if (!keyword) return;
        settings.titleKeywords = parseLineList(settings.titleKeywords.concat([keyword]).join('\n'));
        saveSettings(settings);
        enhanceAll(settings, state);
      });

      authorBlockButton.addEventListener('click', function blockAuthor(event) {
        event.preventDefault();
        event.stopPropagation();
        if (!info.author) return;
        settings.authorKeywords = parseLineList(settings.authorKeywords.concat([info.author]).join('\n'));
        saveSettings(settings);
        enhanceAll(settings, state);
      });

      hideRowButton.addEventListener('click', function hideCurrentRow(event) {
        event.preventDefault();
        event.stopPropagation();
        setThreadRowHiddenClass(info.row, 'spx-filter-hidden', true);
      });

      favoriteButton.addEventListener('click', function favoriteCurrentThread(event) {
        event.preventDefault();
        event.stopPropagation();
        runThreadFavoriteAction(info, settings, state, favoriteButton, '已收藏');
      });

      tools.appendChild(watchButton);
      tools.appendChild(titleBlockButton);
      if (info.author) tools.appendChild(authorBlockButton);
      tools.appendChild(hideRowButton);
      tools.appendChild(favoriteButton);
      info.titleLink.insertAdjacentElement('afterend', tools);
      renderThreadResourceBadges(info, resourceBadges);
      attachThreadHoverPreview(info, settings, state);

      info.titleLink.addEventListener('click', function markRead() {
        state.read[info.id] = Date.now();
        saveMap(READ_KEY, state.read);
      }, { capture: true });
    });

    createForumQuickTools(settings, state, items);
    var threadTable = items[0] && items[0].row && items[0].row.closest ? items[0].row.closest('.spx-thread-list-table') : null;
    createForumSectionTitle(items, threadTable);
    compactForumPrelude(threadTable);
    createForumModuleNavigation(items, state);
    hideForumAnnouncements();
  }

  function getPostAuthor(post) {
    var profileText = '';
    var profile = qs('.readprofile', post) || qs('.user-info', post);
    if (profile) profileText = profile.textContent.replace(/\s+/g, ' ').trim();
    var userLink = getPostUserLink(post);
    var userText = userLink ? userLink.textContent.trim() : '';
    return userText || profileText;
  }

  function compactText(value) {
    return String(value || '').replace(/\s+/g, ' ').trim();
  }

  function getSessionStorage() {
    try {
      if (typeof window !== 'undefined' && window.sessionStorage) return window.sessionStorage;
    } catch (error) {
      return null;
    }
    return null;
  }

  function requestReadProgressRestore(tid, mode) {
    var storage = getSessionStorage();
    if (!storage || !tid) return;
    storage.setItem(RESTORE_PROGRESS_KEY, JSON.stringify({
      tid: String(tid),
      mode: mode === 'last' ? 'last' : 'next',
    }));
  }

  function getReadProgressRestoreRequest(tid) {
    var storage = getSessionStorage();
    if (!storage || !tid) return null;
    var rawValue = storage.getItem(RESTORE_PROGRESS_KEY);
    if (!rawValue) return null;
    if (rawValue === String(tid)) return { tid: String(tid), mode: 'next' };
    try {
      var request = JSON.parse(rawValue);
      if (!request || String(request.tid || '') !== String(tid)) return null;
      return {
        tid: String(request.tid),
        mode: request.mode === 'last' ? 'last' : 'next',
      };
    } catch (error) {
      return null;
    }
  }

  function clearReadProgressRestoreRequest(tid) {
    var storage = getSessionStorage();
    if (!storage || !tid) return;
    if (getReadProgressRestoreRequest(tid)) {
      storage.removeItem(RESTORE_PROGRESS_KEY);
    }
  }

  function getReadPageTitle(root) {
    var scope = root || document;
    var titleNode =
      qs('[id^="subject_"]', scope) ||
      qs('.h1', scope) ||
      qs('h1', scope) ||
      qs('title', scope);
    return compactText(titleNode && titleNode.textContent) || compactText(document.title) || '未命名帖子';
  }

  function syncNativeReadFavoriteFromGuide(info, settings, state, attemptsLeft) {
    var guide = qs('#ajax_guide');
    var guideText = compactText(guide && guide.textContent);
    if (guideText) {
      if (isNewThreadFavoriteResult(guideText)) {
        var added = markThreadFavoriteSeen(info);
        syncFavoriteNavAfterSiteFavorite(info, settings, state, added);
      }
      if (getThreadFavoriteResultText(guideText) !== '已提交') return;
    }
    if (!attemptsLeft) return;
    window.setTimeout(function retryNativeFavoriteSync() {
      syncNativeReadFavoriteFromGuide(info, settings, state, attemptsLeft - 1);
    }, 800);
  }

  function bindNativeReadFavoriteSync(settings, state, tid, originalAuthor) {
    if (!tid) return;
    var nativeFavorite = qs('a[onclick*="action=favor"][onclick*="tid"],a[title*="收藏该主题"]');
    if (!nativeFavorite || nativeFavorite.dataset.spxNativeFavoriteSync === '1') return;
    nativeFavorite.dataset.spxNativeFavoriteSync = '1';
    nativeFavorite.addEventListener('click', function syncNativeFavoriteAfterClick() {
      var info = createReadPageFavoriteInfo(tid, getReadPageTitle(document), originalAuthor, location.href);
      window.setTimeout(function syncNativeFavoriteFromGuide() {
        syncNativeReadFavoriteFromGuide(info, settings, state, 3);
      }, 1800);
    }, true);
  }

  function getCurrentResourceSourceMeta() {
    return {
      sourceTitle: getReadPageTitle(document),
      sourceUrl: location.href.split('#')[0],
    };
  }

  function getScrollTop() {
    return Number(
      window.pageYOffset ||
      document.documentElement.scrollTop ||
      (document.body && document.body.scrollTop) ||
      0
    ) || 0;
  }

  function getPostFloorLabel(index) {
    return index === 0 ? '楼主' : 'B' + index + 'F';
  }

  function getPostAnchorHash(post) {
    if (!post) return '';
    var anchor =
      qs('[id^="td_"]', post) ||
      qs('[id^="read_"]', post) ||
      qs('[id^="p_"]', post) ||
      qs('a[id^="td_read_"]', post) ||
      qs('[id^="subject_"]', post) ||
      qs('a[name]:not([name="topic"]):not([name="avatar"])', post) ||
      qs('a[name]', post);
    if (!anchor) return '';
    var value = anchor.getAttribute('name') || anchor.id || '';
    return value ? ('#' + value) : '';
  }

  function getAbsoluteNodeTop(node) {
    if (!node || !node.getBoundingClientRect) return 0;
    var rect = node.getBoundingClientRect();
    return Math.max(0, Math.round(rect.top + getScrollTop()));
  }

  function getCurrentReadFloorRecord(posts) {
    var list = (posts || qsa('table.js-post'))
      .map(function toReadFloorItem(post, index) {
        return {
          floorIndex: index,
          floorLabel: getPostFloorLabel(index),
          floorHash: getPostAnchorHash(post),
          floorTop: getAbsoluteNodeTop(post),
        };
      })
      .filter(function keepReadFloorItem(item) {
        return !!item.floorLabel;
      });
    if (!list.length) return null;

    var targetTop = getScrollTop() + Math.max(80, Math.min((window.innerHeight || 0) * 0.35, 320));
    var current = list[0];
    list.forEach(function updateCurrentFloor(item) {
      if (item.floorTop <= targetTop) current = item;
    });

    var next = list[Math.min(list.length - 1, current.floorIndex + 1)] || current;
    return {
      floorIndex: current.floorIndex,
      floorLabel: current.floorLabel,
      floorHash: current.floorHash,
      floorTop: current.floorTop,
      nextFloorIndex: next.floorIndex,
      nextFloorLabel: next.floorLabel,
      nextFloorHash: next.floorHash,
      nextFloorTop: next.floorTop,
    };
  }

  function getReadScrollRatio() {
    var doc = document.documentElement;
    var body = document.body;
    var scrollHeight = Math.max(
      doc ? doc.scrollHeight : 0,
      body ? body.scrollHeight : 0
    );
    var viewportHeight = window.innerHeight || (doc && doc.clientHeight) || 0;
    var maxScroll = scrollHeight - viewportHeight;
    if (!(maxScroll > 0)) return 0;
    return clampRatio(getScrollTop() / maxScroll);
  }

  function buildReadProgressRecord(tid) {
    if (!tid) return null;
    var record = {
      title: getReadPageTitle(document),
      url: location.href.split('#')[0],
      page: currentPageNumber(location.href),
      scrollY: Math.max(0, Math.round(getScrollTop())),
      progress: getReadScrollRatio(),
      updatedAt: Date.now(),
    };
    var floorRecord = getCurrentReadFloorRecord();
    if (floorRecord) {
      record.floorIndex = floorRecord.floorIndex;
      record.floorLabel = floorRecord.floorLabel;
      record.floorHash = floorRecord.floorHash;
      record.floorTop = floorRecord.floorTop;
      record.nextFloorIndex = floorRecord.nextFloorIndex;
      record.nextFloorLabel = floorRecord.nextFloorLabel;
      record.nextFloorHash = floorRecord.nextFloorHash;
      record.nextFloorTop = floorRecord.nextFloorTop;
    }
    return record;
  }

  function saveReadProgressRecord(state, tid) {
    var record = buildReadProgressRecord(tid);
    if (!record) return null;
    state.progress = state.progress || {};
    state.progress[tid] = mergeReadProgressRecord(state.progress[tid], record);
    state.progress = pruneReadProgress(state.progress);
    saveReadProgress(state.progress);
    refreshWatchCenter();
    refreshHistoryCenter();
    return state.progress[tid];
  }

  function getReadProgressAnchorNode(hash) {
    var targetId = String(hash || '').replace(/^#/, '');
    if (!targetId) return null;
    return (
      document.getElementById(targetId) ||
      qsa('a[name]', document).filter(function matchAnchor(node) {
        return node.getAttribute('name') === targetId;
      })[0] ||
      null
    );
  }

  function restoreReadProgress(state, tid, mode) {
    var latestProgress = loadReadProgress();
    if (state && latestProgress) state.progress = latestProgress;
    var record = latestProgress && latestProgress[tid]
      ? latestProgress[tid]
      : (state && state.progress ? state.progress[tid] : null);
    if (!record) {
      clearReadProgressRestoreRequest(tid);
      return false;
    }
    clearReadProgressRestoreRequest(tid);
    var target = getReadProgressRestoreTarget(record, mode);
    window.setTimeout(function restoreScroll() {
      var top = Math.max(0, Number(target.top) || 0);
      var anchorNode = getReadProgressAnchorNode(target.hash);
      if (anchorNode && typeof anchorNode.scrollIntoView === 'function') {
        anchorNode.scrollIntoView({ block: 'start', behavior: 'smooth' });
      } else {
        window.scrollTo({ top: top, behavior: 'smooth' });
      }
      window.setTimeout(function fallbackRestoreScroll() {
        if (Math.abs(getScrollTop() - top) <= 8) return;
        if (document.scrollingElement) document.scrollingElement.scrollTop = top;
        if (document.documentElement) document.documentElement.scrollTop = top;
        if (document.body) document.body.scrollTop = top;
      }, 260);
    }, 80);
    return true;
  }

  function restorePendingReadProgress(state, tid) {
    var request = getReadProgressRestoreRequest(tid);
    if (!request) return false;
    return restoreReadProgress(state, tid, request.mode);
  }

  function bindReadPageJumpButtons(state, tid) {
    if (!tid || !document.documentElement) return;
    if (document.documentElement.dataset.spxReadJumpTid === String(tid)) return;
    document.documentElement.dataset.spxReadJumpTid = String(tid);
    document.addEventListener('click', function handleReadJumpClick(event) {
      var target = event.target && event.target.closest
        ? event.target.closest('[data-spx-read-jump]')
        : null;
      if (!target) return;
      var targetTid = target.dataset.spxReadTid || tid;
      if (String(targetTid) !== String(tid)) return;
      event.preventDefault();
      restoreReadProgress(state, tid, target.dataset.spxReadJump);
    }, true);
  }

  function bindReadProgressTracking(state, tid) {
    if (!tid || !document.documentElement) return;
    if (document.documentElement.dataset.spxReadProgressTid === String(tid)) return;
    document.documentElement.dataset.spxReadProgressTid = String(tid);

    var saveTimer = null;
    function saveNow() {
      if (saveTimer) window.clearTimeout(saveTimer);
      saveTimer = null;
      saveReadProgressRecord(state, tid);
    }
    function scheduleSave() {
      if (saveTimer) window.clearTimeout(saveTimer);
      saveTimer = window.setTimeout(saveNow, 500);
    }

    window.addEventListener('scroll', scheduleSave, { passive: true });
    window.addEventListener('pagehide', saveNow);
    document.addEventListener('visibilitychange', function saveBeforeHidden() {
      if (document.visibilityState === 'hidden') saveNow();
    });
    window.setTimeout(saveNow, 700);
  }

  function getPostUserLink(scope) {
    return (
      qsa('a[href*="u.php?action-show"],a[href*="u.php?uid"],a[href*="u.php?action=show"],a[href*="profile.php?action-show"],.readprofile a[href],.user-info a[href],a[href*="u.php"]', scope)
        .filter(function hasUsefulUserText(link) {
          return !!compactText(link.textContent);
        })[0] ||
      qs('a[href*="u.php?action-show"]', scope) ||
      qs('a[href*="u.php?uid"]', scope) ||
      qs('a[href*="u.php?action=show"]', scope) ||
      qs('a[href*="profile.php?action-show"]', scope) ||
      qs('.readprofile a[href]', scope) ||
      qs('.user-info a[href]', scope) ||
      qs('a[href*="u.php"]', scope)
    );
  }

  function getVisiblePostAuthorName(post) {
    var authorCell = qs('th.r_two', post) || qs('td.r_two', post) || qs('.r_two', post);
    var userLink = getPostUserLink(authorCell || post);
    var linkText = compactText(userLink && userLink.textContent);
    if (linkText) return linkText;

    var authorText = compactText(authorCell && authorCell.textContent);
    if (!authorText) return '';
    return authorText
      .split(/\s{2,}|(?=作者资料)|(?=发送短消息)|(?=加为好友)|(?=使用道具)|(?=等级[::])|(?=帖子[::])|(?=注册[::])|(?=威望[::])|(?=金币[::])/)
      .map(compactText)
      .filter(Boolean)[0] || '';
  }

  function getPostAuthorHoverTargets(post, info) {
    var authorCell = qs('th.r_two', post) || qs('td.r_two', post) || qs('.r_two', post);
    var targets = [
      authorCell,
      info && info.source,
      getPostUserLink(post),
      qs('.readprofile', post),
      qs('.user-info', post),
      qs('.user-pic', post),
    ];
    var seen = [];
    return targets.filter(function keepUniqueTarget(node) {
      if (!node || seen.indexOf(node) !== -1) return false;
      seen.push(node);
      return true;
    });
  }

  function extractPostAuthorInfo(post) {
    var authorCell = qs('th.r_two', post) || qs('td.r_two', post) || qs('.r_two', post);
    var scope = authorCell || post;
    var userLink = getPostUserLink(scope) || getPostUserLink(post);
    var avatar = qs('.user-pic img', scope) || qs('img', scope);
    var name = compactText(userLink && userLink.textContent) || getVisiblePostAuthorName(post) || getPostAuthor(post);
    var lines = [];
    var seen = {};

    qsa('.readprofile,.user-info,.user-pic,.f12,div,span', scope).forEach(function collectAuthorLine(node) {
      var text = compactText(node.textContent);
      if (!text || text === name || seen[text]) return;
      if (text.length > 90) return;
      seen[text] = true;
      lines.push(text);
    });

    if (!lines.length && authorCell) {
      compactText(authorCell.textContent)
        .split(/\s{2,}|(?=等级[::])|(?=帖子[::])|(?=注册[::])|(?=威望[::])|(?=金币[::])/)
        .map(compactText)
        .filter(Boolean)
        .forEach(function addFallbackLine(text) {
          if (text === name || seen[text]) return;
          seen[text] = true;
          lines.push(text);
        });
    }

    return {
      name: name || '未知用户',
      url: userLink ? userLink.href : '',
      avatar: avatar ? (avatar.currentSrc || avatar.src || '') : '',
      lines: lines.slice(0, 8),
      source: authorCell || userLink || qs('.readprofile', scope),
    };
  }

  function removeAuthorPopover() {
    var popover = qs('#spx-author-popover');
    if (popover) popover.remove();
  }

  function positionAuthorPopover(popover, event) {
    if (!popover) return;
    var margin = 12;
    var left = (event && event.clientX ? event.clientX : 24) + 14;
    var top = (event && event.clientY ? event.clientY : 24) + 14;
    var width = popover.offsetWidth || 320;
    var height = popover.offsetHeight || 180;
    if (left + width + margin > window.innerWidth) left = window.innerWidth - width - margin;
    if (top + height + margin > window.innerHeight) top = window.innerHeight - height - margin;
    popover.style.left = Math.max(margin, left) + 'px';
    popover.style.top = Math.max(margin, top) + 'px';
  }

  function renderAuthorPopover(info, event) {
    removeAuthorPopover();
    if (!info || !info.name) return;

    var popover = createEl('div', 'spx-author-popover');
    popover.id = 'spx-author-popover';

    var header = createEl('div', 'spx-author-popover-header');
    if (info.avatar) {
      var avatar = createEl('img', 'spx-author-popover-avatar');
      avatar.src = info.avatar;
      avatar.alt = info.name;
      header.appendChild(avatar);
    } else {
      header.appendChild(createEl('div', 'spx-author-popover-avatar'));
    }

    var title = createEl('div');
    title.appendChild(createEl('div', 'spx-author-popover-name', info.name));
    if (info.url) {
      var link = createEl('a', 'spx-author-popover-link', '查看用户主页');
      link.href = info.url;
      link.target = '_blank';
      link.rel = 'noreferrer';
      title.appendChild(link);
    }
    header.appendChild(title);
    popover.appendChild(header);

    var lines = createEl('div', 'spx-author-popover-lines');
    (info.lines && info.lines.length ? info.lines : ['页面未提供更多资料']).forEach(function appendLine(line) {
      lines.appendChild(createEl('div', 'spx-author-popover-line', line));
    });
    popover.appendChild(lines);

    popover.addEventListener('mouseenter', function keepPopover() {
      if (popover.spxHideTimer) window.clearTimeout(popover.spxHideTimer);
    });
    popover.addEventListener('mouseleave', removeAuthorPopover);
    document.body.appendChild(popover);
    positionAuthorPopover(popover, event || {});
  }

  function attachAuthorHoverCard(post) {
    if (!post || post.dataset.spxAuthorHoverReady === '1') return;
    var info = extractPostAuthorInfo(post);
    var targets = getPostAuthorHoverTargets(post, info);
    if (!targets.length) return;
    post.dataset.spxAuthorHoverReady = '1';

    targets.forEach(function bindAuthorTarget(target) {
      target.classList.add('spx-author-hover-source');
      target.addEventListener('mouseenter', function showAuthor(event) {
        renderAuthorPopover(info, event);
      });
      target.addEventListener('mouseover', function showAuthorFallback(event) {
        if (!qs('#spx-author-popover')) renderAuthorPopover(info, event);
      });
      target.addEventListener('mousemove', function moveAuthor(event) {
        positionAuthorPopover(qs('#spx-author-popover'), event);
      });
      target.addEventListener('mouseleave', function hideAuthor() {
        var popover = qs('#spx-author-popover');
        if (!popover) return;
        popover.spxHideTimer = window.setTimeout(removeAuthorPopover, 120);
      });
    });
  }

  function setImportantStyle(node, name, value) {
    if (!node || !node.style) return;
    if (typeof node.style.setProperty === 'function') {
      node.style.setProperty(name, value, 'important');
      return;
    }
    node.style[name] = value;
  }

  function applySiteShellLayout(root) {
    var scope = root || document;
    qsa('#header,#main,#content,#infobox,#notice', scope).forEach(function widenShellNode(node) {
      setImportantStyle(node, 'box-sizing', 'border-box');
      setImportantStyle(node, 'width', 'min(1480px, calc(100vw - 40px))');
      setImportantStyle(node, 'max-width', 'none');
      setImportantStyle(node, 'margin-left', 'auto');
      setImportantStyle(node, 'margin-right', 'auto');
    });
    qsa('#mainNav', scope).forEach(function widenNav(node) {
      setImportantStyle(node, 'box-sizing', 'border-box');
      setImportantStyle(node, 'width', 'min(1480px, calc(100vw - 40px))');
      setImportantStyle(node, 'max-width', 'none');
      setImportantStyle(node, 'margin-left', 'auto');
      setImportantStyle(node, 'margin-right', 'auto');
      setImportantStyle(node, 'height', '38px');
      setImportantStyle(node, 'overflow', 'visible');
      qsa(':scope>div', node).forEach(function hideLegacyNavChild(child) {
        if (qs('#guide', child)) return;
        setImportantStyle(child, 'display', 'none');
      });
    });
    qsa('#mainNav>div[style*="padding-left"]', scope).forEach(function resetNavMenuHost(node) {
      setImportantStyle(node, 'display', 'flex');
      setImportantStyle(node, 'align-items', 'center');
      setImportantStyle(node, 'gap', '6px');
      setImportantStyle(node, 'box-sizing', 'border-box');
      setImportantStyle(node, 'width', '100%');
      setImportantStyle(node, 'max-width', '100%');
      setImportantStyle(node, 'height', '38px');
      setImportantStyle(node, 'padding-left', '8px');
      setImportantStyle(node, 'overflow', 'visible');
    });
    qsa('#mainNav>div[style*="padding-left"] table,#mainNav>div[style*="padding-left"] tbody,#mainNav>div[style*="padding-left"] tr,#mainNav>div[style*="padding-left"] td', scope).forEach(function resetNavTableNode(node) {
      setImportantStyle(node, 'display', 'block');
      setImportantStyle(node, 'box-sizing', 'border-box');
      setImportantStyle(node, 'width', 'auto');
      setImportantStyle(node, 'height', '38px');
      setImportantStyle(node, 'margin', '0');
      setImportantStyle(node, 'padding', '0');
      setImportantStyle(node, 'border', '0');
    });
  }

  function getDirectChildByTag(node, tagName) {
    if (!node || !node.children) return null;
    var expected = String(tagName || '').toUpperCase();
    for (var index = 0; index < node.children.length; index += 1) {
      if (node.children[index].tagName === expected) return node.children[index];
    }
    return null;
  }

  function enhanceSiteNavigation(root) {
    var scope = root || document;
    var configLink = qs('#peacemakerconfig', scope);
    var guide = qs('#guide', scope);

    if (guide) {
      var guideHost = guide.closest ? guide.closest('#mainNav>div') : null;
      if (guideHost && !qs('#spx-nav-brand', guideHost)) {
        var brand = createEl('strong', 'spx-nav-brand', 'South Plus +++');
        brand.id = 'spx-nav-brand';
        guideHost.insertBefore(brand, guideHost.firstChild);
      }
    }

    function getNavUrlKey(href) {
      if (!href || /^javascript:/i.test(String(href))) return '';
      try {
        var parsed = new URL(href, location.href);
        return parsed.origin + parsed.pathname + parsed.search;
      } catch (error) {
        return '';
      }
    }

    function syncCurrentNavItem() {
      if (!guide) return;
      var currentUrl = new URL(location.href);
      var currentKey = currentUrl.origin + currentUrl.pathname + currentUrl.search;
      var discussionPage = /\/(?:thread|read|post|index)\.php$/i.test(currentUrl.pathname) || currentUrl.pathname === '/';
      var matched = null;

      qsa('li', guide).forEach(function resetNavItem(navItem) {
        navItem.classList.remove('spx-nav-current');
      });
      qsa('li>a', guide).forEach(function resetNavLink(link) {
        link.classList.remove('spx-nav-current');
        if (!matched && getNavUrlKey(link.getAttribute('href') || link.href) === currentKey) matched = link;
      });

      if (!matched && discussionPage) matched = qs('#h_index>a', guide);
      if (!matched && currentUrl.pathname === '/search.php') matched = qs('#h_search>a', guide);
      if (!matched) return;

      matched.classList.add('spx-nav-current');
      if (matched.parentElement) matched.parentElement.classList.add('spx-nav-current');
    }

    syncCurrentNavItem();

    if (!configLink) return;

    var item = configLink.closest ? configLink.closest('li') : null;
    var menu = getDirectChildByTag(configLink, 'div');

    if (item) {
      item.classList.add('spx-peacemaker-nav');
      if (item.style && item.style.removeProperty) item.style.removeProperty('width');
    }

    function syncPeacemakerState() {
      var open = !!(menu && !menu.hasAttribute('hidden'));
      configLink.classList.toggle('spx-menu-open', open);
      if (item) item.classList.toggle('spx-nav-active', open);
    }

    if (!configLink.dataset.spxNavReady) {
      configLink.dataset.spxNavReady = '1';
      configLink.addEventListener('click', function syncAfterClick() {
        window.setTimeout(syncPeacemakerState, 0);
      });
      if (menu && typeof MutationObserver !== 'undefined') {
        new MutationObserver(syncPeacemakerState).observe(menu, {
          attributes: true,
          attributeFilter: ['hidden'],
        });
      }
    }

    syncPeacemakerState();
  }

  function applyReadPostLayout(post, settings) {
    if (!post) return;
    var authorCell = qs('th.r_two', post);
    var contentCell = qs('th.r_one', post);
    var compactAuthor = !!(settings && (settings.compactRead || settings.hideUserProfile));
    var authorWidth = compactAuthor ? '112px' : '128px';

    setImportantStyle(post, 'width', '100%');
    setImportantStyle(post, 'max-width', '100%');
    setImportantStyle(post, 'table-layout', 'fixed');

    setImportantStyle(authorCell, 'width', authorWidth);
    setImportantStyle(authorCell, 'max-width', authorWidth);
    setImportantStyle(authorCell, 'min-width', authorWidth);
    setImportantStyle(authorCell, 'padding', '18px 8px');
    setImportantStyle(authorCell, 'overflow', 'hidden');

    qsa('th.r_two img', post).forEach(function shrinkAuthorImage(image) {
      setImportantStyle(image, 'max-width', compactAuthor ? '72px' : '96px');
      setImportantStyle(image, 'height', 'auto');
    });

    setImportantStyle(contentCell, 'width', 'auto');
    setImportantStyle(contentCell, 'min-width', '0');
  }

  function getPostToolsHost(post, content) {
    return qs('th.r_one', post) || (content && content.parentNode) || post;
  }

  function syncHiddenPostShell(post) {
    if (!post || !post.closest || !post.classList) return;
    var shell = post.closest('div.t5.t2');
    if (!shell || !shell.classList) return;
    shell.classList.toggle('spx-post-shell-hidden', post.classList.contains('spx-post-hidden'));
  }

  function setHomeModuleCollapsed(rows, collapsed) {
    (rows || []).forEach(function toggleRow(row) {
      if (!row || !row.style) return;
      if (collapsed) {
        row.style.setProperty('display', 'none', 'important');
      } else {
        row.style.removeProperty('display');
      }
    });
  }

  function mountPreviewPanel(firstPost, content, panel) {
    if (!firstPost || !content || !panel || !content.parentNode) return;
    var split = qs('.spx-post-body-split', firstPost);
    if (!split) {
      split = createEl('div', 'spx-post-body-split');
      content.parentNode.insertBefore(split, content);
    }
    split.appendChild(panel);
    split.appendChild(content);
  }

  function findAutoBuyTarget(root, pageUrl) {
    var scope = root || document;
    var controls = qsa(
      'input[onclick*="buytopic"],button[onclick*="buytopic"],a[href*="buytopic"]',
      scope
    );

    for (var index = 0; index < controls.length; index += 1) {
      var control = controls[index];
      var rawTarget = control.getAttribute('href') || control.getAttribute('onclick') || '';
      var url = extractBuyTopicUrl(rawTarget, pageUrl || location.href);
      if (!url) continue;

      var host = control.closest
        ? control.closest('h6.quote,.quote,.tpc_content,table.js-post')
        : control.parentNode;
      var price = parsePostPrice(host ? host.textContent : '');
      if (price === null) {
        var body = scope.body || scope.documentElement;
        price = parsePostPrice(body ? body.textContent : '');
      }
      if (price === null) continue;

      return {
        control: control,
        host: host || control.parentNode,
        price: price,
        url: url,
      };
    }

    return null;
  }

  function setAutoBuyStatus(target, message, isError) {
    var status = qs('#spx-auto-buy-status');
    if (!status) {
      status = createEl('div', 'spx-auto-buy-status');
      status.id = 'spx-auto-buy-status';
      var host = target && target.host;
      if (host && host.parentNode) {
        host.parentNode.insertBefore(status, host.nextSibling);
      } else {
        document.body.appendChild(status);
      }
    }
    status.textContent = message;
    status.classList.toggle('spx-error', !!isError);
  }

  function fetchCurrentUserSpBalance(settings) {
    var cachedBalance = getCachedUserSpBalance();
    if (cachedBalance !== null) return Promise.resolve(cachedBalance);
    var url = new URL('/userpay.php', location.href).href;
    var balancePolicy = {
      mode: 'interactive',
      label: 'SP 余额检查',
      networkFriendly: isNetworkFriendlyMode(settings),
    };
    return requestWithPolicy(url, {
      credentials: 'include',
      cache: 'no-store',
    }, balancePolicy)
      .then(function readBalanceResponse(response) {
        if (!response.ok) throw new Error('读取 SP 余额失败');
        return readScriptResponseText(response, balancePolicy);
      })
      .then(function parseBalancePage(html) {
        var doc = new DOMParser().parseFromString(html, 'text/html');
        var balance = parseUserSpBalance(doc.body ? doc.body.textContent : '');
        if (balance === null) throw new Error('未识别到 SP 余额');
        rememberUserSpBalance(balance);
        return balance;
      });
  }

  function replaceReadPageContent(html, settings, state) {
    var doc = new DOMParser().parseFromString(html, 'text/html');
    var currentContent = qs('#content') || qs('#main');
    var refreshedContent = qs('#content', doc) || qs('#main', doc);
    if (!currentContent || !refreshedContent) return false;

    currentContent.innerHTML = refreshedContent.innerHTML;
    enhanceAll(settings, state);
    return true;
  }

  function clearAutoBuyPageState(pageRoot) {
    if (!pageRoot) return;
    delete pageRoot.dataset.spxAutoBuyStatus;
    var status = qs('#spx-auto-buy-status');
    if (status) status.remove();
  }

  function createAutoBuyContext(settings) {
    var target = findAutoBuyTarget(document, location.href);
    var maxPrice = Number(settings.autoBuyMaxSp);
    if (!target || !(maxPrice > 0) || !(target.price < maxPrice)) return null;

    var attemptKey = getAutoBuyAttemptKey(target.url, location.href);
    var attempts = loadAutoBuyAttempts();
    return {
      pageRoot: document.documentElement,
      target: target,
      attemptKey: attemptKey,
      networkFriendly: isNetworkFriendlyMode(settings),
      previousAttempt: attemptKey ? attempts[attemptKey] : null,
    };
  }

  function blockAutoBuyContext(context) {
    if (!context || !isAutoBuyAttemptBlocked(context.previousAttempt)) return false;
    context.pageRoot.dataset.spxAutoBuyStatus = 'blocked';
    setAutoBuyStatus(context.target, formatAutoBuyAttemptMessage(context.previousAttempt), true);
    return true;
  }

  function markAutoBuyChecking(context) {
    context.pageRoot.dataset.spxAutoBuyStatus = 'checking';
    recordAutoBuyAttempt(
      context.attemptKey,
      'checking',
      '正在检查账户 SP 余额',
      { price: context.target.price, url: context.target.url }
    );
    setAutoBuyStatus(context.target, '自动购买:正在检查账户 SP 余额…', false);
  }

  function skipAutoBuyContext(context, balance) {
    context.pageRoot.dataset.spxAutoBuyStatus = 'skipped';
    recordAutoBuyAttempt(
      context.attemptKey,
      'skipped',
      '余额 ' + balance + ' SP,帖子价格 ' + context.target.price + ' SP',
      { balance: balance, price: context.target.price, url: context.target.url }
    );
    setAutoBuyStatus(
      context.target,
      '自动购买未执行:当前余额 ' + balance + ' SP,帖子价格 ' + context.target.price + ' SP。',
      true
    );
    return null;
  }

  function requestAutoBuyPurchase(context, balance) {
    context.pageRoot.dataset.spxAutoBuyStatus = 'buying';
    recordAutoBuyAttempt(
      context.attemptKey,
      'buying',
      '已发起支付 ' + context.target.price + ' SP 的购买请求',
      { balance: balance, price: context.target.price, url: context.target.url }
    );
    context.target.control.disabled = true;
    context.target.control.setAttribute('aria-disabled', 'true');
    setAutoBuyStatus(
      context.target,
      '自动购买:正在支付 ' + context.target.price + ' SP 并加载帖子内容…',
      false
    );

    var purchasePolicy = {
      mode: 'action',
      label: '自动购买',
      networkFriendly: context.networkFriendly,
    };
    return requestWithPolicy(context.target.url, {
      credentials: 'include',
      redirect: 'follow',
      cache: 'no-store',
    }, purchasePolicy).then(function readPurchaseResponse(response) {
      if (!response.ok) throw new Error('购买请求失败');
      clearUserSpBalanceCache();
      return readScriptResponseText(response, purchasePolicy);
    });
  }

  function reloadAutoBuyThreadHtml(context, purchaseResult) {
    if (purchaseResult === null) return null;
    var reloadPolicy = {
      mode: 'interactive',
      label: '自动购买后刷新帖子',
      networkFriendly: context && context.networkFriendly,
    };
    return requestWithPolicy(location.href, {
      credentials: 'include',
      cache: 'no-store',
    }, reloadPolicy)
      .then(function readRefreshedThread(response) {
        if (!response.ok) throw new Error('重新加载帖子失败');
        return readScriptResponseText(response, reloadPolicy);
      });
  }

  function applyAutoBuySuccess(context, settings, state, html) {
    if (!html) return;
    var refreshedDoc = new DOMParser().parseFromString(html, 'text/html');
    if (findAutoBuyTarget(refreshedDoc, location.href)) {
      throw new Error('购买后仍存在购买按钮');
    }
    if (!replaceReadPageContent(html, settings, state)) {
      throw new Error('无法更新帖子内容');
    }
    var resourceLinks = getJumpResourceLinks(extractReadPageResourceLinks(qsa('table.js-post'), location.href));
    var resourceSummary = formatResourceJumpSummary(resourceLinks);
    var savedResources = saveResourceLinksToLibrary(resourceLinks, state.resources, getCurrentResourceSourceMeta());
    state.resources = savedResources.resources;
    saveResourceLibrary(state.resources);
    refreshResourceCenter();
    refreshReadResourceRail();
    showAutoBuyResourceJump(resourceLinks, state, savedResources.saved);
    context.pageRoot.dataset.spxAutoBuyStatus = 'done';
    recordAutoBuyAttempt(
      context.attemptKey,
      'done',
      '已支付 ' + context.target.price + ' SP 并加载帖子内容' + (resourceSummary ? ',识别资源:' + resourceSummary : ''),
      { price: context.target.price, url: context.target.url, resourceSummary: resourceSummary }
    );
  }

  function failAutoBuyContext(context, error) {
    var reason = error && error.message ? error.message : '未知错误';
    context.pageRoot.dataset.spxAutoBuyStatus = 'failed';
    context.target.control.disabled = false;
    context.target.control.removeAttribute('aria-disabled');
    recordAutoBuyAttempt(
      context.attemptKey,
      'failed',
      reason,
      { price: context.target.price, url: context.target.url }
    );
    setAutoBuyStatus(
      context.target,
      '自动购买失败:' + reason + '。已保留原购买按钮,请手动购买。',
      true
    );
  }

  function enhanceAutoBuyPost(settings, state) {
    if (detectPageType(location.href) !== 'read') return;

    var pageRoot = document.documentElement;
    if (!settings.autoBuyPost) {
      clearAutoBuyPageState(pageRoot);
      return;
    }
    if (pageRoot.dataset.spxAutoBuyStatus) return;

    var context = createAutoBuyContext(settings);
    if (!context) return;
    if (blockAutoBuyContext(context)) return;
    markAutoBuyChecking(context);

    fetchCurrentUserSpBalance(settings)
      .then(function purchaseWhenAffordable(balance) {
        if (!shouldAutoBuyPost(settings, context.target.price, balance)) {
          return skipAutoBuyContext(context, balance);
        }
        return requestAutoBuyPurchase(context, balance);
      })
      .then(function reloadPurchasedThread(purchaseResult) {
        return reloadAutoBuyThreadHtml(context, purchaseResult);
      })
      .then(function applyRefreshedThread(html) {
        applyAutoBuySuccess(context, settings, state, html);
      })
      .catch(function handleAutoBuyError(error) {
        failAutoBuyContext(context, error);
      });
  }

  function enhanceReadPage(settings, state) {
    if (detectPageType(location.href) !== 'read') return;
    var tid = parseThreadId(location.href);
    if (tid) {
      state.read[tid] = Date.now();
      saveMap(READ_KEY, state.read);
      restorePendingReadProgress(state, tid);
      bindReadProgressTracking(state, tid);
      bindReadPageJumpButtons(state, tid);
    }

    var posts = qsa('table.js-post');
    var originalAuthor = posts.length ? getPostAuthor(posts[0]) : '';
    bindNativeReadFavoriteSync(settings, state, tid, originalAuthor);

    posts.forEach(function enhancePost(post, index) {
      var author = getPostAuthor(post);
      var content = qs('.tpc_content', post);
      if (!content) return;

      applyReadPostLayout(post, settings);
      attachAuthorHoverCard(post);

      post.classList.toggle('spx-post-hidden', matchesBlockRules({ title: content.textContent, author: author }, settings));
      if (settings.onlyOriginalAuthor && originalAuthor && author && author !== originalAuthor) {
        post.classList.add('spx-post-hidden');
      }
      syncHiddenPostShell(post);

      var toolsHost = getPostToolsHost(post, content);
      var existingTools = qs('.spx-post-tools', post);
      if (existingTools && existingTools.parentNode !== toolsHost) {
        toolsHost.insertBefore(existingTools, toolsHost.firstChild);
      }

      function openPostQuickReply(event) {
        if (event) {
          event.preventDefault();
          event.stopPropagation();
          if (typeof event.stopImmediatePropagation === 'function') event.stopImmediatePropagation();
        }
        var floorLabel = getPostFloorLabel(index);
        var contextLabel = '回复 ' + (author || '用户') + ' · ' + floorLabel;
        var replyText = '回 ' + floorLabel + (author ? '(' + author + ')' : '') + ' 的帖子\n';
        openFloatingQuickReplyContext(contextLabel, replyText);
      }

      qsa('.readbot .huifu a,a[onclick*="postreply"]', post).forEach(function bindNativeQuickReply(link) {
        if (link.dataset.spxQuickReplyBound === '1') return;
        link.dataset.spxQuickReplyBound = '1';
        link.addEventListener('click', openPostQuickReply, true);
      });

      if (!existingTools) {
        var tools = createEl('div', 'spx-post-tools');
        var floor = createEl('span', '', getPostFloorLabel(index));
        var quickReply = createEl('button', '', '回复');
        var blockAuthor = createEl('button', '', '屏蔽此人');
        var copyLink = createEl('button', '', '复制链接');
        var extractResources = createEl('button', '', '资源');
        var jumpNextFloor = null;
        var jumpLastFloor = null;
        floor.style.marginRight = 'auto';
        quickReply.type = 'button';
        blockAuthor.type = 'button';
        copyLink.type = 'button';
        extractResources.type = 'button';
        extractResources.title = '提取本页资源链接,可按本楼、作者或类型复制';

        if (index === 0 && tid) {
          jumpNextFloor = createEl('button', '', '未读楼层');
          jumpLastFloor = createEl('button', '', '上次楼层');
          jumpNextFloor.type = 'button';
          jumpLastFloor.type = 'button';
          jumpNextFloor.title = '跳到上次未读位置';
          jumpLastFloor.title = '跳到上次读到的楼层';
          jumpNextFloor.dataset.spxReadJump = 'next';
          jumpNextFloor.dataset.spxReadTid = tid;
          jumpLastFloor.dataset.spxReadJump = 'last';
          jumpLastFloor.dataset.spxReadTid = tid;
        }

        blockAuthor.addEventListener('click', function addAuthorBlock() {
          if (!author) return;
          settings.authorKeywords = parseLineList(settings.authorKeywords.concat([author]).join('\n'));
          saveSettings(settings);
          enhanceAll(settings, state);
        });

        copyLink.addEventListener('click', function copyPostLink() {
          var hash = getPostAnchorHash(post);
          copyTextToClipboard(location.href.split('#')[0] + hash).catch(function noop() {});
        });

        extractResources.addEventListener('click', function openResources() {
          openResourcePanel(posts, index, author, { state: state });
        });

        quickReply.addEventListener('click', openPostQuickReply);

        tools.appendChild(floor);
        if (jumpNextFloor) tools.appendChild(jumpNextFloor);
        if (jumpLastFloor) tools.appendChild(jumpLastFloor);
        tools.appendChild(quickReply);
        tools.appendChild(blockAuthor);
        tools.appendChild(copyLink);
        tools.appendChild(extractResources);
        toolsHost.insertBefore(tools, toolsHost.firstChild);
      }
    });

    cleanupReadSeparators();
    createReadResourceRail(posts, state);
    enhancePreviewGallery(settings, posts);
    if (settings.foldQuotes) foldLongReadBlocks();
    enhanceAutoBuyPost(settings, state);
  }

  function fallbackCopyText(value) {
    return new Promise(function copyWithSelection(resolve, reject) {
      if (!document.body || typeof document.execCommand !== 'function') {
        reject(new Error('当前浏览器不支持复制'));
        return;
      }
      var textarea = createEl('textarea');
      textarea.value = value;
      textarea.setAttribute('readonly', 'readonly');
      textarea.style.position = 'fixed';
      textarea.style.left = '-9999px';
      textarea.style.top = '0';
      document.body.appendChild(textarea);
      textarea.select();
      var copied = false;
      try {
        copied = document.execCommand('copy');
      } catch (error) {
        copied = false;
      }
      textarea.remove();
      if (copied) resolve();
      else reject(new Error('复制失败'));
    });
  }

  function copyTextToClipboard(value) {
    var text = String(value || '');
    if (!text) return Promise.reject(new Error('没有可复制的地址'));
    if (
      typeof navigator !== 'undefined' &&
      navigator.clipboard &&
      typeof navigator.clipboard.writeText === 'function'
    ) {
      return navigator.clipboard.writeText(text).catch(function fallbackClipboard() {
        return fallbackCopyText(text);
      });
    }
    return fallbackCopyText(text);
  }

  function formatResourceDownloadFileName(timestamp) {
    var rawTime = Number(timestamp);
    var date = new Date(isFinite(rawTime) ? rawTime : Date.now());
    var pad = function padDatePart(value) {
      return String(value).padStart(2, '0');
    };
    return [
      'southplus-resources-',
      date.getFullYear(),
      pad(date.getMonth() + 1),
      pad(date.getDate()),
      '-',
      pad(date.getHours()),
      pad(date.getMinutes()),
      '.txt',
    ].join('');
  }

  function downloadBlobFile(filename, blob) {
    if (
      typeof Blob === 'undefined' ||
      typeof URL === 'undefined' ||
      typeof URL.createObjectURL !== 'function' ||
      !document.body ||
      !blob
    ) return false;
    var href = URL.createObjectURL(blob);
    var link = createEl('a');
    link.href = href;
    link.download = filename || 'download';
    link.style.display = 'none';
    document.body.appendChild(link);
    link.click();
    link.remove();
    window.setTimeout(function revokeObjectUrl() {
      URL.revokeObjectURL(href);
    }, 0);
    return true;
  }

  function downloadTextFile(filename, text) {
    if (typeof Blob === 'undefined') return false;
    return downloadBlobFile(filename || formatResourceDownloadFileName(), new Blob([String(text || '')], { type: 'text/plain;charset=utf-8' }));
  }

  function exportResourceDownloadList(entries) {
    var text = formatResourceDownloadList(entries);
    if (!text) return false;
    return downloadTextFile(formatResourceDownloadFileName(), text);
  }

  function setTemporaryText(node, text, restoreText, delay) {
    if (!node) return;
    node.textContent = text;
    window.setTimeout(function restoreTextLater() {
      if (node.isConnected) node.textContent = restoreText;
    }, delay || 1400);
  }

  function isReadResourceRailCollapsed() {
    var storage = getStorage();
    return !!(storage && storage.getItem(READ_RESOURCE_RAIL_COLLAPSED_KEY) === '1');
  }

  function setReadResourceRailCollapsed(collapsed) {
    var storage = getStorage();
    if (storage) storage.setItem(READ_RESOURCE_RAIL_COLLAPSED_KEY, collapsed ? '1' : '0');
    qsa('#spx-read-resource-rail').forEach(function toggleRail(rail) {
      rail.hidden = !!collapsed;
    });
    qsa('#spx-read-resource-launcher').forEach(function toggleLauncher(launcher) {
      launcher.hidden = !collapsed;
    });
  }

  function closeReadResourceRail() {
    qsa('#spx-read-resource-rail,#spx-read-resource-launcher').forEach(function removeResourceRail(node) {
      node.remove();
    });
  }

  function getCurrentReadResourceRailEntries(posts, state) {
    return getResourceRailEntries(
      extractReadPageResourceLinks(posts || [], typeof location !== 'undefined' ? location.href : ''),
      state && state.resources
    );
  }

  function copyReadResourceRailEntries(entries, target, restoreText) {
    copyTextToClipboard(formatResourceLinks(entries)).then(
      function showReadResourceRailCopySuccess() {
        setTemporaryText(target, '已复制 ' + (entries || []).length + ' 条', restoreText);
      },
      function showReadResourceRailCopyFailure() {
        setTemporaryText(target, '复制失败', restoreText);
      }
    );
  }

  function copyReadResourceRailCodes(entries, target) {
    var text = formatResourceRailCodes(entries);
    if (!text) {
      setTemporaryText(target, '无口令', '复制口令');
      return;
    }
    copyTextToClipboard(text).then(
      function showReadResourceRailCodeCopySuccess() {
        setTemporaryText(target, '已复制口令', '复制口令');
      },
      function showReadResourceRailCodeCopyFailure() {
        setTemporaryText(target, '复制失败', '复制口令');
      }
    );
  }

  function saveReadResourceRailEntries(entries, state, target, restoreText) {
    if (!state) {
      setTemporaryText(target, '无法保存', restoreText || '保存当前');
      return;
    }
    var savedResources = saveResourceLinksToLibrary(entries, state.resources, getCurrentResourceSourceMeta());
    state.resources = savedResources.resources;
    saveResourceLibrary(state.resources);
    refreshResourceCenter();
    refreshReadResourceRail();
    setTemporaryText(target, savedResources.saved ? ('已保存 ' + savedResources.saved + ' 条') : '无可保存资源', restoreText || '保存当前');
  }

  function markReadResourceRailEntry(entry, state, status, target) {
    if (!entry || !state) return;
    var savedResources = saveResourceLinksToLibrary([entry], state.resources, getCurrentResourceSourceMeta());
    state.resources = savedResources.resources;
    updateResourceRecords(state, [entry.key], function markReadRailResource(record) {
      record.status = normalizeResourceStatus(status);
    });
    saveResourceCenterState(state);
    refreshReadResourceRail();
    setTemporaryText(target, getResourceStatusLabel(status), target.textContent || '标记');
  }

  function jumpToReadResourceRailEntry(entry, posts) {
    var post = posts && posts[Number(entry && entry.postIndex) || 0];
    if (!post) return;
    post.classList.add('spx-read-resource-floor-active');
    if (typeof post.scrollIntoView === 'function') {
      post.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }
    window.setTimeout(function clearResourceFloorHighlight() {
      if (post.isConnected) post.classList.remove('spx-read-resource-floor-active');
    }, 1800);
  }

  function createReadResourceRail(posts, state) {
    if (detectPageType(location.href) !== 'read' || !document.body) return null;
    readResourceRailContext.posts = posts || [];
    readResourceRailContext.state = state;
    closeReadResourceRail();

    var entries = getCurrentReadResourceRailEntries(posts, state);
    if (!entries.length) return null;

    var rail = createEl('aside', 'spx-read-resource-rail');
    var launcher = createEl('button', 'spx-read-resource-launcher', '资源 ' + entries.length);
    rail.id = 'spx-read-resource-rail';
    rail.setAttribute('role', 'complementary');
    rail.setAttribute('aria-label', '当前帖子资源');
    launcher.id = 'spx-read-resource-launcher';
    launcher.type = 'button';
    launcher.title = '展开当前帖资源栏';

    function setRailAction(button, action, key, status) {
      button.dataset.spxRailAction = action;
      if (key) button.dataset.spxResourceKey = key;
      if (status) button.dataset.spxResourceStatus = status;
      return button;
    }

    function getRailEntryByKey(key) {
      return (rail.spxEntriesByKey || {})[String(key || '')] || null;
    }

    function handleReadResourceRailClick(event) {
      var target = event.target && event.target.closest
        ? event.target.closest('[data-spx-rail-action]')
        : null;
      if (!target || !rail.contains(target)) return;
      var action = target.dataset.spxRailAction;
      var visibleEntries = rail.spxVisibleEntries || [];
      var entry = getRailEntryByKey(target.dataset.spxResourceKey);
      var card = target.closest ? target.closest('.spx-read-resource-card') : null;

      if (action === 'collapse') {
        setReadResourceRailCollapsed(true);
        return;
      }
      if (action === 'copy-all') {
        copyReadResourceRailEntries(visibleEntries, target, '复制全部');
        return;
      }
      if (action === 'save-visible') {
        saveReadResourceRailEntries(visibleEntries, readResourceRailContext.state, target, '保存当前');
        return;
      }
      if (action === 'copy-codes') {
        copyReadResourceRailCodes(visibleEntries, target);
        return;
      }
      if (action === 'filter') {
        readResourceRailContext.filter = target.dataset.spxResourceRailFilter || 'all';
        renderRail();
        return;
      }
      if (!entry) return;
      if (action === 'jump') {
        if (card) card.classList.add('spx-active');
        jumpToReadResourceRailEntry(entry, readResourceRailContext.posts);
        window.setTimeout(function clearActiveCard() {
          if (card && card.isConnected) card.classList.remove('spx-active');
        }, 1800);
        return;
      }
      if (action === 'copy-one') {
        copyReadResourceRailEntries([entry], target, '复制');
        return;
      }
      if (action === 'save-one') {
        saveReadResourceRailEntries([entry], readResourceRailContext.state, target, entry.saved ? '已保存' : '保存');
        return;
      }
      if (action === 'status') {
        markReadResourceRailEntry(entry, readResourceRailContext.state, target.dataset.spxResourceStatus, target);
      }
    }

    function renderRail() {
      var allEntries = getCurrentReadResourceRailEntries(readResourceRailContext.posts, readResourceRailContext.state);
      if (!allEntries.length) {
        closeReadResourceRail();
        return;
      }
      var availableTypes = getAvailableResourceRailFilterTypes(allEntries);
      if (readResourceRailContext.filter !== 'all' && availableTypes.indexOf(readResourceRailContext.filter) === -1) {
        readResourceRailContext.filter = 'all';
      }
      var visibleEntries = filterResourceRailEntries(allEntries, readResourceRailContext.filter);
      var entriesByKey = {};
      rail.textContent = '';
      rail.spxVisibleEntries = visibleEntries;
      visibleEntries.forEach(function indexVisibleRailEntry(entry) {
        entriesByKey[entry.key] = entry;
      });
      rail.spxEntriesByKey = entriesByKey;
      launcher.textContent = '资源 ' + allEntries.length;

      var header = createEl('div', 'spx-read-resource-rail-head');
      var title = createEl('div');
      title.appendChild(createEl('strong', '', '当前帖资源'));
      title.appendChild(createEl('span', 'spx-read-resource-summary', formatResourceRailSummaryText(allEntries)));
      var collapseButton = createEl('button', '', '收起');
      collapseButton.type = 'button';
      setRailAction(collapseButton, 'collapse');
      header.appendChild(title);
      header.appendChild(collapseButton);
      rail.appendChild(header);

      var actions = createEl('div', 'spx-read-resource-actions');
      var copyAllButton = createEl('button', '', '复制全部');
      var saveButton = createEl('button', '', '保存当前');
      var copyCodeButton = createEl('button', '', '复制口令');
      [copyAllButton, saveButton, copyCodeButton].forEach(function setRailActionType(button) {
        button.type = 'button';
        actions.appendChild(button);
      });
      setRailAction(copyAllButton, 'copy-all');
      setRailAction(saveButton, 'save-visible');
      setRailAction(copyCodeButton, 'copy-codes');
      copyAllButton.disabled = !visibleEntries.length;
      saveButton.disabled = !getJumpResourceLinks(visibleEntries).length;
      copyCodeButton.disabled = !visibleEntries.some(function hasCode(entry) { return !!entry.accessCode; });
      rail.appendChild(actions);

      var filters = createEl('div', 'spx-read-resource-filters');
      [{ value: 'all', label: '全部' }].concat(availableTypes.map(function mapResourceRailType(type) {
        return { value: type, label: getResourceRailTypeLabel(type) };
      })).forEach(function appendResourceRailFilter(item) {
        var button = createEl('button', item.value === readResourceRailContext.filter ? 'spx-active' : '', item.label);
        button.type = 'button';
        button.dataset.spxResourceRailFilter = item.value;
        setRailAction(button, 'filter');
        filters.appendChild(button);
      });
      rail.appendChild(filters);

      var list = createEl('div', 'spx-read-resource-list');
      if (!visibleEntries.length) {
        list.appendChild(createEl('div', 'spx-watch-empty', '没有匹配的资源。'));
      }
      visibleEntries.forEach(function appendReadResourceRailEntry(entry) {
        var item = createEl('article', 'spx-read-resource-card');
        item.dataset.key = entry.key;
        item.dataset.type = entry.type;
        var top = createEl('div', 'spx-read-resource-card-top');
        top.appendChild(createEl('span', 'spx-read-resource-type', entry.typeLabel));
        top.appendChild(createEl('span', 'spx-status-badge spx-status-' + entry.status, entry.statusLabel));
        item.appendChild(top);
        var link = createEl('a', 'spx-read-resource-url', entry.url);
        link.href = entry.url;
        link.target = '_blank';
        link.rel = 'noreferrer';
        link.title = entry.url;
        item.appendChild(link);
        var metaParts = [entry.floorLabel, entry.author, entry.accessCode ? ('提取码 ' + entry.accessCode) : '', entry.note ? ('备注 ' + entry.note) : ''];
        item.appendChild(createEl('div', 'spx-read-resource-meta', metaParts.filter(Boolean).join(' · ') || '暂无来源'));
        var itemActions = createEl('div', 'spx-read-resource-card-actions');
        var jumpButton = createEl('button', '', '定位楼层');
        var copyButton = createEl('button', '', '复制');
        var saveOneButton = createEl('button', '', entry.saved ? '已保存' : '保存');
        var todoButton = createEl('button', '', '待下载');
        var doneButton = createEl('button', '', '已处理');
        var invalidButton = createEl('button', '', '失效');
        [jumpButton, copyButton, saveOneButton, todoButton, doneButton, invalidButton].forEach(function setupResourceCardButton(button) {
          button.type = 'button';
          itemActions.appendChild(button);
        });
        setRailAction(jumpButton, 'jump', entry.key);
        setRailAction(copyButton, 'copy-one', entry.key);
        setRailAction(saveOneButton, 'save-one', entry.key);
        setRailAction(todoButton, 'status', entry.key, 'todo');
        setRailAction(doneButton, 'status', entry.key, 'done');
        setRailAction(invalidButton, 'status', entry.key, 'invalid');
        saveOneButton.classList.toggle('spx-action-secondary', entry.saved);
        item.appendChild(itemActions);
        list.appendChild(item);
      });
      rail.appendChild(list);
      setReadResourceRailCollapsed(isReadResourceRailCollapsed());
    }

    rail.spxRender = renderRail;
    rail.addEventListener('click', handleReadResourceRailClick);
    launcher.addEventListener('click', function expandReadResourceRail() {
      setReadResourceRailCollapsed(false);
    });
    document.body.appendChild(rail);
    document.body.appendChild(launcher);
    renderRail();
    return rail;
  }

  function refreshReadResourceRail() {
    var rail = qs('#spx-read-resource-rail');
    if (rail && typeof rail.spxRender === 'function') {
      rail.spxRender();
      return;
    }
    if (readResourceRailContext.posts && readResourceRailContext.posts.length) {
      createReadResourceRail(readResourceRailContext.posts, readResourceRailContext.state);
    }
  }

  function closeResourcePanel() {
    var panel = qs('#spx-resource-panel');
    if (panel) panel.remove();
  }

  function closeAutoBuyResourceJump() {
    var panel = qs('#spx-auto-resource-jump');
    if (panel) panel.remove();
  }

  function mountAutoBuyResourceJump(panel) {
    if (!panel) return;
    var content = qs('#content') || qs('#main') || document.body;
    var firstPost = qsa('table.js-post', content)[0];
    if (firstPost && firstPost.parentNode) {
      firstPost.parentNode.insertBefore(panel, firstPost);
      return;
    }
    content.insertBefore(panel, content.firstChild);
  }

  function showAutoBuyResourceJump(links, state, savedCount) {
    var jumpLinks = getJumpResourceLinks(links);
    closeAutoBuyResourceJump();
    if (!jumpLinks.length || !document.body) return null;

    var panel = createEl('div', 'spx-auto-resource-jump');
    var title = createEl('strong', '', '购买完成,识别到资源:' + formatResourceJumpSummary(jumpLinks) + (savedCount ? ' · 已存资源库' : ''));
    var actions = createEl('div', 'spx-auto-resource-actions');
    var copyButton = createEl('button', '', '复制全部资源');
    var detailButton = createEl('button', '', '资源面板');
    var centerButton = createEl('button', '', '资源工作台');

    panel.id = 'spx-auto-resource-jump';
    copyButton.type = 'button';
    detailButton.type = 'button';
    centerButton.type = 'button';

    jumpLinks.slice(0, 8).forEach(function appendJumpLink(item, index) {
      var label = item.label || getResourceDisplayLabel(item);
      var link = createEl('a', '', label + (item.accessCode ? ' 提取码' : '') + ' #' + (index + 1));
      link.href = item.url;
      link.target = '_blank';
      link.rel = 'noreferrer';
      link.title = item.url + (item.accessCode ? ' 提取码:' + item.accessCode : '');
      actions.appendChild(link);
    });

    copyButton.addEventListener('click', function copyAutoBuyResources() {
      copyButton.disabled = true;
      copyTextToClipboard(formatResourceLinks(jumpLinks)).then(
        function showCopySuccess() {
          copyButton.disabled = false;
          setTemporaryText(copyButton, '已复制 ' + jumpLinks.length + ' 条', '复制全部资源');
        },
        function showCopyFailure() {
          copyButton.disabled = false;
          setTemporaryText(copyButton, '复制失败', '复制全部资源');
        }
      );
    });
    detailButton.addEventListener('click', function openAllAutoBuyResources() {
      openResourcePanel(qsa('table.js-post'), 0, '', { defaultScope: 'all', state: state });
    });
    centerButton.addEventListener('click', function openAutoBuyResourceCenter() {
      var panel = createResourceCenterPanel(null, state || { resources: loadResourceLibrary() });
      if (panel.spxRender) panel.spxRender();
      setCenterPanelHidden(panel, false, '[data-spx-resource-center-button="1"]');
    });

    actions.appendChild(copyButton);
    actions.appendChild(detailButton);
    actions.appendChild(centerButton);
    panel.appendChild(title);
    panel.appendChild(actions);
    mountAutoBuyResourceJump(panel);
    return panel;
  }

  function openResourcePanel(posts, currentPostIndex, currentAuthor, options) {
    if (!document.body) return;
    var config = options || {};
    var allLinks = extractReadPageResourceLinks(posts, location.href);
    closeResourcePanel();

    var panel = createEl('div', 'spx-watch-center spx-resource-panel');
    panel.id = 'spx-resource-panel';
    var header = createEl('div', 'spx-watch-center-header');
    var title = createEl('h3', '', '资源链接');
    var summary = createEl('span', 'spx-watch-summary');
    var closeButton = createEl('button', '', '关闭');
    var controls = createEl('div', 'spx-watch-controls');
    var scopeSelect = createEl('select');
    var typeSelect = createEl('select');
    var list = createEl('div', 'spx-watch-list');
    var copyButton = createEl('button', '', '复制当前');
    var saveButton = createEl('button', '', '保存当前');
    var visibleLinks = [];

    closeButton.type = 'button';
    closeButton.addEventListener('click', closeResourcePanel);
    header.appendChild(title);
    header.appendChild(summary);
    header.appendChild(closeButton);

    [
      { value: 'all', text: '全部楼层' },
      { value: 'floor', text: '仅本楼' },
      { value: 'author', text: currentAuthor ? '仅此作者' : '仅此作者' },
    ].forEach(function appendScopeOption(option) {
      var item = createEl('option', '', option.text);
      item.value = option.value;
      if (option.value === (config.defaultScope || 'floor')) item.selected = true;
      if (option.value === 'author' && !currentAuthor) item.disabled = true;
      scopeSelect.appendChild(item);
    });

    [
      { value: 'all', text: '全部类型' },
      { value: 'magnet', text: '磁力' },
      { value: 'ed2k', text: '电驴' },
      { value: 'torrent', text: '种子' },
      { value: 'archive', text: '压缩包' },
      { value: 'cloud', text: '网盘' },
      { value: 'image', text: '图片' },
      { value: 'external', text: '外链' },
    ].forEach(function appendTypeOption(option) {
      var item = createEl('option', '', option.text);
      item.value = option.value;
      if (option.value === config.defaultCategory) item.selected = true;
      typeSelect.appendChild(item);
    });

    controls.appendChild(scopeSelect);
    controls.appendChild(typeSelect);
    controls.appendChild(copyButton);
    controls.appendChild(saveButton);

    function renderLinks() {
      visibleLinks = filterResourceLinks(allLinks, {
        scope: scopeSelect.value,
        category: typeSelect.value,
        postIndex: currentPostIndex,
        author: currentAuthor,
      });
      list.textContent = '';
      summary.textContent = '当前 ' + visibleLinks.length + ' / 全部 ' + allLinks.length + ' 条';
      copyButton.disabled = !visibleLinks.length;
      saveButton.disabled = !getJumpResourceLinks(visibleLinks).length;

      if (!visibleLinks.length) {
        list.appendChild(createEl('div', 'spx-watch-empty', '没有匹配的资源链接。'));
        return;
      }

      visibleLinks.forEach(function appendResourceLink(item) {
        var row = createEl('div', 'spx-watch-item');
        var meta = createEl(
          'div',
          'spx-watch-meta',
          '[' + (item.label || getResourceDisplayLabel(item)) + '] ' +
            [item.floorLabel, item.author].filter(Boolean).join(' · ')
        );
        var link = createEl('a', 'spx-resource-url', item.url);
        link.href = item.url;
        link.target = '_blank';
        link.rel = 'noreferrer';
        row.appendChild(meta);
        if (item.accessCode) {
          row.appendChild(createEl('div', 'spx-watch-meta', '提取码:' + item.accessCode));
        }
        row.appendChild(link);
        list.appendChild(row);
      });
    }

    scopeSelect.addEventListener('change', renderLinks);
    typeSelect.addEventListener('change', renderLinks);
    copyButton.addEventListener('click', function copyVisibleResourceLinks() {
      copyButton.disabled = true;
      copyTextToClipboard(formatResourceLinks(visibleLinks)).then(
        function showCopySuccess() {
          copyButton.disabled = false;
          setTemporaryText(copyButton, '已复制 ' + visibleLinks.length + ' 条', '复制当前');
        },
        function showCopyFailure() {
          copyButton.disabled = false;
          setTemporaryText(copyButton, '复制失败', '复制当前');
        }
      );
    });
    saveButton.addEventListener('click', function saveVisibleResourceLinks() {
      var targetState = config.state;
      if (!targetState) {
        setTemporaryText(saveButton, '无法保存', '保存当前');
        return;
      }
      var savedResources = saveResourceLinksToLibrary(visibleLinks, targetState.resources, getCurrentResourceSourceMeta());
      targetState.resources = savedResources.resources;
      saveResourceLibrary(targetState.resources);
      refreshResourceCenter();
      refreshReadResourceRail();
      setTemporaryText(saveButton, savedResources.saved ? ('已保存 ' + savedResources.saved + ' 条') : '无可保存资源', '保存当前');
    });

    panel.appendChild(header);
    panel.appendChild(controls);
    panel.appendChild(list);
    document.body.appendChild(panel);
    renderLinks();
  }

  function closePreviewLightbox() {
    var lightbox = qs('#spx-preview-lightbox');
    if (!lightbox) return;
    if (typeof lightbox.spxClose === 'function') {
      lightbox.spxClose();
      return;
    }
    lightbox.remove();
  }

  function openPreviewLightbox(items, initialIndex) {
    var images = (items || []).filter(function hasPreviewSource(item) {
      return item && item.src;
    });
    if (!images.length || typeof document === 'undefined' || !document.body) return;

    closePreviewLightbox();

    var currentIndex = Math.min(
      images.length - 1,
      Math.max(0, Number(initialIndex) || 0)
    );
    var zoom = 1;
    var previousBodyOverflow = document.body.style.overflow || '';
    var copyStatusTimer = null;
    var closed = false;

    var lightbox = createEl('div', 'spx-preview-lightbox');
    lightbox.id = 'spx-preview-lightbox';
    lightbox.setAttribute('role', 'dialog');
    lightbox.setAttribute('aria-modal', 'true');
    lightbox.setAttribute('aria-label', '预览图灯箱');

    var shell = createEl('div', 'spx-preview-lightbox-shell');
    var toolbar = createEl('div', 'spx-preview-lightbox-toolbar');
    var counter = createEl('span', 'spx-preview-lightbox-counter');
    var actions = createEl('div', 'spx-preview-lightbox-actions');
    var stage = createEl('div', 'spx-preview-lightbox-stage');
    var viewport = createEl('div', 'spx-preview-lightbox-viewport');
    var canvas = createEl('div', 'spx-preview-lightbox-canvas');
    var image = createEl('img', 'spx-preview-lightbox-image');
    var caption = createEl('div', 'spx-preview-lightbox-caption');
    var urlText = createEl('span', 'spx-preview-lightbox-url');
    var help = createEl(
      'span',
      'spx-preview-lightbox-help',
      '←/→ 切图 · +/- 缩放 · 0 重置 · Esc 关闭'
    );
    var strip = createEl('div', 'spx-preview-lightbox-strip');

    function createActionButton(text, title, className, onClick) {
      var button = createEl('button', className || '', text);
      button.type = 'button';
      button.title = title;
      button.addEventListener('click', onClick);
      return button;
    }

    var zoomOutButton = createActionButton('−', '缩小(快捷键 -)', '', function zoomOut() {
      setZoom(zoom - 0.25);
    });
    var zoomText = createEl('span', 'spx-preview-lightbox-zoom', '100%');
    var zoomInButton = createActionButton('+', '放大(快捷键 +)', '', function zoomIn() {
      setZoom(zoom + 0.25);
    });
    var resetButton = createActionButton('重置', '恢复适应窗口大小(快捷键 0)', '', function resetZoom() {
      setZoom(1);
    });
    var copyButton = createActionButton('复制地址', '复制当前原图地址', '', function copyUrl() {
      copyButton.disabled = true;
      copyTextToClipboard(images[currentIndex].src).then(
        function showCopySuccess() {
          copyButton.textContent = '已复制';
          copyButton.disabled = false;
          clearTimeout(copyStatusTimer);
          copyStatusTimer = setTimeout(function restoreCopyButton() {
            if (copyButton.isConnected) copyButton.textContent = '复制地址';
          }, 1400);
        },
        function showCopyFailure() {
          copyButton.textContent = '复制失败';
          copyButton.disabled = false;
          clearTimeout(copyStatusTimer);
          copyStatusTimer = setTimeout(function restoreCopyButton() {
            if (copyButton.isConnected) copyButton.textContent = '复制地址';
          }, 1400);
        }
      );
    });
    var closeButton = createActionButton('关闭', '关闭灯箱(快捷键 Esc)', '', close);
    var previousButton = createActionButton(
      '‹',
      '上一张(快捷键 ←)',
      'spx-preview-lightbox-nav spx-preview-lightbox-prev',
      function showPrevious() {
        showImage(currentIndex - 1);
      }
    );
    var nextButton = createActionButton(
      '›',
      '下一张(快捷键 →)',
      'spx-preview-lightbox-nav spx-preview-lightbox-next',
      function showNext() {
        showImage(currentIndex + 1);
      }
    );

    function applyImageSize() {
      var naturalWidth = Number(image.naturalWidth || images[currentIndex].naturalWidth || 0);
      var naturalHeight = Number(image.naturalHeight || images[currentIndex].naturalHeight || 0);
      if (!naturalWidth || !naturalHeight || !viewport.clientWidth || !viewport.clientHeight) return;

      var sidePadding = window.innerWidth <= 760 ? 104 : 172;
      var verticalPadding = window.innerWidth <= 760 ? 52 : 76;
      var availableWidth = Math.max(120, viewport.clientWidth - sidePadding);
      var availableHeight = Math.max(120, viewport.clientHeight - verticalPadding);
      var fitScale = Math.min(
        availableWidth / naturalWidth,
        availableHeight / naturalHeight,
        1
      );
      var displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * zoom));
      var displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * zoom));
      var canvasPadding = window.innerWidth <= 760 ? 100 : 156;

      image.style.width = displayWidth + 'px';
      image.style.height = displayHeight + 'px';
      canvas.style.width = Math.max(viewport.clientWidth, displayWidth + canvasPadding) + 'px';
      canvas.style.height = Math.max(viewport.clientHeight, displayHeight + 72) + 'px';

      window.requestAnimationFrame(function centerZoomedImage() {
        viewport.scrollLeft = Math.max(0, (canvas.scrollWidth - viewport.clientWidth) / 2);
        viewport.scrollTop = Math.max(0, (canvas.scrollHeight - viewport.clientHeight) / 2);
      });
    }

    function setZoom(nextZoom) {
      zoom = clampPreviewZoom(nextZoom);
      zoomText.textContent = Math.round(zoom * 100) + '%';
      applyImageSize();
    }

    function showImage(nextIndex) {
      currentIndex = (nextIndex + images.length) % images.length;
      var nextSrc = images[currentIndex].src;
      zoom = 1;
      zoomText.textContent = '100%';
      counter.textContent = '图片 ' + (currentIndex + 1) + ' / ' + images.length;
      urlText.textContent = nextSrc;
      urlText.title = nextSrc;
      copyButton.textContent = '复制地址';
      image.alt = '预览图 ' + (currentIndex + 1);
      image.dataset.spxPreviewReady = '0';
      image.onload = function handleLightboxImageLoad() {
        markPreviewImageLoaded(image);
        applyImageSize();
      };
      image.onerror = function showImageError() {
        markPreviewImageLoaded(image);
        urlText.textContent = '原图加载失败:' + nextSrc;
      };
      image.src = nextSrc;
      if (image.complete && image.naturalWidth) {
        markPreviewImageLoaded(image);
        applyImageSize();
      }
      qsa('.spx-preview-lightbox-thumb', strip).forEach(function syncThumb(button, thumbIndex) {
        var active = thumbIndex === currentIndex;
        button.classList.toggle('spx-active', active);
        button.setAttribute('aria-current', active ? 'true' : 'false');
        if (active && button.scrollIntoView) button.scrollIntoView({ block: 'nearest', inline: 'center' });
      });
    }

    function handleKeydown(event) {
      var action = getPreviewLightboxKeyAction(event);
      if (!action) return;
      event.preventDefault();
      if (action === 'close') close();
      else if (action === 'previous') showImage(currentIndex - 1);
      else if (action === 'next') showImage(currentIndex + 1);
      else if (action === 'zoomIn') setZoom(zoom + 0.25);
      else if (action === 'zoomOut') setZoom(zoom - 0.25);
      else if (action === 'zoomReset') setZoom(1);
    }

    function handleResize() {
      applyImageSize();
    }

    function close() {
      if (closed) return;
      closed = true;
      clearTimeout(copyStatusTimer);
      document.removeEventListener('keydown', handleKeydown, true);
      window.removeEventListener('resize', handleResize);
      document.body.style.overflow = previousBodyOverflow;
      lightbox.remove();
    }

    lightbox.spxClose = close;
    lightbox.addEventListener('click', function closeFromBackdrop(event) {
      if (event.target === lightbox) close();
    });

    actions.appendChild(zoomOutButton);
    actions.appendChild(zoomText);
    actions.appendChild(zoomInButton);
    actions.appendChild(resetButton);
    actions.appendChild(copyButton);
    actions.appendChild(closeButton);
    toolbar.appendChild(counter);
    toolbar.appendChild(actions);
    canvas.appendChild(image);
    viewport.appendChild(canvas);
    stage.appendChild(viewport);
    stage.appendChild(previousButton);
    stage.appendChild(nextButton);
    caption.appendChild(urlText);
    caption.appendChild(help);
    shell.appendChild(toolbar);
    shell.appendChild(stage);
    shell.appendChild(caption);
    images.forEach(function appendLightboxThumb(item, index) {
      var thumbButton = createEl('button', 'spx-preview-lightbox-thumb');
      var thumbImage = createEl('img');
      thumbButton.type = 'button';
      thumbButton.title = '查看第 ' + (index + 1) + ' 张';
      thumbButton.dataset.spxPreviewThumbIndex = String(index);
      thumbImage.loading = 'lazy';
      thumbImage.decoding = 'async';
      thumbImage.alt = '预览缩略图 ' + (index + 1);
      preparePreviewImageReveal(thumbImage);
      thumbImage.src = item.src;
      if (thumbImage.complete && thumbImage.naturalWidth) markPreviewImageLoaded(thumbImage);
      thumbButton.appendChild(thumbImage);
      strip.appendChild(thumbButton);
    });
    strip.addEventListener('click', function handleLightboxThumbClick(event) {
      var thumb = event.target && event.target.closest
        ? event.target.closest('[data-spx-preview-thumb-index]')
        : null;
      if (!thumb || !strip.contains(thumb)) return;
      showImage(Number(thumb.dataset.spxPreviewThumbIndex) || 0);
    });
    if (images.length > 1) shell.appendChild(strip);
    lightbox.appendChild(shell);
    document.body.appendChild(lightbox);

    previousButton.hidden = images.length < 2;
    nextButton.hidden = images.length < 2;
    document.body.style.overflow = 'hidden';
    document.addEventListener('keydown', handleKeydown, true);
    window.addEventListener('resize', handleResize);
    showImage(currentIndex);
    closeButton.focus();
  }

  function enhancePreviewGallery(settings, posts) {
    restorePreviewGallery();
    if (!settings.unifiedPreviewGallery || !posts || !posts.length) return;

    var firstPost = posts[0];
    var content = qs('.tpc_content', firstPost);
    if (!content) return;

    var panel = createEl('section', 'spx-preview-panel spx-preview-drawer spx-preview-collapsed');
    panel.id = 'spx-preview-panel';
    var header = createEl('div', 'spx-preview-header');
    var title = createEl('strong', '', '预览图');
    var summary = createEl('span', 'spx-preview-summary');
    var actions = createEl('div', 'spx-preview-actions');
    var downloadAllButton = createEl('button', '', '下载全部');
    var copyAllButton = createEl('button', '', '复制全部链接');
    var copyFloorButton = createEl('button', '', '按楼层复制');
    var copyMarkdownButton = createEl('button', '', '复制Markdown');
    var largeOnlyButton = createEl('button', '', '只看大图');
    var masonryButton = createEl('button', '', '瀑布流');
    var drawerButton = createEl('button', '', '侧栏');
    var collapseDrawerButton = createEl('button', '', '收起');
    var downloadPanel = createEl('section', 'spx-preview-download');
    var downloadTop = createEl('div', 'spx-preview-download-top');
    var downloadTitle = createEl('strong', '', '下载准备中');
    var downloadPercent = createEl('span', 'spx-preview-download-percent', '0%');
    var downloadTrack = createEl('div', 'spx-preview-download-track');
    var downloadProgress = createEl('span', 'spx-preview-download-progress');
    var downloadCompact = createEl('div', 'spx-preview-download-compact');
    var downloadBadges = createEl('div', 'spx-preview-download-badges');
    var downloadActions = createEl('div', 'spx-preview-download-actions');
    var packDownloadedButton = createEl('button', 'spx-primary', '打包已完成');
    var retryFailedButton = createEl('button', '', '重试失败');
    var cancelDownloadButton = createEl('button', '', '取消');
    var downloadDetail = createEl('details', 'spx-preview-download-detail');
    var downloadDetailSummary = createEl('summary');
    var downloadDetailText = createEl('span', 'spx-preview-download-detail-text');
    var downloadQueue = createEl('div', 'spx-preview-download-queue');
    var grid = createEl('div', 'spx-preview-grid');
    var loadMoreButton = createEl('button', 'spx-preview-load-more', '加载更多图片');
    var drawerTab = createEl('button', 'spx-preview-drawer-tab', '预览图');
    var showLargeOnly = false;
    var previewImages = null;
    var visiblePreviewImages = [];
    var previewDownloadState = null;
    var renderedPreviewLimit = PREVIEW_GALLERY_BATCH_SIZE;
    var idleScanId = null;
    var idleScanType = '';

    downloadAllButton.type = 'button';
    copyAllButton.type = 'button';
    copyFloorButton.type = 'button';
    copyMarkdownButton.type = 'button';
    largeOnlyButton.type = 'button';
    masonryButton.type = 'button';
    drawerButton.type = 'button';
    collapseDrawerButton.type = 'button';
    packDownloadedButton.type = 'button';
    retryFailedButton.type = 'button';
    cancelDownloadButton.type = 'button';
    loadMoreButton.type = 'button';
    drawerTab.type = 'button';
    downloadAllButton.title = '下载当前筛选范围内的全部原图并打包为 ZIP';
    copyAllButton.title = '复制当前筛选范围内的全部原图地址';
    copyFloorButton.title = '按楼层分组复制当前筛选范围内的原图地址';
    copyMarkdownButton.title = '复制当前筛选范围内的 Markdown 图片清单';
    largeOnlyButton.title = '只显示尺寸较大的预览图';
    masonryButton.title = '切换瀑布流缩略图排列';
    drawerButton.title = '将预览图固定到右侧侧栏';
    collapseDrawerButton.title = '收起右侧预览图侧栏';
    packDownloadedButton.title = '跳过仍失败的图片,只将已下载图片打包为 ZIP';
    retryFailedButton.title = '重新下载失败图片,单轮最多重试 6 次';
    cancelDownloadButton.title = '取消当前批量下载';
    drawerTab.title = '展开预览图侧栏';
    loadMoreButton.title = '继续加载下一批预览图';
    largeOnlyButton.setAttribute('aria-pressed', 'false');
    masonryButton.setAttribute('aria-pressed', 'false');
    drawerButton.setAttribute('aria-pressed', 'false');
    collapseDrawerButton.hidden = true;
    downloadPanel.hidden = true;
    downloadTrack.appendChild(downloadProgress);
    downloadTop.appendChild(downloadTitle);
    downloadTop.appendChild(downloadPercent);
    downloadActions.appendChild(packDownloadedButton);
    downloadActions.appendChild(retryFailedButton);
    downloadActions.appendChild(cancelDownloadButton);
    downloadCompact.appendChild(downloadBadges);
    downloadCompact.appendChild(downloadActions);
    downloadDetailSummary.appendChild(downloadDetailText);
    downloadDetail.appendChild(downloadDetailSummary);
    downloadDetail.appendChild(downloadQueue);
    downloadPanel.appendChild(downloadTop);
    downloadPanel.appendChild(downloadTrack);
    downloadPanel.appendChild(downloadCompact);
    downloadPanel.appendChild(downloadDetail);

    function cancelIdlePreviewScan() {
      if (idleScanId === null) return;
      if (idleScanType === 'idle' && typeof window.cancelIdleCallback === 'function') {
        window.cancelIdleCallback(idleScanId);
      } else {
        window.clearTimeout(idleScanId);
      }
      idleScanId = null;
      idleScanType = '';
    }

    function scanPreviewImages() {
      if (previewImages) return previewImages;
      cancelIdlePreviewScan();
      var nextImages = [];
      posts.forEach(function collectPostImages(post, postIndex) {
        var postContent = qs('.tpc_content', post);
        if (!postContent) return;
        var floorLabel = getPostFloorLabel(postIndex);
        var author = getPostAuthor(post);
        qsa('img', postContent).forEach(function collectImage(img) {
          var item = {
            node: img,
            src: img.currentSrc || img.src,
            naturalWidth: img.naturalWidth,
            naturalHeight: img.naturalHeight,
            width: img.width,
            height: img.height,
            className: img.className,
            alt: img.alt,
            postIndex: postIndex,
            floorLabel: floorLabel,
            author: author,
          };
          if (isPreviewImageCandidate(item)) nextImages.push(item);
        });
      });

      var seen = {};
      previewImages = nextImages.filter(function uniqueImage(item) {
        if (!item.src || seen[item.src]) return false;
        seen[item.src] = true;
        return true;
      });
      visiblePreviewImages = previewImages.slice();
      return previewImages;
    }

    function getVisiblePreviewImages() {
      var images = previewImages || [];
      return showLargeOnly ? images.filter(isLargePreviewImage) : images.slice();
    }

    function scheduleIdlePreviewScan() {
      if (idleScanId !== null) return;
      var callback = function runIdlePreviewScan() {
        idleScanId = null;
        idleScanType = '';
        if (!panel.isConnected) return;
        var images = scanPreviewImages();
        if (!images.length && panel.classList.contains('spx-preview-collapsed')) {
          restorePreviewGallery();
          return;
        }
        visiblePreviewImages = getVisiblePreviewImages();
        if (panel.classList.contains('spx-preview-collapsed')) syncPreviewHeader();
        else renderPreviewGrid();
      };
      if (typeof window.requestIdleCallback === 'function') {
        idleScanType = 'idle';
        idleScanId = window.requestIdleCallback(callback, { timeout: 2200 });
      } else {
        idleScanType = 'timer';
        idleScanId = window.setTimeout(callback, 1200);
      }
    }

    function setPreviewButtonText(button, text, delay) {
      clearTimeout(button.spxPreviewTimer);
      if (!button.dataset.spxOriginalText) button.dataset.spxOriginalText = button.textContent;
      button.textContent = text;
      button.spxPreviewTimer = setTimeout(function restorePreviewButtonText() {
        if (button.isConnected) button.textContent = button.dataset.spxOriginalText || '复制全部链接';
        delete button.dataset.spxOriginalText;
      }, delay || 1400);
    }

    function copyPreviewText(button, text, count) {
      button.disabled = true;
      copyTextToClipboard(text).then(
        function showCopySuccess() {
          button.disabled = false;
          setPreviewButtonText(button, '已复制 ' + count + ' 条');
        },
        function showCopyFailure() {
          button.disabled = false;
          setPreviewButtonText(button, '复制失败');
        }
      );
    }

    function getPreviewDownloadCounts() {
      var entries = previewDownloadState && previewDownloadState.entries ? previewDownloadState.entries : [];
      return entries.reduce(function countPreviewDownload(result, entry) {
        result.total += 1;
        if (entry.status === 'done') result.done += 1;
        else if (entry.status === 'failed') result.failed += 1;
        else if (entry.status === 'downloading' || entry.status === 'retrying') result.active += 1;
        else if (entry.status === 'queued') result.queued += 1;
        else if (entry.status === 'cancelled') result.cancelled += 1;
        return result;
      }, { total: 0, done: 0, failed: 0, active: 0, queued: 0, cancelled: 0, packing: !!(previewDownloadState && previewDownloadState.packing) });
    }

    function getPreviewDownloadProgressPercent() {
      var entries = previewDownloadState && previewDownloadState.entries ? previewDownloadState.entries : [];
      if (!entries.length) return 0;
      var total = entries.reduce(function sumPreviewDownloadProgress(sum, entry) {
        if (entry.status === 'done') return sum + 100;
        if (entry.status === 'failed' || entry.status === 'cancelled') return sum;
        return sum + Math.max(0, Math.min(99, Number(entry.progress) || 0));
      }, 0);
      return Math.round(total / entries.length);
    }

    function createPreviewDownloadBadge(text, className) {
      return createEl('span', ['spx-preview-download-badge', className || ''].filter(Boolean).join(' '), text);
    }

    function getPreviewDownloadEntryStatusText(entry) {
      if (!entry) return '';
      if (entry.status === 'done') return '已完成';
      if (entry.status === 'failed') return entry.attempts >= PREVIEW_DOWNLOAD_MAX_RETRIES ? '已达上限' : '失败';
      if (entry.status === 'cancelled') return '已取消';
      if (entry.status === 'retrying') return '重试中';
      if (entry.status === 'downloading') return (entry.progress || 0) + '%';
      return '等待';
    }

    function renderPreviewDownloadQueue() {
      if (!previewDownloadState) return;
      var counts = getPreviewDownloadCounts();
      var activeEntries = previewDownloadState.entries.filter(function keepVisiblePreviewDownload(entry) {
        return entry.status !== 'done';
      });
      downloadQueue.textContent = '';

      activeEntries.forEach(function appendPreviewDownloadEntry(entry) {
        var row = createEl('div', 'spx-preview-download-row');
        var main = createEl('div');
        var name = createEl('div', 'spx-preview-download-name');
        var label = createEl('b', '', entry.fileName || formatPreviewImageDownloadFileName(entry.item, entry.index, entry.contentType));
        var size = createEl('em', '', entry.loaded && entry.total ? Math.round(entry.loaded / 1024) + ' / ' + Math.round(entry.total / 1024) + ' KB' : ('第 ' + entry.attempts + ' / ' + PREVIEW_DOWNLOAD_MAX_RETRIES + ' 次'));
        var track = createEl('div', 'spx-preview-download-track');
        var progress = createEl('span', 'spx-preview-download-progress');
        var meta = createEl('div', 'spx-preview-download-meta');
        var source = [entry.item && entry.item.floorLabel, entry.item && entry.item.author].filter(Boolean).join(' · ') || '预览图';
        var status = entry.error || getPreviewDownloadEntryStatusText(entry);
        progress.style.width = Math.max(0, Math.min(100, Number(entry.progress) || 0)) + '%';
        name.appendChild(label);
        name.appendChild(size);
        track.appendChild(progress);
        meta.appendChild(createEl('span', '', source));
        meta.appendChild(createEl('span', '', status));
        main.appendChild(name);
        main.appendChild(track);
        main.appendChild(meta);
        row.appendChild(main);
        if (entry.status === 'failed') {
          var retryButton = createEl('button', '', entry.attempts >= PREVIEW_DOWNLOAD_MAX_RETRIES ? '已达上限' : '重试');
          retryButton.type = 'button';
          retryButton.disabled = previewDownloadState.running || previewDownloadState.packing || entry.attempts >= PREVIEW_DOWNLOAD_MAX_RETRIES;
          retryButton.dataset.spxPreviewDownloadRetry = String(entry.index);
          row.appendChild(retryButton);
        } else {
          row.appendChild(createPreviewDownloadBadge(getPreviewDownloadEntryStatusText(entry), entry.status === 'failed' ? 'spx-fail' : ''));
        }
        downloadQueue.appendChild(row);
      });

      if (counts.done) {
        var doneRow = createEl('div', 'spx-preview-download-row');
        var doneMain = createEl('div');
        var doneName = createEl('div', 'spx-preview-download-name');
        var doneTrack = createEl('div', 'spx-preview-download-track');
        var doneProgress = createEl('span', 'spx-preview-download-progress');
        var doneMeta = createEl('div', 'spx-preview-download-meta');
        doneName.appendChild(createEl('b', '', '已完成 ' + counts.done + ' 张'));
        doneName.appendChild(createEl('em', '', '可打包'));
        doneProgress.style.width = '100%';
        doneTrack.appendChild(doneProgress);
        doneMeta.appendChild(createEl('span', '', '完成项默认合并,避免列表过长'));
        doneMeta.appendChild(createEl('span', '', '100%'));
        doneMain.appendChild(doneName);
        doneMain.appendChild(doneTrack);
        doneMain.appendChild(doneMeta);
        doneRow.appendChild(doneMain);
        doneRow.appendChild(createPreviewDownloadBadge('完成', 'spx-ok'));
        downloadQueue.appendChild(doneRow);
      }

      if (counts.failed && counts.done) {
        var report = createEl('div', 'spx-preview-download-report');
        var reportTitle = createEl('strong');
        reportTitle.appendChild(createEl('span', '', 'ZIP 打包'));
        reportTitle.appendChild(createEl('span', '', '可只打包已完成图片'));
        report.appendChild(reportTitle);
        report.appendChild(createEl('span', '', '失败图片和原始链接会写入 download-report.txt。'));
        downloadQueue.appendChild(report);
      }
    }

    function renderPreviewDownload() {
      if (!previewDownloadState || !previewDownloadState.entries.length) {
        downloadPanel.hidden = true;
        return;
      }
      var counts = getPreviewDownloadCounts();
      var percent = getPreviewDownloadProgressPercent();
      downloadPanel.hidden = false;
      downloadAllButton.disabled = previewDownloadState.running || previewDownloadState.packing;
      if (previewDownloadState.error) {
        downloadTitle.textContent = 'ZIP 打包失败:' + previewDownloadState.error;
      } else if (previewDownloadState.packed) {
        downloadTitle.textContent = counts.failed ? ('已打包 ' + counts.done + ' 张 · ' + counts.failed + ' 张失败已记录') : ('已打包 ' + counts.done + ' 张图片');
      } else {
        downloadTitle.textContent = getPreviewDownloadStatusSummary(counts);
      }
      downloadPercent.textContent = percent + '%';
      downloadProgress.style.width = percent + '%';
      downloadBadges.textContent = '';
      if (counts.done) downloadBadges.appendChild(createPreviewDownloadBadge(counts.done + ' 完成', 'spx-ok'));
      if (counts.active) downloadBadges.appendChild(createPreviewDownloadBadge(counts.active + ' 下载中', ''));
      if (counts.queued) downloadBadges.appendChild(createPreviewDownloadBadge(counts.queued + ' 等待', ''));
      if (counts.failed) downloadBadges.appendChild(createPreviewDownloadBadge(counts.failed + ' 失败', 'spx-fail'));
      if (counts.done && counts.failed && !counts.active && !counts.queued) downloadBadges.appendChild(createPreviewDownloadBadge('可打包', 'spx-zip'));
      packDownloadedButton.hidden = !counts.done;
      packDownloadedButton.disabled = previewDownloadState.packing;
      retryFailedButton.disabled = previewDownloadState.running || previewDownloadState.packing || !counts.failed;
      cancelDownloadButton.hidden = !previewDownloadState.running && !previewDownloadState.packing;
      cancelDownloadButton.disabled = previewDownloadState.packing;
      downloadDetailText.textContent = '下载详情:' + counts.active + ' 张下载中,' + counts.failed + ' 张失败,' + counts.done + ' 张已完成';
      renderPreviewDownloadQueue();
    }

    function formatPreviewDownloadError(error) {
      if (!error) return '下载失败';
      if (error.name === 'AbortError') return '已取消';
      var message = String(error.message || error || '下载失败');
      if (/Failed to fetch|NetworkError|Load failed|fetch/i.test(message)) return '网络或跨域限制';
      return message.slice(0, 80);
    }

    function readPreviewImageResponseBlob(response, entry) {
      var type = response && response.headers ? (response.headers.get('content-type') || '') : '';
      var total = Number(response && response.headers ? response.headers.get('content-length') : 0) || 0;
      entry.total = total;
      if (!response || !response.body || typeof response.body.getReader !== 'function') {
        return response.blob().then(function usePreviewResponseBlob(blob) {
          entry.progress = 100;
          entry.loaded = blob.size || total || 0;
          return { blob: blob, contentType: type || blob.type || '' };
        });
      }
      var reader = response.body.getReader();
      var chunks = [];
      var loaded = 0;
      function readNextChunk() {
        return reader.read().then(function handlePreviewChunk(result) {
          if (result.done) {
            entry.progress = 100;
            entry.loaded = loaded;
            return { blob: new Blob(chunks, { type: type || 'application/octet-stream' }), contentType: type };
          }
          var chunk = result.value;
          if (chunk) {
            chunks.push(chunk);
            loaded += chunk.length || chunk.byteLength || 0;
            entry.loaded = loaded;
            var nextProgress = total ? Math.floor((loaded / total) * 100) : Math.min(95, (Number(entry.progress) || 0) + 6);
            if (nextProgress !== entry.progress) {
              entry.progress = Math.max(1, Math.min(99, nextProgress));
              renderPreviewDownload();
            }
          }
          return readNextChunk();
        });
      }
      return readNextChunk();
    }

    function fetchPreviewDownloadBlob(entry) {
      var fetchImpl = typeof window !== 'undefined' && typeof window.fetch === 'function' ? window.fetch.bind(window) : null;
      if (!fetchImpl) return Promise.reject(new Error('当前浏览器不支持 fetch'));
      var controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
      var options = { credentials: 'same-origin', cache: 'force-cache' };
      if (controller) {
        options.signal = controller.signal;
        previewDownloadState.controllers.push(controller);
      }
      return fetchImpl(entry.src, options).then(function inspectPreviewDownloadResponse(response) {
        if (!response || !response.ok) throw new Error('HTTP ' + (response ? response.status : 0));
        return readPreviewImageResponseBlob(response, entry);
      }).finally(function removePreviewDownloadController() {
        if (!controller || !previewDownloadState) return;
        previewDownloadState.controllers = previewDownloadState.controllers.filter(function keepController(item) {
          return item !== controller;
        });
      });
    }

    function delayPreviewDownloadRetry(ms) {
      return new Promise(function waitPreviewDownload(resolve) {
        window.setTimeout(resolve, ms);
      });
    }

    function runPreviewDownloadEntry(entry) {
      function attemptPreviewDownload() {
        if (!previewDownloadState || previewDownloadState.cancelled) {
          entry.status = 'cancelled';
          entry.error = '已取消';
          return Promise.resolve();
        }
        entry.attempts += 1;
        entry.status = entry.attempts > 1 ? 'retrying' : 'downloading';
        entry.progress = 0;
        entry.error = '';
        renderPreviewDownload();
        return fetchPreviewDownloadBlob(entry).then(function markPreviewDownloadDone(result) {
          entry.status = 'done';
          entry.blob = result.blob;
          entry.contentType = result.contentType || (result.blob && result.blob.type) || '';
          entry.fileName = formatPreviewImageDownloadFileName(entry.item, entry.index, entry.contentType);
          entry.progress = 100;
          entry.error = '';
          renderPreviewDownload();
        }).catch(function handlePreviewDownloadFailure(error) {
          entry.error = formatPreviewDownloadError(error);
          if (previewDownloadState && previewDownloadState.cancelled) {
            entry.status = 'cancelled';
            renderPreviewDownload();
            return;
          }
          if (entry.attempts < PREVIEW_DOWNLOAD_MAX_RETRIES) {
            entry.status = 'retrying';
            renderPreviewDownload();
            return delayPreviewDownloadRetry(Math.min(1500, 220 * entry.attempts)).then(attemptPreviewDownload);
          }
          entry.status = 'failed';
          entry.progress = 0;
          renderPreviewDownload();
        });
      }
      return attemptPreviewDownload();
    }

    function runPreviewDownloadQueue(entries) {
      var queue = (entries || []).slice();
      var active = 0;
      return new Promise(function resolvePreviewDownloadQueue(resolve) {
        function pumpPreviewDownloadQueue() {
          if (!previewDownloadState || previewDownloadState.cancelled) {
            if (!active) resolve();
            return;
          }
          while (active < PREVIEW_DOWNLOAD_CONCURRENCY && queue.length) {
            active += 1;
            runPreviewDownloadEntry(queue.shift()).then(function finishOnePreviewDownload() {
              active -= 1;
              pumpPreviewDownloadQueue();
            });
          }
          if (!active && !queue.length) resolve();
        }
        pumpPreviewDownloadQueue();
      });
    }

    function createPreviewDownloadEntries(images) {
      return (images || []).map(function createPreviewDownloadEntry(item, index) {
        return {
          index: index,
          item: item,
          src: getPreviewImageSource(item),
          status: 'queued',
          progress: 0,
          attempts: 0,
          loaded: 0,
          total: 0,
          error: '',
          blob: null,
          fileName: formatPreviewImageDownloadFileName(item, index, ''),
        };
      }).filter(function keepPreviewDownloadEntry(entry) {
        return !!entry.src;
      });
    }

    function getPreviewDownloadedZipFiles() {
      if (!previewDownloadState) return [];
      var files = previewDownloadState.entries.filter(function keepDonePreviewDownload(entry) {
        return entry.status === 'done' && entry.blob;
      }).map(function mapDonePreviewDownload(entry) {
        return { name: entry.fileName || formatPreviewImageDownloadFileName(entry.item, entry.index, entry.contentType), blob: entry.blob };
      });
      var hasFailed = previewDownloadState.entries.some(function hasFailedPreviewDownload(entry) {
        return entry.status === 'failed';
      });
      if (hasFailed) {
        files.push({ name: 'download-report.txt', blob: new Blob([formatPreviewDownloadReport(previewDownloadState.entries)], { type: 'text/plain;charset=utf-8' }) });
      }
      return files;
    }

    function packPreviewDownloadedImages() {
      if (!previewDownloadState || previewDownloadState.packing) return;
      var files = getPreviewDownloadedZipFiles();
      if (!files.length) {
        downloadTitle.textContent = '没有已下载图片可打包';
        return;
      }
      previewDownloadState.packing = true;
      previewDownloadState.error = '';
      renderPreviewDownload();
      createPreviewZipBlob(files).then(function downloadPreviewZip(blob) {
        downloadBlobFile(formatPreviewImageArchiveFileName(), blob);
        previewDownloadState.packed = true;
        previewDownloadState.packing = false;
        renderPreviewDownload();
      }).catch(function handlePreviewZipFailure(error) {
        previewDownloadState.packing = false;
        previewDownloadState.error = formatPreviewDownloadError(error);
        downloadTitle.textContent = 'ZIP 打包失败:' + previewDownloadState.error;
        renderPreviewDownload();
      });
    }

    function finishPreviewDownloadQueue() {
      if (!previewDownloadState) return;
      previewDownloadState.running = false;
      previewDownloadState.controllers = [];
      renderPreviewDownload();
      var counts = getPreviewDownloadCounts();
      if (counts.done && !counts.failed && !counts.cancelled) packPreviewDownloadedImages();
    }

    function startPreviewDownload(entries) {
      var downloadEntries = (entries || []).filter(function keepEntryForPreviewDownload(entry) {
        return entry && entry.src && entry.status !== 'done';
      });
      if (!downloadEntries.length) {
        renderPreviewDownload();
        return;
      }
      previewDownloadState.running = true;
      previewDownloadState.cancelled = false;
      previewDownloadState.packing = false;
      previewDownloadState.packed = false;
      previewDownloadState.error = '';
      renderPreviewDownload();
      runPreviewDownloadQueue(downloadEntries).then(finishPreviewDownloadQueue);
    }

    function startPreviewDownloadAll() {
      if (previewDownloadState && previewDownloadState.running) return;
      ensurePreviewImages(false);
      previewDownloadState = {
        entries: createPreviewDownloadEntries(visiblePreviewImages),
        running: false,
        cancelled: false,
        packing: false,
        packed: false,
        error: '',
        controllers: [],
      };
      if (!previewDownloadState.entries.length) {
        renderPreviewDownload();
        setPreviewButtonText(downloadAllButton, '无可下载图片');
        return;
      }
      startPreviewDownload(previewDownloadState.entries);
    }

    function cancelPreviewDownload() {
      if (!previewDownloadState) return;
      previewDownloadState.cancelled = true;
      previewDownloadState.running = false;
      previewDownloadState.controllers.forEach(function abortPreviewDownload(controller) {
        try { controller.abort(); } catch (error) {}
      });
      previewDownloadState.entries.forEach(function markQueuedPreviewDownloadCancelled(entry) {
        if (entry.status === 'queued' || entry.status === 'downloading' || entry.status === 'retrying') {
          entry.status = 'cancelled';
          entry.error = '已取消';
        }
      });
      renderPreviewDownload();
    }

    function retryFailedPreviewDownloads(resetAll) {
      if (!previewDownloadState || previewDownloadState.running || previewDownloadState.packing) return;
      var failedEntries = previewDownloadState.entries.filter(function keepFailedPreviewDownload(entry) {
        return entry.status === 'failed' && (resetAll || entry.attempts < PREVIEW_DOWNLOAD_MAX_RETRIES);
      });
      failedEntries.forEach(function resetFailedPreviewDownload(entry) {
        entry.status = 'queued';
        entry.progress = 0;
        entry.loaded = 0;
        entry.total = 0;
        entry.error = '';
        if (resetAll) entry.attempts = 0;
      });
      startPreviewDownload(failedEntries);
    }

    function syncPreviewLayoutButtons() {
      var isDrawer = panel.classList.contains('spx-preview-drawer');
      var isMasonry = panel.classList.contains('spx-preview-masonry');
      drawerButton.textContent = isDrawer ? '回正文' : '侧栏';
      drawerButton.setAttribute('aria-pressed', isDrawer ? 'true' : 'false');
      masonryButton.setAttribute('aria-pressed', isMasonry ? 'true' : 'false');
      collapseDrawerButton.hidden = !isDrawer;
    }

    function syncPreviewHeader() {
      var scanned = Array.isArray(previewImages);
      var allImages = previewImages || [];
      var largeCount = scanned ? allImages.filter(isLargePreviewImage).length : 0;
      var renderState = getPreviewGalleryRenderState(visiblePreviewImages.length, renderedPreviewLimit);
      summary.textContent = scanned
        ? formatPreviewGallerySummary(allImages.length, visiblePreviewImages.length, renderState.rendered, showLargeOnly)
        : '展开后加载当前页图片';
      drawerTab.textContent = scanned && allImages.length ? ('预览图 ' + allImages.length) : '预览图';
      largeOnlyButton.hidden = !scanned || !allImages.length || largeCount === allImages.length;
      largeOnlyButton.setAttribute('aria-pressed', showLargeOnly ? 'true' : 'false');
      downloadAllButton.disabled = (previewDownloadState && previewDownloadState.running) || !scanned || !visiblePreviewImages.length;
      copyAllButton.disabled = !scanned || !visiblePreviewImages.length;
      copyFloorButton.disabled = !scanned || !visiblePreviewImages.length;
      copyMarkdownButton.disabled = !scanned || !visiblePreviewImages.length;
      loadMoreButton.hidden = !scanned || !renderState.hasMore;
      loadMoreButton.textContent = '加载更多图片(' + renderState.rendered + ' / ' + renderState.total + ')';
      syncPreviewLayoutButtons();
    }

    function renderPreviewGrid() {
      scanPreviewImages();
      visiblePreviewImages = getVisiblePreviewImages();
      grid.textContent = '';
      var renderState = getPreviewGalleryRenderState(visiblePreviewImages.length, renderedPreviewLimit);
      var renderedImages = visiblePreviewImages.slice(0, renderState.rendered);
      syncPreviewHeader();

      if (!visiblePreviewImages.length) {
        grid.appendChild(createEl('div', 'spx-preview-empty', '当前页没有符合条件的大图'));
        return;
      }

      renderedImages.forEach(function appendPreview(item, index) {
        var link = createEl('a', 'spx-preview-item');
        link.href = item.src;
        link.target = '_blank';
        link.rel = 'noreferrer';
        link.title = '在灯箱中查看第 ' + (index + 1) + ' 张图';
        link.dataset.spxPreviewIndex = String(index);

        var thumb = createEl('img');
        thumb.loading = 'lazy';
        thumb.decoding = 'async';
        thumb.alt = '预览图 ' + (index + 1);
        preparePreviewImageReveal(thumb);
        thumb.src = item.src;
        if (thumb.complete && thumb.naturalWidth) markPreviewImageLoaded(thumb);

        var hoverImage = createEl('img', 'spx-preview-hover-image');
        hoverImage.loading = 'lazy';
        hoverImage.decoding = 'async';
        preparePreviewImageReveal(hoverImage);
        hoverImage.dataset.src = item.src;
        hoverImage.alt = '预览图 ' + (index + 1) + ' 放大预览';

        var label = createEl('span', '', getPreviewImageMetaText(item, index));
        link.appendChild(thumb);
        link.appendChild(hoverImage);
        link.appendChild(label);
        grid.appendChild(link);
      });
    }

    function ensurePreviewImages(render) {
      var images = scanPreviewImages();
      if (render) renderPreviewGrid();
      else {
        visiblePreviewImages = getVisiblePreviewImages();
        syncPreviewHeader();
      }
      return images;
    }

    function loadDelegatedPreviewHoverImage(target) {
      var link = target && target.closest ? target.closest('.spx-preview-item') : null;
      if (!link || !grid.contains(link)) return;
      var hoverImage = qs('.spx-preview-hover-image', link);
      if (hoverImage && !hoverImage.src) {
        hoverImage.src = hoverImage.dataset.src || link.href;
        if (hoverImage.complete && hoverImage.naturalWidth) markPreviewImageLoaded(hoverImage);
      }
    }

    function loadNextPreviewBatch() {
      var renderState = getPreviewGalleryRenderState(visiblePreviewImages.length, renderedPreviewLimit);
      if (!renderState.hasMore) return;
      renderedPreviewLimit = renderState.nextLimit;
      renderPreviewGrid();
    }

    function handlePreviewPanelScroll() {
      if (loadMoreButton.hidden) return;
      if (panel.scrollTop + panel.clientHeight >= panel.scrollHeight - 160) loadNextPreviewBatch();
    }

    downloadAllButton.addEventListener('click', startPreviewDownloadAll);
    copyAllButton.addEventListener('click', function copyAllPreviewLinks() {
      ensurePreviewImages(false);
      copyPreviewText(copyAllButton, formatPreviewImageLinks(visiblePreviewImages), visiblePreviewImages.length);
    });
    copyFloorButton.addEventListener('click', function copyPreviewLinksByFloor() {
      ensurePreviewImages(false);
      copyPreviewText(copyFloorButton, formatPreviewImageLinksByFloor(visiblePreviewImages), visiblePreviewImages.length);
    });
    copyMarkdownButton.addEventListener('click', function copyPreviewMarkdownLinks() {
      ensurePreviewImages(false);
      copyPreviewText(copyMarkdownButton, formatPreviewImageMarkdownLinks(visiblePreviewImages), visiblePreviewImages.length);
    });
    packDownloadedButton.addEventListener('click', packPreviewDownloadedImages);
    retryFailedButton.addEventListener('click', function retryAllFailedPreviewDownloads() {
      retryFailedPreviewDownloads(true);
    });
    cancelDownloadButton.addEventListener('click', cancelPreviewDownload);
    downloadQueue.addEventListener('click', function handlePreviewDownloadQueueClick(event) {
      var button = event.target && event.target.closest ? event.target.closest('[data-spx-preview-download-retry]') : null;
      if (!button || !downloadQueue.contains(button) || !previewDownloadState) return;
      var index = Number(button.dataset.spxPreviewDownloadRetry);
      var entry = previewDownloadState.entries[index];
      if (!entry || entry.status !== 'failed') return;
      entry.status = 'queued';
      entry.progress = 0;
      entry.loaded = 0;
      entry.total = 0;
      entry.error = '';
      startPreviewDownload([entry]);
    });

    largeOnlyButton.addEventListener('click', function toggleLargeOnly() {
      ensurePreviewImages(false);
      showLargeOnly = !showLargeOnly;
      renderedPreviewLimit = PREVIEW_GALLERY_BATCH_SIZE;
      renderPreviewGrid();
    });
    masonryButton.addEventListener('click', function togglePreviewMasonry() {
      panel.classList.toggle('spx-preview-masonry');
      syncPreviewLayoutButtons();
    });
    drawerButton.addEventListener('click', function togglePreviewDrawer() {
      var nextDrawer = !panel.classList.contains('spx-preview-drawer');
      panel.classList.toggle('spx-preview-drawer', nextDrawer);
      panel.classList.remove('spx-preview-collapsed');
      syncPreviewLayoutButtons();
    });
    collapseDrawerButton.addEventListener('click', function collapsePreviewDrawer() {
      panel.classList.add('spx-preview-collapsed');
    });
    drawerTab.addEventListener('click', function expandPreviewDrawer() {
      panel.classList.remove('spx-preview-collapsed');
      ensurePreviewImages(true);
    });
    loadMoreButton.addEventListener('click', loadNextPreviewBatch);
    panel.addEventListener('scroll', handlePreviewPanelScroll);
    grid.addEventListener('click', function handlePreviewGridClick(event) {
      var link = event.target && event.target.closest ? event.target.closest('.spx-preview-item') : null;
      if (!link || !grid.contains(link)) return;
      if (event.button > 0 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
      event.preventDefault();
      openPreviewLightbox(visiblePreviewImages, Number(link.dataset.spxPreviewIndex) || 0);
    });
    grid.addEventListener('mouseover', function handlePreviewGridHover(event) {
      loadDelegatedPreviewHoverImage(event.target);
    });
    grid.addEventListener('focusin', function handlePreviewGridFocus(event) {
      loadDelegatedPreviewHoverImage(event.target);
    });

    actions.appendChild(downloadAllButton);
    actions.appendChild(copyAllButton);
    actions.appendChild(copyFloorButton);
    actions.appendChild(copyMarkdownButton);
    actions.appendChild(largeOnlyButton);
    actions.appendChild(masonryButton);
    actions.appendChild(drawerButton);
    actions.appendChild(collapseDrawerButton);
    header.appendChild(title);
    header.appendChild(summary);
    header.appendChild(actions);
    panel.appendChild(header);
    panel.appendChild(downloadPanel);
    panel.appendChild(grid);
    panel.appendChild(loadMoreButton);
    panel.appendChild(drawerTab);
    panel.spxCleanup = function cleanupPreviewPanel() {
      cancelIdlePreviewScan();
      cancelPreviewDownload();
    };
    syncPreviewHeader();

    mountPreviewPanel(firstPost, content, panel);
    scheduleIdlePreviewScan();
  }

  function restorePreviewGallery() {
    closePreviewLightbox();
    var panel = qs('#spx-preview-panel');
    if (panel) {
      if (typeof panel.spxCleanup === 'function') panel.spxCleanup();
      panel.remove();
    }
    var split = qs('.spx-post-body-split');
    if (split) {
      var content = qs('.tpc_content', split);
      if (content && split.parentNode) split.parentNode.insertBefore(content, split);
      split.remove();
    }
    qsa('[data-spx-preview-source="1"]').forEach(function restoreImage(img) {
      img.classList.remove('spx-preview-source');
      delete img.dataset.spxPreviewSource;
    });
  }

  function isEmptyReadSeparatorNode(node) {
    if (!node) return false;
    if (node.classList && node.classList.contains('js-post')) return false;
    if (node.closest && node.closest('table.js-post')) return false;
    if (qs('table.js-post,.tpc_content,.pages,.page,#breadcrumbs,form,input,textarea,select,button,a[href],img,iframe,video,object,embed', node)) return false;

    var text = compactText(node.textContent);
    if (text) return false;

    return true;
  }

  function cleanupReadSeparators() {
    if (detectPageType(location.href) !== 'read') return;
    qsa('#content>*:not(.js-post),#main>*:not(.js-post)').forEach(function hideSeparator(node) {
      if (!isEmptyReadSeparatorNode(node)) return;
      node.dataset.spxReadSeparatorHidden = '1';
      if (node.style && typeof node.style.setProperty === 'function') {
        node.style.setProperty('display', 'none', 'important');
      } else if (node.style) {
        node.style.display = 'none';
      }
    });
  }

  function foldLongReadBlocks() {
    qsa('.quote, blockquote, .blockquote, .tpc_content .f12').forEach(function fold(node) {
      if (node.dataset.spxFolded) return;
      if ((node.textContent || '').length < 220 && node.scrollHeight < 150) return;
      node.dataset.spxFolded = '1';
      node.classList.add('spx-folded-quote');
      var button = createEl('button', '', '展开引用');
      button.style.margin = '4px 0 8px';
      button.addEventListener('click', function expand() {
        node.classList.remove('spx-folded-quote');
        button.remove();
      });
      node.insertAdjacentElement('afterend', button);
    });
  }

  function getQuickReplyEditor(root) {
    var scope = root || document;
    var selectors = [
      'textarea[name="atc_content"]',
      'textarea[name="content"]',
      'textarea#atc_content',
      'textarea[id*="content"]',
      'textarea',
    ];
    for (var index = 0; index < selectors.length; index += 1) {
      var editor = qsa(selectors[index], scope).filter(isQuickReplyEditorCandidate)[0];
      if (editor) return editor;
    }
    return null;
  }

  function isQuickReplyEditorCandidate(editor) {
    if (!editor) return false;
    if (editor.disabled || editor.readOnly) return false;
    if (
      editor.closest &&
      editor.closest('#spx-settings,#spx-toolbox,#spx-watch-center,#spx-history-center,#spx-auto-buy-center,#spx-resource-center,#spx-read-resource-rail,#spx-preview-lightbox,#spx-quick-reply')
    ) {
      return false;
    }
    return true;
  }

  function insertTextIntoEditor(editor, text) {
    if (!editor || typeof text !== 'string') return false;
    var current = String(editor.value || '');
    var start = typeof editor.selectionStart === 'number' ? editor.selectionStart : current.length;
    var end = typeof editor.selectionEnd === 'number' ? editor.selectionEnd : current.length;
    var prefix = current.slice(0, start);
    var suffix = current.slice(end);
    var spacer = prefix && !/\s$/.test(prefix) ? '\n' : '';
    editor.value = prefix + spacer + text + suffix;
    var cursor = (prefix + spacer + text).length;
    if (typeof editor.setSelectionRange === 'function') editor.setSelectionRange(cursor, cursor);
    editor.dispatchEvent(new Event('input', { bubbles: true }));
    editor.dispatchEvent(new Event('change', { bubbles: true }));
    if (typeof editor.focus === 'function') editor.focus();
    return true;
  }

  function createQuickReplyRequest(form, submitter, pageUrl, FormDataCtor) {
    if (!form || typeof FormDataCtor !== 'function') return null;
    if (String(form.method || '').toUpperCase() !== 'POST') return null;

    var body;
    var url;
    try {
      body = new FormDataCtor(form);
      var formAction = typeof form.getAttribute === 'function'
        ? form.getAttribute('action')
        : form.action;
      url = new URL(formAction || '', pageUrl).href;
    } catch (error) {
      return null;
    }
    if (!body || typeof body.has !== 'function' || typeof body.append !== 'function') return null;

    if (submitter && submitter.name && !body.has(submitter.name)) {
      body.append(submitter.name, submitter.value == null ? '' : submitter.value);
    }

    return {
      url: url,
      options: {
        method: 'POST',
        body: body,
        credentials: 'include',
        redirect: 'follow',
        cache: 'no-store',
      },
    };
  }

  function getQuickReplyFormValue(request, name) {
    var body = request && request.options && request.options.body;
    if (!body || typeof body.get !== 'function') return '';
    try {
      var value = body.get(name);
      return value == null ? '' : String(value).trim();
    } catch (error) {
      return '';
    }
  }

  function buildQuickReplyLastPageUrl(pageUrl, tid) {
    var id = String(tid || '').trim();
    if (!id) return '';
    var parsed = new URL(String(pageUrl || ''), 'https://south-plus.org/');
    return parsed.origin + '/read.php?tid=' + encodeURIComponent(id) + '&page=e#a';
  }

  function resolveQuickReplyRefreshUrl(pageUrl, request, submitResponse) {
    var baseUrl = pageUrl || (typeof location !== 'undefined' ? location.href : 'https://south-plus.org/');
    var responseUrl = '';

    try {
      responseUrl = submitResponse && submitResponse.url
        ? new URL(String(submitResponse.url), baseUrl).href
        : '';
    } catch (error) {
      responseUrl = '';
    }

    if (responseUrl && detectPageType(responseUrl) === 'read') return responseUrl;

    var tid =
      getQuickReplyFormValue(request, 'tid') ||
      parseThreadId(responseUrl) ||
      parseThreadId(baseUrl);
    return tid ? buildQuickReplyLastPageUrl(responseUrl || baseUrl, tid) : baseUrl;
  }

  function shouldUseQuickReplySubmitHtml(html, submitResponse) {
    var text = String(html || '');
    if (!text) return false;
    var responseUrl = '';
    try {
      responseUrl = submitResponse && submitResponse.url ? String(submitResponse.url) : '';
    } catch (error) {
      responseUrl = '';
    }
    if (responseUrl && detectPageType(responseUrl) === 'read') return true;
    return /(?:\bjs-post\b|\btpc_content\b|id=["']read_tpc["'])/i.test(text);
  }

  function performQuickReplySubmit(options) {
    if (
      !options ||
      !options.request ||
      typeof options.fetch !== 'function' ||
      typeof options.applyHtml !== 'function'
    ) {
      return Promise.resolve(false);
    }

    var setPending = typeof options.setPending === 'function' ? options.setPending : function noop() {};
    var onError = typeof options.onError === 'function' ? options.onError : null;
    var shouldResetPending = false;
    var refreshUrl = '';

    function reportQuickReplyError(error) {
      if (!onError) return;
      try {
        onError(error);
      } catch (onErrorFailure) {}
    }

    return Promise.resolve()
      .then(function startQuickReplySubmit() {
        if (typeof options.isPending === 'function' && options.isPending()) return false;

        shouldResetPending = true;
        setPending(true);

        return Promise.resolve()
          .then(function submitReply() {
            return options.fetch(options.request.url, options.request.options);
          })
          .then(function checkSubmitResponse(response) {
            if (!response || response.ok !== true) throw new Error('快捷回复提交失败');
            refreshUrl = typeof options.resolveRefreshUrl === 'function'
              ? options.resolveRefreshUrl(response)
              : resolveQuickReplyRefreshUrl(options.pageUrl, options.request, response);
            if (typeof response.text !== 'function') return false;
            return readScriptResponseText(response, { mode: 'action', label: '快捷回复' })
              .then(function applySubmitHtml(html) {
                if (!shouldUseQuickReplySubmitHtml(html, response)) return false;
                return options.applyHtml(html, refreshUrl || options.pageUrl) !== false;
              });
          })
          .then(function reloadThreadWhenSubmitHtmlUnavailable(appliedSubmitHtml) {
            if (appliedSubmitHtml) return true;
            return options.fetch(refreshUrl || options.pageUrl, {
              credentials: 'include',
              cache: 'no-store',
            });
          })
          .then(function checkReloadResponse(response) {
            if (response === true) return true;
            if (!response || response.ok !== true) throw new Error('重新加载帖子失败');
            return readScriptResponseText(response, { mode: 'action', label: '快捷回复刷新' });
          })
          .then(function applyReloadedHtml(html) {
            if (html === true) return true;
            if (options.applyHtml(html, refreshUrl || options.pageUrl) === false) throw new Error('无法更新帖子内容');
            return true;
          });
      })
      .catch(function handleQuickReplyError(error) {
        reportQuickReplyError(error);
        return false;
      })
      .then(function finishQuickReply(result) {
        if (!shouldResetPending) return result;
        try {
          setPending(false);
        } catch (error) {
          reportQuickReplyError(error);
          return false;
        }
        return result;
      });
  }

  function submitQuickReplyNative(editor) {
    if (!editor) return false;
    var form = editor.closest && editor.closest('form');
    if (form) {
      var submitter = getQuickReplySubmitter(form);
      if (submitter && typeof submitter.click === 'function') {
        submitter.click();
        return true;
      }
      if (typeof form.requestSubmit === 'function') {
        form.requestSubmit();
        return true;
      }
      if (typeof form.submit === 'function') {
        form.submit();
        return true;
      }
    }

    var pageSubmit =
      qs('input[type="submit"]') ||
      qs('button[type="submit"]') ||
      qs('input[name="submit"]') ||
      qs('button[name="submit"]');
    if (pageSubmit && typeof pageSubmit.click === 'function') {
      pageSubmit.click();
      return true;
    }
    return false;
  }

  function isQuickReplySubmitter(node) {
    if (!node || !node.tagName) return false;
    var tag = String(node.tagName || '').toUpperCase();
    var type = String(node.getAttribute && node.getAttribute('type') || '').toLowerCase();
    if (tag === 'BUTTON') return !type || type === 'submit';
    if (tag === 'INPUT') return type === 'submit' || type === 'image';
    return false;
  }

  function getQuickReplySubmitter(form, preferred) {
    if (!form) return null;
    if (preferred && isQuickReplySubmitter(preferred)) return preferred;
    if (form.spxQuickReplySubmitter && isQuickReplySubmitter(form.spxQuickReplySubmitter)) {
      return form.spxQuickReplySubmitter;
    }
    return (
      qs('input[type="submit"]', form) ||
      qs('button[type="submit"]', form) ||
      qs('button:not([type])', form) ||
      qs('input[name="submit"]', form) ||
      qs('button[name="submit"]', form)
    );
  }

  function getQuickReplyClickedSubmitter(target, form) {
    if (!target || !form) return null;
    var node = target.closest ? target.closest('input,button') : target;
    if (!node || (typeof form.contains === 'function' && !form.contains(node))) return null;
    return isQuickReplySubmitter(node) ? node : null;
  }

  function setQuickReplyPanelPending(panel, pending) {
    if (!panel) return;
    if (pending) {
      panel.dataset.spxQuickReplyPending = '1';
    } else {
      delete panel.dataset.spxQuickReplyPending;
    }
    qsa('button', panel).forEach(function toggleQuickReplyButton(button) {
      button.disabled = !!pending;
    });

    var status = qs('.spx-quick-reply-status', panel);
    if (pending && status) {
      status.classList.remove('spx-error');
      status.textContent = '正在提交并更新帖子…';
    }
  }

  function setQuickReplyFormPending(form, pending) {
    if (!form || !form.dataset) return;
    if (pending) {
      form.dataset.spxQuickReplyPending = '1';
    } else {
      delete form.dataset.spxQuickReplyPending;
    }
    qsa('input[type="submit"],button[type="submit"],button:not([type])', form)
      .forEach(function toggleFormSubmit(submitter) {
        submitter.disabled = !!pending;
      });
  }

  function setQuickReplyPending(form, panel, pending) {
    setQuickReplyFormPending(form, pending);
    setQuickReplyPanelPending(panel, pending);
  }

  function setQuickReplyPanelError(panel, error) {
    if (!panel) return;
    var status = qs('.spx-quick-reply-status', panel);
    if (!status) return;
    status.classList.add('spx-error');
    status.textContent = error && error.spxRateLimited
      ? '站点提示操作频繁,内容已保留,请稍后重试。'
      : '提交失败,内容已保留,请重试。';
  }

  function submitQuickReplyAjax(editor, form, submitter, settings, state, panel) {
    if (!editor || !form) return false;
    var fetchImpl = typeof window !== 'undefined' && typeof window.fetch === 'function'
      ? function queueQuickReplyFetch(url, options) {
        return requestWithPolicy(url, options, {
          mode: 'action',
          label: '快捷回复',
          networkFriendly: isNetworkFriendlyMode(settings),
        });
      }
      : null;
    var FormDataCtor = typeof FormData === 'function' ? FormData : null;
    var request = createQuickReplyRequest(
      form,
      submitter,
      location.href,
      FormDataCtor
    );

    var pageType = detectPageType(location.href);
    if (!request || !fetchImpl || (pageType !== 'read' && pageType !== 'post')) {
      return false;
    }

    return performQuickReplySubmit({
      request: request,
      pageUrl: location.href,
      fetch: fetchImpl,
      isPending: function isPending() {
        return !!(
          (panel && panel.dataset.spxQuickReplyPending === '1') ||
          (form.dataset && form.dataset.spxQuickReplyPending === '1')
        );
      },
      setPending: function setPending(pending) {
        setQuickReplyPending(form, panel, pending);
      },
      applyHtml: function applyHtml(html, refreshUrl) {
        var currentUrl = location.href;
        var shouldReplaceUrl =
          refreshUrl &&
          detectPageType(refreshUrl) === 'read' &&
          refreshUrl !== currentUrl &&
          typeof window !== 'undefined' &&
          window.history &&
          typeof window.history.replaceState === 'function';

        if (shouldReplaceUrl) {
          window.history.replaceState(window.history.state, document.title, refreshUrl);
        }

        if (replaceReadPageContent(html, settings, state)) return true;

        if (shouldReplaceUrl) {
          window.history.replaceState(window.history.state, document.title, currentUrl);
        }
        return false;
      },
      onError: function onError(error) {
        setQuickReplyPanelError(panel, error);
      },
    });
  }

  function submitQuickReply(editor, settings, state, submitter) {
    if (!editor) return false;
    var form = editor.closest && editor.closest('form');
    var panel = qs('#spx-quick-reply');
    var ajaxResult = submitQuickReplyAjax(
      editor,
      form,
      getQuickReplySubmitter(form, submitter),
      settings,
      state,
      panel
    );
    if (ajaxResult === false) return submitQuickReplyNative(editor);
    return ajaxResult;
  }

  function bindQuickReplyFormSubmit(editor, settings, state, panel) {
    if (!editor || !editor.closest) return false;
    var form = editor.closest('form');
    if (!form || form.dataset.spxQuickReplySubmitBound === '1') return false;
    form.dataset.spxQuickReplySubmitBound = '1';

    form.addEventListener('click', function rememberQuickReplySubmitter(event) {
      var submitter = getQuickReplyClickedSubmitter(event.target, form);
      if (submitter) form.spxQuickReplySubmitter = submitter;
    }, true);

    form.addEventListener('submit', function handleQuickReplySubmit(event) {
      var submitter = getQuickReplySubmitter(form, event && event.submitter);
      var currentPanel = qs('#spx-quick-reply') || panel;
      var ajaxResult = submitQuickReplyAjax(editor, form, submitter, settings, state, currentPanel);
      if (ajaxResult === false) return;
      if (event && typeof event.preventDefault === 'function') event.preventDefault();
    });
    return true;
  }

  function getQuickReplyMount(editor) {
    if (!editor) return null;
    var form = editor.closest && editor.closest('form');
    var row = editor.closest && editor.closest('tr');
    if (row && row.parentNode && /^tr$/i.test(row.nodeName || '')) {
      return { parent: row.parentNode, before: row };
    }
    return { parent: (form || editor.parentNode), before: editor };
  }

  function ensureQuickReplyControlId(control, prefix) {
    if (!control) return '';
    if (!control.id) {
      control.id = prefix + '-' + Math.random().toString(36).slice(2, 9);
    }
    return control.id;
  }

  function getQuickReplyAttachmentInput(form) {
    if (!form) return null;
    return qsa('input[type="file"]', form).filter(function usableAttachmentInput(input) {
      return input && !input.disabled;
    })[0] || null;
  }

  function getQuickReplyAttachmentDescriptionInput(form, attachmentInput) {
    if (!form || !attachmentInput) return null;
    var row = attachmentInput.closest && attachmentInput.closest('tr,div,p,section');
    var rowInputs = row ? qsa('input[type="text"],input:not([type])', row).filter(function usableDescInput(input) {
      return input && input !== attachmentInput && !input.disabled && !input.readOnly;
    }) : [];
    if (rowInputs.length) return rowInputs[0];
    return qsa('input[type="text"],input:not([type])', form).filter(function likelyDescInput(input) {
      var name = String(input.name || input.id || input.className || '').toLowerCase();
      return !input.disabled && !input.readOnly && /(?:desc|describe|intro|atc)/.test(name);
    })[0] || null;
  }

  function getQuickReplyAttachmentFiles(input) {
    if (!input || !input.files) return [];
    return Array.prototype.slice.call(input.files).filter(function keepImageAttachment(file) {
      return file && (!file.type || /^image\//i.test(file.type));
    });
  }

  function formatQuickReplyFileSize(size) {
    var value = Number(size);
    if (!isFinite(value) || value <= 0) return '未知大小';
    if (value < 1024 * 1024) return Math.max(1, Math.round(value / 1024)) + ' KB';
    return (value / 1024 / 1024).toFixed(1) + ' MB';
  }

  function formatQuickReplyAttachmentSummary(files) {
    var count = (files || []).length;
    return count
      ? '已选择 ' + count + ' 张图片;正式提交会随原站回复表单一起发送。'
      : '沿用原站附件上传;正式提交时随原站回复表单一起发送。';
  }

  function openFloatingQuickReplyContext(label, text) {
    var panel = qs('#spx-quick-reply');
    if (!panel || typeof panel.spxOpenWithContext !== 'function') return false;
    panel.spxOpenWithContext(label, text);
    return true;
  }

  function createQuickReplyPanel(settings, editor, state) {
    var replies = parseQuickReplyList((settings && settings.quickReplies || []).join('\n'));
    if (!editor || !replies.length) return null;

    var form = editor.closest && editor.closest('form');
    var attachmentInput = getQuickReplyAttachmentInput(form);
    var attachmentDesc = getQuickReplyAttachmentDescriptionInput(form, attachmentInput);
    var attachmentId = ensureQuickReplyControlId(attachmentInput, 'spx-quick-reply-attachment');

    if (attachmentInput && !attachmentInput.getAttribute('accept')) {
      attachmentInput.setAttribute('accept', 'image/*');
    }

    var panel = createEl('div', 'spx-quick-reply');
    panel.id = 'spx-quick-reply';
    panel.classList.add('spx-quick-reply-collapsed');
    panel.setAttribute('aria-label', '浮动回复框');

    var launcher = createEl('button', 'spx-quick-reply-launcher', '继续回复');
    launcher.type = 'button';
    launcher.id = 'spx-quick-reply-launcher';

    var selectionMenu = createEl('div', 'spx-quick-reply-selection');
    selectionMenu.id = 'spx-quick-reply-selection';
    selectionMenu.setAttribute('aria-label', '选中文字操作');
    var selectionQuote = createEl('button', '', '引用所选');
    selectionQuote.type = 'button';
    var selectionCancel = createEl('button', '', '取消');
    selectionCancel.type = 'button';
    selectionMenu.append(selectionQuote, selectionCancel);

    var header = createEl('div', 'spx-quick-reply-header');
    var title = createEl('div');
    title.appendChild(createEl('strong', '', '快速回复'));
    var status = createEl('span', 'spx-quick-reply-status', '草稿已保存 · Ctrl+Enter 发送');
    title.appendChild(status);
    var headerActions = createEl('div', 'spx-quick-reply-actions');
    var collapse = createEl('button', 'spx-quick-reply-icon', '最小化');
    collapse.type = 'button';
    collapse.title = '最小化回复框';
    headerActions.appendChild(collapse);
    header.append(title, headerActions);

    var context = createEl('div', 'spx-quick-reply-context');
    var contextText = createEl('span', '', '');
    var clearContext = createEl('button', '', '×');
    clearContext.type = 'button';
    clearContext.title = '清除引用';
    context.append(contextText, clearContext);

    var body = createEl('div', 'spx-quick-reply-body');
    var list = createEl('div', 'spx-quick-reply-list');

    replies.slice(0, 8).forEach(function appendReply(reply, index) {
      var button = createEl('button', 'spx-quick-reply-chip' + (index < 2 ? ' spx-hot' : ''), reply);
      button.type = 'button';
      button.title = '填入回复:' + reply;
      button.addEventListener('click', function useQuickReply() {
        insertMirrorText(reply);
      });
      list.appendChild(button);
    });

    var emotes = createEl('div', 'spx-quick-reply-emotes');
    var emoteHead = createEl('div', 'spx-quick-reply-emote-head');
    emoteHead.append(createEl('span', '', '原站表情'), createEl('span', '', '点击插入表情码'));
    var emoteGrid = createEl('div', 'spx-quick-reply-emote-grid');
    QUICK_REPLY_EMOTES.forEach(function appendEmote(emote) {
      var button = createEl('button', 'spx-quick-reply-emote');
      button.type = 'button';
      button.title = emote.fileName;
      button.dataset.spxEmote = emote.code;
      var img = createEl('img');
      img.src = emote.src;
      img.alt = emote.fileName;
      img.referrerPolicy = 'no-referrer';
      button.appendChild(img);
      emoteGrid.appendChild(button);
    });
    emotes.append(emoteHead, emoteGrid);

    var tools = createEl('div', 'spx-quick-reply-tools');
    tools.append(createEl('span', 'spx-quick-reply-note', '最近常用优先 · 支持楼层引用和选中文字引用'));
    var counter = createEl('span', 'spx-quick-reply-counter', '0 字');
    tools.appendChild(counter);

    var mirror = createEl('textarea', 'spx-quick-reply-editor');
    mirror.placeholder = '输入回复内容。点击楼层“回复”会自动带上下文。';
    mirror.value = String(editor.value || '');

    var attach = createEl('div', 'spx-quick-reply-attach');
    var desc = createEl('input', 'spx-quick-reply-desc');
    desc.type = 'text';
    desc.placeholder = '图片描述,可留空';
    desc.value = attachmentDesc ? String(attachmentDesc.value || '') : '';
    if (!attachmentDesc) desc.disabled = true;
    var picker = attachmentInput
      ? createEl('label', 'spx-quick-reply-picker', '选择图片')
      : createEl('span', 'spx-quick-reply-picker', '无附件入口');
    if (attachmentInput) picker.setAttribute('for', attachmentId);
    var help = createEl('div', 'spx-quick-reply-help', formatQuickReplyAttachmentSummary(getQuickReplyAttachmentFiles(attachmentInput)));
    var attachmentList = createEl('div', 'spx-quick-reply-attachments');
    attach.append(desc, picker, help, attachmentList);

    var footer = createEl('div', 'spx-quick-reply-footer');
    footer.appendChild(createEl('div', 'spx-quick-reply-hint', '失败时保留草稿,成功后局部刷新新楼层。'));
    var submitActions = createEl('div', 'spx-quick-reply-submit-actions');
    var saveDraft = createEl('button', 'spx-quick-reply-action', '保存草稿');
    saveDraft.type = 'button';
    var send = createEl('button', 'spx-quick-reply-action spx-primary', '发送回复');
    send.type = 'button';
    submitActions.append(saveDraft, send);
    footer.appendChild(submitActions);

    body.append(list, emotes, tools, mirror, attach, footer);
    panel.append(header, context, body);

    var draftTimer = null;
    var activeContext = '';
    var previewUrls = [];

    function syncEditor() {
      editor.value = mirror.value;
      editor.dispatchEvent(new Event('input', { bubbles: true }));
      editor.dispatchEvent(new Event('change', { bubbles: true }));
    }

    function updateCounter() {
      counter.textContent = mirror.value.length + ' 字';
    }

    function markDraftSaved() {
      status.classList.remove('spx-error');
      status.textContent = '草稿已保存 · Ctrl+Enter 发送';
    }

    function scheduleDraftSave() {
      syncEditor();
      status.classList.remove('spx-error');
      status.textContent = '正在保存草稿...';
      window.clearTimeout(draftTimer);
      draftTimer = window.setTimeout(markDraftSaved, 420);
    }

    function openReply() {
      panel.classList.remove('spx-quick-reply-collapsed');
      launcher.classList.remove('spx-visible');
      if (typeof mirror.focus === 'function') mirror.focus();
    }

    function collapseReply() {
      panel.classList.add('spx-quick-reply-collapsed');
      launcher.classList.add('spx-visible');
    }

    function setContext(label) {
      activeContext = String(label || '').trim();
      contextText.textContent = activeContext;
      context.classList.toggle('spx-visible', !!activeContext);
    }

    function insertMirrorText(text) {
      var value = mirror.value;
      var start = typeof mirror.selectionStart === 'number' ? mirror.selectionStart : value.length;
      var end = typeof mirror.selectionEnd === 'number' ? mirror.selectionEnd : value.length;
      var prefix = value && !/\n$/.test(value.slice(0, start)) ? '\n' : '';
      mirror.value = value.slice(0, start) + prefix + text + value.slice(end);
      var cursor = start + prefix.length + String(text || '').length;
      if (typeof mirror.setSelectionRange === 'function') mirror.setSelectionRange(cursor, cursor);
      updateCounter();
      scheduleDraftSave();
      openReply();
    }

    function revokePreviewUrls() {
      previewUrls.forEach(function revoke(url) {
        try { URL.revokeObjectURL(url); } catch (error) {}
      });
      previewUrls = [];
    }

    function renderAttachments() {
      var files = getQuickReplyAttachmentFiles(attachmentInput);
      attachmentList.innerHTML = '';
      attachmentList.classList.toggle('spx-visible', !!files.length);
      help.textContent = formatQuickReplyAttachmentSummary(files);
      revokePreviewUrls();
      files.forEach(function appendAttachment(file, index) {
        var row = createEl('div', 'spx-quick-reply-attachment');
        var thumb = createEl('img', 'spx-quick-reply-thumb');
        try {
          thumb.src = URL.createObjectURL(file);
          previewUrls.push(thumb.src);
        } catch (error) {}
        thumb.alt = file.name || '图片附件';
        var meta = createEl('div');
        meta.append(
          createEl('div', 'spx-quick-reply-attachment-name', file.name || '图片附件'),
          createEl('div', 'spx-quick-reply-attachment-size', formatQuickReplyFileSize(file.size))
        );
        var remove = createEl('button', 'spx-quick-reply-remove', '×');
        remove.type = 'button';
        remove.title = '移除图片';
        remove.dataset.spxRemoveAttachment = String(index);
        row.append(thumb, meta, remove);
        attachmentList.appendChild(row);
      });
    }

    function removeAttachment(index) {
      if (!attachmentInput || !attachmentInput.files || typeof DataTransfer !== 'function') {
        status.textContent = '当前浏览器不支持单张移除,请重新选择图片';
        return;
      }
      var dt = new DataTransfer();
      getQuickReplyAttachmentFiles(attachmentInput).forEach(function keepFile(file, fileIndex) {
        if (fileIndex !== index) dt.items.add(file);
      });
      attachmentInput.files = dt.files;
      renderAttachments();
      scheduleDraftSave();
    }

    function selectedText() {
      return String(window.getSelection && window.getSelection().toString() || '').trim();
    }

    function sendReply() {
      syncEditor();
      if (attachmentDesc) {
        attachmentDesc.value = desc.value;
        attachmentDesc.dispatchEvent(new Event('input', { bubbles: true }));
        attachmentDesc.dispatchEvent(new Event('change', { bubbles: true }));
      }
      if (!mirror.value.trim() && !getQuickReplyAttachmentFiles(attachmentInput).length) {
        status.classList.add('spx-error');
        status.textContent = '请输入回复内容或选择图片';
        mirror.focus();
        return;
      }
      window.clearTimeout(draftTimer);
      var result = submitQuickReply(editor, settings, state);
      if (result && typeof result.then === 'function') {
        result.then(function markResult(ok) {
          if (ok) {
            status.classList.remove('spx-error');
            status.textContent = getQuickReplyAttachmentFiles(attachmentInput).length
              ? '提交成功 · 已带图片刷新帖子'
              : '提交成功 · 已刷新帖子';
          }
        });
        return;
      }
      if (result) status.textContent = '已交给原站提交';
    }

    panel.spxOpenWithContext = function spxOpenWithContext(label, text) {
      setContext(label);
      if (text) insertMirrorText(text);
      else openReply();
    };
    function showSelectionMenu() {
      var text = selectedText();
      selectionMenu.classList.toggle('spx-visible', text.length > 2 && !panel.contains(document.activeElement));
    }

    panel.spxCleanup = function spxCleanupQuickReply() {
      revokePreviewUrls();
      window.clearTimeout(draftTimer);
      document.removeEventListener('selectionchange', showSelectionMenu);
      if (launcher.parentNode) launcher.remove();
      if (selectionMenu.parentNode) selectionMenu.remove();
    };
    panel.spxLauncher = launcher;
    panel.spxSelectionMenu = selectionMenu;

    collapse.addEventListener('click', collapseReply);
    launcher.addEventListener('click', openReply);
    clearContext.addEventListener('click', function clearQuickReplyContext() {
      activeContext = '';
      context.classList.remove('spx-visible');
    });
    mirror.addEventListener('input', function handleMirrorInput() {
      updateCounter();
      scheduleDraftSave();
    });
    mirror.addEventListener('keydown', function handleMirrorShortcut(event) {
      if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
        event.preventDefault();
        sendReply();
      }
    });
    desc.addEventListener('input', function handleDescInput() {
      if (attachmentDesc) attachmentDesc.value = desc.value;
      scheduleDraftSave();
    });
    if (attachmentInput) {
      attachmentInput.addEventListener('change', function handleAttachmentInputChange() {
        renderAttachments();
        scheduleDraftSave();
        openReply();
      });
    }
    saveDraft.addEventListener('click', function saveQuickReplyDraft() {
      scheduleDraftSave();
      markDraftSaved();
    });
    send.addEventListener('click', sendReply);
    panel.addEventListener('click', function handleQuickReplyPanelClick(event) {
      var target = event.target;
      if (target.closest('[data-spx-emote]')) {
        insertMirrorText(target.closest('[data-spx-emote]').dataset.spxEmote || '');
        return;
      }
      if (target.closest('[data-spx-remove-attachment]')) {
        removeAttachment(Number(target.closest('[data-spx-remove-attachment]').dataset.spxRemoveAttachment));
      }
    });
    selectionQuote.addEventListener('click', function quoteSelectedText() {
      var text = selectedText();
      if (!text) return;
      setContext('引用所选文字');
      insertMirrorText('[quote]' + text.slice(0, 160) + '[/quote]\n');
      selectionMenu.classList.remove('spx-visible');
    });
    selectionCancel.addEventListener('click', function cancelSelectedQuote() {
      selectionMenu.classList.remove('spx-visible');
    });
    document.addEventListener('selectionchange', showSelectionMenu);

    updateCounter();
    renderAttachments();
    return panel;
  }

  function enhanceQuickReply(settings, state) {
    if (detectPageType(location.href) !== 'read' && detectPageType(location.href) !== 'post') return;
    var oldPanel = qs('#spx-quick-reply');
    if (oldPanel && typeof oldPanel.spxCleanup === 'function') oldPanel.spxCleanup();
    if (oldPanel) oldPanel.remove();
    var oldLauncher = qs('#spx-quick-reply-launcher');
    if (oldLauncher) oldLauncher.remove();
    var oldSelection = qs('#spx-quick-reply-selection');
    if (oldSelection) oldSelection.remove();

    var editor = getQuickReplyEditor(document);
    var panel = createQuickReplyPanel(settings, editor, state);
    bindQuickReplyFormSubmit(editor, settings, state, panel);
    if (!panel || !document.body) return;
    document.body.appendChild(panel);
    if (panel.spxLauncher) document.body.appendChild(panel.spxLauncher);
    if (panel.spxSelectionMenu) document.body.appendChild(panel.spxSelectionMenu);
  }

  function createSettingsPanel(settings, state) {
    var panel = qs('#spx-settings');
    if (panel) return panel;

    var settingLabels = {
      cleanMode: '清爽模式',
      readerMode: '阅读排版优化',
      immersiveRead: '帖子页沉浸全屏',
      nightMode: '夜间模式',
      unifiedPreviewGallery: '预览图集中显示',
      forumDashboard: '论坛仪表盘',
      compactRead: '阅读页紧凑',
      foldQuotes: '折叠长引用',
      hideUserProfile: '隐藏头像资料',
      networkFriendly: '网络友好模式',
      autoBuyPost: '自动购买低价帖子',
      unreadOnly: '列表只看未读',
      onlyOriginalAuthor: '阅读页只看楼主',
    };
    var settingKeys = getSettingsPanelKeys(location.href, document);
    function renderSettingControl(key) {
      return '<label><input type="checkbox" data-key="' + key + '"> ' + settingLabels[key] + '</label>';
    }
    function renderSettingsSection(title, keys, helpText) {
      var controls = (keys || []).filter(function keepSettingKey(key) {
        return settingKeys.indexOf(key) !== -1;
      }).map(renderSettingControl);
      if (!controls.length) return [];
      return [
        '<section class="spx-settings-section">',
        '<h4>' + title + '</h4>',
        helpText ? '<div class="spx-help">' + helpText + '</div>' : '',
        '<div class="spx-settings-grid">',
      ].concat(controls, [
        '</div>',
        '</section>',
      ]);
    }
    var readingControls = renderSettingsSection(
      '阅读体验',
      ['readerMode', 'immersiveRead', 'nightMode', 'compactRead', 'foldQuotes', 'hideUserProfile', 'onlyOriginalAuthor', 'unifiedPreviewGallery'],
      '控制正文排版、沉浸阅读、夜间模式、头像资料和图集显示。'
    );
    var listControls = renderSettingsSection(
      '列表增强',
      ['cleanMode', 'unreadOnly', 'forumDashboard'],
      '控制列表页、首页仪表盘和通用页面的低频内容、未读筛选。'
    );
    var networkControls = renderSettingsSection(
      '网络请求',
      ['networkFriendly'],
      '限制脚本后台请求频率,缓存悬停预览和导航刷新,遇到 504 或操作频繁提示时自动冷却。'
    );
    var autoBuyControls = settingKeys.indexOf('autoBuyPost') !== -1 ? [
      '<section class="spx-settings-section">',
      '<h4>自动购买</h4>',
      renderSettingControl('autoBuyPost'),
      '<label class="spx-number-setting"><span>价格上限</span><span><input type="number" min="0" step="1" data-number="autoBuyMaxSp"> SP</span></label>',
      '<div class="spx-help">仅当帖子价格严格小于该值、账户 SP 足够且页面存在购买按钮时自动购买;默认关闭。</div>',
      '</section>',
    ] : [];
    var navigationControls = [
      '<section class="spx-settings-section">',
      '<h4>导航中心</h4>',
      '<div class="spx-help">控制左侧导航中心的块区域密度,搜索和置顶入口会即时保留在导航栏内。</div>',
      '<label class="spx-choice-setting"><span>导航密度</span><select data-choice="moduleNavDensity">',
      '<option value="compact">紧凑</option>',
      '<option value="standard">标准</option>',
      '<option value="comfortable">舒适</option>',
      '</select></label>',
      '</section>',
    ];

    panel = createEl('div', 'spx-settings');
    panel.id = 'spx-settings';
    panel.hidden = true;
    panel.innerHTML = [
      '<div class="spx-settings-header">',
      '<h3>South Plus 增强设置</h3>',
      '<p class="spx-settings-subtitle">按场景分组管理阅读、列表、购买和本地数据。</p>',
      '</div>',
      '<div class="spx-settings-body">',
    ].concat(readingControls, listControls, networkControls, navigationControls, autoBuyControls, [
      '<section class="spx-settings-section">',
      '<h4>本地屏蔽</h4>',
      '<div class="spx-help">标题和作者关键词每行一个,保存后立即应用到当前页面。</div>',
      '<div>标题屏蔽关键词</div>',
      '<textarea data-list="titleKeywords"></textarea>',
      '<div>作者屏蔽关键词</div>',
      '<textarea data-list="authorKeywords"></textarea>',
      '</section>',
      '<section class="spx-settings-section">',
      '<h4>快捷回复</h4>',
      '<div class="spx-help">帖子详情页或回复页会显示为快捷按钮;快捷语句和帖子页原生提交按钮都会无刷新展示新回复。</div>',
      '<textarea data-list="quickReplies"></textarea>',
      '</section>',
      '<section class="spx-settings-section">',
      '<h4>备份恢复</h4>',
      '<div class="spx-row">',
      '<button data-action="export-backup">导出备份</button>',
      '<button data-action="import-backup">导入备份</button>',
      '</div>',
      '</section>',
      '<section class="spx-settings-section">',
      '<h4>数据管理</h4>',
      '<div class="spx-help">数据健康和体积只读取本地 localStorage;清空类操作不可自动恢复,请先导出备份。</div>',
      '<div class="spx-row">',
      '<button data-action="show-data-health">数据健康</button>',
      '<button data-action="show-storage-usage">本地体积</button>',
      '<button class="spx-danger" data-action="clear-read">清空已读</button>',
      '<button class="spx-danger" data-action="clear-progress">清空进度</button>',
      settingKeys.indexOf('autoBuyPost') !== -1 ? '<button class="spx-danger" data-action="clear-auto-buy">清空自动购买记录</button>' : '',
      '</div>',
      '<div class="spx-data-health" data-role="data-health" hidden></div>',
      '</section>',
      '</div>',
      '<div class="spx-settings-footer">',
      '<button data-action="close">关闭</button>',
      '<button class="spx-primary" data-action="save">保存</button>',
      '</div>',
      '<input type="file" accept="application/json,.json" data-action="import-backup-file" hidden>',
    ]).join('');
    document.body.appendChild(panel);

    function setTemporaryButtonText(button, text) {
      if (!button) return;
      var previous = button.dataset.spxOriginalText || button.textContent;
      button.dataset.spxOriginalText = previous;
      button.textContent = text;
      window.setTimeout(function restoreButtonText() {
        button.textContent = button.dataset.spxOriginalText || previous;
        delete button.dataset.spxOriginalText;
      }, 1800);
    }

    function syncForm() {
      qsa('input[data-key]', panel).forEach(function syncCheckbox(input) {
        input.checked = !!settings[input.dataset.key];
      });
      qsa('textarea[data-list]', panel).forEach(function syncList(textarea) {
        textarea.value = (settings[textarea.dataset.list] || []).join('\n');
      });
      qsa('input[data-number]', panel).forEach(function syncNumber(input) {
        input.value = String(Number(settings[input.dataset.number]) || 0);
      });
      qsa('select[data-choice]', panel).forEach(function syncChoice(select) {
        select.value = settings[select.dataset.choice] || '';
      });
    }

    function saveForm() {
      qsa('input[data-key]', panel).forEach(function readCheckbox(input) {
        settings[input.dataset.key] = input.checked;
      });
      qsa('textarea[data-list]', panel).forEach(function readList(textarea) {
        settings[textarea.dataset.list] = textarea.dataset.list === 'quickReplies'
          ? parseQuickReplyList(textarea.value)
          : parseLineList(textarea.value);
      });
      qsa('input[data-number]', panel).forEach(function readNumber(input) {
        settings[input.dataset.number] = Math.max(0, Number(input.value) || 0);
      });
      qsa('select[data-choice]', panel).forEach(function readChoice(select) {
        if (select.dataset.choice === 'moduleNavDensity') {
          settings.moduleNavDensity = normalizeModuleNavDensity(select.value);
        }
      });
      delete document.documentElement.dataset.spxAutoBuyStatus;
      var autoBuyStatus = qs('#spx-auto-buy-status');
      if (autoBuyStatus) autoBuyStatus.remove();
      Object.assign(settings, normalizeSettings(settings));
      saveSettings(settings);
      enhanceAll(settings, state);
    }

    function getCurrentDataHealthPayload() {
      return {
        settings: settings,
        read: state && state.read,
        watch: state && state.watch,
        progress: state && state.progress,
        autoBuyAttempts: loadAutoBuyAttempts(),
        resources: state && state.resources,
        navigation: loadNavigationPool(),
        navigationPins: loadNavigationPins(),
      };
    }

    function appendStorageUsageDetails(box, storageReport) {
      box.appendChild(createEl('strong', '', '本地数据体积'));
      box.appendChild(createEl('div', 'spx-help', formatStorageUsageSummary(storageReport)));
      var usageList = createEl('div', 'spx-storage-usage');
      storageReport.entries.forEach(function appendStorageEntry(entry) {
        var row = createEl('div', 'spx-storage-usage-row spx-storage-' + getStorageUsageLevel(entry));
        var main = createEl('span', 'spx-storage-usage-main');
        main.appendChild(createEl('b', '', entry.label));
        main.appendChild(createEl('em', '', entry.key));
        row.appendChild(main);
        row.appendChild(createEl('span', 'spx-storage-usage-meta', entry.size + ' · ' + formatStorageUsageLimit(entry)));
        usageList.appendChild(row);
      });
      box.appendChild(usageList);
      box.appendChild(createEl('div', 'spx-storage-suggestions', '清理建议:' + formatStorageUsageWarnings(storageReport)));
    }

    function appendDataHealthActions(box, report, refreshAction) {
      var actions = createEl('div', 'spx-row');
      var refresh = createEl('button', '', refreshAction === 'refresh-storage-usage' ? '刷新体积' : '刷新统计');
      var cleanup = createEl('button', '', '清理重复/过期');
      refresh.type = 'button';
      cleanup.type = 'button';
      refresh.dataset.action = refreshAction || 'refresh-data-health';
      cleanup.dataset.action = 'cleanup-data-health';
      cleanup.disabled = !report.cleanupCount;
      actions.appendChild(refresh);
      actions.appendChild(cleanup);
      box.appendChild(actions);
    }

    function renderDataHealthPanel(message) {
      var box = qs('[data-role="data-health"]', panel);
      if (!box) return;
      var payload = getCurrentDataHealthPayload();
      var report = collectDataHealthReport(payload);
      var storageReport = collectStorageUsageReport(payload);
      box.hidden = false;
      box.textContent = '';
      box.appendChild(createEl('strong', '', '本地数据健康'));
      box.appendChild(createEl('div', 'spx-help', formatDataHealthSummary(report)));
      box.appendChild(createEl('div', 'spx-help', formatDataHealthWarnings(report)));
      appendStorageUsageDetails(box, storageReport);
      if (message) box.appendChild(createEl('div', 'spx-help', message));
      appendDataHealthActions(box, report, 'refresh-data-health');
    }

    function renderStorageUsagePanel(message) {
      var box = qs('[data-role="data-health"]', panel);
      if (!box) return;
      var payload = getCurrentDataHealthPayload();
      var report = collectDataHealthReport(payload);
      var storageReport = collectStorageUsageReport(payload);
      box.hidden = false;
      box.textContent = '';
      appendStorageUsageDetails(box, storageReport);
      if (message) box.appendChild(createEl('div', 'spx-help', message));
      appendDataHealthActions(box, report, 'refresh-storage-usage');
    }

    function applyDataHealthCleanup(button) {
      var cleanup = cleanupDataHealthPayload(getCurrentDataHealthPayload());
      if (!cleanup.before.cleanupCount) {
        renderDataHealthPanel('暂无重复或过期数据需要清理。');
        return;
      }
      if (typeof window.confirm === 'function' && !window.confirm('将清理重复稍后看、重复/过期阅读进度和异常记录,确认继续?')) return;
      var done = applyBackupPayload(cleanup.payload, settings, state);
      renderDataHealthPanel(done ? '已清理 ' + cleanup.before.cleanupCount + ' 项数据。' : '清理失败。');
      setTemporaryButtonText(button, done ? '已清理' : '清理失败');
    }

    panel.addEventListener('click', function handleSettingsClick(event) {
      var action = event.target && event.target.dataset && event.target.dataset.action;
      if (!action) return;
      if (action === 'save') {
        saveForm();
        setSettingsPanelHidden(panel, true, SETTINGS_BUTTON_SELECTOR);
      }
      if (action === 'close') setSettingsPanelHidden(panel, true, SETTINGS_BUTTON_SELECTOR);
      if (action === 'export-backup') {
        var exported = downloadBackupPayload(collectBackupPayload(settings, state));
        setTemporaryButtonText(event.target, exported ? '已导出备份' : '导出失败');
      }
      if (action === 'import-backup') {
        var fileInput = qs('input[data-action="import-backup-file"]', panel);
        if (!fileInput) return;
        fileInput.value = '';
        fileInput.click();
      }
      if (action === 'show-data-health' || action === 'refresh-data-health') {
        renderDataHealthPanel();
      }
      if (action === 'show-storage-usage' || action === 'refresh-storage-usage') {
        renderStorageUsagePanel();
      }
      if (action === 'cleanup-data-health') {
        applyDataHealthCleanup(event.target);
      }
      if (action === 'clear-read') {
        state.read = {};
        saveMap(READ_KEY, state.read);
        enhanceAll(settings, state);
        setTemporaryButtonText(event.target, '已清空已读');
      }
      if (action === 'clear-progress') {
        state.progress = {};
        saveReadProgress(state.progress);
        clearReadProgressRestoreRequest(parseThreadId(location.href));
        refreshWatchCenter();
        refreshHistoryCenter();
        setTemporaryButtonText(event.target, '已清空阅读进度');
      }
      if (action === 'clear-auto-buy') {
        saveAutoBuyAttempts({});
        delete document.documentElement.dataset.spxAutoBuyStatus;
        var autoBuyStatus = qs('#spx-auto-buy-status');
        if (autoBuyStatus) autoBuyStatus.remove();
        refreshAutoBuyCenter();
        setTemporaryButtonText(event.target, '已清空购买记录');
      }
    });

    panel.addEventListener('change', function handleSettingsChange(event) {
      var target = event.target;
      if (!target || !target.dataset || target.dataset.action !== 'import-backup-file') return;
      var file = target.files && target.files[0];
      if (!file) return;
      var importButton = qs('button[data-action="import-backup"]', panel);
      importBackupFile(file, settings, state, function handleBackupImport(done, message) {
        setTemporaryButtonText(importButton, message || (done ? '已导入备份' : '导入失败'));
      });
    });

    panel.spxSync = syncForm;
    syncForm();
    return panel;
  }

  function getToolbarNavigationConfigs(url, root) {
    var page = currentPageNumber(url);
    return [
      {
        show: true,
        group: '页面导航',
        text: '顶',
        label: '回到顶部',
        title: '回到顶部',
        description: '快速滚动到页面开头',
        onClick: function top() {
          window.scrollTo({ top: 0, behavior: 'smooth' });
        },
      },
      {
        show: true,
        group: '页面导航',
        text: '底',
        label: '滚到底部',
        title: '滚到底部',
        description: '快速滚动到页面末尾',
        onClick: function bottom() {
          window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' });
        },
      },
      {
        show: shouldShowToolbarAction('prevPage', url, root),
        group: '页面导航',
        text: '上',
        label: '上一页',
        title: '上一页',
        description: '切换到上一页内容',
        href: buildPageUrl(url, Math.max(1, page - 1)),
      },
      {
        show: shouldShowToolbarAction('nextPage', url, root),
        group: '页面导航',
        text: '下',
        label: '下一页',
        title: '下一页',
        description: '切换到下一页内容',
        href: buildPageUrl(url, page + 1),
      },
      {
        show: shouldShowToolbarFeature('latest'),
        group: '页面导航',
        text: '新',
        label: '最新帖子',
        title: '最新帖子',
        description: '打开站点最新帖子列表',
        href: location.origin + '/search2.php?orderway-postdate-asc-desc-newatc-1.html',
      },
      {
        show: shouldShowToolbarAction('home', url, root),
        group: '页面导航',
        text: '首',
        label: '论坛首页',
        title: '论坛首页',
        description: '回到当前站点首页',
        href: location.origin + '/index.php',
      },
    ];
  }

  function getToolbarToggleConfigs(url, root) {
    return [
      { show: shouldShowToolbarFeature('clean'), group: '阅读模式', key: 'cleanMode', text: '净', label: '清爽模式', title: '切换清爽模式', description: '隐藏低频模块和干扰内容' },
      { show: shouldShowToolbarAction('reader', url, root), group: '阅读模式', key: 'readerMode', text: '字', label: '阅读排版', title: '切换阅读排版优化', description: '优化字体、宽度和正文间距' },
      { show: true, group: '阅读模式', key: 'nightMode', text: '夜', label: '夜间模式', title: '切换夜间模式', description: '切换深色影院式阅读和图集界面' },
      { show: shouldShowToolbarAction('adBlock', url, root), group: '阅读模式', key: 'adBlock', text: '广', label: '隐藏广告', title: '切换隐藏广告', description: '隐藏广告链接、图片和容器' },
      { show: shouldShowToolbarAction('homeDashboard', url, root), group: '阅读模式', key: 'homeDashboard', text: '模', label: '首页模块', title: '切换首页模块全屏', description: '以网格方式整理首页板块' },
      { show: shouldShowToolbarAction('homeDashboard', url, root), group: '阅读模式', key: 'forumDashboard', text: '盘', label: '论坛仪表盘', title: '切换首页论坛仪表盘', description: '展示浏览、收藏、资源和本地积压摘要' },
      { show: shouldShowToolbarAction('immersiveRead', url, root), group: '阅读模式', key: 'immersiveRead', text: '屏', label: '沉浸阅读', title: '切换帖子页沉浸全屏', description: '放大帖子内容并弱化周边' },
      { show: shouldShowToolbarAction('previewGallery', url, root), group: '阅读模式', key: 'unifiedPreviewGallery', text: '图', label: '预览图集', title: '切换预览图集中显示', description: '集中查看当前页正文图片' },
      { show: shouldShowToolbarAction('unreadOnly', url, root), group: '阅读模式', key: 'unreadOnly', text: '未', label: '只看未读', title: '只看未读', description: '论坛列表隐藏已读主题' },
      { show: shouldShowToolbarAction('onlyOriginalAuthor', url, root), group: '阅读模式', key: 'onlyOriginalAuthor', text: '楼', label: '只看楼主', title: '只看楼主', description: '阅读页隐藏非楼主回复' },
    ];
  }

  function getToolbarCenterConfigs() {
    return [
      {
        show: true,
        group: '我的中心',
        text: '存',
        label: '稍后看',
        title: '打开稍后看中心',
        description: '管理保存的主题和续读入口',
        panelId: 'spx-watch-center',
        buttonDataset: 'spxWatchCenterButton',
        buttonSelector: '[data-spx-watch-center-button="1"]',
        createPanel: createWatchCenterPanel,
      },
      {
        show: true,
        group: '我的中心',
        text: '历',
        label: '最近浏览',
        title: '打开最近浏览',
        description: '查看阅读历史和进度',
        panelId: 'spx-history-center',
        buttonDataset: 'spxHistoryCenterButton',
        buttonSelector: '[data-spx-history-center-button="1"]',
        createPanel: createHistoryCenterPanel,
      },
      {
        show: true,
        group: '我的中心',
        text: '买',
        label: '购买记录',
        title: '打开自动购买记录',
        description: '查看自动购买执行状态',
        panelId: 'spx-auto-buy-center',
        buttonDataset: 'spxAutoBuyCenterButton',
        buttonSelector: '[data-spx-auto-buy-center-button="1"]',
        createPanel: createAutoBuyCenterPanel,
      },
      {
        show: true,
        group: '我的中心',
        text: '源',
        label: '资源工作台',
        title: '打开资源工作台',
        description: '管理磁力、网盘和种子资源',
        panelId: 'spx-resource-center',
        buttonDataset: 'spxResourceCenterButton',
        buttonSelector: '[data-spx-resource-center-button="1"]',
        createPanel: createResourceCenterPanel,
      },
    ];
  }

  function getToolbarSettingsConfig() {
    return {
      show: true,
      group: '设置',
      text: '设',
      label: '脚本设置',
      title: '打开脚本设置面板',
      description: '配置开关、备份和数据健康',
      kind: 'settings',
    };
  }

  function getToolboxActionConfigs(url, root) {
    return getToolbarNavigationConfigs(url, root)
      .concat(getToolbarToggleConfigs(url, root))
      .concat(getToolbarCenterConfigs())
      .concat([getToolbarSettingsConfig()])
      .filter(function keepVisibleToolboxAction(config) {
        return config && config.show !== false;
      });
  }

  function getToolboxPriority(config, url) {
    var type = detectPageType(url);
    var key = config.key || config.panelId || config.kind || config.label || '';
    var readPriority = {
      readerMode: 0,
      nightMode: 1,
      immersiveRead: 2,
      onlyOriginalAuthor: 3,
      unifiedPreviewGallery: 4,
      'spx-resource-center': 4,
      'spx-history-center': 5,
    };
    var forumPriority = {
      unreadOnly: 0,
      'spx-watch-center': 1,
      nightMode: 2,
      cleanMode: 3,
      readerMode: 4,
      latest: 5,
    };
    var map = type === 'read' ? readPriority : (type === 'forum' ? forumPriority : null);
    return map && map[key] !== undefined ? map[key] : 50;
  }

  function getToolboxGroups(url, root) {
    var order = ['页面导航', '阅读模式', '我的中心', '设置'];
    var groups = {};
    getToolboxActionConfigs(url, root).forEach(function collectToolboxConfig(config) {
      var group = config.group || '其他';
      if (!groups[group]) groups[group] = [];
      groups[group].push(config);
    });
    return order.concat(Object.keys(groups).filter(function keepExtraGroup(group) {
      return order.indexOf(group) === -1;
    })).filter(function hasGroupItems(group) {
      return groups[group] && groups[group].length;
    }).map(function toToolboxGroup(group) {
      return {
        title: group,
        items: groups[group].slice().sort(function sortToolboxItems(a, b) {
          return getToolboxPriority(a, url) - getToolboxPriority(b, url);
        }),
      };
    });
  }

  function setPopupPanelHidden(panel, hidden, buttonSelector) {
    if (!panel) return;
    panel.hidden = !!hidden;
    if (buttonSelector) {
      qsa(buttonSelector).forEach(function togglePopupButton(button) {
        button.classList.toggle('spx-active', !panel.hidden);
        button.setAttribute('aria-expanded', panel.hidden ? 'false' : 'true');
      });
    }
  }

  function setToolboxHidden(panel, hidden, buttonSelector) {
    setPopupPanelHidden(panel, hidden, buttonSelector);
  }

  function setSettingsPanelHidden(panel, hidden, buttonSelector) {
    setPopupPanelHidden(panel, hidden, buttonSelector);
  }

  function isToolboxActionActive(config, settings) {
    if (!config) return false;
    if (config.key) return !!(settings && settings[config.key]);
    if (config.panelId) {
      var panel = qs('#' + config.panelId);
      return !!(panel && !panel.hidden);
    }
    if (config.kind === 'settings') {
      var settingsPanel = qs('#spx-settings');
      return !!(settingsPanel && !settingsPanel.hidden);
    }
    return false;
  }

  function appendToolboxActionContent(node, config) {
    node.appendChild(createEl('span', 'spx-toolbox-key', config.text || ''));
    var copy = createEl('span', 'spx-toolbox-copy');
    copy.appendChild(createEl('span', 'spx-toolbox-name', config.label || config.title || config.text || '工具'));
    copy.appendChild(createEl('span', 'spx-toolbox-desc', config.description || config.title || ''));
    node.appendChild(copy);
  }

  function createToolboxActionElement(config, settings, state, toolbox) {
    var node = createEl(config.href ? 'a' : 'button', 'spx-toolbox-action');
    node.title = config.title || config.label || '';
    if (config.href) {
      node.href = config.href;
    } else {
      node.type = 'button';
    }
    if (config.buttonDataset) node.dataset[config.buttonDataset] = '1';
    node.classList.toggle('spx-active', isToolboxActionActive(config, settings));
    appendToolboxActionContent(node, config);

    if (config.href) return node;

    node.addEventListener('click', function handleToolboxAction() {
      if (config.kind === 'settings') {
        var settingsPanel = createSettingsPanel(settings, state);
        if (settingsPanel.spxSync) settingsPanel.spxSync();
        setSettingsPanelHidden(settingsPanel, !settingsPanel.hidden, SETTINGS_BUTTON_SELECTOR);
        setToolboxHidden(toolbox, true, TOOLBOX_BUTTON_SELECTOR);
        return;
      }
      if (config.key) {
        settings[config.key] = !settings[config.key];
        saveSettings(settings);
        enhanceAll(settings, state);
        if (toolbox && toolbox.spxRender) toolbox.spxRender();
        return;
      }
      if (config.createPanel) {
        var centerPanel = config.createPanel(settings, state);
        if (centerPanel.spxRender) centerPanel.spxRender();
        setCenterPanelHidden(centerPanel, !centerPanel.hidden, config.buttonSelector);
        setToolboxHidden(toolbox, true, TOOLBOX_BUTTON_SELECTOR);
        return;
      }
      if (typeof config.onClick === 'function') {
        config.onClick();
        setToolboxHidden(toolbox, true, TOOLBOX_BUTTON_SELECTOR);
      }
    });

    return node;
  }

  function createToolboxPanel(settings, state) {
    var panel = qs('#spx-toolbox');
    if (panel) return panel;

    panel = createEl('div', 'spx-toolbox');
    panel.id = 'spx-toolbox';
    panel.setAttribute('role', 'dialog');
    panel.setAttribute('aria-label', 'South Plus 工具箱');
    panel.hidden = true;
    panel.spxRender = function renderToolbox() {
      panel.textContent = '';
      var header = createEl('div', 'spx-toolbox-header');
      var title = createEl('div');
      title.appendChild(createEl('div', 'spx-toolbox-eyebrow', '功能分组'));
      title.appendChild(createEl('h3', '', 'South Plus 工具箱'));
      var closeButton = createEl('button', 'spx-toolbox-close', '×');
      closeButton.type = 'button';
      closeButton.title = '关闭工具箱';
      closeButton.addEventListener('click', function closeToolbox() {
        setToolboxHidden(panel, true, TOOLBOX_BUTTON_SELECTOR);
      });
      header.appendChild(title);
      header.appendChild(closeButton);
      panel.appendChild(header);

      var body = createEl('div', 'spx-toolbox-body');
      getToolboxGroups(location.href, document).forEach(function appendGroup(group) {
        var section = createEl('section', 'spx-toolbox-section');
        var sectionTitle = createEl('div', 'spx-toolbox-section-title');
        sectionTitle.appendChild(createEl('span', '', group.title));
        sectionTitle.appendChild(createEl('span', 'spx-toolbox-count', String(group.items.length)));
        section.appendChild(sectionTitle);
        var grid = createEl('div', 'spx-toolbox-grid');
        group.items.forEach(function appendToolboxAction(config) {
          grid.appendChild(createToolboxActionElement(config, settings, state, panel));
        });
        section.appendChild(grid);
        body.appendChild(section);
      });
      panel.appendChild(body);
    };
    document.body.appendChild(panel);
    return panel;
  }

  function collectCurrentPageCommandItems(root) {
    var doc = root || document;
    var seen = {};
    var items = [];
    qsa('a[href]', doc).forEach(function collectPageCommandLink(link) {
      var href = link.href || link.getAttribute('href') || '';
      var text = compactText(link.textContent || link.title || '');
      if (!href || !text || /^javascript:/i.test(href)) return;
      var isThread = /read\.php\?tid[=-]?\d+|read\.php\?tid-\d+/i.test(href);
      var isForum = /thread\.php\?fid[=-]?\d+|thread\.php\?fid-\d+/i.test(href);
      if (!isThread && !isForum) return;
      if (/^(上一页|下一页|首页|尾页|收藏|回复|引用|编辑|删除)$/.test(text)) return;
      var key = href.replace(/#.*$/, '') + '|' + text;
      if (seen[key]) return;
      seen[key] = true;
      items.push({
        title: text.slice(0, 80),
        href: href,
        category: 'navigate',
        icon: isThread ? '帖' : '版',
        source: isThread ? '当前页面主题' : '当前页面版块',
        description: isThread ? '跳转到当前页面中的主题' : '跳转到当前页面中的版块入口',
        keywords: compactText((link.closest && link.closest('tr') && link.closest('tr').textContent) || ''),
      });
    });
    return items.slice(0, 40);
  }

  function getCommandPaletteData(settings, state) {
    return {
      toolboxConfigs: getToolboxActionConfigs(location.href, document),
      navigationConfigs: getAllModuleNavigationConfigs(),
      pageItems: collectCurrentPageCommandItems(document),
      watch: state && state.watch,
      progress: state && state.progress,
      resources: state && state.resources,
      settings: settings,
    };
  }

  function getStateMapSize(map) {
    return Object.keys(map || {}).length;
  }

  function getCommandPaletteCacheKey(settings, state) {
    var optionKeys = [
      'cleanMode',
      'readerMode',
      'immersiveRead',
      'nightMode',
      'unifiedPreviewGallery',
      'forumDashboard',
      'networkFriendly',
      'unreadOnly',
      'onlyOriginalAuthor',
      'moduleNavDensity',
    ];
    var optionText = optionKeys.map(function mapCommandOption(key) {
      return key + ':' + String(settings && settings[key]);
    }).join('|');
    return [
      location.href,
      enhanceCycle,
      getStateMapSize(state && state.watch),
      getStateMapSize(state && state.progress),
      getStateMapSize(state && state.resources),
      optionText,
    ].join('||');
  }

  function getCommandPaletteEntries(panel, settings, state) {
    var cacheKey = getCommandPaletteCacheKey(settings, state);
    if (!panel.spxAllEntries || panel.spxEntriesCacheKey !== cacheKey) {
      panel.spxAllEntries = collectCommandPaletteEntries(getCommandPaletteData(settings, state));
      panel.spxEntriesCacheKey = cacheKey;
    }
    return panel.spxAllEntries || [];
  }

  function invalidateCommandPaletteCache() {
    var panel = qs('#spx-command-palette-overlay');
    if (!panel) return;
    panel.spxAllEntries = null;
    panel.spxEntriesCacheKey = '';
  }

  function setCommandPaletteHidden(panel, hidden) {
    if (!panel) return;
    panel.hidden = !!hidden;
    if (panel.hidden && panel.spxSearchTimer) {
      window.clearTimeout(panel.spxSearchTimer);
      panel.spxSearchTimer = null;
    }
    qsa(COMMAND_PALETTE_BUTTON_SELECTOR).forEach(function toggleCommandButton(button) {
      button.classList.toggle('spx-active', !panel.hidden);
      button.setAttribute('aria-expanded', panel.hidden ? 'false' : 'true');
    });
    if (!panel.hidden) {
      panel.spxRender();
      window.setTimeout(function focusCommandSearch() {
        var input = qs('.spx-command-search', panel);
        if (input && typeof input.focus === 'function') input.focus();
      }, 0);
    }
  }

  function getCommandCenterPanelConfig(panelId) {
    return getToolbarCenterConfigs().filter(function matchCenterConfig(config) {
      return config.panelId === panelId;
    })[0] || null;
  }

  function openCommandCenterPanel(panelId, settings, state) {
    var config = getCommandCenterPanelConfig(panelId);
    if (!config || !config.createPanel) return false;
    var centerPanel = config.createPanel(settings, state);
    if (centerPanel.spxRender) centerPanel.spxRender();
    setCenterPanelHidden(centerPanel, false, config.buttonSelector);
    return true;
  }

  function openCommandUrl(href) {
    var value = String(href || '');
    if (!value) return false;
    if (/^(?:magnet|ed2k):/i.test(value)) {
      location.href = value;
      return true;
    }
    try {
      var url = new URL(value, location.href);
      if (url.origin === location.origin) {
        location.href = url.href;
      } else if (typeof window.open === 'function') {
        window.open(url.href, '_blank', 'noopener');
      } else {
        location.href = url.href;
      }
      return true;
    } catch (error) {
      location.href = value;
      return true;
    }
  }

  function closeCommandRelatedPanels() {
    var toolbox = qs('#spx-toolbox');
    if (toolbox) setToolboxHidden(toolbox, true, TOOLBOX_BUTTON_SELECTOR);
    var settingsPanel = qs('#spx-settings');
    if (settingsPanel) setSettingsPanelHidden(settingsPanel, true, SETTINGS_BUTTON_SELECTOR);
  }

  function executeCommandPaletteEntry(entry, settings, state, panel) {
    if (!entry) return false;
    var config = entry.payload || null;
    setCommandPaletteHidden(panel, true);
    if (config && config.href) return openCommandUrl(config.href);
    if (config && config.kind === 'settings') {
      closeCommandRelatedPanels();
      var settingsPanel = createSettingsPanel(settings, state);
      if (settingsPanel.spxSync) settingsPanel.spxSync();
      setSettingsPanelHidden(settingsPanel, false, SETTINGS_BUTTON_SELECTOR);
      return true;
    }
    if (config && config.key) {
      settings[config.key] = !settings[config.key];
      saveSettings(settings);
      enhanceAll(settings, state);
      return true;
    }
    if (config && config.createPanel && config.panelId) {
      closeCommandRelatedPanels();
      return openCommandCenterPanel(config.panelId, settings, state);
    }
    if (config && typeof config.onClick === 'function') {
      config.onClick();
      return true;
    }
    if (entry.action === 'open-settings') {
      var panelNode = createSettingsPanel(settings, state);
      if (panelNode.spxSync) panelNode.spxSync();
      setSettingsPanelHidden(panelNode, false, SETTINGS_BUTTON_SELECTOR);
      return true;
    }
    if (entry.action === 'open-panel' && entry.panelId) {
      return openCommandCenterPanel(entry.panelId, settings, state);
    }
    if (entry.action === 'open-watch-entry') {
      var watchEntry = getWatchCenterEntries(state.watch, state.progress).filter(function matchWatchEntry(item) {
        return item.id === entry.recordId;
      })[0];
      if (watchEntry) {
        openProgressEntry(state, watchEntry.id, watchEntry.progressUrl || watchEntry.url, 'next');
        return true;
      }
    }
    if (entry.action === 'open-history-entry') {
      var historyEntry = getHistoryCenterEntries(state.progress).filter(function matchHistoryEntry(item) {
        return item.id === entry.recordId;
      })[0];
      if (historyEntry) {
        openProgressEntry(state, historyEntry.id, historyEntry.url, 'next');
        return true;
      }
    }
    if (entry.href) return openCommandUrl(entry.href);
    return false;
  }

  function isCommandPaletteSearchInput(target) {
    return !!(target && target.classList && target.classList.contains('spx-command-search'));
  }

  function rerenderCommandPaletteAfterSearch(panel, target) {
    if (!panel) return;
    panel.spxState.query = target ? target.value : panel.spxState.query;
    panel.spxState.activeIndex = 0;
    panel.spxRender();
    var nextInput = qs('.spx-command-search', panel);
    if (nextInput) {
      nextInput.focus();
      if (typeof nextInput.setSelectionRange === 'function') {
        nextInput.setSelectionRange(nextInput.value.length, nextInput.value.length);
      }
    }
  }

  function renderCommandPaletteDetail(panel, entry) {
    var detail = qs('.spx-command-detail', panel);
    if (!detail) return;
    detail.textContent = '';
    detail.appendChild(createEl('h3', '', entry ? entry.title : '没有匹配命令'));
    detail.appendChild(createEl('p', '', entry ? entry.description : '换一个关键词或分类继续搜索。'));
    var card = createEl('div', 'spx-command-detail-card');
    [
      ['来源', entry ? getCommandPaletteSourceLabel(entry) : '-'],
      ['目标', entry ? (entry.target || entry.href || entry.panelId || entry.key || '-') : '-'],
      ['风险', entry ? (entry.risk || '低') : '-'],
    ].forEach(function appendDetailRow(item) {
      var isTarget = item[0] === '目标';
      var row = createEl('div', 'spx-command-detail-row' + (isTarget ? ' spx-command-detail-target' : ''));
      row.appendChild(createEl('span', '', item[0]));
      if (isTarget && entry && entry.href && /^https?:/i.test(entry.href)) {
        var link = createEl('a', '', item[1]);
        link.href = entry.href;
        link.target = '_blank';
        link.rel = 'noopener noreferrer';
        link.title = entry.href;
        row.appendChild(link);
      } else {
        row.appendChild(createEl('strong', '', item[1]));
      }
      card.appendChild(row);
    });
    detail.appendChild(card);
  }

  function renderCommandPalettePanel(panel, settings, state) {
    var panelState = panel.spxState || { query: '', filter: 'all', activeIndex: 0 };
    panel.spxState = panelState;
    var allEntries = getCommandPaletteEntries(panel, settings, state);
    var visibleEntries = filterCommandPaletteEntries(allEntries, panelState);
    panelState.activeIndex = Math.min(Math.max(0, Number(panelState.activeIndex) || 0), Math.max(0, visibleEntries.length - 1));
    panel.spxEntries = visibleEntries;
    panel.textContent = '';

    var wrap = createEl('div', 'spx-command-wrap');
    var palette = createEl('section', 'spx-command-palette');
    palette.setAttribute('aria-label', '全局命令面板');
    var head = createEl('div', 'spx-command-head');
    var title = createEl('div', 'spx-command-title');
    var titleCopy = createEl('div');
    titleCopy.appendChild(createEl('strong', '', '全局命令'));
    titleCopy.appendChild(createEl('span', '', '本地数据优先 · Ctrl+K'));
    var closeButton = createEl('button', 'spx-command-close', '×');
    closeButton.type = 'button';
    closeButton.title = '关闭命令面板';
    closeButton.dataset.action = 'close-command-palette';
    title.append(titleCopy, closeButton);
    head.appendChild(title);

    var searchWrap = createEl('div', 'spx-command-search-wrap');
    var search = createEl('input', 'spx-command-search');
    search.type = 'search';
    search.placeholder = '搜索导航、收藏、资源、设置或页面动作';
    search.value = panelState.query || '';
    search.setAttribute('aria-label', '搜索命令');
    searchWrap.appendChild(search);
    searchWrap.appendChild(createEl('span', 'spx-command-search-mark', '/'));
    head.appendChild(searchWrap);

    var tabs = createEl('div', 'spx-command-tabs');
    [
      ['all', '全部'],
      ['navigate', '导航'],
      ['center', '我的中心'],
      ['resource', '资源'],
      ['setting', '设置'],
      ['action', '页面动作'],
    ].forEach(function appendCommandTab(item) {
      var tab = createEl('button', 'spx-command-tab' + (normalizeCommandPaletteFilter(panelState.filter) === item[0] ? ' spx-active' : ''), item[1]);
      tab.type = 'button';
      tab.dataset.filter = item[0];
      tabs.appendChild(tab);
    });
    head.appendChild(tabs);
    palette.appendChild(head);

    var list = createEl('div', 'spx-command-list');
    if (!visibleEntries.length) {
      list.appendChild(createEl('div', 'spx-command-empty', '没有匹配的命令'));
    } else {
      visibleEntries.slice(0, 80).forEach(function appendCommandEntry(entry, index) {
        var item = createEl('button', 'spx-command-item' + (index === panelState.activeIndex ? ' spx-active' : ''));
        item.type = 'button';
        item.dataset.commandId = entry.id;
        item.appendChild(createEl('span', 'spx-command-icon', entry.icon || '令'));
        var main = createEl('span', 'spx-command-main');
        main.appendChild(createEl('strong', '', entry.title));
        main.appendChild(createEl('span', '', entry.description || getCommandPaletteSourceLabel(entry)));
        item.appendChild(main);
        var tail = createEl('span', 'spx-command-tail');
        tail.appendChild(createEl('span', 'spx-command-pill' + (entry.category === 'resource' ? ' spx-ok' : entry.category === 'center' ? ' spx-warn' : ''), getCommandPaletteCategoryLabel(entry.category)));
        if (index === panelState.activeIndex) tail.appendChild(createEl('span', 'spx-command-kbd', 'Enter'));
        item.appendChild(tail);
        list.appendChild(item);
      });
    }
    palette.appendChild(list);
    var foot = createEl('div', 'spx-command-foot');
    foot.appendChild(createEl('span', '', formatCommandPaletteResultSummary(visibleEntries, allEntries, panelState)));
    var keys = createEl('span', 'spx-command-keys');
    ['↑ ↓', 'Enter', 'Esc'].forEach(function appendKeyLabel(label) {
      keys.appendChild(createEl('span', 'spx-command-kbd', label));
    });
    foot.appendChild(keys);
    palette.appendChild(foot);
    wrap.appendChild(palette);

    var detail = createEl('aside', 'spx-command-detail');
    wrap.appendChild(detail);
    panel.appendChild(wrap);
    renderCommandPaletteDetail(panel, visibleEntries[panelState.activeIndex] || null);
  }

  function createCommandPalettePanel(settings, state) {
    var panel = qs('#spx-command-palette-overlay');
    if (panel) return panel;
    panel = createEl('div', 'spx-command-overlay');
    panel.id = 'spx-command-palette-overlay';
    panel.hidden = true;
    panel.setAttribute('role', 'dialog');
    panel.setAttribute('aria-label', '全局命令面板');
    panel.spxState = { query: '', filter: 'all', activeIndex: 0 };
    panel.spxComposing = false;
    panel.spxAllEntries = null;
    panel.spxEntriesCacheKey = '';
    panel.spxSearchTimer = null;
    panel.spxRender = function renderCommandPanel() {
      renderCommandPalettePanel(panel, settings, state);
    };
    panel.addEventListener('compositionstart', function handleCommandCompositionStart(event) {
      if (!isCommandPaletteSearchInput(event.target)) return;
      panel.spxComposing = true;
    });
    panel.addEventListener('compositionend', function handleCommandCompositionEnd(event) {
      if (!isCommandPaletteSearchInput(event.target)) return;
      panel.spxComposing = false;
      rerenderCommandPaletteAfterSearch(panel, event.target);
    });
    panel.addEventListener('input', function handleCommandInput(event) {
      var target = event.target;
      if (!isCommandPaletteSearchInput(target)) return;
      panel.spxState.query = target.value;
      if (panel.spxComposing || event.isComposing) return;
      if (panel.spxSearchTimer) window.clearTimeout(panel.spxSearchTimer);
      panel.spxSearchTimer = window.setTimeout(function renderDebouncedCommandSearch() {
        panel.spxSearchTimer = null;
        rerenderCommandPaletteAfterSearch(panel, target);
      }, 80);
    });
    panel.addEventListener('click', function handleCommandClick(event) {
      if (event.target === panel) {
        setCommandPaletteHidden(panel, true);
        return;
      }
      var close = event.target && event.target.closest && event.target.closest('[data-action="close-command-palette"]');
      if (close) {
        setCommandPaletteHidden(panel, true);
        return;
      }
      var tab = event.target && event.target.closest && event.target.closest('.spx-command-tab');
      if (tab) {
        panel.spxState.filter = normalizeCommandPaletteFilter(tab.dataset.filter);
        panel.spxState.activeIndex = 0;
        panel.spxRender();
        var input = qs('.spx-command-search', panel);
        if (input) input.focus();
        return;
      }
      var item = event.target && event.target.closest && event.target.closest('.spx-command-item');
      if (item) {
        var entries = panel.spxEntries || [];
        var entry = entries.filter(function matchCommand(itemEntry) { return itemEntry.id === item.dataset.commandId; })[0];
        executeCommandPaletteEntry(entry, settings, state, panel);
      }
    });
    panel.addEventListener('keydown', function handleCommandKeys(event) {
      if (panel.spxComposing || event.isComposing || event.keyCode === 229) return;
      if (event.key === 'Escape') {
        event.preventDefault();
        setCommandPaletteHidden(panel, true);
        return;
      }
      var entries = panel.spxEntries || [];
      if (!entries.length) return;
      if (event.key === 'ArrowDown') {
        event.preventDefault();
        panel.spxState.activeIndex = Math.min(entries.length - 1, (panel.spxState.activeIndex || 0) + 1);
        panel.spxRender();
        var downInput = qs('.spx-command-search', panel);
        if (downInput) downInput.focus();
        return;
      }
      if (event.key === 'ArrowUp') {
        event.preventDefault();
        panel.spxState.activeIndex = Math.max(0, (panel.spxState.activeIndex || 0) - 1);
        panel.spxRender();
        var upInput = qs('.spx-command-search', panel);
        if (upInput) upInput.focus();
        return;
      }
      if (event.key === 'Enter') {
        event.preventDefault();
        executeCommandPaletteEntry(entries[panel.spxState.activeIndex || 0], settings, state, panel);
      }
    });
    document.body.appendChild(panel);
    return panel;
  }

  function bindCommandPaletteKeyboard(settings, state) {
    if (document.documentElement.dataset.spxCommandPaletteKeyboardReady === '1') return;
    document.documentElement.dataset.spxCommandPaletteKeyboardReady = '1';
    document.addEventListener('keydown', function handleCommandPaletteShortcut(event) {
      if (!event || (!event.ctrlKey && !event.metaKey) || event.shiftKey || event.altKey) return;
      if (String(event.key || '').toLowerCase() !== 'k') return;
      event.preventDefault();
      var panel = createCommandPalettePanel(settings, state);
      setCommandPaletteHidden(panel, !panel.hidden);
    }, true);
  }

  function createToolbar(settings, state) {
    if (qs('#spx-toolbar')) return;
    var toolbar = createEl('div', 'spx-toolbar');
    toolbar.id = 'spx-toolbar';
    var toolbox = null;
    var commandPalette = null;

    function getToolbox() {
      toolbox = toolbox || createToolboxPanel(settings, state);
      return toolbox;
    }

    function getCommandPalette() {
      commandPalette = commandPalette || createCommandPalettePanel(settings, state);
      return commandPalette;
    }

    function closeToolboxIfLoaded() {
      if (toolbox) setToolboxHidden(toolbox, true, TOOLBOX_BUTTON_SELECTOR);
    }

    function closeCommandPaletteIfLoaded() {
      if (commandPalette) setCommandPaletteHidden(commandPalette, true);
    }

    toolbar.appendChild(toolbarButton('顶部', '回到顶部', function top() {
      window.scrollTo({ top: 0, behavior: 'smooth' });
    }));
    toolbar.appendChild(toolbarButton('底部', '滚到底部', function bottom() {
      window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' });
    }));
    var toolboxButton = toolbarButton('工具', '打开工具箱', function toggleToolbox() {
      var currentToolbox = getToolbox();
      if (currentToolbox.spxRender) currentToolbox.spxRender();
      var nextToolboxHidden = !currentToolbox.hidden;
      setToolboxHidden(currentToolbox, nextToolboxHidden, TOOLBOX_BUTTON_SELECTOR);
      if (!nextToolboxHidden) {
        var settingsPanel = qs('#spx-settings');
        if (settingsPanel) setSettingsPanelHidden(settingsPanel, true, SETTINGS_BUTTON_SELECTOR);
        closeCommandPaletteIfLoaded();
      }
    });
    toolboxButton.dataset.spxToolboxButton = '1';
    toolboxButton.setAttribute('aria-haspopup', 'dialog');
    toolboxButton.setAttribute('aria-controls', 'spx-toolbox');
    toolboxButton.setAttribute('aria-expanded', 'false');
    toolbar.appendChild(toolboxButton);

    var commandButton = toolbarButton('命令', '打开全局命令面板(Ctrl+K)', function toggleCommandPalette() {
      var currentCommandPalette = getCommandPalette();
      var nextHidden = !currentCommandPalette.hidden;
      setCommandPaletteHidden(currentCommandPalette, nextHidden);
      if (!nextHidden) {
        closeToolboxIfLoaded();
        var settingsPanel = qs('#spx-settings');
        if (settingsPanel) setSettingsPanelHidden(settingsPanel, true, SETTINGS_BUTTON_SELECTOR);
      }
    });
    commandButton.dataset.spxCommandPaletteButton = '1';
    commandButton.setAttribute('aria-haspopup', 'dialog');
    commandButton.setAttribute('aria-controls', 'spx-command-palette-overlay');
    commandButton.setAttribute('aria-expanded', 'false');
    toolbar.appendChild(commandButton);

    var settingsButton = toolbarButton('设置', '打开设置', function openSettings() {
      var panel = createSettingsPanel(settings, state);
      if (panel.spxSync) panel.spxSync();
      closeToolboxIfLoaded();
      closeCommandPaletteIfLoaded();
      setSettingsPanelHidden(panel, !panel.hidden, SETTINGS_BUTTON_SELECTOR);
    });
    settingsButton.dataset.spxSettingsButton = '1';
    settingsButton.setAttribute('aria-haspopup', 'dialog');
    settingsButton.setAttribute('aria-controls', 'spx-settings');
    settingsButton.setAttribute('aria-expanded', 'false');
    toolbar.appendChild(settingsButton);
    document.body.appendChild(toolbar);
  }

  function isEditableKeyboardTarget(target) {
    if (!target) return false;
    var tagName = String(target.tagName || '').toLowerCase();
    return (
      tagName === 'input' ||
      tagName === 'textarea' ||
      tagName === 'select' ||
      !!target.isContentEditable ||
      !!(target.closest && target.closest('[contenteditable="true"]'))
    );
  }

  function bindForumKeyboardPaging() {
    if (document.documentElement.dataset.spxForumKeyboardPagingReady === '1') return;
    document.documentElement.dataset.spxForumKeyboardPagingReady = '1';
    document.addEventListener('keydown', function handleForumKeyboardPaging(event) {
      if (!shouldUseForumKeyboardPaging(location.href)) return;
      if (!event || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
      if (isEditableKeyboardTarget(event.target)) return;

      var key = event.key || event.code;
      var page = currentPageNumber(location.href);
      if (key === 'ArrowLeft' || key === 'Left') {
        if (page <= 1) return;
        event.preventDefault();
        location.href = buildPageUrl(location.href, page - 1);
        return;
      }
      if (key === 'ArrowRight' || key === 'Right') {
        event.preventDefault();
        location.href = buildPageUrl(location.href, page + 1);
      }
    }, true);
  }

  function toolbarButton(text, title, onClick) {
    var button = createEl('button', '', text);
    button.type = 'button';
    button.title = title;
    button.addEventListener('click', onClick);
    return button;
  }

  function toolbarLink(text, title, href) {
    var link = createEl('a', '', text);
    link.title = title;
    link.href = href;
    return link;
  }

  function getCurrentForumDashboardPayload(settings, state) {
    return {
      settings: settings,
      read: state && state.read,
      watch: state && state.watch,
      progress: state && state.progress,
      resources: state && state.resources,
      autoBuyAttempts: loadAutoBuyAttempts(),
      favoriteSeen: loadMap(FAVORITE_NAV_SEEN_KEY),
      requestState: scriptRequestState,
    };
  }

  function formatForumDashboardDigest(report) {
    var data = report || collectForumDashboardReport({});
    var stats = data.stats || {};
    var lines = [
      'South Plus +++ 论坛仪表盘',
      '今日浏览:' + (stats.todayViewed || 0),
      '收藏新增:' + (stats.favoriteAdded || 0),
      '未读收藏:' + (stats.unreadFavorites || 0),
      '资源新增:' + (stats.resourceAdded || 0),
      '稍后看积压:' + (stats.watchBacklog || 0),
      '请求状态:' + (stats.requestStatus || '正常'),
      '',
      '值得回看:',
    ];
    (data.worthReviewing || []).forEach(function appendWorthTopic(topic, index) {
      lines.push((index + 1) + '. ' + topic.title + ' - ' + (topic.reason || topic.meta || '本地记录'));
    });
    return lines.join('\n');
  }

  function createForumDashboardStat(label, value, note, className) {
    var card = createEl('article', 'spx-forum-dashboard-stat' + (className ? ' ' + className : ''));
    card.appendChild(createEl('span', 'spx-forum-dashboard-stat-label', label));
    card.appendChild(createEl('strong', '', String(value == null ? 0 : value)));
    card.appendChild(createEl('small', '', note || ''));
    return card;
  }

  function createForumDashboardChip(text, className) {
    return createEl('span', 'spx-forum-dashboard-chip' + (className ? ' ' + className : ''), text);
  }

  function createForumDashboardTopicRow(topic) {
    var row = createEl('article', 'spx-forum-dashboard-topic');
    var body = createEl('div', 'spx-forum-dashboard-topic-main');
    var title = createEl(topic.url ? 'a' : 'span', 'spx-forum-dashboard-topic-title', topic.title || '未命名主题');
    if (topic.url) title.href = topic.url;
    body.appendChild(title);
    body.appendChild(createEl('div', 'spx-forum-dashboard-topic-meta', topic.reason || topic.meta || '本地记录'));
    row.appendChild(body);
    row.appendChild(createEl('div', 'spx-forum-dashboard-score', String(Math.max(0, Math.round(topic.score || 0)))));
    var chips = createEl('div', 'spx-forum-dashboard-chips');
    if (topic.resourceCount) chips.appendChild(createForumDashboardChip('资源 ' + topic.resourceCount, 'spx-green'));
    if (topic.hasWatch) chips.appendChild(createForumDashboardChip('稍后', 'spx-violet'));
    if (topic.hasSiteFavorite) chips.appendChild(createForumDashboardChip('收藏', 'spx-amber'));
    if (topic.progressPercent) chips.appendChild(createForumDashboardChip(topic.progressPercent + '%', ''));
    row.appendChild(chips);
    return row;
  }

  function createForumDashboardRankRows(items, emptyText) {
    var list = createEl('div', 'spx-forum-dashboard-ranks');
    var source = (items || []).filter(function keepRankItem(item) { return item && item.count > 0; }).slice(0, 4);
    var max = source.reduce(function maxRankValue(value, item) { return Math.max(value, Number(item.count) || 0); }, 1);
    if (!source.length) {
      list.appendChild(createEl('div', 'spx-forum-dashboard-empty', emptyText || '暂无数据'));
      return list;
    }
    source.forEach(function appendRank(item) {
      var row = createEl('div', 'spx-forum-dashboard-rank');
      row.appendChild(createEl('span', '', item.label));
      var bar = createEl('b', '');
      var fill = createEl('i', '');
      fill.style.width = Math.max(8, Math.round((Number(item.count) || 0) / max * 100)) + '%';
      bar.appendChild(fill);
      row.appendChild(bar);
      row.appendChild(createEl('strong', '', String(item.count)));
      list.appendChild(row);
    });
    return list;
  }

  function createForumDashboardResourceRow(entry) {
    var row = createEl('article', 'spx-forum-dashboard-resource');
    var body = createEl('div');
    var title = createEl(entry.sourceUrl ? 'a' : 'span', 'spx-forum-dashboard-resource-title', entry.sourceTitle || entry.label || '未命名资源');
    if (entry.sourceUrl) title.href = entry.sourceUrl;
    body.appendChild(title);
    body.appendChild(createEl('div', 'spx-forum-dashboard-topic-meta', [entry.label, entry.provider, entry.statusLabel].filter(Boolean).join(' · ') || '资源记录'));
    row.appendChild(body);
    row.appendChild(createForumDashboardChip(entry.type === 'cloud' ? '网盘' : entry.label, 'spx-green'));
    return row;
  }

  function createForumDashboardActivityRow(item) {
    var row = createEl('article', 'spx-forum-dashboard-activity spx-' + (item.type || 'progress'));
    row.appendChild(createEl('span', 'spx-forum-dashboard-activity-mark'));
    var body = createEl('div');
    body.appendChild(createEl('div', 'spx-forum-dashboard-topic-title', item.title || '本地动态'));
    body.appendChild(createEl('div', 'spx-forum-dashboard-topic-meta', item.meta || ''));
    row.appendChild(body);
    row.appendChild(createEl('time', '', item.time || ''));
    return row;
  }

  function renderForumDashboardPanel(panel, settings, state) {
    if (!panel) return;
    var report = collectForumDashboardReport(getCurrentForumDashboardPayload(settings, state));
    panel.textContent = '';
    var header = createEl('div', 'spx-forum-dashboard-head');
    var title = createEl('div');
    title.appendChild(createEl('h2', '', '论坛仪表盘'));
    title.appendChild(createEl('p', '', '浏览、收藏、资源和本地积压的首页总览。'));
    header.appendChild(title);
    var actions = createEl('div', 'spx-forum-dashboard-actions');
    [['refresh-dashboard', '刷新摘要'], ['open-watch-backlog', '只看未读'], ['copy-dashboard-digest', '复制日报']].forEach(function appendAction(item) {
      var button = createEl('button', item[0] === 'refresh-dashboard' ? 'spx-primary' : '', item[1]);
      button.type = 'button';
      button.dataset.action = item[0];
      actions.appendChild(button);
    });
    header.appendChild(actions);
    panel.appendChild(header);

    var stats = createEl('div', 'spx-forum-dashboard-stats');
    stats.appendChild(createForumDashboardStat('今日浏览', report.stats.todayViewed, '本地已读 / 阅读进度', 'spx-blue'));
    stats.appendChild(createForumDashboardStat('收藏新增', report.stats.favoriteAdded, '站内收藏 + 稍后看', 'spx-amber'));
    stats.appendChild(createForumDashboardStat('未读收藏', report.stats.unreadFavorites, '未完成的收藏主题', 'spx-rose'));
    stats.appendChild(createForumDashboardStat('资源新增', report.stats.resourceAdded, '今日保存资源', 'spx-green'));
    stats.appendChild(createForumDashboardStat('稍后看积压', report.stats.watchBacklog, '未读完稍后看', 'spx-violet'));
    stats.appendChild(createForumDashboardStat('请求状态', report.stats.requestStatus, report.request.detail, report.request.status === '正常' ? 'spx-green' : 'spx-amber'));
    panel.appendChild(stats);

    var grid = createEl('div', 'spx-forum-dashboard-grid');
    var left = createEl('div', 'spx-forum-dashboard-stack');
    var topicsPanel = createEl('section', 'spx-forum-dashboard-card spx-forum-dashboard-wide');
    topicsPanel.appendChild(createEl('h3', '', '值得回看的主题'));
    var topicList = createEl('div', 'spx-forum-dashboard-topic-list');
    if (report.worthReviewing.length) {
      report.worthReviewing.slice(0, 3).forEach(function appendTopic(topic) {
        topicList.appendChild(createForumDashboardTopicRow(topic));
      });
    } else {
      topicList.appendChild(createEl('div', 'spx-forum-dashboard-empty', '暂无需要回看的主题。'));
    }
    topicsPanel.appendChild(topicList);
    left.appendChild(topicsPanel);

    var split = createEl('div', 'spx-forum-dashboard-split');
    var tagPanel = createEl('section', 'spx-forum-dashboard-card');
    tagPanel.appendChild(createEl('h3', '', '高频标签'));
    tagPanel.appendChild(createForumDashboardRankRows(report.tagStats, '暂无标签统计'));
    split.appendChild(tagPanel);
    var backlogPanel = createEl('section', 'spx-forum-dashboard-card');
    backlogPanel.appendChild(createEl('h3', '', '本地积压'));
    backlogPanel.appendChild(createForumDashboardRankRows(report.backlogStats, '暂无积压'));
    split.appendChild(backlogPanel);
    left.appendChild(split);
    grid.appendChild(left);

    var right = createEl('div', 'spx-forum-dashboard-stack');
    var activityPanel = createEl('section', 'spx-forum-dashboard-card');
    activityPanel.appendChild(createEl('h3', '', '最近动态'));
    var activityList = createEl('div', 'spx-forum-dashboard-activity-list');
    if (report.activities.length) {
      report.activities.forEach(function appendActivity(item) {
        activityList.appendChild(createForumDashboardActivityRow(item));
      });
    } else {
      activityList.appendChild(createEl('div', 'spx-forum-dashboard-empty', '暂无本地动态。'));
    }
    activityPanel.appendChild(activityList);
    right.appendChild(activityPanel);

    var resourcePanel = createEl('section', 'spx-forum-dashboard-card');
    resourcePanel.appendChild(createEl('h3', '', '资源速览'));
    var resourceList = createEl('div', 'spx-forum-dashboard-resource-list');
    if (report.resources.length) {
      report.resources.slice(0, 3).forEach(function appendResource(entry) {
        resourceList.appendChild(createForumDashboardResourceRow(entry));
      });
    } else {
      resourceList.appendChild(createEl('div', 'spx-forum-dashboard-empty', '资源工作台暂无记录。'));
    }
    resourcePanel.appendChild(resourceList);
    right.appendChild(resourcePanel);
    grid.appendChild(right);
    panel.appendChild(grid);

    panel.spxDashboardReport = report;
  }

  function createForumDashboardPanel(settings, state) {
    var panel = createEl('section', 'spx-forum-dashboard-panel spx-home-module');
    panel.id = 'spx-forum-dashboard-panel';
    panel.dataset.spxLarge = '1';
    renderForumDashboardPanel(panel, settings, state);
    panel.addEventListener('click', function handleDashboardClick(event) {
      var target = event.target;
      var action = target && target.dataset && target.dataset.action;
      if (!action) return;
      if (action === 'refresh-dashboard') {
        renderForumDashboardPanel(panel, settings, state);
        setTemporaryText(target, '已刷新', '刷新摘要');
      }
      if (action === 'open-watch-backlog') {
        var watchPanel = createWatchCenterPanel(settings, state);
        setCenterPanelHidden(watchPanel, false, '[data-spx-watch-center-button="1"]');
      }
      if (action === 'copy-dashboard-digest') {
        copyTextToClipboard(formatForumDashboardDigest(panel.spxDashboardReport)).then(
          function showCopySuccess() { setTemporaryText(target, '已复制', '复制日报'); },
          function showCopyFailure() { setTemporaryText(target, '复制失败', '复制日报'); }
        );
      }
    });
    return panel;
  }

  function enhanceHome(settings, state) {
    if (detectPageType(location.href) !== 'home') return;
    enhanceHomeDashboard(settings, state);
    if (!settings.cleanMode) {
      qsa('[data-spx-home-hidden="1"]').forEach(function showTable(table) {
        table.style.display = '';
        delete table.dataset.spxHomeHidden;
      });
      return;
    }
    qsa('table').forEach(function markTable(table) {
      if (/友情链接|在线用户/.test(table.textContent || '')) {
        table.dataset.spxHomeHidden = '1';
        table.style.display = 'none';
      }
    });
  }

  function enhanceHomeDashboard(settings, state) {
    restoreHomeDashboard();
    if (!shouldUseHomeDashboard(settings, location.href)) return;

    var modules = qsa('#content .t[id^="t_"]');
    if (!modules.length) return;

    var grid = createEl('div');
    grid.id = 'spx-home-grid';
    modules[0].parentNode.insertBefore(grid, modules[0]);

    if (settings.forumDashboard !== false) {
      var dashboard = createForumDashboardPanel(settings, state);
      grid.appendChild(dashboard);
      queueModuleNavigationConfigs([{
        section: '站点导航',
        label: '论坛仪表盘',
        href: getHomeNavigationBaseUrl(location.origin) + '#spx-forum-dashboard-panel',
        title: '论坛仪表盘',
        target: dashboard,
        active: true,
        alwaysShow: true,
        navigationOnly: true,
      }]);
    }

    modules.forEach(function markModule(module) {
      var marker = createEl('span');
      marker.dataset.spxHomeMarker = module.id || 'module';
      marker.style.display = 'none';
      module.parentNode.insertBefore(marker, module);
      grid.appendChild(module);

      module.classList.add('spx-home-module');
      var rows = qsa('tr.tr3', module);
      if (rows.length > 6) module.dataset.spxLarge = '1';

      var header = qs('.h', module) || qs('h2', module);
      if (header && !qs('.spx-home-collapse', header)) {
        var collapse = createEl('button', 'spx-home-collapse', '折叠');
        collapse.type = 'button';
        collapse.addEventListener('click', function toggleModule() {
          var collapsed = module.dataset.spxCollapsed === '1';
          module.dataset.spxCollapsed = collapsed ? '0' : '1';
          setHomeModuleCollapsed(rows, !collapsed);
          collapse.textContent = collapsed ? '折叠' : '展开';
        });
        header.appendChild(collapse);
      }

      rows.forEach(function markForumRow(row) {
        var titleNode = qs('[id^="fn_"]', row);
        if (!titleNode || titleNode.dataset.spxHomeReady) return;
        titleNode.dataset.spxHomeReady = '1';
        var today = parseTodayCount(titleNode.textContent);
        if (today > 0) {
          row.classList.add('spx-home-hot');
          var badge = createEl('span', 'spx-home-badge', String(today));
          titleNode.appendChild(badge);
        }
      });
    });
    createHomeModuleNavigation(modules);
  }

  function restoreHomeDashboard() {
    restoreModuleNavigation();
    var quick = qs('#spx-home-quick');
    if (quick) quick.remove();
    qsa('.spx-home-module').forEach(function restoreModule(module) {
      var marker = qsa('[data-spx-home-marker]').filter(function findMarker(item) {
        return item.dataset.spxHomeMarker === (module.id || 'module');
      })[0];
      if (marker && marker.parentNode) {
        marker.parentNode.insertBefore(module, marker);
      }
      module.classList.remove('spx-home-module');
      delete module.dataset.spxLarge;
      delete module.dataset.spxCollapsed;
      qsa('.spx-home-collapse', module).forEach(function removeButton(button) {
        button.remove();
      });
      qsa('tr.tr3', module).forEach(function restoreRow(row) {
        setHomeModuleCollapsed([row], false);
        row.classList.remove('spx-home-hot');
      });
      qsa('[data-spx-home-ready="1"]', module).forEach(function restoreTitle(titleNode) {
        delete titleNode.dataset.spxHomeReady;
      });
      qsa('.spx-home-badge', module).forEach(function removeBadge(badge) {
        badge.remove();
      });
    });
    qsa('[data-spx-home-marker]').forEach(function removeMarker(marker) {
      marker.remove();
    });
    var grid = qs('#spx-home-grid');
    if (grid) grid.remove();
  }

  function unwrapTaskLayoutStacks(host) {
    qsa('.spx-task-side-stack,.spx-task-main-stack', host).forEach(function unwrapStack(stack) {
      while (stack.firstChild) {
        stack.parentNode.insertBefore(stack.firstChild, stack);
      }
      stack.remove();
    });
  }

  function isTaskBreadcrumbBlock(node) {
    if (!node || !node.classList) return false;
    return node.classList.contains('bdbA') || !!qs('#breadcrumbs', node);
  }

  function isTaskMainBlock(node) {
    var text = String((node && node.textContent) || '');
    if (!text.trim()) return false;
    if (/(任务时效|上次领取|奖励\s*[::])/.test(text)) return true;
    if (/\bfr\b/.test(String(node.className || '')) && !/社区论坛任务功能简介|版权/.test(text)) return true;
    return false;
  }

  function isTaskSideBlock(node) {
    var text = String((node && node.textContent) || '');
    if (!text.trim()) return false;
    if (/社区论坛任务功能简介|操作|版权|作者\s*[::]|网站\s*[::]/.test(text)) return true;
    return /\bfl\b/.test(String(node.className || ''));
  }

  function getTaskLayoutDirectChild(host, node) {
    var current = node;
    while (current && current.parentNode && current.parentNode !== host) {
      current = current.parentNode;
    }
    return current && current.parentNode === host ? current : node;
  }

  function hasTaskLayoutBlockClass(node) {
    return !!(node && /\b(?:fl|fr|t|t3|t5)\b/.test(String(node.className || '')));
  }

  function getTaskLayoutCandidateNodes(host) {
    var nodes = [];
    function appendNode(node) {
      if (!node || node === host || nodes.indexOf(node) !== -1) return;
      if (isTaskBreadcrumbBlock(node)) return;
      nodes.push(node);
    }
    Array.prototype.slice.call(host.children || []).forEach(appendNode);
    qsa('.fl,.fr,.t,.t3,.t5', host).forEach(appendNode);
    var matched = nodes.filter(function keepTaskLayoutBlock(node) {
      if (!isTaskMainBlock(node) && !isTaskSideBlock(node)) return false;
      if (
        node.parentNode === host &&
        !hasTaskLayoutBlockClass(node) &&
        qsa('.fl,.fr,.t,.t3,.t5', node).some(function hasInnerTaskBlock(inner) {
          return isTaskMainBlock(inner) || isTaskSideBlock(inner);
        })
      ) {
        return false;
      }
      return true;
    });
    return matched.filter(function keepInnermostTaskBlock(node) {
      return !matched.some(function hasSelectedDescendant(other) {
        return other !== node && node.contains(other);
      });
    });
  }

  function sortTaskLayoutNodes(nodes) {
    return nodes.slice().sort(function compareDomOrder(a, b) {
      if (a === b) return 0;
      if (!a.compareDocumentPosition) return 0;
      return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
    });
  }

  function enhanceTaskPageLayout() {
    if (!shouldUseTaskPage(location.href)) return;
    var host = qs('.spx-module-body') || qs('#main');
    if (!host) return;
    unwrapTaskLayoutStacks(host);

    qsa('.spx-task-side-block,.spx-task-main-block', host).forEach(function resetTaskBlockClass(node) {
      node.classList.remove('spx-task-side-block', 'spx-task-main-block');
    });

    var sideItems = [];
    var mainItems = [];
    sortTaskLayoutNodes(getTaskLayoutCandidateNodes(host)).forEach(function collectTaskLayoutItem(node) {
      var isMain = isTaskMainBlock(node);
      var isSide = isTaskSideBlock(node);
      if (isMain && (!isSide || /(任务时效|上次领取|奖励\s*[::])/.test(String(node.textContent || '')))) {
        mainItems.push(node);
        return;
      }
      if (isSide) {
        sideItems.push(node);
      }
    });
    if (!mainItems.length) {
      host.classList.remove('spx-task-layout-body');
      return;
    }

    host.classList.add('spx-task-layout-body');
    var anchorItem = sortTaskLayoutNodes(sideItems.concat(mainItems))[0] || null;
    var anchor = anchorItem ? getTaskLayoutDirectChild(host, anchorItem) : null;
    var sideStack = createEl('div', 'spx-task-side-stack');
    var mainStack = createEl('div', 'spx-task-main-stack');
    host.insertBefore(sideStack, anchor);
    host.insertBefore(mainStack, anchor);
    sideItems.forEach(function moveSideItem(item) {
      item.classList.add('spx-task-side-block');
      sideStack.appendChild(item);
    });
    mainItems.forEach(function moveMainItem(item) {
      item.classList.add('spx-task-main-block');
      mainStack.appendChild(item);
    });
  }

  function enhanceAdBlock(settings) {
    restoreAdBlock();
    if (!settings.adBlock) return;

    qsa('a[href], area[href]').forEach(function hideAdLink(link) {
      if (!isAdUrl(link.href)) return;
      markAdHidden(link);

      var cell = link.closest('td');
      if (cell && isMostlyAdContainer(cell)) markAdHidden(cell);

      var row = link.closest('tr');
      if (row && isMostlyAdContainer(row)) markAdHidden(row);

      var box = link.closest('div');
      if (box && box.id !== 'header' && box.id !== 'mainNav' && isMostlyAdContainer(box)) {
        markAdHidden(box);
      }
    });

    qsa('img[src]').forEach(function hideAdImage(img) {
      if (!isAdUrl(img.src)) return;
      markAdHidden(img);
      var link = img.closest('a');
      if (link) markAdHidden(link);
    });
  }

  function markAdHidden(node) {
    if (!node || node.dataset.spxAdHidden === '1') return;
    node.dataset.spxAdPreviousDisplay = node.style.display || '';
    node.dataset.spxAdHidden = '1';
    node.classList.add('spx-ad-hidden');
    node.style.display = 'none';
  }

  function restoreAdBlock() {
    qsa('[data-spx-ad-hidden="1"]').forEach(function restoreAd(node) {
      node.classList.remove('spx-ad-hidden');
      node.style.display = node.dataset.spxAdPreviousDisplay || '';
      delete node.dataset.spxAdHidden;
      delete node.dataset.spxAdPreviousDisplay;
    });
  }

  function isMostlyAdContainer(node) {
    if (!node) return false;
    var text = (node.textContent || '').replace(/\s+/g, '');
    var links = qsa('a[href], area[href]', node);
    var images = qsa('img', node);
    if (!links.length && !images.length) return false;
    var adLinks = links.filter(function checkLink(link) {
      return isAdUrl(link.href);
    });
    if (adLinks.length && adLinks.length === links.length && text.length < 20) return true;
    if (images.length && text.length < 12 && node.getBoundingClientRect && node.getBoundingClientRect().height <= 180) return true;
    return false;
  }

  function enhanceAll(settings, state) {
    var pageType = detectPageType(location.href);
    enhanceCycle += 1;
    invalidateCommandPaletteCache();
    setBodyClasses(settings);
    pendingModuleNavigationConfigs = [];
    restoreModuleNavigation();
    if (shouldUseSiteShell(location.href)) applySiteShellLayout(document);
    enhanceSiteNavigation(document);
    enhanceFavoriteNavigation(settings, state, document);
    if (pageType === 'profile') enhanceAccountNavigation(document);
    enhanceAdBlock(settings);
    if (pageType === 'home') enhanceHome(settings, state);
    if (pageType === 'forum') enhanceThreadList(settings, state);
    if (pageType === 'read') enhanceReadPage(settings, state);
    if (pageType === 'read' || pageType === 'post') enhanceQuickReply(settings, state);
    if (shouldUseModuleNavigation(settings, location.href, document)) createGlobalModuleNavigation(settings, state);
    if (pageType === 'task') enhanceTaskPageLayout();
    var panel = qs('#spx-settings');
    if (panel && panel.spxSync) panel.spxSync();
  }

  function init() {
    if (testMode || typeof window === 'undefined' || !document.body) return;
    var settings = loadSettings();
    var state = {
      read: loadMap(READ_KEY),
      watch: loadMap(WATCH_KEY),
      progress: loadReadProgress(),
      resources: loadResourceLibrary(),
    };
    injectStyles();
    setBodyClasses(settings);
    createToolbar(settings, state);
    bindCommandPaletteKeyboard(settings, state);
    if (shouldUseForumKeyboardPaging(location.href)) bindForumKeyboardPaging();
    enhanceAll(settings, state);
  }

  return {
    init: init,
    getDefaultSettings: function getDefaultSettings() {
      return copySettings(DEFAULT_SETTINGS);
    },
    parseThreadId: parseThreadId,
    parseLineList: parseLineList,
    parseQuickReplyList: parseQuickReplyList,
    parseTagList: parseTagList,
    createBackupPayload: createBackupPayload,
    normalizeBackupPayload: normalizeBackupPayload,
    formatBackupFileName: formatBackupFileName,
    collectDataHealthReport: collectDataHealthReport,
    cleanupDataHealthPayload: cleanupDataHealthPayload,
    formatDataHealthSummary: formatDataHealthSummary,
    formatDataHealthWarnings: formatDataHealthWarnings,
    collectStorageUsageReport: collectStorageUsageReport,
    formatStorageBytes: formatStorageBytes,
    formatStorageUsageSummary: formatStorageUsageSummary,
    formatStorageUsageWarnings: formatStorageUsageWarnings,
    formatStorageUsageLimit: formatStorageUsageLimit,
    getStorageUsageLevel: getStorageUsageLevel,
    formatStorageUsageEntry: formatStorageUsageEntry,
    formatBackupImportPreview: formatBackupImportPreview,
    formatReadProgress: formatReadProgress,
    getReadProgressRestoreTarget: getReadProgressRestoreTarget,
    mergeReadProgressRecord: mergeReadProgressRecord,
    pruneReadProgress: pruneReadProgress,
    getWatchCenterEntries: getWatchCenterEntries,
    getHistoryCenterEntries: getHistoryCenterEntries,
    getAutoBuyCenterEntries: getAutoBuyCenterEntries,
    collectForumDashboardReport: collectForumDashboardReport,
    getForumDashboardTopicScore: getForumDashboardTopicScore,
    formatForumDashboardDigest: formatForumDashboardDigest,
    filterWatchCenterEntries: filterWatchCenterEntries,
    filterHistoryCenterEntries: filterHistoryCenterEntries,
    filterAutoBuyCenterEntries: filterAutoBuyCenterEntries,
    normalizeCommandPaletteFilter: normalizeCommandPaletteFilter,
    getCommandPaletteCategoryLabel: getCommandPaletteCategoryLabel,
    collectCommandPaletteEntries: collectCommandPaletteEntries,
    filterCommandPaletteEntries: filterCommandPaletteEntries,
    formatCommandPaletteResultSummary: formatCommandPaletteResultSummary,
    matchesBlockRules: matchesBlockRules,
    parseForumFilterQuery: parseForumFilterQuery,
    matchesForumFilter: matchesForumFilter,
    extractPreviewImageUrls: extractPreviewImageUrls,
    isLargePreviewImage: isLargePreviewImage,
    formatPreviewImageLinks: formatPreviewImageLinks,
    getPreviewImageMetaText: getPreviewImageMetaText,
    formatPreviewImageMarkdownLinks: formatPreviewImageMarkdownLinks,
    formatPreviewImageLinksByFloor: formatPreviewImageLinksByFloor,
    getPreviewGalleryRenderState: getPreviewGalleryRenderState,
    formatPreviewGallerySummary: formatPreviewGallerySummary,
    formatPreviewImageArchiveFileName: formatPreviewImageArchiveFileName,
    sanitizePreviewDownloadName: sanitizePreviewDownloadName,
    getPreviewImageDownloadExtension: getPreviewImageDownloadExtension,
    formatPreviewImageDownloadFileName: formatPreviewImageDownloadFileName,
    getPreviewDownloadStatusSummary: getPreviewDownloadStatusSummary,
    formatPreviewDownloadReport: formatPreviewDownloadReport,
    getZipCrc32: getZipCrc32,
    createPreviewZipBlob: createPreviewZipBlob,
    normalizeResourceUrl: normalizeResourceUrl,
    classifyResourceLink: classifyResourceLink,
    getCloudProviderLabel: getCloudProviderLabel,
    getResourceDisplayLabel: getResourceDisplayLabel,
    getResourceBadgeDefinition: getResourceBadgeDefinition,
    getResourceBadgeFromResourceItem: getResourceBadgeFromResourceItem,
    inferResourceBadgesFromText: inferResourceBadgesFromText,
    getThreadResourceBadgeIndex: getThreadResourceBadgeIndex,
    getThreadResourceBadges: getThreadResourceBadges,
    getResourceBadgeTypes: getResourceBadgeTypes,
    getResourceRailTypeKey: getResourceRailTypeKey,
    getResourceRailTypeLabel: getResourceRailTypeLabel,
    getResourceRailEntries: getResourceRailEntries,
    filterResourceRailEntries: filterResourceRailEntries,
    formatResourceRailSummary: formatResourceRailSummary,
    formatResourceRailSummaryText: formatResourceRailSummaryText,
    formatResourceRailCodes: formatResourceRailCodes,
    getAvailableResourceRailFilterTypes: getAvailableResourceRailFilterTypes,
    extractResourceLinksFromText: extractResourceLinksFromText,
    filterResourceLinks: filterResourceLinks,
    formatResourceLinks: formatResourceLinks,
    getJumpResourceLinks: getJumpResourceLinks,
    getResourceDownloadQueueEntries: getResourceDownloadQueueEntries,
    formatResourceDownloadList: formatResourceDownloadList,
    formatResourceMarkdownList: formatResourceMarkdownList,
    formatResourceDownloadFileName: formatResourceDownloadFileName,
    formatResourceJumpSummary: formatResourceJumpSummary,
    normalizeResourceTags: normalizeResourceTags,
    formatResourceTags: formatResourceTags,
    pruneResourceLibrary: pruneResourceLibrary,
    saveResourceLinksToLibrary: saveResourceLinksToLibrary,
    getResourceCenterEntries: getResourceCenterEntries,
    groupResourceCenterEntries: groupResourceCenterEntries,
    filterResourceCenterEntries: filterResourceCenterEntries,
    getSelectedResourceKeys: getSelectedResourceKeys,
    setResourceSelection: setResourceSelection,
    getResourceEntriesByKeys: getResourceEntriesByKeys,
    markThreadsRead: markThreadsRead,
    findThreadIdsByAuthor: findThreadIdsByAuthor,
    hasThreadRowHiddenClass: hasThreadRowHiddenClass,
    isVisibleThreadRow: isVisibleThreadRow,
    isStickyCell: isStickyCell,
    hideStickyThreads: hideStickyThreads,
    hideForumAnnouncementPanels: hideForumAnnouncementPanels,
    isPreviewImageCandidate: isPreviewImageCandidate,
    clampPreviewZoom: clampPreviewZoom,
    getPreviewLightboxKeyAction: getPreviewLightboxKeyAction,
    parsePostPrice: parsePostPrice,
    parseUserSpBalance: parseUserSpBalance,
    shouldAutoBuyPost: shouldAutoBuyPost,
    extractBuyTopicUrl: extractBuyTopicUrl,
    getAutoBuyAttemptKey: getAutoBuyAttemptKey,
    isAutoBuyAttemptBlocked: isAutoBuyAttemptBlocked,
    formatAutoBuyAttemptMessage: formatAutoBuyAttemptMessage,
    isAdUrl: isAdUrl,
    parseTodayCount: parseTodayCount,
    shouldUseSiteShell: shouldUseSiteShell,
    shouldUseForumDashboard: shouldUseForumDashboard,
    shouldUseForumKeyboardPaging: shouldUseForumKeyboardPaging,
    shouldUseSearchPage: shouldUseSearchPage,
    shouldUseProfilePage: shouldUseProfilePage,
    shouldUseTaskPage: shouldUseTaskPage,
    shouldUseReaderMode: shouldUseReaderMode,
    shouldUseImmersiveRead: shouldUseImmersiveRead,
    shouldUseHomeDashboard: shouldUseHomeDashboard,
    shouldUseModuleNavigation: shouldUseModuleNavigation,
    getCurrentForumId: getCurrentForumId,
    isForumGalleryModeUrl: isForumGalleryModeUrl,
    isForumGalleryModeLink: isForumGalleryModeLink,
    getForumListModeUrl: getForumListModeUrl,
    getForumGalleryModeUrl: getForumGalleryModeUrl,
    normalizeModuleNavDensity: normalizeModuleNavDensity,
    getModuleNavigationDensityConfig: getModuleNavigationDensityConfig,
    getModuleNavigationGroupKey: getModuleNavigationGroupKey,
    normalizeNavigationPinMap: normalizeNavigationPinMap,
    getModuleNavigationConfigPinKey: getModuleNavigationConfigPinKey,
    withPinnedModuleNavigationConfigs: withPinnedModuleNavigationConfigs,
    getFavoriteNavUrl: getFavoriteNavUrl,
    extractSiteVerifyHashFromText: extractSiteVerifyHashFromText,
    getSiteVerifyHash: getSiteVerifyHash,
    getThreadFavoriteUrl: getThreadFavoriteUrl,
    getThreadFavoriteResultText: getThreadFavoriteResultText,
    isNewThreadFavoriteResult: isNewThreadFavoriteResult,
    getSiteFavoriteDeleteResultText: getSiteFavoriteDeleteResultText,
    buildFavoriteDeleteRequest: buildFavoriteDeleteRequest,
    normalizeFavoriteDeleteFields: normalizeFavoriteDeleteFields,
    getFavoriteNavDeleteKey: getFavoriteNavDeleteKey,
    getSelectedSiteFavoriteEntries: getSelectedSiteFavoriteEntries,
    createFavoriteNavEntryFromThreadInfo: createFavoriteNavEntryFromThreadInfo,
    createReadPageFavoriteInfo: createReadPageFavoriteInfo,
    formatFavoriteNavCount: formatFavoriteNavCount,
    normalizeFavoriteNavCountCacheEntry: normalizeFavoriteNavCountCacheEntry,
    shouldRefreshFavoriteNavCountCache: shouldRefreshFavoriteNavCountCache,
    inferFavoriteNavTags: inferFavoriteNavTags,
    parseFavoriteSavedAt: parseFavoriteSavedAt,
    applyFavoriteNavSeenTimes: applyFavoriteNavSeenTimes,
    getFavoriteNavEntrySearchText: getFavoriteNavEntrySearchText,
    filterFavoriteNavEntries: filterFavoriteNavEntries,
    sortFavoriteNavEntries: sortFavoriteNavEntries,
    parseFavoriteReplyCount: parseFavoriteReplyCount,
    getFavoriteNavWatchEntries: getFavoriteNavWatchEntries,
    normalizeNavigationCollapseState: normalizeNavigationCollapseState,
    isModuleNavigationGroupCollapsed: isModuleNavigationGroupCollapsed,
    isModuleNavigationConfigActive: isModuleNavigationConfigActive,
    shouldShowToolbarFeature: shouldShowToolbarFeature,
    hasPreviewGalleryImages: hasPreviewGalleryImages,
    shouldShowToolbarAction: shouldShowToolbarAction,
    getSettingsPanelKeys: getSettingsPanelKeys,
    isNetworkFriendlyMode: isNetworkFriendlyMode,
    getScriptRequestPolicyConfig: getScriptRequestPolicyConfig,
    isScriptRateLimitStatus: isScriptRateLimitStatus,
    isScriptRateLimitHtml: isScriptRateLimitHtml,
    getScriptRequestDelay: getScriptRequestDelay,
    getNavigationRefreshKey: getNavigationRefreshKey,
    getThreadPreviewMetaText: getThreadPreviewMetaText,
    getThreadPreviewImageUrls: getThreadPreviewImageUrls,
    getThreadPreviewImageSummary: getThreadPreviewImageSummary,
    getThreadPreviewHoverDelay: getThreadPreviewHoverDelay,
    createQuickReplyRequest: createQuickReplyRequest,
    getQuickReplyAttachmentFiles: getQuickReplyAttachmentFiles,
    formatQuickReplyAttachmentSummary: formatQuickReplyAttachmentSummary,
    formatQuickReplyFileSize: formatQuickReplyFileSize,
    getQuickReplyEmotes: function getQuickReplyEmotes() { return QUICK_REPLY_EMOTES.slice(); },
    isQuickReplySubmitter: isQuickReplySubmitter,
    getQuickReplySubmitter: getQuickReplySubmitter,
    isQuickReplyEditorCandidate: isQuickReplyEditorCandidate,
    performQuickReplySubmit: performQuickReplySubmit,
    resolveQuickReplyRefreshUrl: resolveQuickReplyRefreshUrl,
    shouldUseQuickReplySubmitHtml: shouldUseQuickReplySubmitHtml,
    buildPageUrl: buildPageUrl,
    detectPageType: detectPageType,
    getInjectedStyleText: getInjectedStyleText,
    applySiteShellLayout: applySiteShellLayout,
    enhanceSiteNavigation: enhanceSiteNavigation,
    applyReadPostLayout: applyReadPostLayout,
    isEmptyReadSeparatorNode: isEmptyReadSeparatorNode,
    getPostToolsHost: getPostToolsHost,
    syncHiddenPostShell: syncHiddenPostShell,
    setHomeModuleCollapsed: setHomeModuleCollapsed,
  };
});