e-hentai Plus

Infinite scroll & reader mode with image prefetch and floating controls for E-Hentai / ExHentai

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

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

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

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

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

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

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

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

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

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

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

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

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

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

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name               e-hentai Plus
// @name:zh-CN         E-Hentai 增强阅读
// @namespace          http://tampermonkey.net/
// @version            2.3.0
// @author             Viki
// @description        Infinite scroll & reader mode with image prefetch and floating controls for E-Hentai / ExHentai
// @description:zh-CN  为 E-Hentai / ExHentai 提供无限滚动和阅读模式,支持图片预取与悬浮控制
// @license            MIT
// @homepageURL        https://github.com/Leovikii/e-hentai-plus
// @match              https://e-hentai.org/g/*
// @match              https://exhentai.org/g/*
// @grant              GM_addStyle
// @grant              GM_getValue
// @grant              GM_registerMenuCommand
// @grant              GM_setValue
// ==/UserScript==

(function () {
  'use strict';

  const d=new Set;const importCSS = async e=>{d.has(e)||(d.add(e),(t=>{typeof GM_addStyle=="function"?GM_addStyle(t):(document.head||document.documentElement).appendChild(document.createElement("style")).append(t);})(e));};

  importCSS(" *,:before,:after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / .5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: }::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / .5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: } ");

  const stylesCss = ':root{--accent: #F596AA;--accent-hover: #F7ABBE}html,body{background-color:#111!important;color:#ccc!important;margin:0;overflow-x:hidden}#gdt{display:flex;flex-direction:column;align-items:center;width:100%;max-width:1200px;margin:auto;padding-bottom:100px}.page-batch{width:100%;display:flex;flex-direction:column;align-items:center;margin-bottom:60px}.r-img{display:block;width:auto;max-width:100%;margin-bottom:20px;background:transparent;box-shadow:0 0 20px #00000080}.r-ph{color:#555;margin-bottom:50px;text-align:center;min-height:400px;display:flex;align-items:center;justify-content:center;font-family:sans-serif;font-size:18px;border:1px dashed #333;width:100%;flex-direction:column;gap:10px}.r-ph.loading{color:#888;border-color:#555}.r-ph.error{color:#d44;border-color:#d44}.retry-btn{padding:8px 16px;background:#333;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:14px;margin-top:10px}.retry-btn:hover{background:#555}.float-control{position:fixed;right:30px;bottom:30px;z-index:9999;display:flex;flex-direction:row;align-items:center;transition:opacity .3s;-webkit-user-select:none;user-select:none}.float-control.hidden{opacity:0;pointer-events:none}.side-btn.top-btn{position:absolute;bottom:calc(100% + 10px);left:50%;transform:translate(-50%);opacity:.3;pointer-events:auto}.float-control:hover .side-btn.top-btn{opacity:.8}.side-btn.top-btn:hover{opacity:1!important;background:#555;transform:translate(-50%) scale(1.1)}.side-btn{width:36px;height:36px;background:#333;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .3s;opacity:0;pointer-events:none}.float-control:hover .side-btn{opacity:1;pointer-events:auto}.side-btn:hover{background:#555;transform:scale(1.1)}.side-btn svg{width:18px;height:18px;fill:#fff}.side-btn.active{background:var(--accent)}.side-btn.active:hover{background:var(--accent-hover)}.auto-play-btn.hidden{display:none}.circle-control{position:relative;width:50px;height:50px;background:#1a1a1a;border:2px solid #555;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .3s;box-shadow:0 4px 12px #00000080;margin:0 6px}.circle-control:hover{border-color:#888;box-shadow:0 6px 16px #000000b3;transform:scale(1.05)}.circle-control svg{width:24px;height:24px;fill:#fff}.settings-btn{cursor:pointer}.settings-panel{position:absolute;bottom:calc(100% + 10px);right:0;background:#1a1a1a;border:1px solid #555;border-radius:8px;padding:12px;min-width:180px;opacity:0;pointer-events:none;transition:all .3s;box-shadow:0 4px 12px #00000080;transform:translateY(5px)}.settings-panel.show{opacity:1;pointer-events:auto;transform:translateY(0)}.settings-item{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;font-size:13px;color:#ccc}.settings-item:last-child{margin-bottom:0}.settings-label{margin-right:10px;white-space:nowrap}.toggle-switch{width:40px;height:20px;background:#333;border-radius:10px;position:relative;cursor:pointer;transition:background .3s}.toggle-switch.on{background:var(--accent)}.toggle-slider{width:16px;height:16px;background:#fff;border-radius:50%;position:absolute;top:2px;left:2px;transition:left .3s}.toggle-switch.on .toggle-slider{left:22px}.interval-input{width:60px;background:#333;border:1px solid #555;border-radius:4px;color:#fff;padding:4px 8px;font-size:12px;text-align:center}.interval-input:focus{outline:none;border-color:#888}.single-page-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#000;z-index:9998;display:none;align-items:center;justify-content:center}.single-page-overlay.active{display:flex}.sp-image-container{width:100%;height:100%;display:flex;align-items:center;justify-content:center;position:relative}.sp-current-image{width:100%;height:100%;object-fit:contain;-webkit-user-select:none;user-select:none}.sp-close-btn{position:absolute;top:20px;right:20px;width:40px;height:40px;background:#333c;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:24px;color:#fff;transition:all .3s;z-index:10}.sp-close-btn:hover{background:#555555e6;transform:scale(1.1)}.sp-scrollbar{position:absolute;right:40px;top:10%;width:12px;height:80%;background:#2828284d;border-radius:6px;z-index:10;transition:background .3s}.sp-scrollbar:hover{background:#32323280}.sp-scrollbar-thumb{position:absolute;left:0;width:100%;min-height:60px;background:#fff6;border-radius:6px;transition:background .3s;cursor:grab;-webkit-user-select:none;user-select:none}.sp-scrollbar-thumb:hover{background:#fff9}.sp-scrollbar-thumb:active{cursor:grabbing;background:#ffffffb3}.sp-scrollbar-label{position:absolute;right:calc(100% + 16px);top:50%;transform:translateY(-50%);background:#1a1a1af2;padding:8px 14px;border-radius:8px;color:#fff;font-family:monospace;font-size:14px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .3s;box-shadow:0 2px 8px #0000004d}.sp-scrollbar:hover .sp-scrollbar-label{opacity:1}.sp-thumb-panel{position:absolute;right:calc(100% + 12px);top:50%;transform:translateY(-50%);width:116px;background:#141414f2;border-radius:8px;border:1px solid rgba(255,255,255,.1);box-shadow:0 4px 16px #00000080;opacity:0;pointer-events:none;transition:opacity .2s;z-index:11;overflow:hidden}.sp-scrollbar:hover .sp-thumb-panel,.sp-thumb-panel:hover{opacity:1;pointer-events:auto}.sp-scrollbar:before{content:"";position:absolute;right:100%;top:0;width:140px;height:100%}.sp-thumb-viewport{width:100%;max-height:80vh;overflow:hidden;position:relative}.sp-thumb-content{position:relative;width:100%}.sp-thumb-item{position:absolute;left:8px;width:100px;height:72px;border-radius:4px;overflow:hidden;cursor:pointer;border:2px solid transparent;transition:border-color .15s;box-sizing:border-box}.sp-thumb-item:hover{border-color:#fff6}.sp-thumb-item.sp-thumb-active{border-color:var(--accent)}.sp-thumb-img{width:100%;height:100%;object-fit:cover;pointer-events:none}.sp-thumb-ph{width:100%;height:100%;background:#222;display:flex;align-items:center;justify-content:center;color:#555;font-family:monospace;font-size:13px;-webkit-user-select:none;user-select:none}.sp-thumb-counter{padding:8px 0;text-align:center;color:#999;font-family:monospace;font-size:13px;border-top:1px solid rgba(255,255,255,.08);-webkit-user-select:none;user-select:none}.sp-placeholder{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative}.sp-placeholder-pulse{width:60%;max-width:500px;height:60%;background:#181818;border-radius:8px;animation:sp-pulse 1.5s ease-in-out infinite}.sp-placeholder-text{position:absolute;color:#666;font-family:monospace;font-size:16px;-webkit-user-select:none;user-select:none}@keyframes sp-pulse{0%,to{opacity:.3}50%{opacity:.6}}';
  importCSS(stylesCss);
  var _GM_getValue = (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
  var _GM_registerMenuCommand = (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)();
  var _GM_setValue = (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
  const CFG = {
    nextPage: "3000px 0px",
    prefetchDistance: 5e3,
    maxRetries: 3,
    retryDelay: 1e3
  };
  function loadSettings() {
    return {
      autoScroll: _GM_getValue("autoScroll", true),
      showControl: _GM_getValue("showControl", true),
      autoEnterSinglePage: _GM_getValue("autoEnterSinglePage", false),
      autoPlay: _GM_getValue("autoPlay", false),
      autoPlayInterval: _GM_getValue("autoPlayInterval", 3e3)
    };
  }
  class Store {
    constructor() {
      this.listeners = new Map();
      this.currPage = 1;
      this.totalPage = 1;
      this.nextUrl = null;
      this.isFetching = false;
      this.nextPagePrefetched = false;
      this.currentImageIndex = 0;
      this.allImages = [];
      this.autoPlayTimer = null;
      this._settings = loadSettings();
    }
    get settings() {
      return this._settings;
    }
    updateSetting(key, value) {
      this._settings[key] = value;
      _GM_setValue(key, value);
      this.emit("settingsChanged");
    }
    on(event, listener) {
      if (!this.listeners.has(event)) {
        this.listeners.set(event, new Set());
      }
      this.listeners.get(event).add(listener);
    }
    emit(event) {
      var _a;
      (_a = this.listeners.get(event)) == null ? void 0 : _a.forEach((fn) => fn());
    }
  }
  const store = new Store();
  const q = (selector, root = document) => root.querySelector(selector);
  const qa = (selector, root = document) => root.querySelectorAll(selector);
  const HIDDEN_SELECTORS = ["#nb", "#fb", "#cdiv", ".gt", ".gpc", ".ptt", "#db"];
  function hideOriginalElements() {
    HIDDEN_SELECTORS.forEach((sel) => {
      const el = q(sel);
      if (el) el.style.display = "none";
    });
  }
  function isImageReady(img) {
    return !!(img && img.src && !img.src.includes("data:") && img.complete && img.naturalWidth > 0);
  }
  const sharedParser = new DOMParser();
  function fetchPageLinks(url) {
    return fetch(url).then((r) => r.text()).then((html) => {
      const doc = sharedParser.parseFromString(html, "text/html");
      const links = Array.from(qa("#gdt a", doc)).map((a) => a.href);
      return { doc, links };
    });
  }
  function calcTotal(doc, fallbackLinkCount) {
    const gpc = q(".gpc", doc);
    if (gpc) {
      const txt = gpc.textContent ?? "";
      const m = txt.match(/of\s+(\d+)\s+images/);
      if (m && m[1]) {
        const totalImgs = parseInt(m[1]);
        const perPage = fallbackLinkCount || 20;
        return Math.ceil(totalImgs / perPage);
      }
    }
    const allLinks = Array.from(qa(".ptt td a", doc));
    const lastA = allLinks.pop();
    if (lastA) {
      const t = parseInt(lastA.textContent ?? "");
      if (!isNaN(t)) return t;
    }
    return 1;
  }
  function getNextUrl(doc) {
    const ptt = q(".ptt", doc);
    if (!ptt) return null;
    const nextBtn = Array.from(qa("td a", ptt)).find((a) => (a.textContent ?? "").includes(">"));
    return nextBtn ? nextBtn.href : null;
  }
  const parser = new DOMParser();
  async function loadImageWithRetry(url, retries = 0) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      const html = await response.text();
      const doc = parser.parseFromString(html, "text/html");
      const imgEl = q("#img", doc);
      const imgSrc = imgEl == null ? void 0 : imgEl.src;
      if (!imgSrc) throw new Error("Image not found");
      return imgSrc;
    } catch {
      if (retries < CFG.maxRetries) {
        await new Promise((resolve) => setTimeout(resolve, CFG.retryDelay));
        return loadImageWithRetry(url, retries + 1);
      }
      return null;
    }
  }
  function createRetryHandler(url, placeholder, pIndex, index) {
    return () => {
      placeholder.className = "r-ph loading";
      placeholder.textContent = `P${pIndex}-${index + 1} Reloading...`;
      loadImageWithRetry(url).then((newSrc) => {
        var _a;
        if (newSrc) {
          const newImg = document.createElement("img");
          newImg.src = newSrc;
          newImg.className = "r-img";
          (_a = placeholder.parentNode) == null ? void 0 : _a.replaceChild(newImg, placeholder);
        }
      });
    };
  }
  const prefetchedUrls = new Set();
  function prefetchNextPage() {
    if (!store.nextUrl || store.nextPagePrefetched || prefetchedUrls.has(store.nextUrl)) return;
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
    const windowHeight = window.innerHeight;
    const documentHeight = document.documentElement.scrollHeight;
    const distanceToBottom = documentHeight - (scrollTop + windowHeight);
    if (distanceToBottom < CFG.prefetchDistance) {
      store.nextPagePrefetched = true;
      prefetchedUrls.add(store.nextUrl);
      fetchPageLinks(store.nextUrl).then(({ links }) => {
        links.forEach((url) => {
          loadImageWithRetry(url).then((imgSrc) => {
            if (imgSrc) {
              const preloadImg = new Image();
              preloadImg.src = imgSrc;
            }
          }).catch(() => null);
        });
      }).catch((err) => {
        console.error("[Prefetch Failed]", err);
        store.nextPagePrefetched = false;
        if (store.nextUrl) prefetchedUrls.delete(store.nextUrl);
      });
    }
  }
  function setupPrefetchListener() {
    let scrollTimer;
    window.addEventListener("scroll", () => {
      clearTimeout(scrollTimer);
      scrollTimer = setTimeout(prefetchNextPage, 200);
    }, { passive: true });
  }
  function setErrorState(placeholder, url, pIndex, index) {
    placeholder.className = "r-ph error";
    placeholder.innerHTML = `
    <div>P${pIndex}-${index + 1} Failed</div>
    <button class="retry-btn">Retry</button>
  `;
    const retryBtn = placeholder.querySelector(".retry-btn");
    retryBtn.onclick = createRetryHandler(url, placeholder, pIndex, index);
  }
  function processBatch(links, pIndex) {
    const mainBox = document.querySelector("#gdt");
    const batchDiv = document.createElement("div");
    batchDiv.className = "page-batch";
    const fragment = document.createDocumentFragment();
    links.forEach((url, index) => {
      const placeholder = document.createElement("div");
      placeholder.className = "r-ph loading";
      placeholder.textContent = `P${pIndex}-${index + 1} Loading...`;
      fragment.appendChild(placeholder);
      loadImageWithRetry(url).then((imgSrc) => {
        var _a;
        if (imgSrc) {
          const img = document.createElement("img");
          img.className = "r-img";
          img.onerror = () => {
            if (placeholder.parentNode) {
              setErrorState(placeholder, url, pIndex, index);
              placeholder.parentNode.replaceChild(placeholder, img);
            }
          };
          img.src = imgSrc;
          (_a = placeholder.parentNode) == null ? void 0 : _a.replaceChild(img, placeholder);
        } else {
          setErrorState(placeholder, url, pIndex, index);
        }
      }).catch(() => {
        placeholder.className = "r-ph error";
        placeholder.textContent = `P${pIndex}-${index + 1} Network Error`;
      });
    });
    batchDiv.appendChild(fragment);
    mainBox.appendChild(batchDiv);
  }
  function setupAutoScroll() {
    if (!store.settings.autoScroll) return;
    const scrollSent = document.createElement("div");
    document.body.appendChild(scrollSent);
    const pageObs = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting && store.nextUrl && !store.isFetching && store.settings.autoScroll) {
        store.isFetching = true;
        fetchPageLinks(store.nextUrl).then(({ doc, links }) => {
          const nUrl = getNextUrl(doc);
          store.currPage++;
          processBatch(links, store.currPage);
          store.nextUrl = nUrl;
          store.isFetching = false;
          store.nextPagePrefetched = false;
          if (!store.nextUrl) pageObs.disconnect();
        }).catch(() => {
          store.isFetching = false;
        });
      }
    }, { rootMargin: CFG.nextPage });
    pageObs.observe(scrollSent);
  }
  const ITEM_HEIGHT = 80;
  const VISIBLE_COUNT = 12;
  const BUFFER = 3;
  function createThumbnailPanel(onIndexChange) {
    const panel = document.createElement("div");
    panel.className = "sp-thumb-panel";
    const viewport = document.createElement("div");
    viewport.className = "sp-thumb-viewport";
    const content = document.createElement("div");
    content.className = "sp-thumb-content";
    const counter = document.createElement("div");
    counter.className = "sp-thumb-counter";
    viewport.appendChild(content);
    panel.appendChild(viewport);
    panel.appendChild(counter);
    let scrollOffset = 0;
    let lastCenteredIndex = -1;
    let clickedFromPanel = false;
    const itemPool = [];
    const activeItems = new Map();
    function clamp(val, min, max) {
      return Math.max(min, Math.min(max, val));
    }
    function vpHeight() {
      return Math.min(VISIBLE_COUNT * ITEM_HEIGHT, store.allImages.length * ITEM_HEIGHT);
    }
    function maxOffset() {
      return Math.max(0, store.allImages.length * ITEM_HEIGHT - vpHeight());
    }
    function acquireItem() {
      return itemPool.pop() || (() => {
        const el = document.createElement("div");
        el.className = "sp-thumb-item";
        return el;
      })();
    }
    function releaseItem(el) {
      el.remove();
      itemPool.push(el);
    }
    function renderItemContent(el, index) {
      el.dataset.index = String(index);
      el.classList.toggle("sp-thumb-active", index === store.currentImageIndex);
      const img = store.allImages[index];
      if (img && isImageReady(img)) {
        let thumbImg = el.querySelector("img");
        if (!thumbImg) {
          el.innerHTML = "";
          thumbImg = document.createElement("img");
          thumbImg.className = "sp-thumb-img";
          el.appendChild(thumbImg);
        }
        if (thumbImg.src !== img.src) {
          thumbImg.src = img.src;
        }
      } else {
        if (!el.querySelector(".sp-thumb-ph")) {
          el.innerHTML = "";
          const ph = document.createElement("div");
          ph.className = "sp-thumb-ph";
          ph.textContent = String(index + 1);
          el.appendChild(ph);
        }
      }
    }
    function renderVisibleItems() {
      const total = store.allImages.length;
      if (total === 0) return;
      const vp = vpHeight();
      viewport.style.height = `${vp}px`;
      content.style.height = `${total * ITEM_HEIGHT}px`;
      scrollOffset = clamp(scrollOffset, 0, maxOffset());
      const startIdx = Math.max(0, Math.floor(scrollOffset / ITEM_HEIGHT) - BUFFER);
      const endIdx = Math.min(total - 1, Math.ceil((scrollOffset + vp) / ITEM_HEIGHT) + BUFFER);
      for (const [idx, el] of activeItems) {
        if (idx < startIdx || idx > endIdx) {
          releaseItem(el);
          activeItems.delete(idx);
        }
      }
      for (let i = startIdx; i <= endIdx; i++) {
        let el = activeItems.get(i);
        if (!el) {
          el = acquireItem();
          activeItems.set(i, el);
          content.appendChild(el);
        }
        el.style.transform = `translateY(${i * ITEM_HEIGHT}px)`;
        renderItemContent(el, i);
      }
      content.style.transform = `translateY(${-scrollOffset}px)`;
    }
    function centerOnCurrent() {
      const vp = vpHeight();
      const target = store.currentImageIndex * ITEM_HEIGHT - vp / 2 + ITEM_HEIGHT / 2;
      scrollOffset = clamp(target, 0, maxOffset());
    }
    function ensureVisible() {
      const vp = vpHeight();
      const itemTop = store.currentImageIndex * ITEM_HEIGHT;
      const itemBottom = itemTop + ITEM_HEIGHT;
      if (itemTop < scrollOffset) {
        scrollOffset = itemTop;
      } else if (itemBottom > scrollOffset + vp) {
        scrollOffset = itemBottom - vp;
      }
      scrollOffset = clamp(scrollOffset, 0, maxOffset());
    }
    function update() {
      if (store.currentImageIndex !== lastCenteredIndex) {
        if (clickedFromPanel) {
          ensureVisible();
          clickedFromPanel = false;
        } else {
          centerOnCurrent();
        }
        lastCenteredIndex = store.currentImageIndex;
      }
      renderVisibleItems();
      counter.textContent = `${store.currentImageIndex + 1} / ${store.allImages.length}`;
    }
    viewport.addEventListener("wheel", (e) => {
      e.preventDefault();
      e.stopPropagation();
      scrollOffset = clamp(scrollOffset + e.deltaY, 0, maxOffset());
      renderVisibleItems();
    }, { passive: false });
    content.addEventListener("click", (e) => {
      const item = e.target.closest(".sp-thumb-item");
      if (item == null ? void 0 : item.dataset.index) {
        const index = parseInt(item.dataset.index);
        if (!isNaN(index) && index >= 0 && index < store.allImages.length) {
          clickedFromPanel = true;
          onIndexChange(index);
        }
      }
    });
    return { update, getElement: () => panel };
  }
  function createScrollbar(onIndexChange) {
    const pageIndicator = document.createElement("div");
    pageIndicator.className = "sp-scrollbar";
    const scrollbarThumb = document.createElement("div");
    scrollbarThumb.className = "sp-scrollbar-thumb";
    const scrollbarLabel = document.createElement("div");
    scrollbarLabel.className = "sp-scrollbar-label";
    pageIndicator.appendChild(scrollbarThumb);
    pageIndicator.appendChild(scrollbarLabel);
    const thumbPanel = createThumbnailPanel(onIndexChange);
    pageIndicator.appendChild(thumbPanel.getElement());
    scrollbarLabel.style.display = "none";
    let cachedTrackHeight = 0;
    function refreshTrackHeight() {
      cachedTrackHeight = pageIndicator.offsetHeight;
    }
    window.addEventListener("resize", refreshTrackHeight, { passive: true });
    function update() {
      if (store.allImages.length === 0) return;
      if (!cachedTrackHeight) refreshTrackHeight();
      const trackHeight = cachedTrackHeight;
      let thumbHeight;
      if (store.allImages.length <= 10) {
        thumbHeight = 60;
      } else if (store.allImages.length <= 50) {
        thumbHeight = Math.max(60, trackHeight * (10 / store.allImages.length));
      } else {
        thumbHeight = Math.max(60, trackHeight * (5 / store.allImages.length));
      }
      const scrollProgress = store.currentImageIndex / Math.max(1, store.allImages.length - 1);
      const maxThumbTop = trackHeight - thumbHeight;
      const thumbTop = scrollProgress * maxThumbTop;
      scrollbarThumb.style.height = `${thumbHeight}px`;
      scrollbarThumb.style.top = `${thumbTop}px`;
      scrollbarLabel.textContent = `${store.currentImageIndex + 1} / ${store.allImages.length}`;
      thumbPanel.update();
    }
    pageIndicator.onclick = (e) => {
      if (e.target === scrollbarThumb) return;
      if (thumbPanel.getElement().contains(e.target)) return;
      const rect = pageIndicator.getBoundingClientRect();
      const clickY = e.clientY - rect.top;
      const scrollProgress = Math.min(1, Math.max(0, clickY / rect.height));
      const targetIndex = Math.round(scrollProgress * (store.allImages.length - 1));
      if (targetIndex >= 0 && targetIndex < store.allImages.length) {
        onIndexChange(targetIndex);
      }
    };
    let isDragging = false;
    let dragStartY = 0;
    let thumbStartTop = 0;
    scrollbarThumb.onmousedown = (e) => {
      e.preventDefault();
      e.stopPropagation();
      isDragging = true;
      dragStartY = e.clientY;
      thumbStartTop = scrollbarThumb.offsetTop;
      document.body.style.userSelect = "none";
    };
    document.addEventListener("mousemove", (e) => {
      if (!isDragging) return;
      const deltaY = e.clientY - dragStartY;
      const newTop = thumbStartTop + deltaY;
      const trackHeight = cachedTrackHeight;
      const thumbHeight = scrollbarThumb.offsetHeight;
      const maxTop = trackHeight - thumbHeight;
      const clampedTop = Math.max(0, Math.min(maxTop, newTop));
      const scrollProgress = maxTop > 0 ? clampedTop / maxTop : 0;
      const targetIndex = Math.round(scrollProgress * (store.allImages.length - 1));
      if (targetIndex >= 0 && targetIndex < store.allImages.length && targetIndex !== store.currentImageIndex) {
        onIndexChange(targetIndex);
      }
    });
    document.addEventListener("mouseup", () => {
      if (isDragging) {
        isDragging = false;
        document.body.style.userSelect = "";
      }
    });
    scrollbarThumb.onclick = (e) => e.stopPropagation();
    return { update, getElement: () => pageIndicator };
  }
  function setupNavigation(deps) {
    function hasLoadingPlaceholders() {
      return document.querySelectorAll(".r-ph").length > 0;
    }
    function syncAllImages() {
      const freshImages = Array.from(qa(".r-img"));
      if (freshImages.length !== store.allImages.length) {
        store.allImages = freshImages;
      }
    }
    function nextImage() {
      if (store.currentImageIndex >= store.allImages.length - 3) {
        syncAllImages();
      }
      if (store.currentImageIndex < store.allImages.length - 1) {
        store.currentImageIndex++;
        deps.updateImage();
        deps.checkAndLoadNextPage();
      } else if (hasLoadingPlaceholders()) {
        deps.updateImage();
        deps.checkAndLoadNextPage();
      } else {
        deps.checkAndLoadNextPage();
        if (store.settings.autoPlay) {
          deps.stopAutoPlayAtEnd();
        }
      }
    }
    function previousImage() {
      if (store.currentImageIndex > 0) {
        store.currentImageIndex--;
        deps.updateImage();
        if (store.settings.autoPlay) {
          deps.resetAutoPlay();
        }
      }
    }
    let wheelTimeout;
    let wheelDelta = 0;
    let isScrolling = false;
    const processWheelScroll = () => {
      if (!isScrolling) return;
      const threshold = 100;
      if (Math.abs(wheelDelta) >= threshold) {
        if (wheelDelta > 0) {
          nextImage();
        } else {
          previousImage();
        }
        wheelDelta = wheelDelta > 0 ? wheelDelta - threshold : wheelDelta + threshold;
      }
      if (isScrolling) {
        requestAnimationFrame(processWheelScroll);
      }
    };
    deps.overlay.addEventListener("wheel", (e) => {
      e.preventDefault();
      wheelDelta += e.deltaY;
      if (!isScrolling) {
        isScrolling = true;
        processWheelScroll();
      }
      clearTimeout(wheelTimeout);
      wheelTimeout = setTimeout(() => {
        isScrolling = false;
        wheelDelta = 0;
      }, 150);
    }, { passive: false });
    document.addEventListener("keydown", (e) => {
      if (!deps.overlay.classList.contains("active")) return;
      if (e.key === "Escape") {
        deps.closeSinglePageMode();
      } else if (e.key === "ArrowDown" || e.key === "ArrowRight") {
        nextImage();
      } else if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
        previousImage();
      }
    });
    return { nextImage, previousImage };
  }
  function createAutoPlay(nextImageFn) {
    function start() {
      if (store.autoPlayTimer) clearInterval(store.autoPlayTimer);
      if (store.settings.autoPlay) {
        store.autoPlayTimer = setInterval(nextImageFn, store.settings.autoPlayInterval);
      }
    }
    function stop() {
      if (store.autoPlayTimer) {
        clearInterval(store.autoPlayTimer);
        store.autoPlayTimer = null;
      }
    }
    function reset() {
      if (store.settings.autoPlay) {
        stop();
        start();
      }
    }
    function stopAtEnd() {
      store.updateSetting("autoPlay", false);
      stop();
    }
    return { start, stop, reset, stopAtEnd };
  }
  function createSinglePageOverlay(deps) {
    const overlay = document.createElement("div");
    overlay.className = "single-page-overlay";
    const closeBtn = document.createElement("div");
    closeBtn.className = "sp-close-btn";
    closeBtn.innerHTML = "&#10005;";
    const imageContainer = document.createElement("div");
    imageContainer.className = "sp-image-container";
    const currentImage = document.createElement("img");
    currentImage.className = "sp-current-image";
    imageContainer.appendChild(currentImage);
    let loadPollTimer = null;
    let loadObserver = null;
    function clearLoadPoll() {
      if (loadPollTimer) {
        clearInterval(loadPollTimer);
        loadPollTimer = null;
      }
      if (loadObserver) {
        loadObserver.disconnect();
        loadObserver = null;
      }
    }
    function showPlaceholder() {
      currentImage.style.display = "none";
      const existing = imageContainer.querySelector(".sp-placeholder");
      if (existing) existing.remove();
      const ph = document.createElement("div");
      ph.className = "sp-placeholder";
      ph.innerHTML = `<div class="sp-placeholder-pulse"></div><div class="sp-placeholder-text">${store.currentImageIndex + 1} / ${store.allImages.length}</div>`;
      imageContainer.appendChild(ph);
    }
    function removePlaceholder() {
      const ph = imageContainer.querySelector(".sp-placeholder");
      if (ph) ph.remove();
      currentImage.style.display = "";
    }
    function updateImage() {
      clearLoadPoll();
      const idx = store.currentImageIndex;
      const img = store.allImages[idx];
      if (!img) {
        showPlaceholder();
        scrollbar.update();
        startLoadPoll(idx);
        return;
      }
      if (isImageReady(img)) {
        removePlaceholder();
        currentImage.src = img.src;
        scrollbar.update();
        return;
      }
      showPlaceholder();
      scrollbar.update();
      startLoadPoll(idx);
    }
    function startLoadPoll(idx) {
      const wasAutoPlaying = !!store.autoPlayTimer;
      if (wasAutoPlaying) autoPlay.stop();
      function onImageReady() {
        if (store.currentImageIndex !== idx) return;
        const img2 = store.allImages[idx];
        if (img2 && isImageReady(img2)) {
          clearLoadPoll();
          removePlaceholder();
          currentImage.src = img2.src;
          scrollbar.update();
          if (wasAutoPlaying && store.settings.autoPlay) autoPlay.start();
        }
      }
      const img = store.allImages[idx];
      if (img) {
        img.addEventListener("load", onImageReady, { once: true });
      }
      const mainBox = document.querySelector("#gdt");
      if (mainBox) {
        loadObserver = new MutationObserver(() => {
          if (store.currentImageIndex !== idx) {
            clearLoadPoll();
            return;
          }
          const freshImages = Array.from(qa(".r-img"));
          if (freshImages.length !== store.allImages.length) {
            store.allImages = freshImages;
            scrollbar.update();
            const newImg = store.allImages[idx];
            if (newImg && !img) {
              newImg.addEventListener("load", onImageReady, { once: true });
              if (isImageReady(newImg)) onImageReady();
            }
          }
        });
        loadObserver.observe(mainBox, { childList: true, subtree: true });
      }
      loadPollTimer = setInterval(() => {
        if (store.currentImageIndex !== idx) {
          clearLoadPoll();
          return;
        }
        onImageReady();
      }, 1e3);
    }
    const autoPlay = createAutoPlay(() => nav.nextImage());
    const scrollbar = createScrollbar((index) => {
      store.currentImageIndex = index;
      updateImage();
      autoPlay.reset();
    });
    const nav = setupNavigation({
      overlay,
      updateImage,
      checkAndLoadNextPage: () => checkAndLoadNextPage(),
      resetAutoPlay: () => autoPlay.reset(),
      stopAutoPlayAtEnd: () => autoPlay.stopAtEnd(),
      closeSinglePageMode: () => close()
    });
    overlay.appendChild(closeBtn);
    overlay.appendChild(scrollbar.getElement());
    overlay.appendChild(imageContainer);
    document.body.appendChild(overlay);
    closeBtn.onclick = () => close();
    function open() {
      store.allImages = Array.from(qa(".r-img"));
      if (store.allImages.length === 0) {
        alert("Please wait for images to load");
        return;
      }
      const viewportCenter = window.scrollY + window.innerHeight / 2;
      const searchRange = window.innerHeight * 2;
      let closestIndex = 0;
      let minDistance = Infinity;
      store.allImages.forEach((img, index) => {
        const rect = img.getBoundingClientRect();
        const imgTop = rect.top + window.scrollY;
        if (Math.abs(imgTop - viewportCenter) < searchRange) {
          const imgCenter = imgTop + rect.height / 2;
          const distance = Math.abs(imgCenter - viewportCenter);
          if (distance < minDistance) {
            minDistance = distance;
            closestIndex = index;
          }
        }
      });
      store.currentImageIndex = closestIndex;
      overlay.classList.add("active");
      document.body.style.overflow = "hidden";
      updateImage();
      if (store.settings.autoPlay) {
        autoPlay.start();
      }
    }
    function close() {
      clearLoadPoll();
      autoPlay.stop();
      overlay.classList.remove("active");
      document.body.style.overflow = "";
      const currentImages = Array.from(qa(".r-img"));
      if (store.currentImageIndex >= 0 && store.currentImageIndex < currentImages.length) {
        const targetImg = currentImages[store.currentImageIndex];
        if (targetImg) {
          setTimeout(() => {
            targetImg.scrollIntoView({ behavior: "smooth", block: "center" });
          }, 100);
        }
      }
    }
    store.on("settingsChanged", () => {
      if (!overlay.classList.contains("active")) return;
      if (store.settings.autoPlay) {
        autoPlay.start();
      } else {
        autoPlay.stop();
      }
    });
    function checkAndLoadNextPage() {
      if (!store.settings.autoScroll || !store.nextUrl || store.isFetching) return;
      const remainingImages = store.allImages.length - store.currentImageIndex;
      if (remainingImages <= 10) {
        store.isFetching = true;
        fetchPageLinks(store.nextUrl).then(({ doc, links }) => {
          deps.onLoadNextPage(links, doc);
          const mainBox = document.querySelector("#gdt");
          if (mainBox) {
            const expectedTotal = store.allImages.length + links.length;
            const obs = new MutationObserver(() => {
              const newImages = Array.from(qa(".r-img"));
              if (newImages.length !== store.allImages.length) {
                store.allImages = newImages;
                scrollbar.update();
              }
              if (newImages.length >= expectedTotal) {
                obs.disconnect();
              }
            });
            obs.observe(mainBox, { childList: true, subtree: true });
            setTimeout(() => {
              obs.disconnect();
              const finalImages = Array.from(qa(".r-img"));
              if (finalImages.length !== store.allImages.length) {
                store.allImages = finalImages;
                scrollbar.update();
              }
            }, 3e4);
          }
          store.nextUrl = getNextUrl(doc);
          store.isFetching = false;
          store.nextPagePrefetched = false;
        }).catch((err) => {
          console.error("[Single Page] Load failed", err);
          store.isFetching = false;
        });
      }
    }
    function jumpTo(index) {
      if (!overlay.classList.contains("active")) return;
      store.currentImageIndex = Math.max(0, Math.min(index, store.allImages.length - 1));
      updateImage();
      autoPlay.reset();
    }
    return {
      open,
      close,
      isActive: () => overlay.classList.contains("active"),
      getOverlayElement: () => overlay,
      jumpTo
    };
  }
  function initSinglePageMode() {
    const spm = createSinglePageOverlay({
      onLoadNextPage: (links, doc) => {
        store.currPage++;
        processBatch(links, store.currPage);
        store.nextUrl = getNextUrl(doc);
      }
    });
    return spm;
  }
  const svgSettings = `<svg viewBox="0 0 24 24"><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg>`;
  const svgReader = `<svg viewBox="0 0 24 24"><path d="M21 5c-1.11-.35-2.33-.5-3.5-.5-1.95 0-4.05.4-5.5 1.5-1.45-1.1-3.55-1.5-5.5-1.5S2.45 4.9 1 6v14.65c0 .25.25.5.5.5.1 0 .15-.05.25-.05C3.1 20.45 5.05 20 6.5 20c1.95 0 4.05.4 5.5 1.5 1.35-.85 3.8-1.5 5.5-1.5 1.65 0 3.35.3 4.75 1.05.1.05.15.05.25.05.25 0 .5-.25.5-.5V6c-.6-.45-1.25-.75-2-1zm0 13.5c-1.1-.35-2.3-.5-3.5-.5-1.7 0-4.15.65-5.5 1.5V8c1.35-.85 3.8-1.5 5.5-1.5 1.2 0 2.4.15 3.5.5v11.5z"/></svg>`;
  const svgPlay = `<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>`;
  const svgPause = `<svg viewBox="0 0 24 24"><path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/></svg>`;
  const svgTop = `<svg viewBox="0 0 24 24"><path d="M4 4h16v2H4V4zm4 8l1.41 1.41L11 11.83V22h2V11.83l1.59 1.58L16 12l-4-4-4 4z"/></svg>`;
  const SETTINGS = [
    { label: "Show Control", key: "showControl" },
    { label: "Auto Scroll", key: "autoScroll" },
    { label: "Auto Enter Reader", key: "autoEnterSinglePage" }
  ];
  function createSettingsPanel() {
    const settingsBtn = document.createElement("div");
    settingsBtn.className = "settings-btn";
    const settingsPanel = document.createElement("div");
    settingsPanel.className = "settings-panel";
    SETTINGS.forEach(({ label, key }) => {
      const item = document.createElement("div");
      item.className = "settings-item";
      const labelEl = document.createElement("span");
      labelEl.className = "settings-label";
      labelEl.textContent = label;
      const toggle = document.createElement("div");
      toggle.className = `toggle-switch${store.settings[key] ? " on" : ""}`;
      const slider = document.createElement("div");
      slider.className = "toggle-slider";
      toggle.appendChild(slider);
      toggle.onclick = () => {
        const newValue = !store.settings[key];
        store.updateSetting(key, newValue);
        toggle.classList.toggle("on", newValue);
      };
      item.appendChild(labelEl);
      item.appendChild(toggle);
      settingsPanel.appendChild(item);
    });
    const intervalItem = document.createElement("div");
    intervalItem.className = "settings-item";
    const intervalLabel = document.createElement("span");
    intervalLabel.className = "settings-label";
    intervalLabel.textContent = "Play Interval";
    const intervalRight = document.createElement("div");
    intervalRight.style.cssText = "display:flex;align-items:center;gap:4px;";
    const intervalInput = document.createElement("input");
    intervalInput.type = "number";
    intervalInput.className = "interval-input";
    intervalInput.min = "1";
    intervalInput.max = "60";
    intervalInput.step = "0.5";
    intervalInput.value = String(store.settings.autoPlayInterval / 1e3);
    intervalInput.onclick = (e) => e.stopPropagation();
    intervalInput.onchange = (e) => {
      const value = parseFloat(e.target.value);
      if (!isNaN(value) && value >= 1 && value <= 60) {
        store.updateSetting("autoPlayInterval", value * 1e3);
      }
    };
    const intervalUnit = document.createElement("span");
    intervalUnit.textContent = "s";
    intervalUnit.style.cssText = "font-size:12px;color:#888;";
    intervalRight.appendChild(intervalInput);
    intervalRight.appendChild(intervalUnit);
    intervalItem.appendChild(intervalLabel);
    intervalItem.appendChild(intervalRight);
    settingsPanel.appendChild(intervalItem);
    settingsBtn.onclick = (e) => {
      e.stopPropagation();
      settingsPanel.classList.toggle("show");
    };
    document.addEventListener("click", (e) => {
      if (!settingsPanel.contains(e.target) && !settingsBtn.contains(e.target)) {
        settingsPanel.classList.remove("show");
      }
    });
    return {
      getButtonElement: () => settingsBtn,
      getPanelElement: () => settingsPanel
    };
  }
  function createFloatControl(spmHandle) {
    const floatControl = document.createElement("div");
    floatControl.className = `float-control${store.settings.showControl ? "" : " hidden"}`;
    const autoPlayBtn = document.createElement("div");
    autoPlayBtn.className = `side-btn auto-play-btn hidden${store.settings.autoPlay ? " active" : ""}`;
    autoPlayBtn.innerHTML = store.settings.autoPlay ? svgPause : svgPlay;
    autoPlayBtn.title = "Auto Play";
    autoPlayBtn.onclick = (e) => {
      e.stopPropagation();
      const newValue = !store.settings.autoPlay;
      store.updateSetting("autoPlay", newValue);
      autoPlayBtn.innerHTML = newValue ? svgPause : svgPlay;
      autoPlayBtn.classList.toggle("active", newValue);
    };
    const circleControl = document.createElement("div");
    circleControl.className = "circle-control";
    circleControl.innerHTML = svgReader;
    circleControl.title = "Reader Mode";
    circleControl.onclick = (e) => {
      var _a;
      if (e.target !== circleControl && !((_a = circleControl.querySelector("svg")) == null ? void 0 : _a.contains(e.target))) return;
      if (spmHandle.isActive()) {
        spmHandle.close();
        autoPlayBtn.classList.add("hidden");
      } else {
        spmHandle.open();
        autoPlayBtn.classList.remove("hidden");
        autoPlayBtn.innerHTML = store.settings.autoPlay ? svgPause : svgPlay;
        autoPlayBtn.classList.toggle("active", store.settings.autoPlay);
      }
    };
    const settings = createSettingsPanel();
    const settingsBtn = settings.getButtonElement();
    settingsBtn.className = "side-btn";
    settingsBtn.innerHTML = svgSettings;
    settingsBtn.title = "Settings";
    const topBtn = document.createElement("div");
    topBtn.className = "side-btn top-btn";
    topBtn.innerHTML = svgTop;
    topBtn.title = "Back to Top";
    topBtn.onclick = (e) => {
      e.stopPropagation();
      if (spmHandle.isActive()) {
        spmHandle.jumpTo(0);
      } else {
        window.scrollTo({ top: 0, behavior: "smooth" });
      }
    };
    circleControl.appendChild(topBtn);
    floatControl.appendChild(autoPlayBtn);
    floatControl.appendChild(circleControl);
    floatControl.appendChild(settingsBtn);
    floatControl.appendChild(settings.getPanelElement());
    document.body.appendChild(floatControl);
  }
  function registerMenuCommands() {
    _GM_registerMenuCommand("Toggle Auto Scroll", () => {
      store.updateSetting("autoScroll", !store.settings.autoScroll);
      alert(`Auto Scroll ${store.settings.autoScroll ? "Enabled" : "Disabled"}`);
      location.reload();
    });
    _GM_registerMenuCommand("Toggle Control Display", () => {
      store.updateSetting("showControl", !store.settings.showControl);
      alert(`Control Display ${store.settings.showControl ? "Enabled" : "Disabled"}`);
      location.reload();
    });
    _GM_registerMenuCommand("Toggle Auto Enter Single Page", () => {
      store.updateSetting("autoEnterSinglePage", !store.settings.autoEnterSinglePage);
      alert(`Auto Enter Single Page ${store.settings.autoEnterSinglePage ? "Enabled" : "Disabled"}`);
      location.reload();
    });
  }
  (function main() {
    hideOriginalElements();
    const mainBox = document.querySelector("#gdt");
    if (!mainBox) return;
    const urlP = new URLSearchParams(window.location.search).get("p");
    store.currPage = urlP ? parseInt(urlP) + 1 : 1;
    const initLinks = Array.from(qa("#gdt a", document)).map((a) => a.href);
    const galleryId = window.location.pathname;
    const savedTotal = localStorage.getItem(`eh_total_${galleryId}`);
    if (savedTotal && parseInt(savedTotal) > 0) {
      store.totalPage = parseInt(savedTotal);
    } else {
      store.totalPage = calcTotal(document, initLinks.length);
      localStorage.setItem(`eh_total_${galleryId}`, String(store.totalPage));
    }
    store.nextUrl = getNextUrl(document);
    mainBox.innerHTML = "";
    processBatch(initLinks, store.currPage);
    let spmHandle;
    createFloatControl({
      open: () => spmHandle.open(),
      close: () => spmHandle.close(),
      isActive: () => spmHandle.isActive(),
      getOverlayElement: () => spmHandle.getOverlayElement(),
      jumpTo: (index) => spmHandle.jumpTo(index)
    });
    spmHandle = initSinglePageMode();
    setupAutoScroll();
    setupPrefetchListener();
    registerMenuCommands();
    if (store.settings.autoEnterSinglePage) {
      setTimeout(() => spmHandle.open(), 1e3);
    }
  })();

})();