Greasy Fork

Greasy Fork is available in English.

漫画人现代阅读器(双页+垂直模式)

现代化漫画阅读器:支持双页分栏和垂直滚动模式,触控优化,缩放功能

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         漫画人现代阅读器(双页+垂直模式)
// @namespace    https://tampermonkey.net/
// @version      3.0.0
// @description  现代化漫画阅读器:支持双页分栏和垂直滚动模式,触控优化,缩放功能
// @author       you
// @match        https://www.manhuaren.com/m*/
// @match        https://www.manhuaren.com/m*/*
// @run-at       document-idle
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
  'use strict';

  // ===== 工具函数 =====
  const $ = (sel, root = document) => root.querySelector(sel);
  const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel));
  const store = {
    get(k, def) { try { return JSON.parse(localStorage.getItem(k)) ?? def; } catch { return def; } },
    set(k, v) { localStorage.setItem(k, JSON.stringify(v)); }
  };
  const clamp = (n, min, max) => Math.max(min, Math.min(max, n));

  // ===== 状态管理 =====
  const state = {
    pages: [],
    currentIndex: 0,
    mode: store.get('mcr_mode', 'dual'),           // 'dual' | 'vertical'
    rtl: store.get('mcr_rtl', true),               // 右到左
    dualFit: store.get('mcr_dualFit', 'height'),   // 'height' | 'width'
    dualGap: store.get('mcr_dualGap', 16),
    firstSingle: store.get('mcr_firstSingle', true),
    verticalZoom: store.get('mcr_verticalZoom', 100), // 50-200%
    settingsOpen: false,
    
    save() {
      store.set('mcr_mode', this.mode);
      store.set('mcr_rtl', this.rtl);
      store.set('mcr_dualFit', this.dualFit);
      store.set('mcr_dualGap', this.dualGap);
      store.set('mcr_firstSingle', this.firstSingle);
      store.set('mcr_verticalZoom', this.verticalZoom);
    }
  };

  // ===== 图片收集 =====
  async function collectImages(maxWait = 15000) {
    const start = Date.now();
    while (Date.now() - start < maxWait) {
      if (window.newImgs?.length) return [...new Set(window.newImgs.map(s => s.replace(/^\/\//, location.protocol + '//')))];
      const domImgs = $$('#cp_img img').map(n => n.dataset.src || n.src).filter(Boolean);
      if (domImgs.length) {
        await new Promise(r => setTimeout(r, 200));
        if (window.newImgs?.length) return [...new Set(window.newImgs.map(s => s.replace(/^\/\//, location.protocol + '//')))];
        return [...new Set(domImgs.map(s => s.replace(/^\/\//, location.protocol + '//')))];
      }
      await new Promise(r => setTimeout(r, 150));
    }
    return [];
  }

  // ===== 样式 =====
  const CSS = `
    :root {
      --mcr-bg: #0a0a0a;
      --mcr-surface: rgba(20, 20, 20, 0.95);
      --mcr-border: rgba(255, 255, 255, 0.08);
      --mcr-text: #e8e8e8;
      --mcr-text-dim: #a0a0a0;
      --mcr-primary: #4a9eff;
      --mcr-primary-hover: #5dadff;
      --mcr-gap: 16px;
      --mcr-radius: 12px;
      --mcr-transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
    }

    #mcr-root {
      position: fixed;
      inset: 0;
      z-index: 2147483647;
      background: var(--mcr-bg);
      color: var(--mcr-text);
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
      overflow: hidden;
      touch-action: none;
    }

    /* ===== 顶栏 ===== */
    #mcr-header {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      height: 56px;
      background: var(--mcr-surface);
      backdrop-filter: blur(20px);
      border-bottom: 1px solid var(--mcr-border);
      display: flex;
      align-items: center;
      padding: 0 16px;
      gap: 12px;
      z-index: 100;
      transition: var(--mcr-transition);
    }

    #mcr-header.hidden { transform: translateY(-100%); }

    .mcr-btn {
      height: 38px;
      padding: 0 16px;
      border: none;
      border-radius: 8px;
      background: rgba(255, 255, 255, 0.08);
      color: var(--mcr-text);
      font-size: 13px;
      font-weight: 500;
      cursor: pointer;
      transition: var(--mcr-transition);
      white-space: nowrap;
    }

    .mcr-btn:hover { background: rgba(255, 255, 255, 0.15); }
    .mcr-btn:active { transform: scale(0.96); }
    .mcr-btn.primary { background: var(--mcr-primary); color: #fff; }
    .mcr-btn.primary:hover { background: var(--mcr-primary-hover); }
    .mcr-btn:disabled { opacity: 0.4; cursor: not-allowed; }

    .mcr-icon-btn {
      width: 38px;
      height: 38px;
      padding: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 18px;
    }

    .mcr-spacer { flex: 1; }

    .mcr-progress {
      font-size: 13px;
      color: var(--mcr-text-dim);
      padding: 0 8px;
    }

    /* ===== 双页模式 ===== */
    #mcr-dual-container {
      position: absolute;
      top: 56px;
      left: 0;
      right: 0;
      bottom: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: var(--mcr-gap);
      overflow: hidden;
    }

    #mcr-dual-container.fit-height .mcr-page-img {
      max-height: calc(100vh - 56px);
      width: auto;
      height: auto;
    }

    #mcr-dual-container.fit-width {
      align-items: flex-start;
      overflow: auto;
    }

    #mcr-dual-container.fit-width .mcr-page-img {
      width: calc((100vw - var(--mcr-gap) - 32px) / 2);
      height: auto;
    }

    .mcr-page-wrap {
      position: relative;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: opacity 0.3s;
    }

    .mcr-page-wrap.hidden { visibility: hidden; opacity: 0; }

    .mcr-page-img {
      display: block;
      max-width: 100%;
      border-radius: 8px;
      box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
      user-select: none;
      -webkit-user-drag: none;
    }

    .mcr-page-num {
      position: absolute;
      bottom: 12px;
      right: 12px;
      background: rgba(0, 0, 0, 0.7);
      backdrop-filter: blur(8px);
      padding: 6px 12px;
      border-radius: 16px;
      font-size: 12px;
      font-weight: 600;
      border: 1px solid rgba(255, 255, 255, 0.1);
    }

    .mcr-page-wrap.left .mcr-page-num { right: auto; left: 12px; }

    /* ===== 垂直模式 ===== */
    #mcr-vertical-container {
      position: absolute;
      top: 56px;
      left: 0;
      right: 0;
      bottom: 0;
      overflow-y: auto;
      overflow-x: hidden;
      display: flex;
      flex-direction: column;
      align-items: center;
      padding: 20px 0;
      gap: 12px;
    }

    #mcr-vertical-container .mcr-page-img {
      max-width: 95vw;
      height: auto;
      transition: transform 0.2s;
    }

    /* ===== 点击区域 ===== */
    #mcr-click-overlay {
      position: absolute;
      inset: 56px 0 0 0;
      display: grid;
      grid-template-columns: 1fr 1fr;
      pointer-events: all;
      z-index: 50;
    }

    .mcr-click-zone {
      cursor: pointer;
      transition: background 0.2s;
    }

    .mcr-click-zone:active { background: rgba(255, 255, 255, 0.03); }

    /* ===== 设置面板 ===== */
    #mcr-settings {
      position: fixed;
      top: 56px;
      right: -360px;
      width: 340px;
      bottom: 0;
      background: var(--mcr-surface);
      backdrop-filter: blur(20px);
      border-left: 1px solid var(--mcr-border);
      padding: 24px;
      overflow-y: auto;
      transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      z-index: 101;
    }

    #mcr-settings.open { right: 0; }

    #mcr-settings h3 {
      margin: 0 0 16px 0;
      font-size: 16px;
      font-weight: 600;
    }

    .mcr-setting-group {
      margin-bottom: 24px;
    }

    .mcr-setting-label {
      display: block;
      font-size: 13px;
      color: var(--mcr-text-dim);
      margin-bottom: 8px;
      font-weight: 500;
    }

    .mcr-btn-group {
      display: flex;
      gap: 8px;
      flex-wrap: wrap;
    }

    .mcr-btn-group .mcr-btn {
      flex: 1;
      min-width: 0;
    }

    .mcr-btn.active {
      background: var(--mcr-primary);
      color: #fff;
    }

    .mcr-slider-container {
      display: flex;
      align-items: center;
      gap: 12px;
    }

    .mcr-slider {
      flex: 1;
      height: 32px;
      -webkit-appearance: none;
      appearance: none;
      background: rgba(255, 255, 255, 0.08);
      border-radius: 16px;
      outline: none;
    }

    .mcr-slider::-webkit-slider-thumb {
      -webkit-appearance: none;
      appearance: none;
      width: 20px;
      height: 20px;
      border-radius: 50%;
      background: var(--mcr-primary);
      cursor: pointer;
      transition: var(--mcr-transition);
    }

    .mcr-slider::-webkit-slider-thumb:hover {
      transform: scale(1.2);
      background: var(--mcr-primary-hover);
    }

    .mcr-slider-value {
      min-width: 50px;
      text-align: right;
      font-size: 13px;
      font-weight: 600;
    }

    .mcr-checkbox {
      display: flex;
      align-items: center;
      gap: 8px;
      cursor: pointer;
      padding: 8px 0;
    }

    .mcr-checkbox input {
      width: 20px;
      height: 20px;
      cursor: pointer;
    }

    /* ===== 遮罩 ===== */
    #mcr-overlay {
      position: fixed;
      inset: 0;
      background: rgba(0, 0, 0, 0.6);
      backdrop-filter: blur(2px);
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.3s;
      z-index: 99;
    }

    #mcr-overlay.visible {
      opacity: 1;
      pointer-events: all;
    }

    /* ===== 隐藏原站 ===== */
    body.mcr-active > *:not(#mcr-root) { display: none !important; }
    html, body { overflow: hidden !important; }

    /* ===== 响应式 ===== */
    @media (max-width: 768px) {
      #mcr-header { padding: 0 8px; gap: 6px; height: 48px; }
      .mcr-btn { height: 36px; padding: 0 12px; font-size: 12px; }
      #mcr-settings { width: 100%; right: -100%; }
      .mcr-progress { display: none; }
    }
  `;

  // ===== HTML 结构 =====
  function createUI() {
    const root = document.createElement('div');
    root.id = 'mcr-root';
    root.innerHTML = `
      <div id="mcr-header">
        <button class="mcr-btn mcr-icon-btn" id="mcr-prev">←</button>
        <button class="mcr-btn mcr-icon-btn" id="mcr-next">→</button>
        <span class="mcr-progress" id="mcr-progress">1 / 1</span>
        <span class="mcr-spacer"></span>
        <button class="mcr-btn" id="mcr-mode-toggle">切换模式</button>
        <button class="mcr-btn" id="mcr-prev-ch">上一章</button>
        <button class="mcr-btn" id="mcr-next-ch">下一章</button>
        <button class="mcr-btn primary" id="mcr-settings-btn">⚙ 设置</button>
      </div>

      <div id="mcr-dual-container" class="fit-height" style="--mcr-gap: ${state.dualGap}px">
        <div class="mcr-page-wrap left">
          <img class="mcr-page-img" id="mcr-img-left" />
          <div class="mcr-page-num" id="mcr-num-left"></div>
        </div>
        <div class="mcr-page-wrap right">
          <img class="mcr-page-img" id="mcr-img-right" />
          <div class="mcr-page-num" id="mcr-num-right"></div>
        </div>
      </div>

      <div id="mcr-vertical-container" style="display: none;"></div>

      <div id="mcr-click-overlay">
        <div class="mcr-click-zone" id="mcr-zone-left"></div>
        <div class="mcr-click-zone" id="mcr-zone-right"></div>
      </div>

      <div id="mcr-overlay"></div>

      <div id="mcr-settings">
        <h3>阅读设置</h3>
        
        <div class="mcr-setting-group">
          <div class="mcr-setting-label">阅读模式</div>
          <div class="mcr-btn-group">
            <button class="mcr-btn" data-mode="dual">双页</button>
            <button class="mcr-btn" data-mode="vertical">垂直</button>
          </div>
        </div>

        <div class="mcr-setting-group" id="mcr-dual-settings">
          <div class="mcr-setting-label">双页方向</div>
          <div class="mcr-btn-group">
            <button class="mcr-btn" data-rtl="true">右→左</button>
            <button class="mcr-btn" data-rtl="false">左→右</button>
          </div>

          <div class="mcr-setting-label" style="margin-top: 16px;">双页适配</div>
          <div class="mcr-btn-group">
            <button class="mcr-btn" data-fit="height">按高度</button>
            <button class="mcr-btn" data-fit="width">按宽度</button>
          </div>

          <div class="mcr-setting-label" style="margin-top: 16px;">页面间距</div>
          <div class="mcr-slider-container">
            <input type="range" class="mcr-slider" id="mcr-gap-slider" min="0" max="48" step="4" value="${state.dualGap}">
            <span class="mcr-slider-value" id="mcr-gap-value">${state.dualGap}px</span>
          </div>

          <label class="mcr-checkbox">
            <input type="checkbox" id="mcr-first-single" ${state.firstSingle ? 'checked' : ''}>
            <span>首页单页显示</span>
          </label>
        </div>

        <div class="mcr-setting-group" id="mcr-vertical-settings" style="display: none;">
          <div class="mcr-setting-label">页面缩放</div>
          <div class="mcr-slider-container">
            <input type="range" class="mcr-slider" id="mcr-zoom-slider" min="50" max="200" step="5" value="${state.verticalZoom}">
            <span class="mcr-slider-value" id="mcr-zoom-value">${state.verticalZoom}%</span>
          </div>
        </div>
      </div>
    `;

    document.body.appendChild(root);
    document.body.classList.add('mcr-active');

    const style = document.createElement('style');
    style.textContent = CSS;
    document.head.appendChild(style);

    return root;
  }

  // ===== 事件绑定 =====
  function bindEvents() {
    // 导航按钮
    $('#mcr-prev').onclick = () => navigate(-1);
    $('#mcr-next').onclick = () => navigate(1);

    // 点击区域
    $('#mcr-zone-left').onclick = () => navigate(state.rtl && state.mode === 'dual' ? 1 : -1);
    $('#mcr-zone-right').onclick = () => navigate(state.rtl && state.mode === 'dual' ? -1 : 1);

    // 模式切换
    $('#mcr-mode-toggle').onclick = () => {
      state.mode = state.mode === 'dual' ? 'vertical' : 'dual';
      state.currentIndex = 0;
      state.save();
      updateUI();
      render();
    };

    // 设置按钮
    $('#mcr-settings-btn').onclick = () => {
      state.settingsOpen = !state.settingsOpen;
      $('#mcr-settings').classList.toggle('open', state.settingsOpen);
      $('#mcr-overlay').classList.toggle('visible', state.settingsOpen);
    };

    $('#mcr-overlay').onclick = () => {
      state.settingsOpen = false;
      $('#mcr-settings').classList.remove('open');
      $('#mcr-overlay').classList.remove('visible');
    };

    // 设置面板
    $$('[data-mode]').forEach(btn => {
      btn.onclick = () => {
        state.mode = btn.dataset.mode;
        state.currentIndex = 0;
        state.save();
        updateUI();
        render();
      };
    });

    $$('[data-rtl]').forEach(btn => {
      btn.onclick = () => {
        state.rtl = btn.dataset.rtl === 'true';
        state.save();
        updateUI();
        render();
      };
    });

    $$('[data-fit]').forEach(btn => {
      btn.onclick = () => {
        state.dualFit = btn.dataset.fit;
        state.save();
        updateUI();
        render();
      };
    });

    $('#mcr-gap-slider').oninput = (e) => {
      state.dualGap = parseInt(e.target.value);
      $('#mcr-gap-value').textContent = state.dualGap + 'px';
      $('#mcr-dual-container').style.setProperty('--mcr-gap', state.dualGap + 'px');
      state.save();
    };

    $('#mcr-zoom-slider').oninput = (e) => {
      state.verticalZoom = parseInt(e.target.value);
      $('#mcr-zoom-value').textContent = state.verticalZoom + '%';
      updateVerticalZoom();
      state.save();
    };

    $('#mcr-first-single').onchange = (e) => {
      state.firstSingle = e.target.checked;
      state.currentIndex = 0;
      state.save();
      render();
    };

    // 键盘
    window.addEventListener('keydown', (e) => {
      if (/INPUT|TEXTAREA|SELECT/.test(e.target.tagName)) return;
      
      switch (e.key) {
        case 'ArrowRight': case ' ': e.preventDefault(); navigate(1); break;
        case 'ArrowLeft': case 'Backspace': e.preventDefault(); navigate(-1); break;
        case 'm': case 'M': $('#mcr-mode-toggle').click(); break;
        case 's': case 'S': $('#mcr-settings-btn').click(); break;
        case 'r': case 'R': state.rtl = !state.rtl; state.save(); updateUI(); render(); break;
      }
    });

    // 触控滑动
    let touchStartX = 0;
    let touchStartY = 0;
    
    $('#mcr-root').addEventListener('touchstart', (e) => {
      if (e.target.closest('#mcr-settings')) return;
      touchStartX = e.touches[0].clientX;
      touchStartY = e.touches[0].clientY;
    }, { passive: true });

    $('#mcr-root').addEventListener('touchend', (e) => {
      if (e.target.closest('#mcr-settings')) return;
      const dx = e.changedTouches[0].clientX - touchStartX;
      const dy = e.changedTouches[0].clientY - touchStartY;
      
      if (Math.abs(dx) > 50 && Math.abs(dx) > Math.abs(dy)) {
        navigate(dx > 0 ? -1 : 1);
      }
    }, { passive: true });

    // 章节导航
    const [prevUrl, nextUrl] = findChapterUrls();
    $('#mcr-prev-ch').disabled = !prevUrl;
    $('#mcr-next-ch').disabled = !nextUrl;
    if (prevUrl) $('#mcr-prev-ch').onclick = () => location.href = prevUrl;
    if (nextUrl) $('#mcr-next-ch').onclick = () => location.href = nextUrl;
  }

  function findChapterUrls() {
    const links = $$('a');
    const prev = links.find(a => a.textContent.trim() === '上一章');
    const next = links.find(a => a.textContent.trim() === '下一章');
    
    const getUrl = (a) => {
      if (!a) return '';
      const href = a.getAttribute('href') || '';
      const m = href.match(/pushHistory\('([^']+)'\)/);
      return m ? location.origin + m[1] : (href.startsWith('/') ? location.origin + href : '');
    };
    
    return [getUrl(prev), getUrl(next)];
  }

  // ===== 导航 =====
  function navigate(delta) {
    if (state.mode === 'dual') {
      const total = getDualPairCount();
      state.currentIndex = clamp(state.currentIndex + delta, 0, total - 1);
    } else {
      state.currentIndex = clamp(state.currentIndex + delta, 0, state.pages.length - 1);
    }
    render();
  }

  // ===== 双页逻辑 =====
  function getDualPairCount() {
    const n = state.pages.length;
    return state.firstSingle ? 1 + Math.ceil((n - 1) / 2) : Math.ceil(n / 2);
  }

  function getDualPair(idx) {
    const n = state.pages.length;
    
    if (state.firstSingle) {
      if (idx === 0) {
        return { left: null, right: state.pages[0], leftNum: null, rightNum: 1 };
      }
      const start = 1 + (idx - 1) * 2;
      let l = state.pages[start] || null;
      let r = state.pages[start + 1] || null;
      let ln = l ? start + 1 : null;
      let rn = r ? start + 2 : null;
      if (state.rtl) { [l, r, ln, rn] = [r, l, rn, ln]; }
      return { left: l, right: r, leftNum: ln, rightNum: rn };
    } else {
      const start = idx * 2;
      let l = state.pages[start] || null;
      let r = state.pages[start + 1] || null;
      let ln = l ? start + 1 : null;
      let rn = r ? start + 2 : null;
      if (state.rtl) { [l, r, ln, rn] = [r, l, rn, ln]; }
      return { left: l, right: r, leftNum: ln, rightNum: rn };
    }
  }

  // ===== 渲染 =====
  function render() {
    if (state.mode === 'dual') {
      renderDual();
    } else {
      renderVertical();
    }
    updateProgress();
  }

  function renderDual() {
    $('#mcr-dual-container').style.display = 'flex';
    $('#mcr-vertical-container').style.display = 'none';
    $('#mcr-click-overlay').style.display = 'grid';

    const { left, right, leftNum, rightNum } = getDualPair(state.currentIndex);

    const imgL = $('#mcr-img-left');
    const imgR = $('#mcr-img-right');
    const wrapL = imgL.closest('.mcr-page-wrap');
    const wrapR = imgR.closest('.mcr-page-wrap');

    if (left) {
      imgL.src = left;
      wrapL.classList.remove('hidden');
      $('#mcr-num-left').textContent = leftNum;
    } else {
      wrapL.classList.add('hidden');
    }

    if (right) {
      imgR.src = right;
      wrapR.classList.remove('hidden');
      $('#mcr-num-right').textContent = rightNum;
    } else {
      wrapR.classList.add('hidden');
    }

    // 预加载
    const nextIdx = state.currentIndex + 1;
    if (nextIdx < getDualPairCount()) {
      const next = getDualPair(nextIdx);
      [next.left, next.right].filter(Boolean).forEach(src => {
        const img = new Image();
        img.src = src;
      });
    }
  }

  function renderVertical() {
    $('#mcr-dual-container').style.display = 'none';
    $('#mcr-vertical-container').style.display = 'flex';
    $('#mcr-click-overlay').style.display = 'none';

    const container = $('#mcr-vertical-container');
    if (container.children.length === 0) {
      state.pages.forEach((src, i) => {
        const wrap = document.createElement('div');
        wrap.className = 'mcr-page-wrap';
        const img = document.createElement('img');
        img.className = 'mcr-page-img';
        img.src = src;
        img.alt = `第 ${i + 1} 页`;
        wrap.appendChild(img);
        container.appendChild(wrap);
      });
      updateVerticalZoom();
    }
  }

  function updateVerticalZoom() {
    const imgs = $$('#mcr-vertical-container .mcr-page-img');
    imgs.forEach(img => {
      img.style.transform = `scale(${state.verticalZoom / 100})`;
    });
  }

  function updateProgress() {
    const total = state.mode === 'dual' ? getDualPairCount() : state.pages.length;
    const current = state.currentIndex + 1;
    $('#mcr-progress').textContent = `${current} / ${total}`;
  }

  // ===== UI 更新 =====
  function updateUI() {
    // 模式按钮
    $$('[data-mode]').forEach(btn => {
      btn.classList.toggle('active', btn.dataset.mode === state.mode);
    });

    // RTL按钮
    $$('[data-rtl]').forEach(btn => {
      btn.classList.toggle('active', (btn.dataset.rtl === 'true') === state.rtl);
    });

    // 适配按钮
    $$('[data-fit]').forEach(btn => {
      btn.classList.toggle('active', btn.dataset.fit === state.dualFit);
    });

    // 显示/隐藏设置组
    $('#mcr-dual-settings').style.display = state.mode === 'dual' ? 'block' : 'none';
    $('#mcr-vertical-settings').style.display = state.mode === 'vertical' ? 'block' : 'none';

    // 双页适配类
    const container = $('#mcr-dual-container');
    container.classList.toggle('fit-height', state.dualFit === 'height');
    container.classList.toggle('fit-width', state.dualFit === 'width');
  }

  // ===== 启动 =====
  async function boot() {
    if (!/\/m\d+\/?$/.test(location.pathname)) return;

    createUI();
    bindEvents();

    const imgs = await collectImages();
    if (!imgs.length) {
      alert('未能获取到章节图片,请刷新重试');
      return;
    }

    state.pages = imgs;
    updateUI();
    render();
  }

  setTimeout(boot, 0);
})();