Yamibo 漫画阅读器

一键进入漫画阅读器,支持自动生成系列目录,智能匹配章节标题。

当前为 2025-11-22 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Yamibo 漫画阅读器
// @namespace    https://bbs.yamibo.com/
// @version      3.6.2
// @author       hitori酱
// @description  一键进入漫画阅读器,支持自动生成系列目录,智能匹配章节标题。
// @match        https://bbs.yamibo.com/thread-*
// @match        https://bbs.yamibo.com/forum.php?mod=viewthread&*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @connect      bbs.yamibo.com
// @require      https://cdn.jsdelivr.net/npm/[email protected]
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/cn2t.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/t2cn.js
// @run-at       document-start
// @noframes
// @license      MIT License
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB2AAAAdgB+lymcgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAr3SURBVHic7Zt7cNTVFcc/v32H7Ca72c2LhEcS5JFIgUB5JSioQCvKWOVRqSMjxAeoVaYKdapFaccq0OfUOlrKjLUUR1HGUstDMSIJRAyEAAnQvNk8NgnJPvLYTbK7v/6xyYbFZMluNgaK35nfzO7v3HvOud/fueeee3+70D9UwO8AEyDeoJcJ+C2g7G+Qgh8CtgM/Cw+TExsdToXRglSQMDpS56fL9QVTa4vY3tUpANuAjX218UdATZhKFl+R84QQawhHMW4bMWEaqjf8akicHQqYWm0k/+kV7M6uWiChrzYSP/1jDboRQqwhfGi8+xYQp47AMEINENtfG38E3BT4joDhdmC48R0Bw+3AcOM7AobbgeHGTU+ALFSKutwudpw6zq6z+Zy/bEIqSEiNjuPhKTNZPWUmUsHDtUt0c6SylANl5/m69hIV5ibMjnYANAol4/UxPHjrdB5Nn+uj3yW6eafwBH8vPEFxowmX6GaSIY6fTJ5BVvoc5BJpUH6HhIC6Vhv37n6Lk3VGj1KpR+2RqlKOVJWysyCP3Q+s5r1zp/h9XjZ1rTaf/nKZHIlEQmtbCzUtVkrarNw2dxa3dEhxuVw029u5//0dHDNW+OjPNZaTayznbwXH2ffg48SrIwL2fdAEdLld3sEbIvWkJqehVUcCYLaZOVdeRK6xnLF/fBm3KCIIAjG6GGKjYoiLjic+Jg6pzPP03KKb1rZWVEoVr+cXoteqOfT5J5ytrERERKfRcWtKGjqNZ0NmabVSVF7EyTojS997m2NrNgQcCYPOATsL8jhZZ0QfqWf25NnewQPoInRERUZ1D04kITqB+em3M/vWWSQlJDEytnfwABJBQoQ6AoVcAUCTpRWUGkREr76ewQNo1ZHMmTwbfaSe/NpL7CzIC9j/QROw62w+AGnJqUgEX3UF/z1NWXUZcpmc70+awfSJ6WhGaABQKBRIpNd+WtPT0lmUsRCVUkV5TTkFFwt8ByBISEtO9fElEAyagPOXTcikMrRqre/9ygsY642oFCpumzqPeEO8j1yp6veM4htIiE3gnvlLUI9QY2yo5nzlBR+5Vq1FJpVR1FgXsP+DJkAplSGKbm+YAjSYGygxlqCQK5gzeQ7hYb5bakEQkMvlAdnRhGtYnLkIlVJFibGEBnODVyYiIooiKllgOiEEBEyKjsPldtNsNQPgcrkoLDkDwJRx30Pj2Y9TWFLIsbPHAZDKpAhC71nMl19/SfZX2X3qP3z8MLmncgGIUEeQOT3To6/0DC6XC4BmqxmX20VqdFzA/g+agDVTZwNQVF6Ey+2iylSFvcPOyOiR3rDvcnZxqd5Im70NANlVT8poqqa2oe/wNdvMlFSV0tnVCcCouESSEpOwO+xUmqpwuV0UlRcBkDVtTsD+D5qA5anTWDD2FiytFnJO51BaUwbAhFHjvW2arE2IoohBawBAJhu42ThDHKIoYrps8t5LT52GIAiU1ZSRczoHS6uFO5PGsyx1asD+D5oAmUTCRyuyuCt5AtY2G44OB9HaaDThGm+bZptnehgi9R6jA8j+PYg1eMK6oal3zkeoIxgZE4+jw4G1zcbC5InsWb7WW20GgpDsBbSqMA49tJ6Vaekep6NifOTtDk/oq7vzgUQycLNajaeuaGlr8bk/ZuQYAFampXPwoXVoVWFB+R6yzZCAgLXDDoC++0n3oK271g9XeVaDQAjQqD2RdDUBMXoPyRaHHcHv4bZ/hGwzBHCuO5H1ZPseOJ1OAA7nfw7gswIA3gS3a98/+9XdbDX3KT/bUBu8w4SQgPauTmpsVgRBQB1+VZEjqPz2De//xY1fudPmpLbFSmtnB2rFwAurKxEyAsrNTYiIhCkVNOb7PilppCFUZnygHnsHbe0OKixNTI4ZGZSOkOWAcvNlAOyOTiy2Nl+h2x0qM16YLS20tTu6bTcFrSd0BFh6nTDWNvrIPj1ygi3bd+Lo6AxYb0dnF1u27+Q/n/nmleq63mWxrJv8YBDyCAD47NhpH9k/PtjP5q07WLT8GQqLSr336xubudxs8X5vMlsxNfQSeaa4lIXLfsrmrTvY/dEhH52Hsk94P1cMIgJCmgN68K/PvmLDmvu833+z8REKz1dwNK+QqQseJiE+GplUSlW1ibQJSZw7uguAjCWPc7H0EmMS43C6XNTUeSJpSto4tr38lI+9jw982Wvbch1FQLwmgtyTxZy7WOWVxeo05H3yFtteforUCUnUNzZTVW0iZWwCjz3cS9STax5gXFIiVdUm6hubSZ2QxNbNT5J3YAdxMb21xdnzZeSeOOM9AitrDp6AkESAiEilpZnoEWp+nrGQZw58yAvb32HfX3/pbaHAxXPrV/Hc+lU4nS5cbjdKhe+m6Oms5TydtZyOzi6kEgkyWd8l88ZX3sDtFnkhcxG/PnqQSkszblFEIgReEIUkAmpbbNidXSTp9DyaPpcx2ij2f5HPRwePeduITgeiy1MQyWTSbwz+SigV8n4Hv2dfNgc+z2NMZBRZ6XNI1unpcDmpabH02f5aCAkBPeGfojMQJpOz895VCILAmo1/4OyFSk8jEdztNhDF/hVdA8UXK1j77KsIgsDb9/yYMJmc5O4dZrCJMEQEeIwnaT3z9I6k8bw4bzGt7Q7ufXQLZy54jrNxu3G3twCBk1BYVMoPVm7A1tLGS/MWsyhlIgDJOo/NYJfCkEZAjzMAW+bfzePTM6g2Xea2lZu800F0duJuawkoEvbsyyZjyWMYaxt4Ynomr8y/2yvrsRlsMRTSCEjR+Za8by5ZwWt3LqXd3sGKp15j8eqXOF1cjujsxNVqQXR1+dVbfLGCFVkvsnztL2i3d7Ap4y7euHu5T5vkbpsVluAICMkq0LMOJ+l8t8ECApsy7mJaXCJP7v+Aw8cKmfmjDcxJn8TSO2exMHMqiaNGYoiLRZDIaDJbqa5t5NMjJ/h4/1GOfX0Gt1tkXFQ0f/7hMhanTPqG7cFOgZAQUGFuQiGVkhih7VO+KGUi59a9wJv5ObyZn0NufjG5+cVset0jVyk9L0KuLpXH62NYNyOTdTMyUUr7djVBo0UplQWdBENCQOboFEbI5X6PpJRSGc/Oms8zs24n51I52ZUl5Fwqp9pmpsnuOTBJ0ugYFakjY1QyC8beQubo5GsedkgEgVWTZ9Bkb/Pbrj+EhID3lz0y4LYCAvNGpzBvdEooTAOwc+mqoPve9L8PuOkJGPAUWLJgHNFNQ3OyM5wYMAF7/3I/vBvcsdP1jJt+CgyYgNUb/83T+/cMpS/DggETsHtfMXsvFA6lL8OC/+spUNNipbG9VQT6rZP9lVlOlVImnTROj7Wl44b8x0iNzUqH5xBmO/B8oP2dDP9/fgZ7uYGt+Fnt/EaAUiGXnjnyLuNTRl+LrOsOo6beR3VtgxPw+7sZvzkgJjrqhhx8IBhwIbT22Vf5IvfUUPoSUlz5gsUfBkxAfWMz5VWDexV9PcJvDhiVECu9VLD3GwJV4u1EaSTU7p3ncz98UTZqdQT1xZ+E2M3AEYocUH+5yUJdffDv3W4E+JsCu+yOjudTZi4T42P0PpHS2eUEFNdUvn7jdg5mB/773VCgOwdIgR1AVjA6lHj+clpDH2tsvF4plr2XId4z1yAumx8jGj+cJ45QSkVBEBpEy1GdaDmqUyrln/bV91u+9gcz+GvBoZRLxPAwqbvHUKRa5pZKBBGoHwqD1xscgChAF7AZeJ3eyvGmIOAL4AQw5Yp7s4BC4OPhcChY/A/fkn/A85DrtAAAAABJRU5ErkJggg==
// ==/UserScript==

