South Plus +++

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

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         South Plus +++
// @namespace    https://south-plus.org/
// @version      0.2.7
// @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 AUTO_BUY_CHECK_TTL = 10 * 60 * 1000;
  var AUTO_BUY_ATTEMPT_LIMIT = 100;
  var RESOURCE_LIMIT = 500;
  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 TOOLBOX_BUTTON_SELECTOR = '[data-spx-toolbox-button="1"]';
  var SETTINGS_BUTTON_SELECTOR = '[data-spx-settings-button="1"]';
  var RESOURCE_STATUSES = {
    saved: '已保存',
    todo: '待下载',
    done: '已处理',
    invalid: '已失效',
  };
  var RESOURCE_CATEGORIES = {
    magnet: '磁力',
    ed2k: '电驴',
    torrent: '种子',
    archive: '压缩包',
    cloud: '网盘',
    image: '图片',
    external: '外链',
  };
  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,
    immersiveFontSize: 16,
    unifiedPreviewGallery: true,
    homeDashboard: true,
    adBlock: true,
    compactRead: true,
    unreadOnly: false,
    onlyOriginalAuthor: false,
    foldQuotes: true,
    hideUserProfile: false,
    autoBuyPost: false,
    autoBuyMaxSp: 5,
    titleKeywords: [],
    authorKeywords: [],
    quickReplies: [
      '感谢分享',
      '支持一下',
      '楼主辛苦了',
      '感谢楼主分享',
      '收藏备用',
      '马克一下',
      '先顶后看',
      '感谢整理',
      '资源很棒',
      '正好需要',
      '学习一下',
      '感谢补档',
    ],
  };

  function parseThreadId(value) {
    var text = String(value || '');
    var match =
      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 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 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 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 combined = title + ' ' + author;

    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 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 isVisibleThreadRow(row) {
    if (!row) return false;
    if (row.offsetParent === null) return false;
    if (row.classList && row.classList.contains('spx-filter-hidden')) 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 {
      return new URL(text, pageUrl || 'https://south-plus.org/').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 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\.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 (/\/(?: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;
    return qsa('td[id^="td_"]', root).some(function hasThreadTitle(cell) {
      return parseThreadId(cell.id) && !!qs('a[id^="a_ajax_"]', cell);
    });
  }

  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 shouldUseHomeDashboard(settings, url) {
    return !!(settings && settings.homeDashboard) && detectPageType(url) === 'home';
  }

  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',
      'unifiedPreviewGallery',
      'compactRead',
      'foldQuotes',
      'hideUserProfile',
      '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 === 'unifiedPreviewGallery' && shouldShowToolbarAction('previewGallery', 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 === '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 forum = href.match(/thread\.php\?fid[=-](\d+)/);

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

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

    return href;
  }

  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.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)),
      },
    };
  }

  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,
    }, 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,
    });
  }

  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),
    ];
    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,
    }, 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 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));
    saveSettings(settings);
    saveMap(READ_KEY, state.read);
    saveMap(WATCH_KEY, state.watch);
    saveReadProgress(state.progress);
    saveAutoBuyAttempts(data.autoBuyAttempts);
    saveResourceLibrary(state.resources);
    clearReadProgressRestoreRequest(parseThreadId(location.href));
    refreshWatchCenter();
    refreshHistoryCenter();
    refreshAutoBuyCenter();
    refreshResourceCenter();
    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-bg:#f7f8fb;--spx-page-bg:#eef2f5;--spx-panel:#fff;--spx-panel-muted:#f8fafc;--spx-line:#cbd5e1;--spx-line-soft:#e2e8f0;--spx-text:#1f2937;--spx-strong:#0f172a;--spx-sub:#64748b;--spx-muted:#94a3b8;--spx-link:#075985;--spx-accent:#0f766e;--spx-accent-soft:#ccfbf1;--spx-accent-wash:#f0fdfa;--spx-danger:#b91c1c;--spx-danger-soft:#fef2f2;--spx-warn:#b45309;--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;}',
      '@media(max-width:900px){:root{--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;background:var(--spx-page-bg)!important;color:#172033!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:#2f343b!important;box-shadow:0 3px 10px rgba(15,23,42,.12)!important;overflow:visible!important;}',
      '.spx-site-shell #mainNav a{color:#dbe6ef!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:#dbe6ef!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:#3a414a!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:#f1f5f9!important;color:#111827!important;text-decoration:none!important;box-shadow:inset 0 -3px 0 #94a3b8!important;}',
      '.spx-site-shell #guide>li.spx-nav-active>a,.spx-site-shell #guide>li>a.spx-menu-open{background:#3a414a!important;color:#fff!important;text-decoration:none!important;box-shadow:none!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:#dbe6ef!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-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 input[type="text"],.spx-search-page input[type="search"],.spx-search-page input[name="keyword"],.spx-search-page input[name="username"]{box-sizing:border-box!important;width:min(520px,calc(100vw - 96px))!important;min-width:min(420px,calc(100vw - 96px))!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 input[type="text"]:focus,.spx-search-page input[type="search"]:focus,.spx-search-page input[name="keyword"]:focus,.spx-search-page 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-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()).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;}',
      '.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{box-sizing:border-box!important;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)))!important;margin:14px auto 0!important;display:grid!important;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))!important;gap:10px!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;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;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 #infobox,.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{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;margin:0 0 10px!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 .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 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-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-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 #infobox,.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-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 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-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{box-sizing:border-box;margin:10px 0 12px;padding:10px 12px;border:1px solid var(--spx-line);border-radius:8px;background:#f8fafc;color:var(--spx-text);font:13px/1.45 Arial,Helvetica,sans-serif;}',
      '.spx-quick-reply-header{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px;color:var(--spx-sub);}',
      '.spx-quick-reply-header strong{color:var(--spx-text);font-size:14px;}',
      '.spx-quick-reply-list{display:flex;flex-wrap:wrap;gap:6px;}',
      '.spx-quick-reply button{border:1px solid var(--spx-line);border-radius:999px;background:#fff;color:var(--spx-text);padding:5px 10px;cursor:pointer;font-size:13px;line-height:1.25;}',
      '.spx-quick-reply button:hover{border-color:var(--spx-accent);color:var(--spx-accent);background:#ecfdf5;}',
      '.spx-quick-reply button:disabled{cursor:wait;opacity:.55;}',
      '.spx-quick-reply-status.spx-error{color:#b91c1c;font-weight:600;}',
      '.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-filter-hidden{display:none!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-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-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-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:calc(100vw - 16px)!important;flex-wrap:wrap}.spx-forum-tools input{flex-basis:100%!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 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) {
    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-search-page', shouldUseSearchPage(location.href));
    document.documentElement.classList.toggle('spx-profile-page', shouldUseProfilePage(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');
  }

  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 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 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 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();
  }

  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 input = createEl('input');
    input.type = 'search';
    input.placeholder = '快速过滤:关键词、!排除、作者:用户名';
    var markVisibleButton = createEl('button', '', '可见已读');
    var watchVisibleButton = createEl('button', '', '可见稍后');
    var preloadNextButton = createEl('button', '', '预载下页');
    var clearButton = createEl('button', '', '清空过滤');
    tools.appendChild(input);
    tools.appendChild(markVisibleButton);
    tools.appendChild(watchVisibleButton);
    tools.appendChild(preloadNextButton);
    tools.appendChild(clearButton);

    function applyFilter() {
      var parsed = parseForumFilterQuery(input.value);
      items.forEach(function toggleItem(item) {
        var hidden = !!input.value.trim() && !matchesForumFilter(item, parsed);
        if (item.row && item.row.classList) item.row.classList.toggle('spx-filter-hidden', hidden);
      });
    }

    input.addEventListener('input', applyFilter);
    clearButton.addEventListener('click', function clearFilter() {
      input.value = '';
      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);
      preloadNextButton.disabled = true;
      preloadNextButton.textContent = '预载中';
      window.fetch(nextUrl, {
        credentials: 'include',
        cache: 'force-cache',
      })
        .then(function readNextPage(response) {
          if (!response.ok) throw new Error('下一页加载失败');
          return response.text();
        })
        .then(function showNextPageCount(html) {
          var doc = new DOMParser().parseFromString(html, 'text/html');
          var count = qsa('td[id^="td_"]', 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 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) panel.remove();
  }

  function renderPreviewPanel(info, payload, event) {
    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', (info.author ? '作者:' + info.author + ' · ' : '') + '悬停预览');
    var text = createEl('div', 'spx-preview-text', payload.text || '未提取到文字预览');
    panel.appendChild(title);
    panel.appendChild(meta);
    panel.appendChild(text);

    if (payload.images && payload.images.length) {
      var grid = createEl('div', 'spx-preview-images');
      payload.images.forEach(function appendPreviewImage(url) {
        var link = createEl('a');
        link.href = url;
        link.target = '_blank';
        link.rel = 'noopener noreferrer';
        var image = createEl('img');
        image.src = url;
        image.loading = 'lazy';
        link.appendChild(image);
        grid.appendChild(link);
      });
      panel.appendChild(grid);
    } else {
      panel.appendChild(createEl('div', 'spx-preview-status', '没有可预览图片'));
    }

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

  function extractPreviewPayloadFromDocument(doc, url) {
    var content = doc && doc.querySelector ? doc.querySelector('.tpc_content') : null;
    var images = content ? Array.prototype.slice.call(content.querySelectorAll('img')) : [];
    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
      ),
    };
  }

  function attachThreadHoverPreview(info) {
    if (!info || !info.titleLink || info.titleLink.dataset.spxPreviewReady) return;
    info.titleLink.dataset.spxPreviewReady = '1';
    var timer = null;
    info.titleLink.addEventListener('mouseenter', function schedulePreview(event) {
      timer = window.setTimeout(function loadPreview() {
        var url = info.titleLink.href;
        renderPreviewPanel(info, { text: '正在加载预览...', images: [] }, event);
        window.fetch(url, { credentials: 'include' })
          .then(function parseResponse(response) {
            return response.text();
          })
          .then(function renderHtml(html) {
            var doc = new DOMParser().parseFromString(html, 'text/html');
            renderPreviewPanel(info, extractPreviewPayloadFromDocument(doc, url), event);
          })
          .catch(function showPreviewError() {
            renderPreviewPanel(info, { text: '预览加载失败', images: [] }, event);
          });
      }, 420);
    });
    info.titleLink.addEventListener('mousemove', function movePreview(event) {
      var panel = qs('#spx-preview-popover');
      if (panel) positionPreviewPanel(panel, event);
    });
    info.titleLink.addEventListener('mouseleave', function cancelPreview() {
      if (timer) window.clearTimeout(timer);
      timer = null;
      window.setTimeout(removeThreadPreview, 120);
    });
  }

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

    cells.forEach(function enhanceCell(cell) {
      var table = cell.closest ? cell.closest('.t') : null;
      if (table) table.classList.add('spx-thread-list-table');
      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);
      info.row.classList.toggle('spx-unread-hidden', !!settings.unreadOnly && isRead);
      info.row.classList.toggle('spx-hidden-rule', matchesBlockRules(info, settings));

      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)) 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 hideAuthorPageButton = createEl('button', '', '本页屏人');

      watchButton.title = '切换本地稍后看';
      titleBlockButton.title = '把标题加入本地屏蔽关键词';
      authorBlockButton.title = '把作者加入本地屏蔽关键词';
      hideRowButton.title = '临时隐藏当前行';
      hideAuthorPageButton.title = '临时隐藏本页同作者帖子';

      watchButton.addEventListener('click', function toggleWatch(event) {
        event.preventDefault();
        event.stopPropagation();
        if (state.watch[info.id]) {
          delete state.watch[info.id];
          watchButton.textContent = '稍后';
          var badge = qs('.spx-watch-badge', info.cell);
          if (badge) badge.remove();
        } else {
          state.watch[info.id] = {
            title: info.title,
            url: info.titleLink.href,
            savedAt: Date.now(),
          };
          watchButton.textContent = '已存';
          info.titleLink.insertAdjacentElement('afterend', createWatchBadge(info.id));
        }
        saveMap(WATCH_KEY, state.watch);
        refreshWatchCenter();
      });

      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();
        info.row.classList.add('spx-filter-hidden');
      });

      hideAuthorPageButton.addEventListener('click', function hideSameAuthorOnPage(event) {
        event.preventDefault();
        event.stopPropagation();
        findThreadIdsByAuthor(items, info.author).forEach(function hideThreadId(id) {
          var item = items.filter(function matchId(candidate) {
            return candidate.id === id;
          })[0];
          if (item && item.row) item.row.classList.add('spx-filter-hidden');
        });
      });

      tools.appendChild(watchButton);
      tools.appendChild(titleBlockButton);
      if (info.author) tools.appendChild(authorBlockButton);
      tools.appendChild(hideRowButton);
      if (info.author) tools.appendChild(hideAuthorPageButton);
      info.titleLink.insertAdjacentElement('afterend', tools);
      attachThreadHoverPreview(info);

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

    createForumQuickTools(settings, state, items);
    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 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(1680px, calc(100vw - 44px))');
      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(1680px, calc(100vw - 44px))');
      setImportantStyle(node, 'max-width', 'none');
      setImportantStyle(node, 'margin-left', 'auto');
      setImportantStyle(node, 'margin-right', 'auto');
      setImportantStyle(node, 'height', '30px');
      setImportantStyle(node, 'overflow', 'visible');
    });
    qsa('#mainNav>div[style*="padding-left"]', scope).forEach(function resetNavMenuHost(node) {
      setImportantStyle(node, 'box-sizing', 'border-box');
      setImportantStyle(node, 'width', '100%');
      setImportantStyle(node, 'max-width', '100%');
      setImportantStyle(node, 'height', '30px');
      setImportantStyle(node, 'padding-left', '250px');
      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', '30px');
      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);

    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', 'min(1680px, calc(100vw - 64px))');
    setImportantStyle(post, 'max-width', 'none');
    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() {
    var url = new URL('/userpay.php', location.href).href;
    return window.fetch(url, {
      credentials: 'include',
      cache: 'no-store',
    })
      .then(function readBalanceResponse(response) {
        if (!response.ok) throw new Error('读取 SP 余额失败');
        return response.text();
      })
      .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 余额');
        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,
      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
    );

    return window.fetch(context.target.url, {
      credentials: 'include',
      redirect: 'follow',
      cache: 'no-store',
    }).then(function readPurchaseResponse(response) {
      if (!response.ok) throw new Error('购买请求失败');
      return response.text();
    });
  }

  function reloadAutoBuyThreadHtml(purchaseResult) {
    if (purchaseResult === null) return null;
    return window.fetch(location.href, {
      credentials: 'include',
      cache: 'no-store',
    })
      .then(function readRefreshedThread(response) {
        if (!response.ok) throw new Error('重新加载帖子失败');
        return response.text();
      });
  }

  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();
    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()
      .then(function purchaseWhenAffordable(balance) {
        if (!shouldAutoBuyPost(settings, context.target.price, balance)) {
          return skipAutoBuyContext(context, balance);
        }
        return requestAutoBuyPurchase(context, balance);
      })
      .then(reloadAutoBuyThreadHtml)
      .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]) : '';

    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);
      }

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

        if (index === 0 && tid) {
          jumpNextFloor = createEl('button', '', '未读楼层');
          jumpLastFloor = createEl('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 });
        });

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

    cleanupReadSeparators();
    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 downloadTextFile(filename, text) {
    if (
      typeof Blob === 'undefined' ||
      typeof URL === 'undefined' ||
      typeof URL.createObjectURL !== 'function' ||
      !document.body
    ) return false;
    var blob = new Blob([String(text || '')], { type: 'text/plain;charset=utf-8' });
    var href = URL.createObjectURL(blob);
    var link = createEl('a');
    link.href = href;
    link.download = filename || formatResourceDownloadFileName();
    link.style.display = 'none';
    document.body.appendChild(link);
    link.click();
    link.remove();
    window.setTimeout(function revokeObjectUrl() {
      URL.revokeObjectURL(href);
    }, 0);
    return true;
  }

  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 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();
      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;
      zoom = 1;
      zoomText.textContent = '100%';
      counter.textContent = '图片 ' + (currentIndex + 1) + ' / ' + images.length;
      urlText.textContent = images[currentIndex].src;
      urlText.title = images[currentIndex].src;
      copyButton.textContent = '复制地址';
      image.alt = '预览图 ' + (currentIndex + 1);
      image.onload = applyImageSize;
      image.onerror = function showImageError() {
        urlText.textContent = '原图加载失败:' + images[currentIndex].src;
      };
      image.src = images[currentIndex].src;
      if (image.complete && image.naturalWidth) 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) + ' 张';
      thumbImage.src = item.src;
      thumbImage.loading = 'lazy';
      thumbImage.decoding = 'async';
      thumbImage.alt = '预览缩略图 ' + (index + 1);
      thumbButton.appendChild(thumbImage);
      thumbButton.addEventListener('click', function showThumbImage() {
        showImage(index);
      });
      strip.appendChild(thumbButton);
    });
    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 previewImages = [];
    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)) previewImages.push(item);
      });
    });

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

    if (!previewImages.length) 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 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 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 visiblePreviewImages = previewImages.slice();
    var renderedPreviewLimit = PREVIEW_GALLERY_BATCH_SIZE;

    copyAllButton.type = 'button';
    copyFloorButton.type = 'button';
    copyMarkdownButton.type = 'button';
    largeOnlyButton.type = 'button';
    masonryButton.type = 'button';
    drawerButton.type = 'button';
    collapseDrawerButton.type = 'button';
    loadMoreButton.type = 'button';
    drawerTab.type = 'button';
    copyAllButton.title = '复制当前筛选范围内的全部原图地址';
    copyFloorButton.title = '按楼层分组复制当前筛选范围内的原图地址';
    copyMarkdownButton.title = '复制当前筛选范围内的 Markdown 图片清单';
    largeOnlyButton.title = '只显示尺寸较大的预览图';
    masonryButton.title = '切换瀑布流缩略图排列';
    drawerButton.title = '将预览图固定到右侧侧栏';
    collapseDrawerButton.title = '收起右侧预览图侧栏';
    drawerTab.title = '展开预览图侧栏';
    loadMoreButton.title = '继续加载下一批预览图';
    largeOnlyButton.setAttribute('aria-pressed', 'false');
    masonryButton.setAttribute('aria-pressed', 'false');
    drawerButton.setAttribute('aria-pressed', 'false');
    collapseDrawerButton.hidden = true;

    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 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 largeCount = previewImages.filter(isLargePreviewImage).length;
      var renderState = getPreviewGalleryRenderState(visiblePreviewImages.length, renderedPreviewLimit);
      summary.textContent = formatPreviewGallerySummary(
        previewImages.length,
        visiblePreviewImages.length,
        renderState.rendered,
        showLargeOnly
      );
      largeOnlyButton.hidden = largeCount === previewImages.length;
      largeOnlyButton.setAttribute('aria-pressed', showLargeOnly ? 'true' : 'false');
      copyAllButton.disabled = !visiblePreviewImages.length;
      copyFloorButton.disabled = !visiblePreviewImages.length;
      copyMarkdownButton.disabled = !visiblePreviewImages.length;
      loadMoreButton.hidden = !renderState.hasMore;
      loadMoreButton.textContent = '加载更多图片(' + renderState.rendered + ' / ' + renderState.total + ')';
      syncPreviewLayoutButtons();
    }

    function renderPreviewGrid() {
      visiblePreviewImages = showLargeOnly ? previewImages.filter(isLargePreviewImage) : previewImages.slice();
      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.addEventListener('click', function openLightbox(event) {
          if (
            event.button > 0 ||
            event.altKey ||
            event.ctrlKey ||
            event.metaKey ||
            event.shiftKey
          ) {
            return;
          }
          event.preventDefault();
          openPreviewLightbox(visiblePreviewImages, index);
        });

        var thumb = createEl('img');
        thumb.src = item.src;
        thumb.loading = 'lazy';
        thumb.decoding = 'async';
        thumb.alt = '预览图 ' + (index + 1);

        var hoverImage = createEl('img', 'spx-preview-hover-image');
        hoverImage.loading = 'lazy';
        hoverImage.decoding = 'async';
        hoverImage.dataset.src = item.src;
        hoverImage.alt = '预览图 ' + (index + 1) + ' 放大预览';
        function loadHoverImage() {
          if (!hoverImage.src) hoverImage.src = hoverImage.dataset.src || item.src;
        }
        link.addEventListener('mouseenter', loadHoverImage);
        link.addEventListener('focus', loadHoverImage);

        var label = createEl('span', '', getPreviewImageMetaText(item, index));
        link.appendChild(thumb);
        link.appendChild(hoverImage);
        link.appendChild(label);
        grid.appendChild(link);
      });
    }

    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();
    }

    copyAllButton.addEventListener('click', function copyAllPreviewLinks() {
      copyPreviewText(copyAllButton, formatPreviewImageLinks(visiblePreviewImages), visiblePreviewImages.length);
    });
    copyFloorButton.addEventListener('click', function copyPreviewLinksByFloor() {
      copyPreviewText(copyFloorButton, formatPreviewImageLinksByFloor(visiblePreviewImages), visiblePreviewImages.length);
    });
    copyMarkdownButton.addEventListener('click', function copyPreviewMarkdownLinks() {
      copyPreviewText(copyMarkdownButton, formatPreviewImageMarkdownLinks(visiblePreviewImages), visiblePreviewImages.length);
    });

    largeOnlyButton.addEventListener('click', function toggleLargeOnly() {
      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');
    });
    loadMoreButton.addEventListener('click', loadNextPreviewBatch);
    panel.addEventListener('scroll', handlePreviewPanelScroll);

    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(grid);
    panel.appendChild(loadMoreButton);
    panel.appendChild(drawerTab);
    renderPreviewGrid();

    mountPreviewPanel(firstPost, content, panel);
  }

  function restorePreviewGallery() {
    closePreviewLightbox();
    var panel = qs('#spx-preview-panel');
    if (panel) 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-preview-lightbox')
    ) {
      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 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;

    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('快捷回复提交失败');
            return options.fetch(options.pageUrl, {
              credentials: 'include',
              cache: 'no-store',
            });
          })
          .then(function checkReloadResponse(response) {
            if (!response || response.ok !== true) throw new Error('重新加载帖子失败');
            return response.text();
          })
          .then(function applyReloadedHtml(html) {
            if (options.applyHtml(html) === 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) {
    if (!panel) return;
    var status = qs('.spx-quick-reply-status', panel);
    if (!status) return;
    status.classList.add('spx-error');
    status.textContent = '提交失败,内容已保留,请重试。';
  }

  function submitQuickReplyAjax(editor, form, submitter, settings, state, panel) {
    if (!editor || !form) return false;
    var fetchImpl = typeof window !== 'undefined' && typeof window.fetch === 'function'
      ? window.fetch.bind(window)
      : null;
    var FormDataCtor = typeof FormData === 'function' ? FormData : null;
    var request = createQuickReplyRequest(
      form,
      submitter,
      location.href,
      FormDataCtor
    );

    if (!request || !fetchImpl || detectPageType(location.href) !== 'read') {
      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) {
        return replaceReadPageContent(html, settings, state);
      },
      onError: function onError() {
        setQuickReplyPanelError(panel);
      },
    });
  }

  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 createQuickReplyPanel(settings, editor, state) {
    var replies = parseQuickReplyList((settings && settings.quickReplies || []).join('\n'));
    if (!editor || !replies.length) return null;

    var panel = createEl('div', 'spx-quick-reply');
    panel.id = 'spx-quick-reply';
    var header = createEl('div', 'spx-quick-reply-header');
    header.appendChild(createEl('strong', '', '快捷回复'));
    header.appendChild(createEl(
      'span',
      'spx-quick-reply-status',
      '快捷语句或提交按钮都会无刷新展示'
    ));
    var list = createEl('div', 'spx-quick-reply-list');

    replies.forEach(function appendReply(reply) {
      var button = createEl('button', '', reply);
      button.type = 'button';
      button.title = '填入并提交回复:' + reply;
      button.addEventListener('click', function useQuickReply() {
        if (insertTextIntoEditor(editor, reply)) {
          submitQuickReply(editor, settings, state);
        }
      });
      list.appendChild(button);
    });

    panel.appendChild(header);
    panel.appendChild(list);
    return panel;
  }

  function enhanceQuickReply(settings, state) {
    if (detectPageType(location.href) !== 'read' && detectPageType(location.href) !== 'post') return;
    var oldPanel = qs('#spx-quick-reply');
    if (oldPanel) oldPanel.remove();

    var editor = getQuickReplyEditor(document);
    var panel = createQuickReplyPanel(settings, editor, state);
    bindQuickReplyFormSubmit(editor, settings, state, panel);
    var mount = getQuickReplyMount(editor);
    if (!panel || !mount || !mount.parent) return;

    if (mount.before && /^tr$/i.test(mount.before.nodeName || '') && /^tbody$/i.test(mount.parent.nodeName || '')) {
      var tr = createEl('tr');
      var td = createEl('td');
      td.colSpan = 20;
      tr.appendChild(td);
      td.appendChild(panel);
      mount.parent.insertBefore(tr, mount.before);
      return;
    }
    mount.parent.insertBefore(panel, mount.before);
  }

  function createSettingsPanel(settings, state) {
    var panel = qs('#spx-settings');
    if (panel) return panel;

    var settingLabels = {
      cleanMode: '清爽模式',
      readerMode: '阅读排版优化',
      immersiveRead: '帖子页沉浸全屏',
      unifiedPreviewGallery: '预览图集中显示',
      compactRead: '阅读页紧凑',
      foldQuotes: '折叠长引用',
      hideUserProfile: '隐藏头像资料',
      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', 'compactRead', 'foldQuotes', 'hideUserProfile', 'onlyOriginalAuthor', 'unifiedPreviewGallery'],
      '控制正文排版、沉浸阅读、头像资料和图集显示。'
    );
    var listControls = renderSettingsSection(
      '列表增强',
      ['cleanMode', 'unreadOnly'],
      '控制列表页和通用页面的低频内容、未读筛选。'
    );
    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>',
    ] : [];

    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, 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);
      });
    }

    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);
      });
      delete document.documentElement.dataset.spxAutoBuyStatus;
      var autoBuyStatus = qs('#spx-auto-buy-status');
      if (autoBuyStatus) autoBuyStatus.remove();
      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,
      };
    }

    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: 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('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,
      immersiveRead: 1,
      onlyOriginalAuthor: 2,
      unifiedPreviewGallery: 3,
      'spx-resource-center': 4,
      'spx-history-center': 5,
    };
    var forumPriority = {
      unreadOnly: 0,
      'spx-watch-center': 1,
      cleanMode: 2,
      readerMode: 3,
      latest: 4,
    };
    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);
    };
    panel.spxRender();
    document.body.appendChild(panel);
    return panel;
  }

  function createToolbar(settings, state) {
    if (qs('#spx-toolbar')) return;
    var toolbar = createEl('div', 'spx-toolbar');
    toolbar.id = 'spx-toolbar';
    var toolbox = createToolboxPanel(settings, state);

    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() {
      if (toolbox.spxRender) toolbox.spxRender();
      var nextToolboxHidden = !toolbox.hidden;
      setToolboxHidden(toolbox, nextToolboxHidden, TOOLBOX_BUTTON_SELECTOR);
      if (!nextToolboxHidden) {
        var settingsPanel = qs('#spx-settings');
        if (settingsPanel) setSettingsPanelHidden(settingsPanel, true, SETTINGS_BUTTON_SELECTOR);
      }
    });
    toolboxButton.dataset.spxToolboxButton = '1';
    toolboxButton.setAttribute('aria-haspopup', 'dialog');
    toolboxButton.setAttribute('aria-controls', 'spx-toolbox');
    toolboxButton.setAttribute('aria-expanded', 'false');
    toolbar.appendChild(toolboxButton);

    var settingsButton = toolbarButton('设置', '打开设置', function openSettings() {
      var panel = createSettingsPanel(settings, state);
      if (panel.spxSync) panel.spxSync();
      setToolboxHidden(toolbox, true, TOOLBOX_BUTTON_SELECTOR);
      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 enhanceHome(settings) {
    if (detectPageType(location.href) !== 'home') return;
    enhanceHomeDashboard(settings);
    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) {
    restoreHomeDashboard();
    if (!shouldUseHomeDashboard(settings, location.href)) return;

    createHomeQuickLinks();
    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]);

    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);
        }
      });
    });
  }

  function restoreHomeDashboard() {
    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 createHomeQuickLinks() {
    if (qs('#spx-home-quick')) return;
    var quickItems = [
      { label: '茶馆', fid: '9' },
      { label: '询问&求物', fid: '48' },
      { label: '免空资源区', fid: '13' },
      { label: 'GALGAME汉化区', fid: '128' },
      { label: 'AI交流', fid: '208' },
      { label: '最新帖子', href: '/search2.php?orderway-postdate-asc-desc-newatc-1.html' },
    ];
    var quick = createEl('nav', 'spx-home-quick');
    quick.id = 'spx-home-quick';
    quickItems.forEach(function appendQuick(item) {
      var link = createEl('a');
      link.href = item.href ? location.origin + item.href : location.origin + '/thread.php?fid-' + item.fid + '.html';
      link.textContent = item.label;
      link.appendChild(createEl('span', '', item.href ? 'new' : 'fid-' + item.fid));
      quick.appendChild(link);
    });
    var main = qs('#main') || document.body;
    var content = qs('#content');
    if (content && content.parentNode) {
      content.parentNode.insertBefore(quick, content);
    } else {
      main.insertBefore(quick, main.firstChild);
    }
  }

  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) {
    setBodyClasses(settings);
    applySiteShellLayout(document);
    enhanceSiteNavigation(document);
    enhanceAccountNavigation(document);
    enhanceAdBlock(settings);
    enhanceHome(settings);
    enhanceThreadList(settings, state);
    enhanceReadPage(settings, state);
    enhanceQuickReply(settings, state);
    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);
    createSettingsPanel(settings, state);
    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,
    filterWatchCenterEntries: filterWatchCenterEntries,
    filterHistoryCenterEntries: filterHistoryCenterEntries,
    filterAutoBuyCenterEntries: filterAutoBuyCenterEntries,
    matchesBlockRules: matchesBlockRules,
    parseForumFilterQuery: parseForumFilterQuery,
    matchesForumFilter: matchesForumFilter,
    extractPreviewImageUrls: extractPreviewImageUrls,
    isLargePreviewImage: isLargePreviewImage,
    formatPreviewImageLinks: formatPreviewImageLinks,
    getPreviewImageMetaText: getPreviewImageMetaText,
    formatPreviewImageMarkdownLinks: formatPreviewImageMarkdownLinks,
    formatPreviewImageLinksByFloor: formatPreviewImageLinksByFloor,
    getPreviewGalleryRenderState: getPreviewGalleryRenderState,
    formatPreviewGallerySummary: formatPreviewGallerySummary,
    normalizeResourceUrl: normalizeResourceUrl,
    classifyResourceLink: classifyResourceLink,
    getCloudProviderLabel: getCloudProviderLabel,
    getResourceDisplayLabel: getResourceDisplayLabel,
    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,
    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,
    shouldUseReaderMode: shouldUseReaderMode,
    shouldUseImmersiveRead: shouldUseImmersiveRead,
    shouldUseHomeDashboard: shouldUseHomeDashboard,
    shouldShowToolbarFeature: shouldShowToolbarFeature,
    hasPreviewGalleryImages: hasPreviewGalleryImages,
    shouldShowToolbarAction: shouldShowToolbarAction,
    getSettingsPanelKeys: getSettingsPanelKeys,
    createQuickReplyRequest: createQuickReplyRequest,
    isQuickReplySubmitter: isQuickReplySubmitter,
    getQuickReplySubmitter: getQuickReplySubmitter,
    isQuickReplyEditorCandidate: isQuickReplyEditorCandidate,
    performQuickReplySubmit: performQuickReplySubmit,
    buildPageUrl: buildPageUrl,
    detectPageType: detectPageType,
    getInjectedStyleText: getInjectedStyleText,
    applySiteShellLayout: applySiteShellLayout,
    enhanceSiteNavigation: enhanceSiteNavigation,
    applyReadPostLayout: applyReadPostLayout,
    isEmptyReadSeparatorNode: isEmptyReadSeparatorNode,
    getPostToolsHost: getPostToolsHost,
    syncHiddenPostShell: syncHiddenPostShell,
    setHomeModuleCollapsed: setHomeModuleCollapsed,
  };
});