(function () {
  "use strict";
  // 旧版本缓存清理
  const currentVersion = GM_info.script.version;
  const lastVersion = GM_getValue("script_version", "0.0.0");
  if (currentVersion !== lastVersion) {
    console.log(`检测到脚本更新: 从 v${lastVersion} 更新到 v${currentVersion}`);
    const keys = GM_listValues();
    keys.forEach((key) => {
      if (key !== "script_version") {
        // 保留版本号字段
        GM_deleteValue(key);
      }
    });
    console.log("已清除旧版本缓存数据");
    GM_setValue("script_version", currentVersion);
  }

  // 静态常量预编译
  const EXCLUDE_WORDS_REGEX =
    /个人汉化|汉化组|汉化|个人翻译|個人翻譯|个汉|提灯喵|翻译|生肉|未翻译|填坑组|粮食组|保护协会|創作百合|创作百合|熟肉|字幕|工作室|社团|官方中字|出版|同人志|汉化工房|漫画屋|同好會|翻译组|汉化委员会|渣翻渣嵌|合作汉化|完结|连载|短篇|短篇合集|百合短篇合集|同人|raw|韩漫|杂志|英译|单行本|插画|特典|番外|外传|彩页|合订本|猫岛汉化组|和菓子漫画屋|大友同好會|透明声彩汉化组|最终|终章|序章|尾声/i;
  const FATAL_KEYWORDS_REGEX =
    /汉化|翻译|个人|组|工作室|社团|同人|英译|中字|生肉|raw|sample|合同志|填坑|粮食|发布|校对|嵌字|图源|扫图|合集|短篇|短篇集|连载/i;
  const BLOCK_IMG_REGEX =
    /\/uc_server\/data\/avatar\/|avatar|user_avatar|usericon|static\/image\/common\/|static\/image\/smiley\/|template\/|none\.gif|loading\.gif|logo\.png|logo\.gif|qq\.gif|qq_big\.gif|qq_group|userinfo\.gif|forumlink\.gif|online_admin|online_member|online_team|icon_quote|collapse|expand|rating|score|grade|star|magic/i;
  const NUM_CHAR = "[\\d①-⑳一二三四五六七八九十百千万零〇]";
  const NUM_RANGE_UNIT = `(?:${NUM_CHAR}|[-~—–])`;

  // 样式常量预编译
  const BUTTON_CSS = `
    /* === 入口按钮 === */
    #reader-toggle {
      position: fixed; top: 20%; right: 10px; transform: translateY(-50%);
      z-index: 99999; width: 60px; height: 60px; border-radius: 60px;
      background: white; border: none; cursor: pointer;
      box-shadow: 0 10px 25px rgba(0,0,0,0.1);
      display: flex; justify-content: center; align-items: center;
      transition: 0.5s;
      font-family: "IBM Plex Sans SC", "PingFang SC", "Microsoft YaHei", Arial, sans-serif !important;
    }
    #reader-toggle:hover { width: 180px; box-shadow: none; }

    /* 渐变背景层 */
    #reader-toggle::before, #reader-toggle::after {
      content: ""; position: absolute; border-radius: 40px;
      background: linear-gradient(45deg, #56CCF2, #2F80ED); opacity: 0; transition: 0.5s;
    }
    #reader-toggle::before { inset: 0; }
    #reader-toggle::after { top: 10px; width: 100%; height: 100%; filter: blur(15px); z-index: -1; }

    #reader-toggle:hover::before { opacity: 1; }
    #reader-toggle:hover::after { opacity: 0.5; }

    /* 图标与文字 */
    #reader-toggle .icon {
      position: absolute; left: 0; top: 0; width: 60px; height: 60px;
      display: flex; align-items: center; justify-content: center;
      font-size: 2em; color: #777; transition: 0.5s;
    }
    #reader-toggle .icon ion-icon { width: 24px; height: 24px; }

    #reader-toggle .title {
      position: absolute; color: #fff; font-size: 1.4em; letter-spacing: 0.1em;
      transform: scale(0); transition: 0.5s;
    }

    #reader-toggle:hover .icon { transform: scale(0); }
    #reader-toggle:hover .title { transform: scale(1); transition-delay: 0.2s; }

    /* === 目录侧边栏 === */
    #directory-sidebar {
      position: fixed; top: 0; right: -400px; width: 400px; height: 100%;
      background: #1e293b; /* 纯色性能更好,且配合阅读器暗色 */
      z-index: 2147483647; transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      box-shadow: -15px 0 40px rgba(0,0,0,0.4);
      display: flex; flex-direction: column; color: white; border: none;
    }
    #directory-sidebar.open { right: 0; }

    /* 侧边栏头部 */
    #directory-header {
      display: flex; justify-content: space-between; align-items: center;
      padding: 15px 20px; border-bottom: 1px solid rgba(255,255,255,0.1);
    }
    #directory-title { font-size: 16px; font-weight: 600; letter-spacing: 0.5px; }
    #directory-close {
      background: rgba(255,255,255,0.1); border: none; color: white; cursor: pointer;
      width: 32px; height: 32px; border-radius: 6px; font-size: 18px;
      display: flex; align-items: center; justify-content: center; transition: 0.2s;
    }
    #directory-close:hover { background: rgba(255,255,255,0.2); transform: scale(1.1); }

    /* 目录列表内容 */
    #directory-content {
      flex: 1; overflow-y: auto; padding: 10px 0;
      scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.3) transparent;
    }
    #directory-content::-webkit-scrollbar { width: 6px; }
    #directory-content::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.3); border-radius: 3px; }

    #directory-list { list-style: none; padding: 0 16px; margin: 0; }

    .directory-item {
      display: block; padding: 12px 16px; margin-bottom: 8px;
      background: rgba(255,255,255,0.05); border-radius: 8px;
      color: rgba(255,255,255,0.8); text-decoration: none !important;
      font-size: 14px; transition: all 0.2s; cursor: pointer; position: relative; overflow: hidden;
    }

    /* 悬停高亮 */
    .directory-item:hover {
      background: rgba(255,255,255,0.1); color: #fff; transform: translateX(5px);
    }
    /* 当前章节 */
    .directory-item.current {
      background: linear-gradient(135deg, #FF6B6B, #FF8E53);
      color: white; font-weight: 600; box-shadow: 0 4px 12px rgba(255,107,107,0.3);
    }
    /* 主楼特殊样式 */
    .directory-item.mainpost { background: linear-gradient(135deg, #4CAF50, #45a049); color: white; }

    /* 状态提示 */
    #directory-loading, #directory-empty {
      text-align: center; padding: 60px 20px; color: rgba(255,255,255,0.6); font-size: 15px;
    }
    #directory-loading::before { content: '📚'; display: block; font-size: 32px; margin-bottom: 10px; animation: pulse 1.5s infinite; }
    #directory-empty::before { content: '📖'; display: block; font-size: 32px; margin-bottom: 10px; opacity: 0.5; }
    @keyframes pulse { 50% { opacity: 0.6; } }

    /* === 遮罩层 === */
    #directory-overlay {
      position: fixed; top: 0; left: 0; width: 100%; height: 100%;
      background: rgba(0,0,0,0.6); z-index: 2147483646;
      opacity: 0; visibility: hidden; transition: 0.3s; backdrop-filter: blur(2px);
    }
    #directory-overlay.show { opacity: 1; visibility: visible; }
`;
  const READER_CSS = `
    /* === 全局基础 === */
    html, body {
    margin: 0;
    padding: 0;
    background: #616161;
    overflow-y: auto;
    color: #fff;
    font-family: "IBM Plex Sans SC", "PingFang SC", "Microsoft YaHei", Arial, sans-serif !important;
    }
    body {
  --img-width: 35vw;
}
body.light-bg {
  background: #f5f5f5 !important;
  color: #222 !important;
}
body.light-bg #cw-title-bar {
  color: #222 !important;
}
   /* === 图片容器 === */
    #cw-container {
      display: flex !important;
      flex-direction: column !important;
      align-items: center !important;
      padding: 20px 0 60px !important;
      gap: 20px !important;
      margin: 0 auto !important;
      will-change: transform;
    }

    #cw-title-bar {
  margin-top: 0;
  margin-bottom: 0;
  margin-left: 0;
  padding-left: 0;
  font-size: 1.25em;
  font-weight: bold;
  font-family: "IBM Plex Sans SC", "PingFang SC", "Microsoft YaHei", Arial, sans-serif !important;
  color: #fff;
  background: none;
  border: none;
  border-radius: 0;
  text-align: left;
  letter-spacing: 0.04em;
  box-shadow: none;
  position: relative;
  z-index: 1;
  user-select: text;
  word-break: break-all;
  width: fit-content;
  max-width: 100vw;
  transition: color 0.3s;
}

    #cw-container img {
      display: block !important;
       width: var(--img-width) !important;
      height: auto !important;
      content-visibility: auto !important;
      contain-intrinsic-size: var(--img-width, 35vw) calc(var(--img-width, 35vw) * 1.5) !important;
      min-height: 650px !important;
      
      transition: transform 0.2s ease, box-shadow 0.2s ease !important;

      background-color: #e0e0e0 !important;
      background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25'%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' font-family='sans-serif' font-size='16' fill='%23999999'%3E图片加载中……%3C/text%3E%3C/svg%3E") !important;
      background-position: center center !important;
      background-repeat: no-repeat !important;
      background-size: contain !important;
      border-radius: 0 !important;
      user-select: none !important;
      margin: 0 auto !important;
      box-shadow: 0 4px 8px rgba(0,0,0,0.3);
    }
    #cw-container img.loaded {
      background-image: none !important; 
      background-color: transparent !important;
      min-height: 0 !important;}
    #cw-container img[data-original-size="small"] {
      width: auto !important;
      max-width: var(--img-width, 35vw) !important;
      aspect-ratio: auto !important; /* 小图可能不是漫画页,重置比例 */
    }
    /* === 悬浮控件基础 (退出/工具栏/底部栏) === */
    #cw-exit, #cw-toolbar, #cw-bottom-bar {
        position: fixed; z-index: 2147483647; pointer-events: auto;
    }

    /* 左上退出 */
    #cw-exit {
      top: 15px; left: 20px; background: rgba(0,0,0,0.6); color: #fff; border: none;
      padding: 6px 10px; border-radius: 5px; cursor: pointer; font-size: 14px; backdrop-filter: blur(4px);
    }
      #cw-refresh {
      position: fixed; 
      top: 65px; 
      left: 20px; 
      background: rgba(0,0,0,0.6); 
      color: #fff; 
      border: none;
      padding: 6px 10px; 
      width: 32px;
      text-align: center; 
      border-radius: 5px; 
      cursor: pointer; 
      font-size: 14px; 
      backdrop-filter: blur(4px);
      z-index: 2147483647; 
      transition: 0.2s;
    }
    #cw-refresh:hover { background: rgba(0,0,0,0.8); transform: scale(1.05); }
    /* 右上工具栏 */
    #cw-toolbar { top: 15px; right: 20px; display: flex; flex-direction: column; gap: 10px; }

    /* 底部栏 */
    #cw-bottom-bar {
      bottom: 15px; right: 20px; left: auto !important; width: auto;
      display: flex; align-items: center; gap: 15px;
      background: rgba(0,0,0,0.6); padding: 8px 12px; border-radius: 5px;
      font-size: 14px; backdrop-filter: blur(4px); white-space: nowrap;
      z-index: 2147483650; /* 最高层级 */
    }

    /* 通用按钮样式 */
    #cw-toolbar button, #cw-bottom-bar button {
        color: #fff; border: none; cursor: pointer; transition: 0.2s;
        display: flex; align-items: center; justify-content: center;
    }
    #cw-toolbar button {
        background: rgba(0,0,0,0.6); padding: 6px; width: 32px; height: 32px;
        border-radius: 5px; font-size: 14px; backdrop-filter: blur(4px);
    }
    #cw-bottom-bar button {
        background: rgba(255,255,255,0.2); padding: 4px 8px; border-radius: 3px; font-size: 12px;
    }

    /* 悬停效果 */
    #cw-toolbar button:hover { background: rgba(0,0,0,0.8); transform: scale(1.05); }
    #cw-bottom-bar button:hover { background: rgba(255,255,255,0.3); }

    /* 缩放组 */
    #cw-zoom { display: flex; flex-direction: column; gap: 5px; }
    #cw-zoom-in, #cw-zoom-out { font-size: 20px !important; font-weight: bold !important; }
    #cw-full[data-fullscreen="true"] { font-size: 24px !important; line-height: 1; }
    #cw-page-info { font-weight: bold; color: #fff; }

    /* === 全屏模式 === */
    /* 合并全屏选择器 */
    :is(:fullscreen, :-webkit-full-screen) #cw-toolbar,
    :is(:fullscreen, :-webkit-full-screen) #cw-exit {
        opacity: 0; pointer-events: none; transition: 0.3s;
    }
    :is(:fullscreen, :-webkit-full-screen).tools-visible #cw-toolbar,
    :is(:fullscreen, :-webkit-full-screen).tools-visible #cw-exit {
        opacity: 1; pointer-events: auto;
    }
    /* 全屏下提高 Tooltip 层级 */
    :is(:fullscreen, :-webkit-full-screen) [data-tooltip]:hover::after,
    :is(:fullscreen, :-webkit-full-screen) [data-tooltip]:hover::before {
        z-index: 2147483649 !important;
    }

    /* === Tooltip  === */
    @keyframes tooltipFadeIn { from { opacity: 0; transform: translate(-50%, 5px); } to { opacity: 1; transform: translate(-50%, 0); } }
    @keyframes tooltipFadeInRight { from { opacity: 0; transform: translate(5px, -50%); } to { opacity: 1; transform: translate(0, -50%); } }

    [data-tooltip] { position: relative; overflow: visible !important; }

    /* 共有样式 */
    [data-tooltip]:hover::after {
        content: attr(data-tooltip); position: absolute;
        background: rgba(0,0,0,0.9); color: #fff; padding: 6px 10px;
        border-radius: 4px; font-size: 12px; white-space: nowrap;
        z-index: 2147483648; pointer-events: none; box-shadow: 0 2px 8px rgba(0,0,0,0.3);
        opacity: 0;
    }
    [data-tooltip]:hover::before {
        content: ''; position: absolute; z-index: 2147483648; pointer-events: none; opacity: 0;
        border: 6px solid transparent;
    }

    /* 1. 默认下方 (左上退出按钮) */
    #cw-exit[data-tooltip]:hover::after {
        top: 100%; left: 0; margin-top: 8px; animation: tooltipFadeIn 0.2s ease forwards;
    }
    #cw-exit[data-tooltip]:hover::before {
        top: 100%; left: 15px; margin-top: -4px; border-bottom-color: rgba(0,0,0,0.9); animation: tooltipFadeIn 0.2s ease forwards;
    }

    /* 2. 左侧显示 (右上工具栏) */
    #cw-toolbar [data-tooltip]:hover::after {
        top: 50%; right: 100%; transform: translateY(-50%); margin-right: 10px;
        animation: tooltipFadeInRight 0.2s ease forwards;
    }
    #cw-toolbar [data-tooltip]:hover::before {
        top: 50%; right: 100%; transform: translateY(-50%); margin-right: -2px;
        border-left-color: rgba(0,0,0,0.9); animation: tooltipFadeInRight 0.2s ease forwards;
    }

    /* 3. 上方显示 (底部栏) */
    #cw-bottom-bar [data-tooltip]:hover::after {
        bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: 10px;
        animation: tooltipFadeIn 0.2s ease forwards;
    }
    #cw-bottom-bar [data-tooltip]:hover::before {
        bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: -2px;
        border-top-color: rgba(0,0,0,0.9); animation: tooltipFadeIn 0.2s ease forwards;
    }
`;

  // 阅读器全局变量定义
  let isReading = false;
  let images = [];
  let bgIsBlack = true;
  let zoomLevel = 35; // 默认缩放比例35%
  let autoReaderEnabled = false; // 全局阅读器开关

  // 目录识别相关变量
  let seriesDirectory = [];
  let currentSeriesKey = "";
  let isDirectoryMode = false;
  let savedThreadTitle = "";
  let originalSeriesTitle = "";
  let searchCache = new Map();
  let directoryMemoryCache = new Map();

  // 保存原始页面HTML
  let readerStartUrl = ""; // 记录进入阅读器时的 URL
  let originalPageHTML = "";
  let originalPageTitle = "";

  // 事件处理与状态
  let readerEventHandlers = {};
  let readerEventsBound = false;
  let readerToolsTimer = null;
  let unbindReaderEvents = null;

  // 缓存前缀与过期时长
  const CACHE_PREFIX = "yamibo-directory-cache-";
  const CACHE_EXPIRY = 24 * 60 * 60 * 1000;

  // 持久化缓存索引键
  const GM_INDEX_KEY = CACHE_PREFIX + "gm-index";

  // 通用存储封装
  function storageGet(key, defaultVal = null) {
    try {
      if (typeof GM_getValue === "function") {
        let val;
        try {
          val = GM_getValue(key, undefined);
        } catch (e) {
          val = undefined;
        }
        if (val === undefined) {
        } else if (val === null) {
          return defaultVal;
        } else if (typeof val === "string") {
          try {
            return JSON.parse(val);
          } catch (e) {
            return val;
          }
        } else {
          return val;
        }
      }
    } catch (e) {}

    try {
      const raw = sessionStorage.getItem(key);
      return raw ? JSON.parse(raw) : defaultVal;
    } catch (e) {
      return defaultVal;
    }
  }

  function storageSet(key, value) {
    try {
      if (typeof GM_setValue === "function") {
        try {
          GM_setValue(key, value);
        } catch (e) {
          try {
            GM_setValue(key, JSON.stringify(value));
          } catch (ee) {
            throw ee;
          }
        }

        try {
          let idx = GM_getValue(GM_INDEX_KEY, undefined);
          if (idx === undefined || idx === null) idx = [];
          if (typeof idx === "string") {
            try {
              idx = JSON.parse(idx);
            } catch (e) {
              idx = [];
            }
          }
          if (!Array.isArray(idx)) idx = [];
          if (!idx.includes(key)) {
            idx.push(key);
            GM_setValue(GM_INDEX_KEY, idx);
          }
        } catch (e) {}
        return true;
      }
    } catch (e) {}

    try {
      sessionStorage.setItem(key, JSON.stringify(value));
      return true;
    } catch (e) {
      console.warn("⚠️ storageSet 失败:", e);
      return false;
    }
  }

  function storageRemove(key) {
    try {
      if (typeof GM_deleteValue === "function") {
        try {
          GM_deleteValue(key);
        } catch (e) {
          try {
            GM_setValue(key, null);
          } catch (ee) {}
        }

        try {
          let idx = GM_getValue(GM_INDEX_KEY, undefined);
          if (typeof idx === "string") {
            try {
              idx = JSON.parse(idx);
            } catch (e) {
              idx = [];
            }
          }
          if (!Array.isArray(idx)) idx = [];
          const newIdx = idx.filter((k) => k !== key);
          GM_setValue(GM_INDEX_KEY, newIdx);
        } catch (e) {}

        return true;
      }
    } catch (e) {}

    try {
      sessionStorage.removeItem(key);
      return true;
    } catch (e) {
      return false;
    }
  }

  // 清理过期缓存:同时支持 sessionStorage 前缀与 GM 索引
  function cleanExpiredCache() {
    let cleanedCount = 0;

    // 1) 清理 sessionStorage 前缀缓存
    try {
      const sKeys = Object.keys(sessionStorage).filter((k) =>
        k.startsWith(CACHE_PREFIX)
      );
      sKeys.forEach((key) => {
        try {
          const raw = sessionStorage.getItem(key);
          if (!raw) return;
          const obj = JSON.parse(raw);
          const ts = obj.ts || obj.timestamp || 0;
          if (Date.now() - ts > CACHE_EXPIRY) {
            sessionStorage.removeItem(key);
            cleanedCount++;
          }
        } catch (e) {
          try {
            sessionStorage.removeItem(key);
            cleanedCount++;
          } catch (ee) {}
        }
      });
    } catch (e) {}

    // 2) 清理 GM_* 存储(使用索引枚举)
    try {
      if (
        typeof GM_getValue === "function" &&
        typeof GM_setValue === "function"
      ) {
        let idx = GM_getValue(GM_INDEX_KEY, undefined);
        if (typeof idx === "string") {
          try {
            idx = JSON.parse(idx);
          } catch (e) {
            idx = [];
          }
        }
        if (!Array.isArray(idx)) idx = [];

        const remaining = [];
        for (const key of idx) {
          try {
            const stored = storageGet(key, null);
            if (!stored) continue;
            const ts = stored.ts || stored.timestamp || 0;
            if (Date.now() - ts > CACHE_EXPIRY) {
              storageRemove(key);
              cleanedCount++;
            } else {
              remaining.push(key);
            }
          } catch (e) {
            try {
              storageRemove(key);
              cleanedCount++;
            } catch (ee) {}
          }
        }

        // 更新索引为剩余项
        try {
          GM_setValue(GM_INDEX_KEY, remaining);
        } catch (e) {}
      }
    } catch (e) {}

    if (cleanedCount > 0)
      console.log("🧹 清理了", cleanedCount, "个过期缓存 (session+GM)");
    return cleanedCount;
  }

  // 在配额不足或需要收缩时清理最旧的缓存(同时支持 sessionStorage 与 GM)
  function cleanOldestCache() {
    try {
      const entries = [];

      // 收集 sessionStorage 条目
      try {
        Object.keys(sessionStorage).forEach((key) => {
          if (!key.startsWith(CACHE_PREFIX)) return;
          try {
            const obj = JSON.parse(sessionStorage.getItem(key));
            const ts = obj.ts || obj.timestamp || 0;
            entries.push({ key, ts, source: "session" });
          } catch (e) {
            entries.push({ key, ts: 0, source: "session" });
          }
        });
      } catch (e) {}

      // 收集 GM_* 条目 via index
      try {
        if (typeof GM_getValue === "function") {
          let idx = GM_getValue(GM_INDEX_KEY, undefined);
          if (typeof idx === "string") {
            try {
              idx = JSON.parse(idx);
            } catch (e) {
              idx = [];
            }
          }
          if (!Array.isArray(idx)) idx = [];

          for (const key of idx) {
            try {
              const stored = storageGet(key, null);
              const ts = stored ? stored.ts || stored.timestamp || 0 : 0;
              entries.push({ key, ts, source: "gm" });
            } catch (e) {
              entries.push({ key, ts: 0, source: "gm" });
            }
          }
        }
      } catch (e) {}

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

      // 按时间戳排序,最旧的在前
      entries.sort((a, b) => (a.ts || 0) - (b.ts || 0));

      const toDelete = Math.ceil(entries.length / 2);
      for (let i = 0; i < toDelete; i++) {
        const e = entries[i];
        try {
          if (e.source === "session") sessionStorage.removeItem(e.key);
          else storageRemove(e.key);
        } catch (err) {}
      }

      return toDelete;
    } catch (e) {
      console.warn("cleanOldestCache failed", e);
      return 0;
    }
  }

  // === 全局搜索限流控制器 ===
  // 防止

  const SEARCH_INTERVAL_MS = 12000; // 限制间隔 12 秒 (论坛限制 10 秒,留 2 秒缓冲)
  const MAX_WAIT_TIME = 60000; // 最大等待时间 60 秒,超过则放弃排队

  /**
   * 安全的搜索请求包装器
   * @param {Function} requestFn - 真正执行请求的函数,需要返回 Promise
   * @returns {Promise<any>}
   */
  async function safeSearchRequest(requestFn) {
    let lastTime = 0;
    try {
      if (typeof GM_getValue === "function") {
        lastTime = GM_getValue("last_search_time", 0);
      } else {
        lastTime = parseInt(
          localStorage.getItem("yamibo_last_search_time") || "0"
        );
      }
    } catch (e) {
      lastTime = 0;
    }

    const now = Date.now();
    let waitTime = 0;

    // 计算需要等待的时间
    if (now - lastTime < SEARCH_INTERVAL_MS) {
      waitTime = SEARCH_INTERVAL_MS - (now - lastTime);
      // 添加随机抖动
      waitTime += Math.random() * 1000;
    }

    // 异常等待时间重置
    if (waitTime > MAX_WAIT_TIME) {
      console.warn(`[限流] 等待时间过长 (${waitTime}ms),重置计时器`);
      waitTime = 1000;
    }

    // 执行等待 (静默模式)
    if (waitTime > 0) {
      console.log(
        `[限流] 搜索请求过于频繁,后台静默排队 ${(waitTime / 1000).toFixed(
          1
        )} 秒...`
      );
      await new Promise((resolve) => setTimeout(resolve, waitTime));
    }

    // 更新全局时间戳 (抢锁)
    try {
      const newNow = Date.now();
      if (typeof GM_setValue === "function") {
        GM_setValue("last_search_time", newNow);
      } else {
        localStorage.setItem("yamibo_last_search_time", newNow.toString());
      }
    } catch (e) {
      console.error("[限流] 更新时间戳失败", e);
    }

    // 执行真正的请求
    return await requestFn();
  }

  // 检查是否启用了全局阅读器模式
  function checkAutoReaderStatus() {
    const stored = localStorage.getItem("yamibo-auto-reader");
    return stored === "true";
  }

  // 设置全局阅读器模式
  function setAutoReaderStatus(enabled) {
    autoReaderEnabled = enabled;
    localStorage.setItem("yamibo-auto-reader", enabled.toString());
    console.log("🔧 全局阅读器模式:", enabled ? "开启" : "关闭");
  }

  /*** 标题标准化函数  ***/
  function normalizeSeriesTitle(rawTitle) {
    if (!rawTitle) return "";

    // 首先清理页面title的后缀
    let title = rawTitle;
    if (title.includes(" - ")) {
      title = title.split(" - ")[0].trim();
    }

    // 移除页数标注
    title = title.replace(/[((]\s*\d+\s*p\s*[))]\s*$/i, "");

    // 移除章节标识 - 参考脚本的模式
    title = title.replace(
      /第[\d一二三四五六七八九十百千万〇零兩两1234567890\.]+[话話章节節回卷篇]/gi,
      " "
    );
    title = title.replace(/\d+(?:\.\d+)?\s*[话話章节節回卷篇]/gi, " ");
    title = title.replace(
      /\d+(?:\.\d+)?\s*[上下前后前後左右中篇部卷期全完]+(?:\s*[++&和及與并並,,/]\s*[上下前后前後左右中篇部卷期全完]+)+/gi,
      " "
    );
    title = title.replace(
      /[((][上下前后前後中全完]+(?:\s*[,,++&和及與并並/]\s*[上下前后前後中全完]+)*[))]/g,
      " "
    );
    title = title.replace(
      /\d+(?:\.\d+)?\s*[上下前后前後左右中篇部卷期全完]+/gi,
      " "
    );
    title = title.replace(
      /(?:\s+|[-‐‑‒–—―-~~·•_、::])?\d+(?:\.\d+)*(?:\s*[上下前后前後左右中篇部卷期話话节節全完])?\s*$/g,
      " "
    );
    title = title.replace(/[--—–~~\u2013\u2014\s]+$/g, " ");
    title = title.replace(/[\[\]【】()()]/g, " ");
    title = title.replace(/\s+/g, " ").trim();

    if (!title) {
      return rawTitle.trim();
    }
    return title;
  }

  function buildSeriesKey(title) {
    const normalized = normalizeSeriesTitle(title || "");
    const base = normalized || (title || "").trim();
    return base.toLowerCase();
  }

  function generateSeriesKey(title) {
    try {
      const k = buildSeriesKey(title || "");
      // 限制长度并移除特殊字符,避免存储键过长或包含非法字符
      return k.replace(/[\s\/:\\#\?&=\+%\*\|<>"'`]/g, "-").substring(0, 120);
    } catch (e) {
      return String(title || "")
        .toLowerCase()
        .substring(0, 120);
    }
  }

  // 统一的排除词汇列表,避免重复定义
  const EXCLUDE_WORDS = [
    "个人汉化",
    "汉化组",
    "汉化",
    "个人翻译",
    "個人翻譯",
    "个汉",
    "翻译",
    "生肉",
    "未翻译",
    "填坑组",
    "粮食组",
    "保护协会",
    "創作百合",
    "创作百合",
    "熟肉",
    "字幕",
    "工作室",
    "提灯喵",
    "社团",
    "官方中字",
    "出版",
    "汉化工房",
    "猫岛汉化组",
    "和菓子漫画屋",
    "大友同好會",
    "透明声彩汉化组",
    "翻译组",
    "汉化委员会",
    "渣翻渣嵌",
    "合作汉化",
    "完结",
    "连载",
    "短篇",
    "合集",
    "同人",
    "raw",
    "韩漫",
    "杂志",
    "英译",
    "单行本",
    "插画",
    "特典",
    "番外",
    "外传",
    "彩页",
    "合订本",
  ].map((s) => s.toLowerCase());

  // 统一的文本过滤函数:检查是否为噪音词汇
  function isNoiseText(text) {
    if (!text || typeof text !== "string") return true;
    // [优化] 直接使用预编译正则,替代循环 includes
    if (EXCLUDE_WORDS_REGEX.test(text)) return true;

    // 常见格式判断:纯数字视为噪音
    if (/^\d+$/.test(text)) return true;

    // 智能长度检查
    const hasCJK =
      /[\u4e00-\u9fa5\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF]/.test(text);
    const minLength = hasCJK ? 2 : 3;
    if (text.length < minLength) return true;

    return false;
  }

  // 保持向后兼容的 isGenericTag 函数
  function isGenericTag(text) {
    return isNoiseText(text);
  }

  function getCachedDirectory(seriesKey) {
    // 🚫 临时禁用缓存读取,强制测试双语解析逻辑
    console.log("🔄 缓存读取已禁用,将执行新的双语解析逻辑");
    return null;

    if (!seriesKey) return null;

    // 1) 先检查内存缓存
    try {
      if (directoryMemoryCache && directoryMemoryCache.has(seriesKey)) {
        const cached = directoryMemoryCache.get(seriesKey);
        if (cached && cached.ts && Date.now() - cached.ts < CACHE_EXPIRY) {
          return cached.data || cached.d || null;
        } else {
          directoryMemoryCache.delete(seriesKey);
        }
      }
    } catch (e) {}

    const key = CACHE_PREFIX + seriesKey;

    // 2) 尝试 GM_getValue
    try {
      if (typeof GM_getValue === "function") {
        let stored = GM_getValue(key, undefined);
        if (typeof stored === "string") {
          try {
            stored = JSON.parse(stored);
          } catch (e) {
            /* leave as-is */
          }
        }
        if (stored && (stored.d || stored.data)) {
          const ts = stored.ts || stored.timestamp || 0;
          if (Date.now() - ts < CACHE_EXPIRY) {
            try {
              directoryMemoryCache.set(seriesKey, {
                d: stored.d || stored.data,
                data: stored.d || stored.data,
                ts: ts || Date.now(),
              });
            } catch (e) {}
            return stored.d || stored.data;
          } else {
            try {
              storageRemove(key);
            } catch (e) {}
            return null;
          }
        }
      }
    } catch (e) {}

    // 3) 尝试 sessionStorage
    try {
      const raw = sessionStorage.getItem(key);
      if (raw) {
        let obj = raw;
        try {
          obj = JSON.parse(raw);
        } catch (e) {
          obj = raw;
        }
        if (obj && (obj.d || obj.data)) {
          const ts = obj.ts || obj.timestamp || 0;
          if (Date.now() - ts < CACHE_EXPIRY) {
            try {
              directoryMemoryCache.set(seriesKey, {
                d: obj.d || obj.data,
                data: obj.d || obj.data,
                ts: ts || Date.now(),
              });
            } catch (e) {}
            return obj.d || obj.data;
          } else {
            try {
              sessionStorage.removeItem(key);
            } catch (e) {}
            return null;
          }
        }
      }
    } catch (e) {}

    return null;
  }

  function setCachedDirectory(seriesKey, directory) {
    if (!seriesKey || !directory) return false;
    const key = CACHE_PREFIX + seriesKey;
    const payload = { ts: Date.now(), data: directory, d: directory };

    try {
      directoryMemoryCache.set(seriesKey, {
        d: directory,
        data: directory,
        ts: payload.ts,
      });
    } catch (e) {}

    try {
      if (typeof GM_setValue === "function") {
        try {
          GM_setValue(key, payload);
        } catch (e) {
          try {
            GM_setValue(key, JSON.stringify(payload));
          } catch (ee) {
            /* ignore */
          }
        }
        return true;
      }
    } catch (e) {}

    try {
      sessionStorage.setItem(key, JSON.stringify(payload));
      return true;
    } catch (e) {
      console.warn("⚠️ setCachedDirectory 写入失败:", e);
      return false;
    }
  }

  /*** 相似度计算辅助函数  ***/

  // 简繁体标准化函数 - 使用 OpenCC 库
  let converter = null;

  function normalizeChineseVariants(text) {
    try {
      // 初始化转换器(繁体转简体)
      if (!converter && typeof OpenCC !== "undefined") {
        converter = OpenCC.Converter({ from: "tw", to: "cn" });
      }

      if (converter) {
        return converter(text);
      }

      return text;
    } catch (error) {
      console.warn("简繁转换失败,使用原文本:", error);
      return text;
    }
  }
  //中外语种统一判断
  function isForeignText(str) {
    return (
      /[A-Za-z\u3040-\u30FF\uAC00-\uD7AF]/.test(str) &&
      !/[\u4e00-\u9fa5]/.test(str)
    );
  }
  // 提取有意义的关键词(排除常见无关词汇)
  function extractMeaningfulKeywords(title) {
    const core = extractCoreWorkName(title);
    if (typeof core === "object" && core._dualLanguage) {
      return [core._dualLanguage.chinese, core._dualLanguage.foreign].filter(
        Boolean
      );
    }
    if (Array.isArray(core)) {
      return core.filter(Boolean);
    }
    return [core.toString().trim()];
  }
  // 生成搜索关键词
  function generateSearchTerms(seriesTitle) {
    if (!seriesTitle) return [];
    const core = extractCoreWorkName(seriesTitle);
    if (typeof core === "object" && core._dualLanguage) {
      return [core._dualLanguage.foreign, core._dualLanguage.chinese].filter(
        Boolean
      );
    }
    if (Array.isArray(core)) {
      return core.filter(Boolean);
    }
    return [core.toString().trim()];
  }

  // 计算核心作品名相似度
  function calculateCoreWorkSimilarity(core1, core2) {
    const arr1 = Array.isArray(core1) ? core1 : [core1];
    const arr2 = Array.isArray(core2) ? core2 : [core2];
    let maxScore = 0;
    for (const s1 of arr1) {
      for (const s2 of arr2) {
        const a = s1.toString().trim().toLowerCase();
        const b = s2.toString().trim().toLowerCase();
        if (!a || !b) continue;
        if (a === b) return 1.0;
        if (a.includes(b) || b.includes(a)) {
          const longer = a.length > b.length ? a : b;
          const shorter = a.length <= b.length ? a : b;
          maxScore = Math.max(
            maxScore,
            (shorter.length / longer.length) * 0.95
          );
        }
        // 最长公共子串
        let maxCommonLength = 0;
        for (let i = 0; i < a.length; i++) {
          for (let j = 0; j < b.length; j++) {
            let commonLength = 0;
            while (
              i + commonLength < a.length &&
              j + commonLength < b.length &&
              a[i + commonLength] === b[j + commonLength]
            ) {
              commonLength++;
            }
            maxCommonLength = Math.max(maxCommonLength, commonLength);
          }
        }
        const minLength = Math.min(a.length, b.length);
        if (maxCommonLength >= 4) {
          const similarity = (maxCommonLength / minLength) * 0.9;
          maxScore = Math.max(maxScore, similarity);
        }
      }
    }
    return maxScore;
  }

  // 检查关键词匹配(排除翻译组等无关信息)
  function checkKeywordMatch(core1, core2) {
    const kws1 = extractMeaningfulKeywords(core1);
    const kws2 = extractMeaningfulKeywords(core2);
    let bestMatch = 0;
    for (const kw1 of kws1) {
      for (const kw2 of kws2) {
        if (kw1.length >= 2 && kw2.length >= 2) {
          if (kw1 === kw2) {
            return 0.95;
          } else if (kw1.includes(kw2) || kw2.includes(kw1)) {
            const longer = kw1.length > kw2.length ? kw1 : kw2;
            const shorter = kw1.length <= kw2.length ? kw1 : kw2;
            const match = shorter.length / longer.length;
            bestMatch = Math.max(bestMatch, match * 0.9);
          }
        }
      }
    }
    return bestMatch;
  }

  // 提取核心作品名
  function extractCoreWorkName(title) {
    console.log(`   🔍 重构版标题处理: "${title}"`);
    if (!title || typeof title !== "string") return "";

    // === 0. 引用全局噪音 ===

    function getScriptType(str) {
      const hasKana = /[\u3040-\u30FF]/.test(str);
      const hasHanzi = /[\u4e00-\u9fa5]/.test(str);
      const hasLatin = /[a-zA-Z]/.test(str);
      const hasKorean = /[\uAC00-\uD7AF]/.test(str);
      if (hasKorean) return "KR";
      if (hasKana) return "JP";
      if (hasHanzi && !hasKana) return "ZH";
      if (hasLatin) return "EN";
      return "OTHER";
    }

    function isFatalNoise(text) {
      return FATAL_KEYWORDS_REGEX.test(text);
    }

    // === 1. 去头 ===
    let body = title.trim();
    while (
      body.match(/^(\s*【[^】]*】\s*)+/) ||
      body.match(/^(\s*\[[^\]]*\]\s*)+/) ||
      body.match(/^(\s*[\[【((][^\]】))]*[\]】))]\s*)+/)
    ) {
      body = body
        .replace(/^(\s*【[^】]*】\s*)+/g, "")
        .replace(/^(\s*\[[^\]]*\]\s*)+/g, "")
        .replace(/^(\s*[\[【((][^\]】))]*[\]】))]\s*)+/g, "")
        .replace(/^[\]】))]\s*/, "")
        .trim();
    }
    if (!body) body = title.trim();

    // === 1.5 枢轴切割 ===
    const fatalSource = FATAL_KEYWORDS_REGEX.source;
    const pivotRegex = new RegExp(
      `[【\\[][^\\]】]*(?:${fatalSource})[^\\]】]*[】\\]]`,
      "gi"
    );
    const pivotParts = body.split(pivotRegex);

    if (pivotParts.length > 1) {
      const lastSegment = pivotParts[pivotParts.length - 1].trim();
      // 检查最后一段是否包含有效字符 (字母、数字、汉字),防止只剩下 " / " 这种符号
      if (/[a-zA-Z0-9\u4e00-\u9fa5]/.test(lastSegment)) {
        console.log(
          `   ✂️ Step 1.5 枢轴切割: 丢弃左侧元数据,保留 "${lastSegment}"`
        );
        body = lastSegment;
      }
    }

    // === 2. 断尾 ===
    let coreCandidate = body;

    // [修正] 使用 {1,20} 替代 +,避免 Nothing to repeat 错误
    const explicitRe = new RegExp(
      `(` +
        `(?:最终|完结|后日谈|前日谈|特别|番外|短篇|尾声|序章|终章)[话話章节節篇]?|` +
        `第?${NUM_RANGE_UNIT}{1,20}[话話章节節回卷篇期部]|` +
        `(?:Vol|Part|Ch|Ep|Ex|番外|篇)\\.?\\s*${NUM_RANGE_UNIT}{1,20}|` +
        `[##]${NUM_RANGE_UNIT}{1,20}` +
        `)`,
      "i"
    );

    const spaceNumRe = new RegExp(
      `\\s+${NUM_RANGE_UNIT}{1,20}(?:[..]\\d+)?(?:\\s+|$)`
    );
    const stickyNumRe = new RegExp(
      `([^a-zA-Z0-9\\s])(${NUM_CHAR}{1,4}(?:[..]\\d+)?)(?:\\s+|$)`
    );

    const mExplicit = body.match(explicitRe);
    const mSpace = body.match(spaceNumRe);
    const mSticky = body.match(stickyNumRe);

    let splitIndex = body.length;
    let splitType = "";

    if (mExplicit && mExplicit.index < splitIndex) {
      splitIndex = mExplicit.index;
      splitType = "显式章节";
    }
    if (mSpace && mSpace.index < splitIndex) {
      splitIndex = mSpace.index;
      splitType = "隐式空格数字";
    }
    if (mSticky) {
      const stickyCutIndex = mSticky.index + mSticky[1].length;
      if (stickyCutIndex < splitIndex) {
        splitIndex = stickyCutIndex;
        splitType = "粘连数字";
      }
    }

    // 后方保护
    if (splitIndex < body.length) {
      const remainingPart = body.substring(splitIndex);
      if (/[\/||/]/.test(remainingPart)) {
        console.log(`   🛡️ Step 2 触发后方保护: 检测到分隔符,跳过断尾`);
      } else {
        const potentialTitle = body.substring(0, splitIndex).trim();
        if (potentialTitle.length >= 1) {
          console.log(
            `   🗡️ Step 2 ${splitType}切割: 保留左侧 "${potentialTitle}"`
          );
          coreCandidate = potentialTitle;
        }
      }
    }

    // === 3. 分词 ===
    coreCandidate = coreCandidate.replace(/\s+[-_—–]\s+/g, "/");
    coreCandidate = coreCandidate.replace(
      /([^\x00-\x7F])\s*[-_—–]\s*([^\x00-\x7F])/g,
      "$1/$2"
    );
    coreCandidate = coreCandidate.replace(
      /([^\x00-\x7F])\s*[-_—–]\s*([a-zA-Z0-9])/g,
      "$1/$2"
    );
    coreCandidate = coreCandidate.replace(
      /([a-zA-Z0-9])\s*[-_—–]\s*([^\x00-\x7F])/g,
      "$1/$2"
    );

    const SPLIT_RE = /[\/||/()()\[\]【】]+/;
    let rawParts = coreCandidate
      .split(SPLIT_RE)
      .map((p) => p.trim())
      .filter(Boolean);

    // === 4. 清洗 ===
    function cleanTitlePart(str) {
      if (!str) return "";
      let s = str.trim();
      if (isFatalNoise(s)) return "";

      s = s.replace(/「[^」]*」/g, "");
      s = s.replace(/『[^』]*』/g, "");
      s = s.replace(/C\d{2,3}/gi, "");
      // [优化] 使用全局正则替换噪音词
      s = s.replace(EXCLUDE_WORDS_REGEX, "");

      // 移除章节号 [修正正则]
      s = s.replace(
        new RegExp(
          `第?${NUM_RANGE_UNIT}{1,20}[话話章节節回卷篇期部](?:[\\s\\d]+)?`,
          "g"
        ),
        ""
      );
      s = s.replace(new RegExp(`其${NUM_RANGE_UNIT}+`, "g"), "");
      s = s.replace(
        /(?:最终|完结|后日谈|前日谈|特别|番外|短篇|尾声|序章|终章)[话話章节節篇]?/g,
        ""
      );
      s = s.replace(
        new RegExp(
          `(?:Vol|Part|Ch|Ep|Ex|番外|篇)\\.?\\s*${NUM_RANGE_UNIT}{1,20}`,
          "ig"
        ),
        ""
      );
      s = s.replace(new RegExp(`[##]${NUM_RANGE_UNIT}{1,20}`, "g"), "");

      s = s.trim();
      // 移除末尾版本号/数字
      s = s.replace(/v\d+(\.\d+)?/gi, "");
      s = s.replace(
        new RegExp(`\\s+${NUM_RANGE_UNIT}{1,20}(\\.\\d+)?$`, "g"),
        ""
      );
      s = s.replace(
        new RegExp(`(${NUM_RANGE_UNIT}{1,20}(\\.\\d+)?)$`, "g"),
        ""
      );
      s = s.replace(/[①-⑳]$/, "");
      s = s.replace(/[-~—–]+$/g, "");

      return s.trim();
    }

    let validParts = [];
    let seen = new Set();

    for (const p of rawParts) {
      const cleaned = cleanTitlePart(p);
      const isCJK = /[\u4e00-\u9fa5\u3040-\u30FF]/.test(cleaned);
      if (cleaned && !/^\d+$/.test(cleaned) && !seen.has(cleaned)) {
        if ((isCJK && cleaned.length >= 2) || (!isCJK && cleaned.length >= 3)) {
          validParts.push(cleaned);
          seen.add(cleaned);
        }
      }
    }
    console.log(`   🧺 Step 3 清洗后候选词: ${JSON.stringify(validParts)}`);

    // === 5. 输出 ===
    const zhParts = [];
    const foreignParts = [];

    for (const part of validParts) {
      const type = getScriptType(part);
      if (type === "ZH") zhParts.push(part);
      else if (type === "JP" || type === "EN" || type === "KR")
        foreignParts.push(part);
      else {
        if (/[\u4e00-\u9fa5]/.test(part)) zhParts.push(part);
        else foreignParts.push(part);
      }
    }

    if (zhParts.length > 0 && foreignParts.length > 0) {
      const bestChinese = zhParts.sort((a, b) => b.length - a.length)[0];
      const bestForeign = foreignParts[0];
      const result = new String(bestChinese);
      result._dualLanguage = { chinese: bestChinese, foreign: bestForeign };
      return result;
    }

    if (zhParts.length > 0) {
      if (zhParts.length >= 2) {
        const result = zhParts;
        result._multiChinese = true;
        return result;
      }
      return zhParts[0];
    }

    if (foreignParts.length > 0) {
      if (foreignParts.length >= 2) {
        if (foreignParts[1].includes(foreignParts[0])) return foreignParts[1];
        if (foreignParts[0].includes(foreignParts[1])) return foreignParts[0];
        const result = [foreignParts[0], foreignParts[1]];
        result._multiForeign = true;
        return result;
      }
      return foreignParts[0];
    }

    if (validParts.length === 0) {
      return coreCandidate || body;
    }

    return validParts[0];
  }

  // 智能相似度匹配函数 - 专注核心作品名匹配
  function calculateSimilarity(searchTitle, resultTitle) {
    console.log(`🔄 计算相似度: "${searchTitle}" vs "${resultTitle}"`);

    // 提取两个标题的核心作品名(去除翻译组、章节号等干扰信息)
    const searchCore = extractCoreWorkName(searchTitle);
    const resultCore = extractCoreWorkName(resultTitle);

    console.log(`   📝 搜索核心: "${searchCore}"`);
    console.log(`   📝 结果核心: "${resultCore}"`);

    // 调试:检查是否有双语信息
    if (typeof searchCore === "object" && searchCore._dualLanguage) {
      console.log(
        `   🌐 搜索核心双语信息: 中文="${searchCore._dualLanguage.chinese}" 外文="${searchCore._dualLanguage.foreign}"`
      );
    }
    if (typeof resultCore === "object" && resultCore._dualLanguage) {
      console.log(
        `   🌐 结果核心双语信息: 中文="${resultCore._dualLanguage.chinese}" 外文="${resultCore._dualLanguage.foreign}"`
      );
    }

    // 智能长度检查:CJK字符2个即有效,英文需要3个
    function isValidCore(core) {
      if (!core) return false;
      const coreStr = core.toString();
      const hasCJK =
        /[\u4e00-\u9fa5\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF]/.test(coreStr);
      return hasCJK ? coreStr.length >= 2 : coreStr.length >= 3;
    }

    if (!isValidCore(searchCore) || !isValidCore(resultCore)) {
      console.log(`   ❌ 核心标题无效,跳过`);
      return 0;
    }

    // 检查是否有关键词直接匹配(排除翻译组等无关词汇)
    const keywordMatch = checkKeywordMatch(searchCore, resultCore);
    if (keywordMatch > 0) {
      console.log(`   ✅ 关键词匹配度: ${(keywordMatch * 100).toFixed(1)}%`);
      return keywordMatch;
    }

    // 计算核心作品名的相似度
    const similarity = calculateCoreWorkSimilarity(searchCore, resultCore);
    console.log(`   ✅ 核心作品相似度: ${(similarity * 100).toFixed(1)}%`);

    return similarity;
  }

  /*** 目录识别功能 ***/

  /*** 搜索流程 ***/
  // 解析搜索结果页面 HTML,返回原始结果列表
  function parseSearchHtml(html) {
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, "text/html");
    const results = [];
    const seenUrls = new Set();

    // 兼容多种选择器,覆盖不同主题和页面结构
    const selectors = [
      "#threadlist li.pbw h3.xs3 a",
      "#threadlist ul li.pbw h3.xs3 a",
      "#threadlist .slst ul li.pbw h3 a",
      "#threadlist li h3.xs3 a",
      "#threadlist .xst",
      "a.xst",
      "#threadlist tbody .subject a",
      ".threadlist .title a",
      'tbody[id^="normalthread"] .xst',
    ];

    selectors.forEach((selector) => {
      const elements = doc.querySelectorAll(selector);
      elements.forEach((link) => {
        let url = link.href;
        const title = link.textContent.trim();

        // 基础过滤
        if (!url || !title || title === "快速") return;

        // URL 补全
        if (url.startsWith("/")) {
          url = `https://bbs.yamibo.com${url}`;
        } else if (url.startsWith("forum.php")) {
          url = `https://bbs.yamibo.com/${url}`;
        }

        // 提取 Thread ID
        const threadId =
          url.match(/tid=(\d+)/)?.[1] || url.match(/thread-(\d+)-/)?.[1];
        if (!threadId) return;

        // 去重
        if (!seenUrls.has(threadId)) {
          seenUrls.add(threadId);
          results.push({
            threadId,
            title, // 原始标题
            url,
          });
        }
      });
    });

    // 检查是否有下一页
    const hasNextPage = !!doc.querySelector('.nxt, .next, a[href*="page=2"]');

    return { results, hasNextPage };
  }
  // 执行单次关键词搜索 (修正翻页逻辑)
  async function fetchSearchResults(searchTerm, forumId) {
    console.log(`🔍 执行搜索: "${searchTerm}" (fid=${forumId})`);
    const allResults = [];

    // 1. 获取 FormHash
    let formHash = getFormHash();
    if (!formHash) {
      try {
        const preRes = await fetch("https://bbs.yamibo.com/search.php");
        const preHtml = await preRes.text();
        const m = preHtml.match(/name="formhash"\s+value="([^"]+)"/);
        if (m) formHash = m[1];
      } catch (e) {}
    }

    // 2. 准备第一页请求 (这算一次搜索,受限流控制)

    const performFirstPage = async () => {
      const encodedTerm = encodeURIComponent(searchTerm);
      // 尝试 GET 搜索
      const getUrl = `https://bbs.yamibo.com/search.php?mod=forum&srchtxt=${encodedTerm}&formhash=${formHash}&srchfid=${forumId}&orderby=dateline&ascdesc=desc&searchsubmit=yes`;

      let response = await fetch(getUrl, {
        method: "GET",
        credentials: "include",
        headers: { "Cache-Control": "no-cache" },
      });

      // GET 失败尝试 POST
      if (!response.ok) {
        const formData = new FormData();
        formData.append("formhash", formHash);
        formData.append("srchtxt", searchTerm);
        formData.append("srchfid[]", forumId);
        formData.append("orderby", "dateline");
        formData.append("ascdesc", "desc");
        formData.append("searchsubmit", "yes");

        response = await fetch("https://bbs.yamibo.com/search.php?mod=forum", {
          method: "POST",
          credentials: "include",
          body: formData,
        });
      }
      return response;
    };

    try {
      // === 第1页搜索 (消耗限流配额) ===
      const response = await safeSearchRequest(performFirstPage);

      if (!response.ok) {
        console.warn(`❌ 搜索请求失败: ${response.status}`);
        return [];
      }

      // **关键点:获取最终 URL 以提取 searchid**
      // fetch 会自动跟随重定向,response.url 就是最终的 searchid URL
      const finalUrl = response.url;
      console.log(`   🔗 搜索结果URL: ${finalUrl}`);

      const html = await response.text();
      if (html.includes("需要登录")) return [];

      const { results, hasNextPage } = parseSearchHtml(html);
      allResults.push(...results);
      console.log(`   📄 第1页找到 ${results.length} 个结果`);

      // === 第2页搜索 (利用 searchid 翻页,不消耗限流配额) ===
      if (hasNextPage) {
        const searchIdMatch = finalUrl.match(/searchid=(\d+)/);

        if (searchIdMatch) {
          const searchId = searchIdMatch[1];
          console.log(
            `   📄 检测到第2页,使用 searchid=${searchId} 快速翻页 (不触发限流)`
          );

          // 构造纯翻页链接
          const page2Url = `https://bbs.yamibo.com/search.php?mod=forum&searchid=${searchId}&orderby=dateline&ascdesc=desc&searchsubmit=yes`;

          try {
            const page2Res = await fetch(page2Url, {
              method: "GET",
              credentials: "include",
            });

            if (page2Res.ok) {
              const page2Html = await page2Res.text();
              const page2Data = parseSearchHtml(page2Html);
              allResults.push(...page2Data.results);
              console.log(`   📄 第2页找到 ${page2Data.results.length} 个结果`);
            }
          } catch (e) {
            console.warn("⚠️ 快速翻页失败:", e);
          }
        } else {
          console.warn("⚠️ 未能提取 searchid,跳过第2页以避免限流");
        }
      }
    } catch (error) {
      console.error(`❌ 搜索词 "${searchTerm}" 执行出错:`, error);
    }

    return allResults;
  }
  // 主搜索流程
  async function searchSeriesDirectory(seriesTitle) {
    if (!seriesTitle) return [];

    // 1. 检查内存缓存
    if (searchCache.has(seriesTitle)) {
      console.log("📦 命中内存缓存");
      return searchCache.get(seriesTitle);
    }

    // 2. 检查持久化缓存
    const seriesKey = generateSeriesKey(seriesTitle);
    const cached = getCachedDirectory(seriesKey);
    if (cached && cached.length > 0) {
      console.log("📦 命中持久化缓存");
      searchCache.set(seriesTitle, cached);
      return cached;
    }

    console.log("🔍 开始搜索系列目录:", seriesTitle);
    const forumId = detectForumId();

    // 3. 生成关键词列表
    // 直接使用 extractCoreWorkName 的结果,不做多余的双语判断
    const coreName = extractCoreWorkName(seriesTitle);
    let searchTerms = [];

    if (typeof coreName === "object" && coreName._dualLanguage) {
      // 双语对象:分别搜中文和外文
      searchTerms = [
        coreName._dualLanguage.chinese,
        coreName._dualLanguage.foreign,
      ];
    } else if (Array.isArray(coreName)) {
      // 多中文别名:全部加入
      searchTerms = coreName;
    } else {
      // 单语:直接搜
      searchTerms = [coreName.toString()];
    }

    // 过滤空词和重复词
    searchTerms = [...new Set(searchTerms)].filter((t) => t && t.length >= 2);
    console.log("🎯 生成搜索关键词:", searchTerms);

    // 4. 执行搜索并合并结果
    // 使用 Map 按 threadId 去重,保留相似度高的
    const resultMap = new Map();

    // 限制最多搜前2个词 (避免请求过多)
    const termsToSearch = searchTerms.slice(0, 2);

    for (const term of termsToSearch) {
      const results = await fetchSearchResults(term, forumId);

      results.forEach((item) => {
        // 计算相似度
        const similarity = calculateSimilarity(seriesTitle, item.title);
        console.log(
          `   [匹配] "${item.title}" 相似度: ${(similarity * 100).toFixed(1)}%`
        );

        // 只有相似度达标才保留 (0.7 阈值)
        if (similarity > 0.7) {
          const existing = resultMap.get(item.threadId);
          // 如果是新结果,或者新结果相似度更高,则更新
          if (!existing || similarity > existing.similarity) {
            resultMap.set(item.threadId, {
              ...item,
              originalTitle: item.title, // 保持字段兼容
              normalizedTitle: normalizeSeriesTitle(item.title),
              similarity,
              searchTerm: term,
            });
          }
        }
      });
    }

    // 5. 结果排序与转换
    let directory = Array.from(resultMap.values());

    // 按 Thread ID 升序 (老帖子在前,新帖子在后,符合漫画章节发布顺序)
    directory.sort((a, b) => parseInt(a.threadId) - parseInt(b.threadId));

    console.log(`📊 最终筛选结果: ${directory.length} 个章节`);

    // 6. 兜底策略:如果没搜到,返回当前页作为单章节目录
    if (directory.length === 0) {
      const currentTid = getCurrentThreadId();
      if (currentTid) {
        console.log("🔧 搜索无果,使用当前章节兜底");
        return [
          {
            threadId: currentTid,
            title: seriesTitle,
            originalTitle: seriesTitle,
            url: window.location.href,
            normalizedTitle: normalizeSeriesTitle(seriesTitle),
            similarity: 1.0,
            searchTerm: "current",
          },
        ];
      }
    } else {
      // 写入缓存
      searchCache.set(seriesTitle, directory);
      setCachedDirectory(seriesKey, directory);
    }

    return directory;
  }

  // 提取章节号(支持小数格式和复杂排序)
  function extractChapterNumber(title) {
    if (!title || typeof title !== "string") return 0;
    // 特殊章节(番外/彩页等)放到最后
    const specialPatterns = [
      /番外|外传|特别篇|SP|特典|彩页|后记|前记|预告|PV|CM/i,
      /extra|special|omake|bonus/i,
    ];
    for (const p of specialPatterns) {
      if (p.test(title)) return 99999;
    }

    const patterns = [
      {
        regex: /(?:第\s*)?(\d+\.\d+)\s*[话話章节節回卷篇期]?/i,
        priority: 1,
        type: "decimal",
      },
      {
        regex: /第\s*(\d+)\s*[话話章节節回卷篇]/i,
        priority: 2,
        type: "standard",
      },
      { regex: /(\d+)\s*[话話章节節]/i, priority: 3, type: "numbered" },
      {
        regex: /[\[\((](\d+(?:\.\d+)?)[\]\))]/,
        priority: 4,
        type: "bracketed",
      },
      { regex: /(?:第\s*)?(\d+)\s*卷/i, priority: 5, type: "volume" },
      { regex: /\s(\d+(?:\.\d+)?)$/, priority: 6, type: "trailing" },
      { regex: /^(\d+(?:\.\d+)?)\s/, priority: 7, type: "leading" },
      { regex: /(\d+(?:\.\d+)?)/, priority: 8, type: "any" },
    ];

    let bestMatch = null;
    let bestPriority = Infinity;
    for (const p of patterns) {
      const m = title.match(p.regex);
      if (m && p.priority < bestPriority) {
        bestMatch = m[1];
        bestPriority = p.priority;
      }
    }

    if (bestMatch != null) {
      if (bestMatch.includes(".")) {
        // 小数章如 9.5 -> 9500(保持整数比较)
        const v = parseFloat(bestMatch);
        if (!isNaN(v)) return Math.round(v * 1000);
      } else {
        const v = parseInt(bestMatch, 10);
        if (!isNaN(v)) return v;
      }
    }

    // 简单中文数字映射(可扩展)
    const chineseNumbers = {
      零: 0,
      一: 1,
      二: 2,
      三: 3,
      四: 4,
      五: 5,
      六: 6,
      七: 7,
      八: 8,
      九: 9,
      十: 10,
      十一: 11,
      十二: 12,
      十三: 13,
      十四: 14,
      十五: 15,
      十六: 16,
      十七: 17,
      十八: 18,
      十九: 19,
      二十: 20,
    };
    for (const [ch, num] of Object.entries(chineseNumbers)) {
      if (
        title.includes(`第${ch}`) ||
        title.includes(`${ch}话`) ||
        title.includes(`${ch}章`)
      ) {
        return num;
      }
    }

    return 0;
  }

  function getFormHash() {
    if (isReading) {
      console.log("🔑 FormHash: 阅读模式下跳过FormHash");
      return "";
    }
    const formHashInput = document.querySelector('input[name="formhash"]');
    return formHashInput ? formHashInput.value : "";
  }

  // 从URL识别帖子所属版块
  function detectForumId(url = window.location.href) {
    // 方式1: 从URL参数中提取 fid
    const fidMatch = url.match(/[?&]fid=(\d+)/);
    if (fidMatch) {
      const fid = fidMatch[1];
      console.log(`📍 从URL参数识别版块: fid=${fid}`);
      return fid;
    }

    // 方式2: 从forum.php路径中提取
    const forumMatch = url.match(/forum-(\d+)-/);
    if (forumMatch) {
      const fid = forumMatch[1];
      console.log(`📍 从forum路径识别版块: fid=${fid}`);
      return fid;
    }

    // 方式3: 从thread URL中推断(访问页面时检查页面元素)
    try {
      const breadcrumbs = document.querySelectorAll('.z a[href*="forum"]');
      const forumIds = [];

      for (const breadcrumb of breadcrumbs) {
        // 尝试从 forum-数字- 格式提取
        const breadcrumbMatch1 = breadcrumb.href.match(/forum-(\d+)-/);
        if (breadcrumbMatch1) {
          const fid = breadcrumbMatch1[1];
          const name = breadcrumb.textContent.trim();
          forumIds.push({ fid, name, element: breadcrumb });
          continue;
        }
        // 尝试从 fid= 参数提取
        const breadcrumbMatch2 = breadcrumb.href.match(/fid=(\d+)/);
        if (breadcrumbMatch2) {
          const fid = breadcrumbMatch2[1];
          const name = breadcrumb.textContent.trim();
          forumIds.push({ fid, name, element: breadcrumb });
        }
      }

      // ✨ 选择最后一个版块(最精确的子版块)
      if (forumIds.length > 0) {
        const lastForum = forumIds[forumIds.length - 1];
        console.log(
          `📍 从面包屑导航识别版块: fid=${lastForum.fid} (${lastForum.name})`
        );
        if (forumIds.length > 1) {
          console.log(
            `   💡 跳过了 ${forumIds.length - 1} 个父级版块,选择最精确的子版块`
          );
        }
        return lastForum.fid;
      }
    } catch (e) {
      console.log("⚠️ 无法从面包屑导航识别版块:", e.message);
    }

    // 方式4: 从当前页面的返回链接中提取
    try {
      const backLinks = document.querySelectorAll('a[href*="forum"]');
      for (const backLink of backLinks) {
        const linkMatch = backLink.href.match(/fid=(\d+)/);
        if (linkMatch) {
          const fid = linkMatch[1];
          console.log(`📍 从返回链接识别版块: fid=${fid}`);
          return fid;
        }
      }
    } catch (e) {
      // ignore
    }

    // 默认返回中文百合漫画区 (fid=30)
    console.log("📍 无法识别版块,默认使用中文百合漫画区 (fid=30)");
    return "30";
  }

  // 获取版块名称(用于日志显示)
  function getForumName(fid) {
    const forumNames = {
      30: "中文百合漫画区",
      37: "百合漫画图源区",
    };
    return forumNames[fid] || `版块${fid}`;
  }

  // 与本贴相似度匹配检查
  function containsSimilarWords(title1, title2) {
    const words1 = title1
      .toLowerCase()
      .split(/[\s\-_]+/)
      .filter((w) => w.length > 1);
    const words2 = title2
      .toLowerCase()
      .split(/[\s\-_]+/)
      .filter((w) => w.length > 1);

    console.log(`🔍 词汇比较: "${title1}" vs "${title2}"`);
    console.log(`   词汇1: [${words1.join(", ")}]`);
    console.log(`   词汇2: [${words2.join(", ")}]`);

    let commonWords = 0;
    const matchedPairs = [];

    for (const word1 of words1) {
      for (const word2 of words2) {
        if (word1.includes(word2) || word2.includes(word1)) {
          commonWords++;
          matchedPairs.push(`"${word1}" ⟷ "${word2}"`);
          break;
        }
      }
    }

    const threshold = Math.min(2, Math.min(words1.length, words2.length));
    const isMatch = commonWords >= threshold;

    console.log(`   匹配词汇: ${matchedPairs.join(", ")}`);
    console.log(
      `   匹配数量: ${commonWords}/${threshold} (需要: ${threshold})`
    );
    console.log(`   结果: ${isMatch ? "✅ 匹配" : "❌ 不匹配"}`);

    return isMatch;
  }

  /*** 悬浮按钮 ***/
  const ioniconsModule = document.createElement("script");
  ioniconsModule.type = "module";
  ioniconsModule.src =
    "https://unpkg.com/[email protected]/dist/ionicons/ionicons.esm.js";
  document.head.appendChild(ioniconsModule);

  const ioniconsNomodule = document.createElement("script");
  ioniconsNomodule.noModule = true;
  ioniconsNomodule.src =
    "https://unpkg.com/[email protected]/dist/ionicons/ionicons.js";
  document.head.appendChild(ioniconsNomodule);

  const fontLink = document.createElement("link");
  fontLink.href =
    "https://fonts.googleapis.com/css2?family=Poppins&display=swap";
  fontLink.rel = "stylesheet";
  document.head.appendChild(fontLink);

  const button = document.createElement("button");
  button.id = "reader-toggle";
  button.innerHTML = `
    <span class="icon"><ion-icon name="book-outline"></ion-icon></span>
    <span class="title">进入阅读模式</span>
  `;

  document.body.appendChild(button);

  GM_addStyle(BUTTON_CSS);

  // 添加快捷键:Ctrl+Shift+R 切换阅读模式
  document.addEventListener("keydown", (e) => {
    if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "r") {
      e.preventDefault();
      if (!isReading) enterReader();
      else exitReader();
    }
  });

  button.addEventListener("click", () => {
    if (!isReading) {
      enterReader();
      setAutoReaderStatus(true); // 开启全局阅读器模式
    } else {
      exitReader();
    }
  });

  /*** 目录功能 ***/
  async function showDirectoryModal() {
    // 创建遮罩层
    const overlay = document.createElement("div");
    overlay.id = "directory-overlay";

    // 创建侧边栏
    const sidebar = document.createElement("div");
    sidebar.id = "directory-sidebar";
    sidebar.innerHTML = `
      <div id="directory-header">
        <div id="directory-title">目录</div>
        <button id="directory-close">×</button>
      </div>
      <div id="directory-content">
        <div id="directory-loading">正在搜索相关章节...</div>
        <ul id="directory-list" style="display: none;"></ul>
        <div id="directory-empty" style="display: none;">未找到相关章节</div>
      </div>
    `;

    document.body.appendChild(overlay);
    document.body.appendChild(sidebar);

    // 显示遮罩和侧边栏
    setTimeout(() => {
      overlay.classList.add("show");
      sidebar.classList.add("open");
    }, 10);

    // 关闭按钮事件
    const closeBtn = sidebar.querySelector("#directory-close");
    closeBtn.addEventListener("click", closeSidebar);

    // 点击遮罩关闭
    overlay.addEventListener("click", closeSidebar);

    function closeSidebar() {
      overlay.classList.remove("show");
      sidebar.classList.remove("open");

      setTimeout(() => {
        overlay.remove();
        sidebar.remove();
      }, 300);
    }

    // 开始搜索目录
    loadSeriesDirectory(sidebar);
  }

  async function loadSeriesDirectory(modal) {
    console.log("📖 开始加载系列目录...");

    // 如果已有预加载的目录,直接使用
    if (seriesDirectory.length > 0) {
      console.log("✅ 使用预加载的目录:", seriesDirectory.length, "个章节");
      displayDirectory(modal, seriesDirectory);
      return;
    }

    // 如果预加载目录为空,先检查持久化缓存
    if (originalSeriesTitle || savedThreadTitle) {
      const titleToCheck = originalSeriesTitle || savedThreadTitle;
      const seriesKey = generateSeriesKey(titleToCheck);
      const cachedDirectory = getCachedDirectory(seriesKey);
      if (cachedDirectory && cachedDirectory.length > 0) {
        console.log(
          "✅ 从持久化缓存加载目录:",
          cachedDirectory.length,
          "个章节"
        );
        seriesDirectory = cachedDirectory;
        displayDirectory(modal, seriesDirectory);
        return;
      }
    }

    // 尝试多种方式获取页面标题
    let titleElement = null;
    let threadTitle = "";

    console.log("🔍 尝试查找页面标题...");

    // 简化标题提取逻辑
    const h1Element = document.querySelector("h1.ts");
    if (h1Element) {
      const subjectSpan = h1Element.querySelector(
        '#thread_subject, span[id^="thread_subject"]'
      );
      if (subjectSpan) {
        threadTitle = subjectSpan.textContent.trim();
        console.log(`✅ 找到标题: "${threadTitle}"`);
      } else {
        threadTitle = h1Element.textContent.trim();
        console.log(`✅ 找到h1文本: "${threadTitle}"`);
      }
    } else {
      // 兜底:使用原有逻辑
      const titleSelectors = [
        "#thread_subject",
        'span[id^="thread_subject"]',
        ".ts a",
        "h1.ts",
        ".ntn a",
        ".ntn",
        ".bm .mbm h1",
        ".ts",
      ];

      for (const selector of titleSelectors) {
        titleElement = document.querySelector(selector);
        if (titleElement) {
          threadTitle = titleElement.textContent.trim();
          console.log(`✅ 找到标题元素: "${selector}" -> "${threadTitle}"`);
          break;
        } else {
          console.log(`❌ 未找到: "${selector}"`);
        }
      }
    }

    // 在阅读模式下,优先使用原始系列标题进行目录搜索(避免使用错误的标题)
    if (isReading) {
      if (originalSeriesTitle) {
        threadTitle = originalSeriesTitle;
        console.log("🎯 阅读模式下使用原始系列标题:", threadTitle);
      } else if (savedThreadTitle) {
        threadTitle = savedThreadTitle;
        console.log("📦 阅读模式下使用预存标题:", threadTitle);
      }
    } else if (!threadTitle && originalSeriesTitle) {
      threadTitle = originalSeriesTitle;
      console.log("🎯 使用原始系列标题:", threadTitle);
    } else if (!threadTitle && savedThreadTitle) {
      threadTitle = savedThreadTitle;
      console.log("📦 使用预存标题:", threadTitle);
    }
    // 如果还是没找到,尝试从页面title获取
    if (!threadTitle) {
      const pageTitle = document.title;
      console.log("🔍 尝试从页面title获取:", pageTitle);

      // 移除论坛名称等后缀,只保留漫画标题部分
      if (pageTitle.includes(" - ")) {
        threadTitle = pageTitle.split(" - ")[0].trim();
        console.log("📝 从页面title提取:", threadTitle);
      } else {
        threadTitle = pageTitle.replace(/\s*-\s*百合会.*$/, "").trim();
        console.log("📝 清理后的标题:", threadTitle);
      }
    }

    const currentThreadId = getCurrentThreadId();

    console.log("📋 当前页面信息:");
    console.log("   标题元素:", titleElement ? "✅ 找到" : "❌ 未找到");
    console.log("   页面标题:", threadTitle);
    console.log("   线程ID:", currentThreadId);

    // 调试:打印页面中所有可能的标题元素
    if (!titleElement) {
      console.log("🔍 调试:查找所有可能的标题元素...");

      const allH1 = document.querySelectorAll("h1");
      const allSpans = document.querySelectorAll(
        'span[id*="thread"], span[id*="subject"]'
      );
      const allTs = document.querySelectorAll(".ts, .ntn");

      console.log("  - h1元素数量:", allH1.length);
      allH1.forEach((h1, index) => {
        if (index < 5) {
          console.log(
            `    h1[${index}]: "${h1.textContent
              .trim()
              .substring(0, 50)}" (class: ${h1.className})`
          );
        }
      });

      console.log("  - 包含thread/subject的span数量:", allSpans.length);
      allSpans.forEach((span, index) => {
        if (index < 5) {
          console.log(
            `    span[${index}]: id="${span.id}", text="${span.textContent
              .trim()
              .substring(0, 50)}"`
          );
        }
      });

      console.log("  - .ts/.ntn元素数量:", allTs.length);
      allTs.forEach((ts, index) => {
        if (index < 5) {
          console.log(
            `    ts[${index}]: class="${ts.className}", text="${ts.textContent
              .trim()
              .substring(0, 50)}"`
          );
        }
      });
    }

    if (!threadTitle) {
      console.log("❌ 页面标题为空,显示空状态");
      showDirectoryEmpty(modal);
      return;
    }

    try {
      console.log("🔍 开始搜索目录...");
      const directory = await searchSeriesDirectory(threadTitle);

      console.log("📊 搜索结果:", directory.length, "个章节");

      if (directory.length === 0) {
        console.log("❌ 未找到章节,显示空状态");
        showDirectoryEmpty(modal);
        return;
      }

      console.log("✅ 找到章节,开始显示目录界面");
      displayDirectory(modal, directory, threadTitle);
    } catch (error) {
      console.error("❌ 加载目录失败:", error);
      console.error("错误详情:", error.message);
      console.error("错误堆栈:", error.stack);
      showDirectoryEmpty(modal, "加载失败,请稍后重试");
    }
  }

  function displayDirectory(modal, directory, threadTitle = "") {
    // 显示目录
    const loading = modal.querySelector("#directory-loading");
    const list = modal.querySelector("#directory-list");
    const title = modal.querySelector("#directory-title");

    loading.style.display = "none";
    list.style.display = "block";

    // 获取页面标题(如果没有提供)
    if (!threadTitle) {
      if (savedThreadTitle) {
        threadTitle = savedThreadTitle;
      } else {
        threadTitle = document.title.split(" - ")[0] || "漫画目录";
      }
    }

    // 简化标题,只显示"目录"
    title.textContent = "目录";

    console.log("📝 目录标题: 目录");

    list.innerHTML = "";

    const currentThreadId = getCurrentThreadId();

    directory.forEach((item, index) => {
      const li = document.createElement("li");
      const link = document.createElement("a");
      link.className = "directory-item";
      if (item.source === "mainpost") {
        link.classList.add("mainpost");
      }

      link.href = item.url;

      // 为主楼提取的目录优化显示格式
      if (item.source === "mainpost") {
        let displayText = item.title;

        // 识别不同的章节格式并优化显示
        if (item.title.match(/^\d{1,2}$/)) {
          displayText = `第${String(item.title).padStart(2, "0")}话`;
        } else if (item.title.match(/^\d+\.\d+$/)) {
          displayText = `第${item.title}话`;
        } else if (item.title.match(/^\d+[话話章节節回卷篇]/i)) {
          displayText = item.title;
        } else if (item.title.match(/卷|番外|彩页|特典/i)) {
          displayText = item.title;
        } else if (item.title.match(/^0\d+$/)) {
          displayText = `第${item.title}话`;
        }

        link.textContent = displayText;
      } else {
        link.textContent = item.title;
      }

      // 添加点击事件处理,无缝加载新章节内容
      link.addEventListener("click", async (e) => {
        e.preventDefault();
        console.log("🔄 正在加载章节:", item.title);

        // 关闭侧边栏
        const overlay = document.getElementById("directory-overlay");
        const sidebar = document.getElementById("directory-sidebar");
        if (overlay && sidebar) {
          overlay.classList.remove("show");
          sidebar.classList.remove("open");
          setTimeout(() => {
            overlay.remove();
            sidebar.remove();
          }, 300);
        }

        // 无缝加载新章节内容
        await loadNewChapter(item.url, item.title);
      });

      // 高亮当前章节
      if (item.threadId === currentThreadId) {
        link.classList.add("current");
        console.log(`⭐ 当前章节: ${item.title}`);
      }

      li.appendChild(link);
      list.appendChild(li);

      console.log(`📄 章节 ${index + 1}: ${item.title}`);
    });

    console.log("✅ 目录界面显示完成");
  }

  function showDirectoryEmpty(modal, message = "未找到相关章节") {
    const loading = modal.querySelector("#directory-loading");
    const empty = modal.querySelector("#directory-empty");

    loading.style.display = "none";
    empty.style.display = "block";
    empty.textContent = message;
  }

  function getCurrentThreadId() {
    const href = window.location.href;
    let match = href.match(/thread-(\d+)-/);
    if (match) {
      return match[1];
    }

    try {
      const url = new URL(href);
      const tidParam = url.searchParams.get("tid");
      if (tidParam) {
        return tidParam;
      }
    } catch (e) {
      // ignore
    }

    match = href.match(/[?&]tid=(\d+)/);
    return match ? match[1] : null;
  }

  // 清理所有缓存,并清空内存缓存
  function clearAllCache() {
    console.log("🧹 清理所有目录缓存 (session + GM) ...");
    let cleanedCount = 0;

    // 1) 清理 sessionStorage 前缀条目
    try {
      const sKeys = Object.keys(sessionStorage).filter((k) =>
        k.startsWith(CACHE_PREFIX)
      );
      sKeys.forEach((k) => {
        try {
          sessionStorage.removeItem(k);
          cleanedCount++;
        } catch (e) {}
      });
    } catch (e) {}

    // 2) 清理 GM_* 条目(通过索引枚举)
    try {
      if (
        typeof GM_getValue === "function" &&
        typeof GM_setValue === "function"
      ) {
        let idx = GM_getValue(GM_INDEX_KEY, undefined);
        if (typeof idx === "string") {
          try {
            idx = JSON.parse(idx);
          } catch (e) {
            idx = [];
          }
        }
        if (!Array.isArray(idx)) idx = [];

        for (const key of idx) {
          try {
            storageRemove(key);
            cleanedCount++;
          } catch (e) {}
        }

        try {
          GM_setValue(GM_INDEX_KEY, []);
        } catch (e) {}
      }
    } catch (e) {}

    // 3) 清理内存缓存
    try {
      if (searchCache && searchCache.clear) searchCache.clear();
    } catch (e) {}
    try {
      if (directoryMemoryCache && directoryMemoryCache.clear)
        directoryMemoryCache.clear();
    } catch (e) {}

    console.log(`✅ 已清理 ${cleanedCount} 个持久化缓存条目,且已清空内存缓存`);
    return cleanedCount;
  }

  // 查看缓存状态(session + GM 索引 + 内存统计)
  function viewCacheStatus() {
    console.log("📊 缓存状态统计:");

    // sessionStorage 条目
    let sessionList = [];
    try {
      sessionList = Object.keys(sessionStorage).filter((k) =>
        k.startsWith(CACHE_PREFIX)
      );
    } catch (e) {
      sessionList = [];
    }

    // GM 索引条目
    let gmList = [];
    try {
      if (typeof GM_getValue === "function") {
        let idx = GM_getValue(GM_INDEX_KEY, undefined);
        if (typeof idx === "string") {
          try {
            idx = JSON.parse(idx);
          } catch (e) {
            idx = [];
          }
        }
        if (Array.isArray(idx)) gmList = idx.slice();
      }
    } catch (e) {
      gmList = [];
    }

    console.log(`📦 sessionStorage 缓存数量: ${sessionList.length}`);
    console.log(`📦 GM_* 缓存数量 (index): ${gmList.length}`);
    console.log(
      `🧠 内存缓存 (searchCache): ${
        typeof searchCache !== "undefined" && searchCache.size !== undefined
          ? searchCache.size
          : "未知"
      }`
    );
    console.log(
      `🧠 内存目录缓存 (directoryMemoryCache): ${
        typeof directoryMemoryCache !== "undefined" &&
        directoryMemoryCache.size !== undefined
          ? directoryMemoryCache.size
          : "未知"
      }`
    );

    if (sessionList.length > 0) {
      console.log("\n📋 sessionStorage 详情:");
      sessionList.forEach((k, i) => {
        try {
          const obj = JSON.parse(sessionStorage.getItem(k));
          const age = Math.round(
            (Date.now() - (obj.ts || obj.timestamp || 0)) / (1000 * 60 * 60)
          );
          const count = obj.d || obj.data ? (obj.d || obj.data).length : "未知";
          console.log(
            `   ${i + 1}. ${k.replace(
              CACHE_PREFIX + "session-",
              ""
            )}: ${count}章, ${age}小时前`
          );
        } catch (e) {
          console.log(`   ${i + 1}. ${k} (无法解析)`);
        }
      });
    }

    if (gmList.length > 0) {
      console.log("\n📋 GM_* 详情 (由索引列出):");
      gmList.forEach((k, i) => {
        try {
          const obj = storageGet(k, null);
          const age = obj
            ? Math.round(
                (Date.now() - (obj.ts || obj.timestamp || 0)) / (1000 * 60 * 60)
              )
            : "未知";
          const count = obj
            ? obj.d || obj.data
              ? (obj.d || obj.data).length
              : "未知"
            : "未知";
          console.log(
            `   ${i + 1}. ${k.replace(
              CACHE_PREFIX,
              ""
            )}: ${count}章, ${age}小时前`
          );
        } catch (e) {
          console.log(`   ${i + 1}. ${k} (无法读取)`);
        }
      });
    }

    return {
      sessionCount: sessionList.length,
      gmCount: gmList.length,
      memorySearchCount:
        typeof searchCache !== "undefined" && searchCache.size !== undefined
          ? searchCache.size
          : null,
      memoryDirCount:
        typeof directoryMemoryCache !== "undefined" &&
        directoryMemoryCache.size !== undefined
          ? directoryMemoryCache.size
          : null,
    };
  }

  // 智能图片收集函数:强力过滤 + 智能兜底
  function collectImagesFromDocument(doc = document) {
    try {
      // URL 标准化
      const normalizeUrl = (u) => {
        if (!u) return null;
        u = String(u).trim();
        if (!u) return null;
        if (u.startsWith("//")) u = (location.protocol || "https:") + u;
        if (!/^https?:\/\//i.test(u))
          u = "https://bbs.yamibo.com/" + u.replace(/^\/+/, "");
        u = u.replace(/^https?:\/\/https?:\/\//, "https://");
        return u;
      };

      // 强力噪音判定
      const isIgnoredImage = (img, src) => {
        try {
          if (!src && !img) return true;
          if (BLOCK_IMG_REGEX.test(src)) return true;

          // === 1. 文件名与路径黑名单 (精准打击) ===
          const blockKeywords = [
            // 基础UI
            "/uc_server/data/avatar/",
            "avatar",
            "user_avatar", // 头像
            "static/image/common/",
            "static/image/smiley/", // 系统图标/表情
            "template/", // 模板图片 (如 userinfo.gif, forumlink.gif)

            // 具体文件名特征
            "none.gif",
            "loading.gif",
            "logo.png",
            "logo.gif",
            "qq.gif",
            "qq_big.gif",
            "qq_group", // QQ相关
            "userinfo.gif",
            "forumlink.gif", // 用户资料/网站链接
            "online_admin",
            "online_member",
            "online_team", // 在线状态
            "icon_quote",
            "collapse",
            "expand", // 引用/折叠图标

            // 评分与功能
            "rating",
            "score",
            "grade",
            "star",
            "magic",
          ];

          if (blockKeywords.some((kw) => s.includes(kw))) return true;

          // === 2. CSS 类名/ID 过滤 ===
          const cls = img && img.className ? img.className : "";
          if (/avatar|logo|vm|authicn/i.test(cls)) return true;
          const id = img && img.id ? img.id : "";
          if (/authicon|logo/i.test(id)) return true;

          // === 3. 容器过滤 (防止误杀正文,但要排除侧边栏等) ===
          if (
            img &&
            (img.closest(".postrate") ||
              img.closest(".post-ratings") ||
              img.closest(".postratedby") ||
              img.closest(".poster") ||
              img.closest(".author") ||
              img.closest(".avatar") ||
              img.closest(".user") ||
              img.closest(".pls") || // .pls 是用户信息侧边栏
              img.closest(".p_pop") || // 弹出菜单
              img.closest("#ft") || // 页脚
              img.closest(".po") || // 帖子底部操作栏
              img.closest(".a_pr")) // 广告位
          ) {
            return true;
          }

          // === 4. 尺寸过滤 (针对未被上述规则命中的漏网之鱼) ===
          const wAttr = img && (img.getAttribute("width") || img.width);
          const hAttr = img && (img.getAttribute("height") || img.height);
          const w = wAttr ? parseInt(wAttr, 10) : 0;
          const h = hAttr ? parseInt(hAttr, 10) : 0;

          // 只有当宽高都非常明确且很小时才过滤 (小于 100px 通常是图标)
          // 漫画图片通常宽度远大于 100
          if (w > 0 && h > 0 && (w < 100 || h < 100)) return true;

          // 还可以检测 style 属性中的宽高
          if (img && img.style) {
            const styleW = parseInt(img.style.width || "0");
            const styleH = parseInt(img.style.height || "0");
            if (styleW > 0 && styleH > 0 && (styleW < 100 || styleH < 100))
              return true;
          }

          return false;
        } catch (e) {
          return false;
        }
      };

      // 优先选择器 (大图通常有的属性)
      const preferredSelectors = [
        "ignore_js_op img",
        ".savephotop img",
        "div.t_f img",
        "td.t_f img",
        ".pcb img",
        "img[zoomfile]",
        "img[file]",
        "img[aid]",
        "img.zoom",
      ];

      // 辅助:执行收集
      const doCollect = (context, selectors) => {
        const list = [];
        const visited = new Set();

        // 尝试优先选择器
        for (const sel of selectors) {
          const imgs = context.querySelectorAll(sel);
          imgs.forEach((img) => {
            const u = normalizeUrl(
              img.getAttribute("zoomfile") ||
                img.getAttribute("file") ||
                img.getAttribute("src")
            );
            if (u && !visited.has(u) && !isIgnoredImage(img, u)) {
              visited.add(u);
              list.push(u);
            }
          });
        }

        // 如果优先选择器没找到,尝试所有图片但严格过滤
        if (list.length === 0) {
          const allImgs = context.querySelectorAll("img");
          allImgs.forEach((img) => {
            const u = normalizeUrl(img.getAttribute("src"));
            // 这里只收录有 zoomfile 或看起来很大的图
            const hasZoom =
              img.getAttribute("zoomfile") || img.getAttribute("file");
            if (u && !visited.has(u) && !isIgnoredImage(img, u)) {
              if (hasZoom) {
                visited.add(u);
                list.push(u);
              }
            }
          });
        }
        return list;
      };

      let resultsA = [];
      let resultsB = [];

      // === 策略 A: 按楼主 UID 过滤 (增强版) ===
      let authorUid = null;

      // 1. 查找所有帖子楼层
      const allPostDivs = Array.from(doc.querySelectorAll('div[id^="post_"]'));
      // 过滤干扰项,只留真正楼层
      const realPosts = allPostDivs.filter(
        (p) => p.id && /^post_\d+$/.test(p.id)
      );

      // 2. 尝试从主楼 (一楼) 提取 UID
      if (realPosts.length > 0) {
        const firstPost = realPosts[0];
        const links = firstPost.querySelectorAll(
          'a[href*="uid"], a[href*="space"]'
        );
        for (const a of links) {
          const href = a.getAttribute("href");
          const m = href.match(/(?:uid[=-]|space-uid-)(\d+)/);
          if (m) {
            authorUid = m[1];
            break;
          }
        }
      }

      // 3. 备用: 全局查找 "楼主" 标识
      if (!authorUid) {
        const louzhu = doc.querySelector('.authicn[title="楼主"], .louzhu');
        if (louzhu) {
          const parent = louzhu.closest(".pi") || louzhu.closest(".authi");
          if (parent) {
            const a = parent.querySelector('a[href*="uid"]');
            if (a) {
              const m = a.href.match(/(?:uid[=-]|space-uid-)(\d+)/);
              if (m) authorUid = m[1];
            }
          }
        }
      }

      // 4. 如果找到了 UID,收集所有该 UID 的楼层
      if (authorUid) {
        const authorPosts = realPosts.filter((p) => {
          // 检查楼层内的用户信息链接是否包含该 UID
          // 这种检查方式比 innerHTML includes 更快且更准
          const userLink = p.querySelector(
            `a[href*="uid=${authorUid}"], a[href*="uid-${authorUid}"]`
          );
          return !!userLink;
        });

        authorPosts.forEach((p) => {
          const imgs = doCollect(p, preferredSelectors);
          resultsA.push(...imgs);
        });
      }

      // === 策略 B: 兜底模式 (扫描所有内容区域 .t_f) ===
      // Discuz 的帖子内容通常都在 .t_f 或 .pcb 中
      const contentAreas = doc.querySelectorAll(".t_f, .pcb");
      contentAreas.forEach((area) => {
        const imgs = doCollect(area, preferredSelectors);
        resultsB.push(...imgs);
      });

      // === 决策 ===

      // 去重合并
      const uniqueA = [...new Set(resultsA)];
      const uniqueB = [...new Set(resultsB)];

      console.log(
        `📊 图片提取: 楼主模式=${uniqueA.length}, 兜底模式=${uniqueB.length}`
      );

      if (uniqueA.length > 0) {
        // 如果楼主模式找到了图,通常比较准
        return uniqueA;
      } else {
        // 否则使用兜底模式
        if (uniqueB.length > 0) {
          console.log("⚠️ 楼主模式未找到图片,使用兜底结果");
        }
        return uniqueB;
      }
    } catch (e) {
      console.warn("collectImagesFromDocument error", e);
      return [];
    }
  }
  // ====== localForage 图片缓存工具函数 ======
  async function getCachedImageUrl(imgUrl) {
    const cached = await localforage.getItem(imgUrl);
    if (cached) {
      return URL.createObjectURL(cached);
    }
    const resp = await fetch(imgUrl);
    const blob = await resp.blob();
    await localforage.setItem(imgUrl, blob);
    return URL.createObjectURL(blob);
  }
  // 初始化时检查全局阅读器状态
  function initAutoReader() {
    autoReaderEnabled = checkAutoReaderStatus();
    console.log("🚀 初始化全局阅读器状态:", autoReaderEnabled);

    // 清理过期缓存
    cleanExpiredCache();

    // 检查是否有漫画内容,优先检测主楼
    const comicImages = collectImagesFromDocument(document);
    if (comicImages && comicImages.length > 0) {
      console.log("🎯 检测到漫画内容,立即开始预取目录...");

      // 立即开始预取目录信息,不管是否进入阅读模式
      preloadDirectoryInfo();

      // 如果全局阅读器开启,自动进入阅读模式
      if (autoReaderEnabled && !isReading) {
        console.log("🎯 自动进入阅读模式");
        setTimeout(() => {
          if (button && button.parentNode) {
            button.remove();
          }
          enterReader();
        }, 1000);
      }
    } else {
      console.log("🔍 尝试预取目录信息...");
      preloadDirectoryInfo();
    }
  }
  // 预加载目录信息
  async function preloadDirectoryInfo() {
    if (seriesDirectory.length > 0) {
      console.log("� 目录已预加载,跳过重复加载");
      return;
    }

    try {
      console.log("�📚 开始预加载目录信息...");

      // 获取当前页面标题
      let threadTitle = "";
      const h1Element = document.querySelector("h1.ts");
      if (h1Element) {
        const subjectSpan = h1Element.querySelector(
          '#thread_subject, span[id^="thread_subject"]'
        );
        if (subjectSpan) {
          threadTitle = subjectSpan.textContent.trim();
        } else {
          threadTitle = h1Element.textContent.trim();
        }
      } else {
        const titleElement = document.querySelector(
          '#thread_subject, span[id^="thread_subject"]'
        );
        if (titleElement) {
          threadTitle = titleElement.textContent.trim();
        } else {
          const pageTitle = document.title;
          threadTitle = pageTitle.includes(" - ")
            ? pageTitle.split(" - ")[0].trim()
            : pageTitle.replace(/\s*-\s*百合会.*$/, "").trim();
        }
      }

      if (threadTitle) {
        // 保存标题信息
        savedThreadTitle = threadTitle;
        originalSeriesTitle = threadTitle;

        console.log("📖 开始预取目录,使用标题:", threadTitle);

        // 先检查持久化缓存
        const seriesKey = generateSeriesKey(threadTitle);
        const cachedDirectory = getCachedDirectory(seriesKey);
        if (cachedDirectory && cachedDirectory.length > 0) {
          console.log(
            "✅ 从持久化缓存预加载完成:",
            cachedDirectory.length,
            "个章节"
          );
          seriesDirectory = cachedDirectory;
          return;
        }

        // 如果没有缓存,立即开始异步搜索目录
        searchSeriesDirectory(threadTitle)
          .then((directory) => {
            if (directory && directory.length > 0) {
              seriesDirectory = directory;
              console.log("✅ 预加载完成,获得", directory.length, "个章节");
            } else {
              console.log("⚠️ 预加载未找到目录");
            }
          })
          .catch((error) => {
            console.warn("⚠️ 预加载目录失败:", error.message);
          });
        console.log("🚀 预加载任务已启动,后台进行中...");
      } else {
        console.log("⚠️ 无法获取页面标题,跳过预加载");
      }
    } catch (error) {
      console.warn("⚠️ 预加载目录失败:", error.message);
    }
  }

  // 页面加载完成后初始化
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", () => {
      setTimeout(initAutoReader, 1000); // 确保页面元素完全加载
    });
  } else {
    setTimeout(initAutoReader, 1000); // 给页面充足时间渲染
  }

  /*** 进入阅读模式 ***/
  function enterReader() {
    console.log("[enterReader] called");
    isReading = true;

    // 保存原始页面HTML和标题
    readerStartUrl = window.location.href;
    originalPageHTML = document.body.innerHTML;
    originalPageTitle = document.title;
    console.log("💾 已保存原始页面内容");

    button.remove();

    // 修复 Discuz 原生 JS (showWindow) 依赖的容器
    if (!document.getElementById("append_parent")) {
      const ap = document.createElement("div");
      ap.id = "append_parent";
      document.body.appendChild(ap);
    }
    if (!document.getElementById("ajaxwaitid")) {
      const aw = document.createElement("div");
      aw.id = "ajaxwaitid";
      aw.style.display = "none";
      document.body.appendChild(aw);
    }

    // 在替换页面内容之前,先提取并保存标题
    console.log("🔍 进入阅读模式,保存页面标题...");

    // 简化标题提取逻辑
    const h1Element = document.querySelector("h1.ts");
    if (h1Element) {
      const subjectSpan = h1Element.querySelector(
        '#thread_subject, span[id^="thread_subject"]'
      );
      if (subjectSpan) {
        savedThreadTitle = subjectSpan.textContent.trim();
        console.log("💾 保存标题:", savedThreadTitle);
      } else {
        savedThreadTitle = h1Element.textContent.trim();
        console.log("💾 保存h1文本:", savedThreadTitle);
      }
    } else {
      // 兜底:尝试其他方式
      const threadTitleElement = document.querySelector(
        '#thread_subject, span[id^="thread_subject"]'
      );
      if (threadTitleElement) {
        savedThreadTitle = threadTitleElement.textContent.trim();
        console.log("💾 保存标题元素:", savedThreadTitle);
      } else {
        // 最后兜底用<title>
        const pageTitle = document.title;
        savedThreadTitle = pageTitle.includes(" - ")
          ? pageTitle.split(" - ")[0].trim()
          : pageTitle.replace(/\s*-\s*百合会.*$/, "").trim();
        console.log("💾 从页面title保存:", savedThreadTitle);
      }
    }

    // 保存原始系列标题,用于后续目录搜索
    originalSeriesTitle = savedThreadTitle;
    console.log("🎯 保存原始系列标题:", originalSeriesTitle);

    // === 图片提取逻辑 ===
    // 只用 collectImagesFromDocument,避免复用噪音图片
    images = collectImagesFromDocument(document)
      .map((u) => {
        let url = String(u);
        if (url.startsWith("//")) url = "https:" + url;
        if (!/^https?:\/\//i.test(url))
          url = "https://bbs.yamibo.com/" + url.replace(/^\/+/, "");
        if (url.startsWith("http://"))
          url = url.replace(/^http:\/\//, "https://");
        url = url.replace(/^https?:\/\/https?:\/\//, "https://");
        return url;
      })
      .filter(Boolean);
    console.log("📸 提取到", images.length, "张图片,URL已处理为HTTPS");
    // 新增:预加载首图,加速 LCP
    if (images.length > 0) {
      const preload = document.createElement("link");
      preload.rel = "preload";
      preload.as = "image";
      preload.href = images[0];
      document.head.appendChild(preload);
    }
    // 检查是否已有预加载的目录数据
    if (seriesDirectory.length > 0) {
      console.log("✅ 使用预加载的目录数据:", seriesDirectory.length, "个章节");
      // 新增:如果在阅读模式,立即启用按钮
      if (isReading) {
        const prevBtn = document.getElementById("cw-prev");
        const nextBtn = document.getElementById("cw-next");
        if (prevBtn) prevBtn.disabled = !seriesDirectory.length;
        if (nextBtn) nextBtn.disabled = !seriesDirectory.length;
      }
    } else {
      // 检查持久化缓存
      const seriesKey = generateSeriesKey(savedThreadTitle);
      const cachedDirectory = getCachedDirectory(seriesKey);
      if (cachedDirectory && cachedDirectory.length > 0) {
        console.log(
          "📦 从持久化缓存加载目录:",
          cachedDirectory.length,
          "个章节"
        );
        seriesDirectory = cachedDirectory;
        // 新增:如果在阅读模式,立即启用按钮
        if (isReading) {
          const prevBtn = document.getElementById("cw-prev");
          const nextBtn = document.getElementById("cw-next");
          if (prevBtn) prevBtn.disabled = !seriesDirectory.length;
          if (nextBtn) nextBtn.disabled = !seriesDirectory.length;
        }
      } else {
        // 缓存也没有,异步搜索
        console.log("🔍 开始异步搜索目录...");
        searchSeriesDirectory(savedThreadTitle)
          .then((directory) => {
            seriesDirectory = directory;
            console.log("📋 异步获取到目录:", seriesDirectory.length, "个章节");
            // 新增:如果在阅读模式,提示用户按钮已可用
            if (isReading) {
              showErrorMessage("目录已加载,上一话/下一话可用");
              const prevBtn = document.getElementById("cw-prev");
              const nextBtn = document.getElementById("cw-next");
              if (prevBtn) prevBtn.disabled = !seriesDirectory.length;
              if (nextBtn) nextBtn.disabled = !seriesDirectory.length;
            }
          })
          .catch((error) => {
            console.error("❌ 异步获取目录失败:", error);
          });
      }
    }

    // 渲染阅读器UI
    document.body.innerHTML = `
      <button id="cw-exit" data-tooltip="退出阅读模式">⏻</button>
      <div id="cw-toolbar">
        <button id="cw-full" data-tooltip="全屏模式 (F)">⛶</button>
        <button id="cw-bg" data-tooltip="切换背景色 (B)">◐</button>
        <div id="cw-zoom">
          <button id="cw-zoom-in" data-tooltip="放大图片 (+)">﹢</button>
          <button id="cw-zoom-out" data-tooltip="缩小图片 (-)">﹣</button>
        </div>
        <button id="cw-directory" data-tooltip="查看目录 (M)">☰</button>
        <button id="cw-prev" data-tooltip="上一话">≪</button>
        <button id="cw-next" data-tooltip="下一话">≫</button>
        <button id="cw-favorite" data-tooltip="收藏">☆</button>
        <button id="cw-comment" data-tooltip="去评论">❞</button>
      </div>
      <div id="cw-container">
        <div id="cw-title-bar" class="cw-title-bar">${savedThreadTitle}</div>
        <div id="cw-image-list"></div>
      </div>
      <div id="cw-bottom-bar">
        <span id="cw-page-info">1/${images.length}</span>
        <button id="cw-to-top" data-tooltip="返回顶部 (T)">⬆</button>
      </div>
    `;
    // 重新插入阅读器样式和依赖脚本
    if (!document.getElementById("yamibo-reader-style")) {
      const styleElement = GM_addStyle(READER_CSS);
      if (styleElement) styleElement.id = "yamibo-reader-style";
    }
    if (!document.getElementById("yamibo-ionicons-module")) {
      const ioniconsModule = document.createElement("script");
      ioniconsModule.type = "module";
      ioniconsModule.src =
        "https://unpkg.com/[email protected]/dist/ionicons/ionicons.esm.js";
      ioniconsModule.id = "yamibo-ionicons-module";
      document.head.appendChild(ioniconsModule);
    }
    if (!document.getElementById("yamibo-ionicons-nomodule")) {
      const ioniconsNomodule = document.createElement("script");
      ioniconsNomodule.noModule = true;
      ioniconsNomodule.src =
        "https://unpkg.com/[email protected]/dist/ionicons/ionicons.js";
      ioniconsNomodule.id = "yamibo-ionicons-nomodule";
      document.head.appendChild(ioniconsNomodule);
    }
    if (!document.getElementById("yamibo-font-link")) {
      const fontLink = document.createElement("link");
      fontLink.href =
        "https://fonts.googleapis.com/css2?family=Poppins&display=swap";
      fontLink.rel = "stylesheet";
      fontLink.id = "yamibo-font-link";
      document.head.appendChild(fontLink);
    }
    if (!document.getElementById("yamibo-plex-sc-font-link")) {
      const ibmFontLink = document.createElement("link");
      ibmFontLink.rel = "stylesheet";
      ibmFontLink.href =
        "https://cdn.jsdelivr.net/npm/@ibm/plex-sans-sc/css/ibm-plex-sans-sc.min.css";
      ibmFontLink.id = "yamibo-plex-sc-font-link";
      document.head.appendChild(ibmFontLink);
    }
    setTimeout(() => {
      try {
        initReaderUI(); // 渲染图片
        console.log("✅ 阅读器 UI 初始化完成");
      } catch (err) {
        console.error("❌ 初始化阅读器UI失败:", err);
      }
    }, 0);
  }

 // ====== 渲染图片统一函数 ======
  function renderImages(imgList, images) {
    if (!imgList) return;
    imgList.innerHTML = "";
    images.forEach((url, idx) => {
      const img = document.createElement("img");
      img.alt = ""; 
      img.dataset.index = String(idx + 1);
      
      img.onload = function () {
        img.classList.add("loaded"); // ✅ 加载成功后,添加 loaded 类
      };
      
      img.onerror = function () {
        img.style.backgroundImage =
          "url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25'%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' font-family='sans-serif' font-size='16' fill='%23ff6b6b'%3E图片加载失败%3C/text%3E%3C/svg%3E\")";
        img.style.backgroundColor = "#f8d7da";
        img.removeAttribute("src");
        img.dataset.error = "1";
      };
      
      imgList.appendChild(img); // 图片添加到 DOM

      getCachedImageUrl(url)
        .then((blobUrl) => {
          img.src = blobUrl;
        })
        .catch(() => {
          img.src = url;
        });
    });
  }

  // ====== 初始化阅读器UI(渲染图片并绑定按钮) ======
  function initReaderUI() {
    console.log("[initReaderUI] start");
    const container = document.getElementById("cw-container");
    if (!container) {
      console.warn("⚠️ cw-container 未找到,无法渲染图片");
      return;
    }
    // 保证有图片列表容器
    let imgList = document.getElementById("cw-image-list");
    if (!imgList) {
      imgList = document.createElement("div");
      imgList.id = "cw-image-list";
      container.appendChild(imgList);
    }
    // 设置初始缩放(使用 CSS 变量控制宽度)
    const applyZoom = () => {
      const vw = Math.max(10, Math.min(90, zoomLevel));
      document.body.style.setProperty("--img-width", `${vw}vw`);
    };
    applyZoom();
    // ===== 新增:插入标题区 =====
    let titleDiv = document.getElementById("cw-title-bar");
    if (!titleDiv) {
      titleDiv = document.createElement("div");
      titleDiv.id = "cw-title-bar";
      titleDiv.className = "cw-title-bar";
      const titleText =
        savedThreadTitle ||
        originalSeriesTitle ||
        document.title.split(" - ")[0] ||
        "漫画标题";
      titleDiv.textContent = titleText;
      container.insertBefore(titleDiv, container.firstChild);
    } else {
      const titleText =
        savedThreadTitle ||
        originalSeriesTitle ||
        document.title.split(" - ")[0] ||
        "漫画标题";
      titleDiv.textContent = titleText;
    }
    // 渲染图片列表
    renderImages(imgList, images);

    // 更新页码显示(靠近视口中心的图片)
    const pageInfo = document.getElementById("cw-page-info");
    function updateCurrentPageInfo() {
      const imgs = container.querySelectorAll("img");
      if (!imgs.length) return;
      if (window.scrollY < 50) {
        if (pageInfo) pageInfo.textContent = `1/${imgs.length}`;
        return;
      }

      const viewportMid = window.innerHeight / 2;
      let current = 1;
      let minDist = Infinity;

      imgs.forEach((img, i) => {
        const rect = img.getBoundingClientRect();
        const imgMidRel = rect.top + rect.height / 2;
        const dist = Math.abs(imgMidRel - viewportMid);

        if (dist < minDist) {
          minDist = dist;
          current = i + 1;
        }
      });

      if (pageInfo) pageInfo.textContent = `${current}/${imgs.length}`;
    }
    // 退出、目录、全屏、背景、缩放、回顶部
    readerEventHandlers.onExitClick = () => exitReader();
    readerEventHandlers.onDirectoryClick = () => showDirectoryModal();
    readerEventHandlers.onFullClick = async () => {
      try {
        if (!document.fullscreenElement)
          await document.documentElement.requestFullscreen();
        else await document.exitFullscreen();
      } catch (e) {
        console.warn("全屏切换失败:", e);
      }
    };
    readerEventHandlers.onBgClick = () => {
      bgIsBlack = !bgIsBlack;
      if (bgIsBlack) {
        document.body.classList.remove("light-bg");
      } else {
        document.body.classList.add("light-bg");
      }
    };
    readerEventHandlers.onZoomIn = () => {
      zoomLevel = Math.min(90, zoomLevel + 5);
      applyZoom();
    };
    readerEventHandlers.onZoomOut = () => {
      zoomLevel = Math.max(10, zoomLevel - 5);
      applyZoom();
    };
    readerEventHandlers.onToTop = () =>
      window.scrollTo({ top: 0, behavior: "smooth" });

    const throttle = (fn, wait = 100) => {
      let t = null;
      return (...args) => {
        if (t) return;
        t = setTimeout(() => {
          fn(...args);
          t = null;
        }, wait);
      };
    };

    readerEventHandlers.updateCurrentPageInfo = throttle(
      updateCurrentPageInfo,
      120
    );

    // 工具栏显示控制
    readerEventHandlers.showTools = function () {
      try {
        document.documentElement.classList.add("tools-visible");
        if (readerToolsTimer) clearTimeout(readerToolsTimer);
        readerToolsTimer = setTimeout(() => {
          document.documentElement.classList.remove("tools-visible");
          readerToolsTimer = null;
        }, 3000);
      } catch (e) {
        // ignore
      }
    };

    readerEventHandlers.onMousemove = readerEventHandlers.showTools;
    readerEventHandlers.onKeydown = readerEventHandlers.showTools;

    readerEventHandlers.onFullscreen = function () {
      if (document.fullscreenElement) {
        readerEventHandlers.showTools();
      } else {
        document.documentElement.classList.remove("tools-visible");
        if (readerToolsTimer) {
          clearTimeout(readerToolsTimer);
          readerToolsTimer = null;
        }
      }
    };

    // ===== 工具栏按钮事件统一绑定 =====
    function initToolbarEvents() {
      const exitBtn = document.getElementById("cw-exit");
      if (exitBtn)
        exitBtn.addEventListener("click", readerEventHandlers.onExitClick);

      const dirBtn = document.getElementById("cw-directory");
      if (dirBtn)
        dirBtn.addEventListener("click", readerEventHandlers.onDirectoryClick);

      const fullBtn = document.getElementById("cw-full");
      if (fullBtn)
        fullBtn.addEventListener("click", readerEventHandlers.onFullClick);

      const bgBtn = document.getElementById("cw-bg");
      if (bgBtn) bgBtn.addEventListener("click", readerEventHandlers.onBgClick);

      const zoomIn = document.getElementById("cw-zoom-in");
      const zoomOut = document.getElementById("cw-zoom-out");
      if (zoomIn)
        zoomIn.addEventListener("click", readerEventHandlers.onZoomIn);
      if (zoomOut)
        zoomOut.addEventListener("click", readerEventHandlers.onZoomOut);

      const toTop = document.getElementById("cw-to-top");
      if (toTop) toTop.addEventListener("click", readerEventHandlers.onToTop);

      // 上一话、下一话、收藏、去评论
      const prevBtn = document.getElementById("cw-prev");
      const nextBtn = document.getElementById("cw-next");
      // 初始时根据目录数据禁用/启用
      if (prevBtn) prevBtn.disabled = !seriesDirectory.length;
      if (nextBtn) nextBtn.disabled = !seriesDirectory.length;
      const favBtn = document.getElementById("cw-favorite");
      const commentBtn = document.getElementById("cw-comment");

      // 上一话
      if (prevBtn)
        prevBtn.onclick = async () => {
          const currentTid = String(getCurrentThreadId());
          const idx = seriesDirectory.findIndex(
            (item) => String(item.threadId) === currentTid
          );
          if (idx > 0) {
            const prevItem = seriesDirectory[idx - 1];
            await loadNewChapter(prevItem.url, prevItem.title);
          } else {
            showErrorMessage("已经是第一话了");
          }
        };

      // 下一话
      if (nextBtn)
        nextBtn.onclick = async () => {
          const currentTid = String(getCurrentThreadId());
          const idx = seriesDirectory.findIndex(
            (item) => String(item.threadId) === currentTid
          );
          if (idx !== -1 && idx < seriesDirectory.length - 1) {
            const nextItem = seriesDirectory[idx + 1];
            await loadNewChapter(nextItem.url, nextItem.title);
          } else {
            showErrorMessage("已经是最后一话了");
          }
        };

      // 收藏
      if (favBtn)
        favBtn.onclick = async () => {
          if (favBtn.dataset.loading) return;
          favBtn.dataset.loading = true;
          favBtn.style.opacity = "0.7";
          // 兼容<i>或纯文本
          const iTag = favBtn.querySelector("i");
          const originalText = iTag ? iTag.innerText : favBtn.innerText;
          if (iTag) iTag.innerText = "...";
          else favBtn.innerText = "...";
          const tid = getCurrentThreadId();
          const href = `/home.php?mod=spacecp&ac=favorite&type=thread&id=${tid}`;
          try {
            const res = await fetch(href + "&infloat=yes&handlekey=k_favorite");
            const text = await res.text();
            if (text.includes("成功") || text.includes("succeed")) {
              showErrorMessage("收藏成功");
              favBtn.classList.add("active");
              if (iTag) iTag.innerText = "★";
              else favBtn.innerText = "★";
            } else if (text.includes("重复") || text.includes("repeat")) {
              showErrorMessage("已收藏");
              if (iTag) iTag.innerText = "★";
              else favBtn.innerText = "★";
            } else {
              showErrorMessage("请求已发送");
              if (iTag) iTag.innerText = originalText;
              else favBtn.innerText = originalText;
            }
          } catch (err) {
            showErrorMessage("请求失败");
            if (iTag) iTag.innerText = originalText;
            else favBtn.innerText = originalText;
          } finally {
            favBtn.dataset.loading = false;
            favBtn.style.opacity = "1";
          }
        };

      // 去评论
      if (commentBtn)
        commentBtn.onclick = () => {
          exitReader();
          setTimeout(() => {
            const fastpost = document.getElementById("fastpostmessage");
            if (fastpost) {
              fastpost.scrollIntoView({ behavior: "smooth", block: "center" });
              fastpost.focus();
            }
          }, 500);
        };
    }

    // ====== 绑定所有工具栏事件 ======
    initToolbarEvents();
    setupRefreshButton();
    // 全局事件绑定
    if (!readerEventsBound) {
      window.addEventListener(
        "scroll",
        readerEventHandlers.updateCurrentPageInfo,
        { passive: true }
      );
      window.addEventListener("mousemove", readerEventHandlers.onMousemove, {
        passive: true,
      });
      window.addEventListener("keydown", readerEventHandlers.onKeydown, {
        passive: true,
      });
      document.addEventListener(
        "fullscreenchange",
        readerEventHandlers.onFullscreen
      );
      readerEventsBound = true;
    }

    // 立即更新一次页码并短暂显示工具栏
    setTimeout(updateCurrentPageInfo, 200);
    readerEventHandlers.showTools();

    // 提供解绑函数,供 exitReader 调用
    unbindReaderEvents = function () {
      try {
        if (!readerEventsBound) return;

        // 移除 DOM 绑定
        try {
          if (exitBtn)
            exitBtn.removeEventListener(
              "click",
              readerEventHandlers.onExitClick
            );
        } catch (e) {}
        try {
          if (dirBtn)
            dirBtn.removeEventListener(
              "click",
              readerEventHandlers.onDirectoryClick
            );
        } catch (e) {}
        try {
          if (fullBtn)
            fullBtn.removeEventListener(
              "click",
              readerEventHandlers.onFullClick
            );
        } catch (e) {}
        try {
          if (bgBtn)
            bgBtn.removeEventListener("click", readerEventHandlers.onBgClick);
        } catch (e) {}
        try {
          if (zoomIn)
            zoomIn.removeEventListener("click", readerEventHandlers.onZoomIn);
        } catch (e) {}
        try {
          if (zoomOut)
            zoomOut.removeEventListener("click", readerEventHandlers.onZoomOut);
        } catch (e) {}
        try {
          if (toTop)
            toTop.removeEventListener("click", readerEventHandlers.onToTop);
        } catch (e) {}

        // 移除全局事件
        try {
          window.removeEventListener(
            "scroll",
            readerEventHandlers.updateCurrentPageInfo,
            { passive: true }
          );
        } catch (e) {}
        try {
          window.removeEventListener(
            "mousemove",
            readerEventHandlers.onMousemove,
            { passive: true }
          );
        } catch (e) {}
        try {
          window.removeEventListener("keydown", readerEventHandlers.onKeydown, {
            passive: true,
          });
        } catch (e) {}
        try {
          document.removeEventListener(
            "fullscreenchange",
            readerEventHandlers.onFullscreen
          );
        } catch (e) {}
      } catch (e) {
        // ignore
      }
      // 清理定时器与状态
      if (readerToolsTimer) {
        clearTimeout(readerToolsTimer);
        readerToolsTimer = null;
      }
      readerEventHandlers = {};
      readerEventsBound = false;
      unbindReaderEvents = null;
    };

    // 立即初始化阅读器 UI
    //  initReaderUI();
  }

  /*** 退出阅读模式 ***/
  function exitReader() {
    isReading = false;
    setAutoReaderStatus(false); // 关闭全局阅读器模式

    // 解绑阅读器事件
    try {
      if (typeof unbindReaderEvents === "function") unbindReaderEvents();
    } catch (e) {}

    // 退出全屏
    if (document.fullscreenElement) {
      document.exitFullscreen().catch((e) => {});
    }
    // 如果当前 URL 和进入时的 URL 不一致,说明用户切换了章节
    if (readerStartUrl && window.location.href !== readerStartUrl) {
      window.location.reload();
      return;
    }

    // 如果 URL 没变,说明还是原来那个帖子,直接恢复 DOM 快照(秒开,无需刷新)
    if (originalPageHTML) {
      console.log("🔄 恢复原始页面内容...");

      // 移除阅读器样式
      const readerStyle = document.getElementById("yamibo-reader-style");
      if (readerStyle) {
        readerStyle.remove();
        console.log("🧹 已移除阅读器样式");
      }

      const loaderStyle = document.getElementById("chapter-loader-style");
      if (loaderStyle) loaderStyle.remove();

      document.body.innerHTML = originalPageHTML;
      document.title = originalPageTitle;

      // 重新绑定入口按钮事件
      const restoredButton = document.getElementById("reader-toggle");
      if (restoredButton) {
        restoredButton.addEventListener("click", () => {
          if (!isReading) {
            enterReader();
            setAutoReaderStatus(true);
          } else {
            exitReader();
          }
        });
      }
      console.log("✅ 页面内容已恢复");
    } else {
      location.reload();
    }
  }
  // 无缝章节加载:加载提示与错误显示
  function showLoadingIndicator(message = "正在加载章节...") {
    try {
      // 移除已存在的加载提示
      const existing = document.getElementById("chapter-loader");
      if (existing) existing.remove();

      const loader = document.createElement("div");
      loader.id = "chapter-loader";
      loader.innerHTML = `
        <div class="loader-content">
          <div class="loader-spinner"></div>
          <div class="loader-text">${message}</div>
        </div>
      `;

      const style = document.createElement("style");
      style.id = "chapter-loader-style";
      style.textContent = `
        #chapter-loader { position: fixed; top:0; left:0; width:100%; height:100%; background: rgba(0,0,0,0.6); display:flex; align-items:center; justify-content:center; z-index:2147483648; }
        #chapter-loader .loader-content { color:#fff; padding:18px 24px; border-radius:10px; background: rgba(0,0,0,0.45); text-align:center; }
        #chapter-loader .loader-spinner { width:30px; height:30px; border:3px solid rgba(255,255,255,0.25); border-top:3px solid #fff; border-radius:50%; animation: yamibo-spin 1s linear infinite; margin:0 auto 8px }
        @keyframes yamibo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
        #chapter-loader .loader-text { font-size:14px; }
      `;

      document.head.appendChild(style);
      document.body.appendChild(loader);
    } catch (e) {
      console.warn("showLoadingIndicator error", e);
    }
  }

  function hideLoadingIndicator() {
    try {
      const loader = document.getElementById("chapter-loader");
      if (loader) {
        loader.remove();
      }
      const style = document.getElementById("chapter-loader-style");
      if (style) style.remove();
    } catch (e) {
      // ignore
    }
  }
// ====== 刷新和工具函数 (新添加) ======

  function manualRefresh() {
    // 1. 获取图片容器(ID 在 initReaderUI 中定义)
    const cwContainer = document.getElementById('cw-container');
    
    // 2. 检查全局图片列表 'images' 和容器是否存在
    // 假设 'images' 是全局变量,存储了当前章节的图片 URL 数组
    if (!cwContainer || !Array.isArray(images) || images.length === 0) {
        console.error("❌ 无法重新渲染图片:图片容器或图片列表为空。");
        showErrorMessage("图片列表为空或容器未找到。");
        return;
    }

    // 3. 调用图片渲染函数
    console.log("手动刷新:重新渲染图片...");
    showLoadingIndicator(); 
    
    // 假设 renderImages(containerElement, imageListArray)
    // 这是您脚本中实际渲染图片的函数
    renderImages(cwContainer, images); 
    
    // 滚动到顶部 (可选)
    window.scrollTo({ top: 0, behavior: "smooth" });

    // 延迟隐藏加载指示器,给用户一个反馈
    setTimeout(hideLoadingIndicator, 300);
}

  function setupRefreshButton() {
    // 1. 创建新的刷新按钮元素
    const refreshBtn = document.createElement('button');
    refreshBtn.id = 'cw-refresh';
    refreshBtn.dataset.tooltip = '手动刷新 (R)';
    refreshBtn.innerHTML = '↻';

    // 2. 将新按钮添加到 body (CSS 会负责定位)
    document.body.appendChild(refreshBtn);

    // 3. 绑定点击事件
    refreshBtn.addEventListener('click', manualRefresh);

    // 4. 绑定键盘快捷键 R (避免在输入框中触发)
    document.addEventListener("keydown", (e) => {
        if (e.key === "r" || e.key === "R") {
            // 确保焦点不在输入框
            if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
                e.preventDefault(); 
                manualRefresh();
            }
        }
    });
 }

  function showErrorMessage(message) {
    try {
      const id = "chapter-error-msg";
      const existing = document.getElementById(id);
      if (existing) existing.remove();

      const el = document.createElement("div");
      el.id = id;
      el.style.cssText =
        "position:fixed;left:50%;top:40%;transform:translate(-50%,-50%);background:#ff6b6b;color:#fff;padding:12px  18px;border-radius:8px;z-index:2147483649;font-size:14px;box-shadow:0 8px 30px rgba(0,0,0,0.4)";
      el.textContent = message;
      document.body.appendChild(el);
      setTimeout(() => {
        try {
          el.style.opacity = "0";
          setTimeout(() => el.remove(), 250);
        } catch (e) {}
      }, 2500);
    } catch (e) {
      console.warn("showErrorMessage error", e);
    }
  }

  // 无缝章节切换
  async function loadNewChapter(chapterUrl, chapterTitle) {
    try {
      console.log("📖 开始无缝加载章节:", chapterTitle);
      showLoadingIndicator("正在加载章节...");

      const response = await fetch(chapterUrl, {
        method: "GET",
        credentials: "include",
        headers: {
          Accept:
            "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
          "Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
          Referer: window.location.href,
          "User-Agent": navigator.userAgent,
        },
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      const html = await response.text();
      const parser = new DOMParser();
      const doc = parser.parseFromString(html, "text/html");

      // 提取图片 URL
      const newImages = collectImagesFromDocument(doc)
        .map((u) => {
          if (!u) return null;
          let url = String(u);
          if (url.startsWith("//")) url = "https:" + url;
          if (!/^https?:\/\//i.test(url))
            url = "https://bbs.yamibo.com/" + url.replace(/^\/+/, "");
          if (url.startsWith("http://"))
            url = url.replace(/^http:\/\//, "https://");
          url = url.replace(/^https?:\/\/https?:\/\//, "https://");
          return url;
        })
        .filter(Boolean);

      if (!newImages || newImages.length === 0) {
        hideLoadingIndicator();
        showErrorMessage("未找到漫画图片,已跳转到原帖");
        location.href = chapterUrl;
        return;
      }

      // 更新全局图片数组
      images = newImages;
      // 更新标题栏
      const titleDiv = document.getElementById("cw-title-bar");
      if (titleDiv)
        titleDiv.textContent =
          savedThreadTitle ||
          originalSeriesTitle ||
          document.title.split(" - ")[0] ||
          "漫画标题";
      // 尝试提取并保存新章节标题
      const newH1 = doc.querySelector("h1.ts");
      if (newH1) {
        const subjectSpan = newH1.querySelector(
          '#thread_subject, span[id^="thread_subject"]'
        );
        savedThreadTitle = subjectSpan
          ? subjectSpan.textContent.trim()
          : newH1.textContent.trim();
      } else {
        const newTitleEl = doc.querySelector(
          '#thread_subject, span[id^="thread_subject"]'
        );
        if (newTitleEl) savedThreadTitle = newTitleEl.textContent.trim();
      }

      // 更新浏览器地址栏(不刷新页面)
      try {
        window.history.pushState({}, "", chapterUrl);
      } catch (e) {
        /* ignore */
      }

      // 统一刷新UI(标题、图片、缩放、按钮等)
      initReaderUI();
      // 滚动到顶部
      window.scrollTo({ top: 0, behavior: "smooth" });

      hideLoadingIndicator();

      console.log("🎉 章节加载完成:", chapterTitle);
    } catch (error) {
      console.error("❌ 加载章节失败:", error);
      hideLoadingIndicator();
      showErrorMessage(
        "加载章节失败: " +
          (error && error.message ? error.message : String(error))
      );
      // 若失败,1s 后回退到原帖(避免卡死在阅读器)
      setTimeout(() => {
        try {
          location.href = chapterUrl;
        } catch (e) {}
      }, 1000);
    }
  }

  // 手动清理缓存功能
  if (typeof unsafeWindow !== "undefined") {
    unsafeWindow.clearAllCache = clearAllCache;
    unsafeWindow.viewCacheStatus = viewCacheStatus;
  } else {
    window.clearAllCache = clearAllCache;
    window.viewCacheStatus = viewCacheStatus;
  }
})();