A high-performance universal reader for image galleries, now with mobile and touch device support, plus infinite scroll, immersive reader mode, and smart image loading, with ongoing multi-site support (E-Hentai, 18comic, 4KHD).
// ==UserScript==
// @name Hentai Reader
// @name:zh-CN 绅士阅读器
// @namespace http://tampermonkey.net/
// @version 3.2.0
// @author Leovikii
// @description A high-performance universal reader for image galleries, now with mobile and touch device support, plus infinite scroll, immersive reader mode, and smart image loading, with ongoing multi-site support (E-Hentai, 18comic, 4KHD).
// @description:zh-CN 面向图库的高性能通用阅读器,现已适配移动端与触控设备。支持无限卷轴、沉浸式阅读器与智能图片加载,多网站持续适配(E-Hentai、禁漫天堂、4KHD)
// @license MIT
// @icon data:image/png;base64,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
// @homepageURL https://github.com/Leovikii/Hentai-Reader
// @match https://e-hentai.org/g/*
// @match https://exhentai.org/g/*
// @match https://e-hentai.org/s/*
// @match https://exhentai.org/s/*
// @match *://*.4khd.com/*
// @match *://*.xxtt.ink/*
// @match *://*.uuss.uk/*
// @match *://*.ssuu.uk/*
// @match *://*.18comic.vip/*
// @match *://*.18comic.ink/*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant unsafeWindow
// ==/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));};
const photoswipeCss = "/*! PhotoSwipe main CSS by Dmytro Semenov | photoswipe.com */.pswp{--pswp-bg: #000;--pswp-placeholder-bg: #222;--pswp-root-z-index: 100000;--pswp-preloader-color: rgba(79, 79, 79, .4);--pswp-preloader-color-secondary: rgba(255, 255, 255, .9);--pswp-icon-color: #fff;--pswp-icon-color-secondary: #4f4f4f;--pswp-icon-stroke-color: #4f4f4f;--pswp-icon-stroke-width: 2px;--pswp-error-text-color: var(--pswp-icon-color)}.pswp{position:fixed;top:0;left:0;width:100%;height:100%;z-index:var(--pswp-root-z-index);display:none;touch-action:none;outline:0;opacity:.003;contain:layout style size;-webkit-tap-highlight-color:rgba(0,0,0,0)}.pswp:focus{outline:0}.pswp *{box-sizing:border-box}.pswp img{max-width:none}.pswp--open{display:block}.pswp,.pswp__bg{transform:translateZ(0);will-change:opacity}.pswp__bg{opacity:.005;background:var(--pswp-bg)}.pswp,.pswp__scroll-wrap{overflow:hidden}.pswp__scroll-wrap,.pswp__bg,.pswp__container,.pswp__item,.pswp__content,.pswp__img,.pswp__zoom-wrap{position:absolute;top:0;left:0;width:100%;height:100%}.pswp__img,.pswp__zoom-wrap{width:auto;height:auto}.pswp--click-to-zoom.pswp--zoom-allowed .pswp__img{cursor:-webkit-zoom-in;cursor:-moz-zoom-in;cursor:zoom-in}.pswp--click-to-zoom.pswp--zoomed-in .pswp__img{cursor:move;cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.pswp--click-to-zoom.pswp--zoomed-in .pswp__img:active{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img,.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img:active,.pswp__img{cursor:-webkit-zoom-out;cursor:-moz-zoom-out;cursor:zoom-out}.pswp__container,.pswp__img,.pswp__button,.pswp__counter{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pswp__item{z-index:1;overflow:hidden}.pswp__hidden{display:none!important}.pswp__content{pointer-events:none}.pswp__content>*{pointer-events:auto}.pswp__error-msg-container{display:grid}.pswp__error-msg{margin:auto;font-size:1em;line-height:1;color:var(--pswp-error-text-color)}.pswp .pswp__hide-on-close{opacity:.005;will-change:opacity;transition:opacity var(--pswp-transition-duration) cubic-bezier(.4,0,.22,1);z-index:10;pointer-events:none}.pswp--ui-visible .pswp__hide-on-close{opacity:1;pointer-events:auto}.pswp__button{position:relative;display:block;width:50px;height:60px;padding:0;margin:0;overflow:hidden;cursor:pointer;background:none;border:0;box-shadow:none;opacity:.85;-webkit-appearance:none;-webkit-touch-callout:none}.pswp__button:hover,.pswp__button:active,.pswp__button:focus{transition:none;padding:0;background:none;border:0;box-shadow:none;opacity:1}.pswp__button:disabled{opacity:.3;cursor:auto}.pswp__icn{fill:var(--pswp-icon-color);color:var(--pswp-icon-color-secondary)}.pswp__icn{position:absolute;top:14px;left:9px;width:32px;height:32px;overflow:hidden;pointer-events:none}.pswp__icn-shadow{stroke:var(--pswp-icon-stroke-color);stroke-width:var(--pswp-icon-stroke-width);fill:none}.pswp__icn:focus{outline:0}div.pswp__img--placeholder,.pswp__img--with-bg{background:var(--pswp-placeholder-bg)}.pswp__top-bar{position:absolute;left:0;top:0;width:100%;height:60px;display:flex;flex-direction:row;justify-content:flex-end;z-index:10;pointer-events:none!important}.pswp__top-bar>*{pointer-events:auto;will-change:opacity}.pswp__button--close{margin-right:6px}.pswp__button--arrow{position:absolute;width:75px;height:100px;top:50%;margin-top:-50px}.pswp__button--arrow:disabled{display:none;cursor:default}.pswp__button--arrow .pswp__icn{top:50%;margin-top:-30px;width:60px;height:60px;background:none;border-radius:0}.pswp--one-slide .pswp__button--arrow{display:none}.pswp--touch .pswp__button--arrow{visibility:hidden}.pswp--has_mouse .pswp__button--arrow{visibility:visible}.pswp__button--arrow--prev{right:auto;left:0}.pswp__button--arrow--next{right:0}.pswp__button--arrow--next .pswp__icn{left:auto;right:14px;transform:scaleX(-1)}.pswp__button--zoom{display:none}.pswp--zoom-allowed .pswp__button--zoom{display:block}.pswp--zoomed-in .pswp__zoom-icn-bar-v{display:none}.pswp__preloader{position:relative;overflow:hidden;width:50px;height:60px;margin-right:auto}.pswp__preloader .pswp__icn{opacity:0;transition:opacity .2s linear;animation:pswp-clockwise .6s linear infinite}.pswp__preloader--active .pswp__icn{opacity:.85}@keyframes pswp-clockwise{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pswp__counter{height:30px;margin-top:15px;margin-inline-start:20px;font-size:14px;line-height:30px;color:var(--pswp-icon-color);text-shadow:1px 1px 3px var(--pswp-icon-color-secondary);opacity:.85}.pswp--one-slide .pswp__counter{display:none}";
importCSS(photoswipeCss);
const globalCss = ":root{--hr-accent: #F596AA;--hr-accent-hover: #F7ABBE}html.hr-instant-scroll,html.hr-instant-scroll body{scroll-behavior:auto!important}@keyframes hr-spin{to{transform:rotate(360deg)}}.hr-spinner{animation:hr-spin 1s linear infinite}html.scroll-mode body{margin:0}html.scroll-mode #gdt{display:flex;flex-direction:column;align-items:center;width:100%;max-width:1200px;margin:auto;padding-bottom:100px}.hr-page-batch{width:100%;display:flex;flex-direction:column;align-items:center;margin-bottom:60px}html.hr-reader-open .hr-page-batch{visibility:hidden}.r-img{display:block;width:auto;max-width:100%;margin-bottom:20px;background:transparent;box-shadow:0 0 20px #00000080;position:relative;content-visibility:auto;contain-intrinsic-size:auto 100vh}.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;content-visibility:auto;contain-intrinsic-size:auto 400px}.r-ph.loading{color:#888;border-color:#555}.r-ph.error{color:#d44;border-color:#d44}";
importCSS(globalCss);
var _GM_getValue = (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
var _GM_setValue = (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
const CFG = {
nextPage: "3000px 0px",
maxConcurrent: 3
};
function loadSettings(adapterName) {
const prefix = adapterName ? `${adapterName}_` : "";
const is4KHD = adapterName === "4KHD";
const globalScrollMode = _GM_getValue("scrollMode", true);
return {
scrollMode: _GM_getValue(`${prefix}scrollMode`, is4KHD ? true : globalScrollMode),
showControl: _GM_getValue("showControl", true),
autoEnterSinglePage: _GM_getValue("autoEnterSinglePage", false),
clickToEnterReader: _GM_getValue("clickToEnterReader", true),
autoPlayInterval: _GM_getValue("autoPlayInterval", 5e3),
thumbnailPosition: _GM_getValue("thumbnailPosition", "bottom")
};
}
class Store {
constructor() {
this.listeners = new Map();
this.activeAdapter = null;
this.currPage = 1;
this.perPage = 20;
this.imageOffset = 0;
this.nextUrl = null;
this.prevUrl = null;
this.isFetching = false;
this.galleryItems = [];
this.autoPlay = false;
this._settings = loadSettings();
}
get settings() {
return this._settings;
}
reloadSettings() {
var _a;
this._settings = loadSettings((_a = this.activeAdapter) == null ? void 0 : _a.name);
}
updateSetting(key, value) {
this._settings[key] = value;
if (key === "scrollMode" && this.activeAdapter) {
_GM_setValue(`${this.activeAdapter.name}_scrollMode`, value);
} else {
_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);
return () => {
const listeners = this.listeners.get(event);
listeners == null ? void 0 : listeners.delete(listener);
if ((listeners == null ? void 0 : listeners.size) === 0) this.listeners.delete(event);
};
}
emit(event) {
var _a;
(_a = this.listeners.get(event)) == null ? void 0 : _a.forEach((fn) => fn());
}
}
const store = new Store();
const systemClock = {
now: () => Date.now(),
setTimeout: (callback, delay) => setTimeout(callback, delay),
clearTimeout: (id) => clearTimeout(id)
};
class NetLimiter {
constructor(maxConcurrent, clock = systemClock) {
this.queue = [];
this.active = 0;
this.pausedUntil = 0;
this.seq = 0;
this.pauseTimer = null;
this.maxConcurrent = maxConcurrent;
this.clock = clock;
}
run(task, priority = 0) {
return new Promise((resolve, reject) => {
this.queue.push({
run: task,
resolve,
reject,
priority,
seq: this.seq++
});
this.pump();
});
}
pauseFor(ms) {
const until = this.clock.now() + ms;
if (until <= this.pausedUntil) return;
this.pausedUntil = until;
this.schedulePauseWake(true);
}
cancel(predicate) {
if (this.queue.length === 0) return;
const kept = [];
for (const job of this.queue) {
if (predicate(job.priority, job.seq)) {
job.reject({ cancelled: true });
} else {
kept.push(job);
}
}
this.queue = kept;
}
pump() {
const now = this.clock.now();
if (now < this.pausedUntil) {
this.schedulePauseWake();
return;
}
if (this.pauseTimer !== null) {
this.clock.clearTimeout(this.pauseTimer);
this.pauseTimer = null;
}
while (this.active < this.maxConcurrent && this.queue.length > 0) {
let bestIdx = 0;
for (let i = 1; i < this.queue.length; i++) {
const cand = this.queue[i];
const best = this.queue[bestIdx];
if (cand.priority > best.priority || cand.priority === best.priority && cand.seq < best.seq) {
bestIdx = i;
}
}
const job = this.queue.splice(bestIdx, 1)[0];
this.active++;
Promise.resolve().then(() => job.run()).then(
(value) => {
job.resolve(value);
this.active--;
this.pump();
},
(reason) => {
job.reject(reason);
this.active--;
this.pump();
}
);
}
}
schedulePauseWake(reschedule = false) {
if (reschedule && this.pauseTimer !== null) {
this.clock.clearTimeout(this.pauseTimer);
this.pauseTimer = null;
}
if (this.pauseTimer !== null) return;
const delay = Math.max(0, this.pausedUntil - this.clock.now() + 10);
this.pauseTimer = this.clock.setTimeout(() => {
this.pauseTimer = null;
this.pump();
}, delay);
}
}
const LOAD_PRIORITY = {
scroll: 0,
warmup: 5,
thumbnail: 10,
pageHtml: 50,
foreground: 100
};
function paginationUrl(doc, marker) {
const pagination = doc.querySelector(".ptt");
if (!pagination) return null;
const link = Array.from(pagination.querySelectorAll("td a")).find((anchor) => (anchor.textContent ?? "").includes(marker));
return (link == null ? void 0 : link.href) ?? null;
}
function getEHentaiNextUrl(doc) {
return paginationUrl(doc, ">");
}
function getEHentaiPrevUrl(doc) {
return paginationUrl(doc, "<");
}
function extractEHentaiItems(doc) {
return Array.from(doc.querySelectorAll("#gdt a")).map((element) => {
const anchor = element;
const url = anchor.href;
let thumb;
let thumbX;
let thumbY;
let thumbW;
let thumbH;
const sprite = anchor.querySelector('div[style*="background"]');
if (sprite) {
const style = sprite.getAttribute("style") || "";
const source = style.match(/url\(['"]?([^)'"]+)['"]?\)/);
if (source) thumb = source[1];
const afterUrl = style.slice(style.indexOf(source ? source[0] : "") + (source ? source[0].length : 0));
const position = afterUrl.match(/(-?\d+)(?:px)?\s+(-?\d+)(?:px)?/);
if (position) {
thumbX = Math.abs(parseInt(position[1], 10));
thumbY = Math.abs(parseInt(position[2], 10));
}
const width = style.match(/width:\s*(\d+)px/);
const height = style.match(/height:\s*(\d+)px/);
if (width) thumbW = parseInt(width[1], 10);
if (height) thumbH = parseInt(height[1], 10);
} else {
const img = anchor.querySelector("img");
if ((img == null ? void 0 : img.src) && !img.src.endsWith("x.gif")) thumb = img.src;
}
let preview = { kind: "none" };
if (thumb && thumbW !== void 0 && thumbH !== void 0 && (thumbX !== void 0 || thumbY !== void 0)) {
preview = {
kind: "sprite",
src: thumb,
crop: {
x: thumbX ?? 0,
y: thumbY ?? 0,
width: thumbW,
height: thumbH
}
};
} else if (thumb) {
preview = {
kind: "url",
src: thumb,
...thumbW !== void 0 && thumbH !== void 0 ? { size: { width: thumbW, height: thumbH } } : {}
};
}
return { key: url, viewerUrl: url, preview };
});
}
function parseEHentaiPageMetadata(doc, itemCount) {
var _a;
let totalPages = 1;
for (const cell of Array.from(doc.querySelectorAll(".ptt td"))) {
const page = parseInt(cell.textContent ?? "", 10);
if (!Number.isNaN(page) && page > totalPages) totalPages = page;
}
const range = ((_a = doc.querySelector(".gpc")) == null ? void 0 : _a.textContent) ?? "";
const match = range.match(/([\d,]+)\s*-\s*([\d,]+)[^\d]+([\d,]+)/);
if (!match) return { totalPages };
const start = parseInt(match[1].replace(/,/g, ""), 10);
const end = parseInt(match[2].replace(/,/g, ""), 10);
const total = parseInt(match[3].replace(/,/g, ""), 10);
const imageOffset = start - 1;
let perPage;
if (start === 1) perPage = end;
else if (end < total) perPage = end - start + 1;
else if (totalPages > 1) perPage = Math.round(imageOffset / (totalPages - 1));
else perPage = itemCount;
return { totalPages, imageOffset, perPage };
}
function buildEHentaiViewerUrl(url, nlToken) {
if (!nlToken) return url;
return `${url}${url.includes("?") ? "&" : "?"}nl=${nlToken}`;
}
function parseEHentaiViewer(doc) {
var _a;
const image = doc.querySelector("#img");
if (!(image == null ? void 0 : image.src)) return null;
const onerror = image.getAttribute("onerror") || "";
const token = (_a = onerror.match(/nl\(['"]([^'"]+)['"]\)/)) == null ? void 0 : _a[1];
return { src: image.src, ...token ? { nl: token } : {} };
}
const parser$1 = new DOMParser();
const PAGE_FETCH_PRIORITY = LOAD_PRIORITY.pageHtml;
const ehLimiter = new NetLimiter(CFG.maxConcurrent);
function limitedFetch(url, priority = 0, signal) {
return ehLimiter.run(() => fetch(url, { signal }), priority);
}
const EHentaiAdapter = {
name: "E-Hentai/ExHentai",
match(url) {
return /https?:\/\/(e-|ex)hentai\.org\/(g|s)\//.test(url);
},
async loadInitialPage(doc, url) {
const items = extractEHentaiItems(doc);
const metadata = parseEHentaiPageMetadata(doc, items.length);
return {
pageUrl: url,
items,
nextUrl: getEHentaiNextUrl(doc),
prevUrl: getEHentaiPrevUrl(doc),
totalPages: metadata.totalPages,
position: {
startIndex: metadata.imageOffset ?? 0,
pageSize: metadata.perPage ?? items.length
}
};
},
async resolveImage(url, nlToken, priority = 0) {
const response = await limitedFetch(buildEHentaiViewerUrl(url, nlToken), priority);
if (response.status === 429 || response.status === 503) {
ehLimiter.pauseFor(5e3);
throw new Error(`Rate limited: HTTP ${response.status}`);
}
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const html = await response.text();
const doc = parser$1.parseFromString(html, "text/html");
const viewer = parseEHentaiViewer(doc);
if (!viewer) throw new Error("Image not found");
return viewer;
},
async loadPage(url, signal) {
const response = await limitedFetch(url, PAGE_FETCH_PRIORITY, signal);
if (!response.ok) throw new Error("Failed to fetch page");
const html = await response.text();
const doc = parser$1.parseFromString(html, "text/html");
return {
pageUrl: url,
items: extractEHentaiItems(doc),
nextUrl: getEHentaiNextUrl(doc),
prevUrl: getEHentaiPrevUrl(doc)
};
},
getContainer() {
return document.querySelector("#gdt") || document.querySelector(".gm");
},
hideOriginalElements() {
const hiddenSelectors = [
".c1",
".c2",
".c3",
".c4",
".c5",
".c6",
".c7",
".c8",
".ptt",
".ptb",
".gdtl",
".gdtm",
"#gdo",
"#cdiv",
"table.itg"
];
document.querySelectorAll(hiddenSelectors.join(",")).forEach((element) => {
element.style.display = "none";
});
},
cancelPrefetch(_keepUrls) {
ehLimiter.cancel((priority) => priority <= 10);
},
onReaderClose(globalIndex, context) {
if (context.scrollMode) return;
const targetPage = Math.floor(globalIndex / context.pageSize);
const url = new URL(window.location.href);
const currentPage = parseInt(url.searchParams.get("p") || "0", 10);
if (targetPage !== currentPage) {
url.searchParams.set("p", String(targetPage));
window.location.href = url.toString();
}
}
};
function extract4KHDImages(doc) {
const images = Array.from(doc.querySelectorAll(
"figure.wp-block-image img, #basicExample img, .entry-content p img"
));
return images.map((image) => {
const img = image;
let src = img.getAttribute("data-src") || img.getAttribute("data-lazy-src") || img.src;
let thumb = src;
thumb = thumb.replace(/i\d\.wp\.com\//, "");
thumb = thumb.replace("pic.4khd.com", "img.4khd.com");
thumb = thumb.replace(/\/w\d+-rw\//, "/w300-h300-rw/");
src = src.replace(/i\d\.wp\.com\//, "");
src = src.replace("pic.4khd.com", "img.4khd.com");
src = src.replace(/\?.+$/, "");
src = src.replace(/\/w\d+-rw\//, "/w2500-h2500-rw/");
return {
key: src,
viewerUrl: src,
preview: { kind: "url", src: thumb }
};
}).filter((item) => item.viewerUrl && !item.viewerUrl.includes("avatar"));
}
function findPageLink(doc, direction, currentUrl) {
const pageBox = doc.querySelector(".page-link-box, .pagination, .nav-links, .nav-previous");
if (!pageBox) return null;
const current = pageBox.querySelector(".current, .active") || Array.from(pageBox.querySelectorAll("span")).find((span) => !span.querySelector("a"));
if (current) {
const currentPageNum = parseInt(current.textContent || "1", 10);
const targetPage = direction === "next" ? currentPageNum + 1 : currentPageNum - 1;
if (!Number.isNaN(currentPageNum) && targetPage >= 1) {
const link = Array.from(pageBox.querySelectorAll("a")).find(
(anchor) => parseInt(anchor.textContent || "0", 10) === targetPage
);
if ((link == null ? void 0 : link.href) && link.href !== currentUrl) return link.href;
}
}
const selector = direction === "next" ? "a.next, a.next.page-numbers" : "a.prev, a.prev.page-numbers";
const fallback = pageBox.querySelector(selector) || doc.querySelector(selector);
if ((fallback == null ? void 0 : fallback.href) && fallback.href !== currentUrl) return fallback.href;
return null;
}
function get4KHDNextUrl(doc, currentUrl) {
return findPageLink(doc, "next", currentUrl);
}
function get4KHDPrevUrl(doc, currentUrl) {
return findPageLink(doc, "prev", currentUrl);
}
const parser = new DOMParser();
const FourKHDAdapter = {
name: "4KHD",
match(url) {
return url.includes("4khd.com") || url.includes("xxtt.ink") || url.includes("uuss.uk") || url.includes("ssuu.uk");
},
async loadInitialPage(doc, url) {
const items = extract4KHDImages(doc);
const totalPage = 1;
const pageBox = doc.querySelector(".page-link-box, .pagination, .nav-links, .nav-previous");
let currentPageNum = 1;
if (pageBox) {
const current = pageBox.querySelector(".current, .active") || Array.from(pageBox.querySelectorAll("span")).find((s) => !s.querySelector("a"));
if (current) {
currentPageNum = parseInt(current.textContent || "1", 10);
if (isNaN(currentPageNum)) currentPageNum = 1;
}
}
const perPage = items.length > 0 ? items.length : 20;
return {
pageUrl: url,
items,
nextUrl: get4KHDNextUrl(doc, url),
prevUrl: get4KHDPrevUrl(doc, url),
totalPages: totalPage,
position: {
startIndex: (currentPageNum - 1) * perPage,
pageSize: perPage
}
};
},
async resolveImage(url) {
return { src: url };
},
async loadPage(url, signal) {
const response = await fetch(url, { signal });
if (!response.ok) throw new Error("Failed to fetch page");
const html = await response.text();
const doc = parser.parseFromString(html, "text/html");
const items = extract4KHDImages(doc);
return {
pageUrl: url,
items,
nextUrl: get4KHDNextUrl(doc, url),
prevUrl: get4KHDPrevUrl(doc, url)
};
},
getContainer() {
const entryContent = document.querySelector(".entry-content, .wp-block-post-content");
if (entryContent) return entryContent;
const basicExample = document.querySelector("#basicExample");
if (basicExample && basicExample.parentElement) return basicExample.parentElement;
return document.querySelector(".post-content");
},
hideOriginalElements() {
const HIDDEN_SELECTORS = [
".centbtd",
".popup",
".wp-container-13",
".popup-iframe",
"#basicExample",
".wp-block-image",
".page-link-box"
];
document.querySelectorAll(HIDDEN_SELECTORS.join(",")).forEach((el) => {
el.style.display = "none";
});
}
};
function isMaterializeData(value) {
if (!value || typeof value !== "object") return false;
const data = value;
return data.kind === "18comic-scramble" && typeof data.aid === "string" && typeof data.imageId === "string";
}
function resolve18ComicSource(url) {
const parsed = new URL(url);
const aid = parsed.searchParams.get("18aid");
const scrambleId = parsed.searchParams.get("18scid");
parsed.searchParams.delete("18aid");
parsed.searchParams.delete("18scid");
const src = parsed.toString();
if (src.includes(".gif") || !aid || !scrambleId || Number(aid) < Number(scrambleId)) {
return { src };
}
const fileName = parsed.pathname.split("/").pop() || "";
const imageId = fileName.split(".")[0];
if (!imageId) return { src };
return {
src,
materializeData: { kind: "18comic-scramble", aid, imageId }
};
}
async function materialize18ComicImage(resolved, signal, deps) {
if (!isMaterializeData(resolved.materializeData)) return resolved;
const sourceBlob = await deps.fetchBlob(resolved.src, signal);
if (signal.aborted) throw new DOMException("Materialization cancelled", "AbortError");
const bitmap = await deps.createBitmap(sourceBlob);
try {
const { aid, imageId } = resolved.materializeData;
const segments = deps.getSegmentCount(aid, imageId);
if (!segments) throw new Error("18comic segment count is unavailable");
if (segments <= 1) return { src: resolved.src };
const canvas = deps.createCanvas(bitmap.width, bitmap.height);
const context = canvas.getContext("2d");
if (!context) throw new Error("18comic 2D canvas context is unavailable");
context.fillStyle = "#ffffff";
context.fillRect(0, 0, bitmap.width, bitmap.height);
const remainder = bitmap.height % segments;
const segmentHeight = Math.floor(bitmap.height / segments);
let sourceY = bitmap.height - remainder - segmentHeight;
let destinationY = remainder + segmentHeight;
context.drawImage(
bitmap,
0,
sourceY,
bitmap.width,
remainder + segmentHeight,
0,
0,
bitmap.width,
remainder + segmentHeight
);
for (let index = 1; index < segments; index++) {
sourceY -= segmentHeight;
context.drawImage(
bitmap,
0,
sourceY,
bitmap.width,
segmentHeight,
0,
destinationY,
bitmap.width,
segmentHeight
);
destinationY += segmentHeight;
}
if (signal.aborted) throw new DOMException("Materialization cancelled", "AbortError");
const output = await canvas.convertToBlob({ type: "image/jpeg", quality: 0.85 });
return { src: deps.createObjectUrl(output), ownsObjectUrl: true };
} finally {
bitmap.close();
}
}
function create18ComicBrowserMaterializer(getSegmentCount) {
const deps = {
async fetchBlob(src, signal) {
const response = await fetch(src, { signal });
if (!response.ok) throw new Error(`Failed to fetch image: HTTP ${response.status}`);
return response.blob();
},
createBitmap: (blob) => createImageBitmap(blob),
createCanvas: (width, height) => new OffscreenCanvas(width, height),
createObjectUrl: (blob) => URL.createObjectURL(blob),
getSegmentCount
};
return (resolved, signal) => materialize18ComicImage(resolved, signal, deps);
}
function extractChapterItems(doc, pageUrl, useUnsafeFallback, suppressNativeDecode) {
const html = doc.documentElement.innerHTML;
const aidMatch = html.match(/aid\s*=\s*['"]?(\d+)['"]?/);
const scrambleMatch = html.match(/scramble_id\s*=\s*['"]?(\d+)['"]?/);
const aid = (aidMatch == null ? void 0 : aidMatch[1]) || (useUnsafeFallback && unsafeWindow.aid ? String(unsafeWindow.aid) : "");
const scrambleId = (scrambleMatch == null ? void 0 : scrambleMatch[1]) || (useUnsafeFallback && unsafeWindow.scramble_id ? String(unsafeWindow.scramble_id) : "");
const items = [];
const seenUrls = new Set();
const images = doc.querySelectorAll(".scramble-page img[id], .owl-item .center img[id]");
images.forEach((element) => {
const image = element;
const source = image.getAttribute("data-original") || image.getAttribute("data-src") || image.src;
if (!source) return;
const url = new URL(source, pageUrl);
if (aid) url.searchParams.set("18aid", aid);
if (scrambleId) url.searchParams.set("18scid", scrambleId);
const viewerUrl = url.toString();
if (!seenUrls.has(viewerUrl)) {
seenUrls.add(viewerUrl);
items.push({ key: viewerUrl, viewerUrl, preview: { kind: "none" } });
}
if (suppressNativeDecode) {
try {
image.getBoundingClientRect = () => ({
top: 999999,
left: 0,
right: 0,
bottom: 999999,
width: 0,
height: 0,
x: 0,
y: 0,
toJSON: () => {
}
});
} catch {
}
}
});
return items;
}
function getNextUrl(doc) {
var _a;
const active = doc.querySelector(".pagination li.active");
const pageLink = (_a = active == null ? void 0 : active.nextElementSibling) == null ? void 0 : _a.querySelector("a:not(.prevnext)");
if (pageLink) return pageLink.href;
const nextChapter = Array.from(doc.querySelectorAll('.menu-bolock-ul a[href^="/photo/"]')).find((anchor) => {
var _a2;
return (_a2 = anchor.textContent) == null ? void 0 : _a2.includes("涓嬩竴");
});
return nextChapter ? nextChapter.href : null;
}
function getPrevUrl(doc) {
var _a;
const active = doc.querySelector(".pagination li.active");
const pageLink = (_a = active == null ? void 0 : active.previousElementSibling) == null ? void 0 : _a.querySelector("a:not(.prevnext)");
if (pageLink) return pageLink.href;
const prevChapter = Array.from(doc.querySelectorAll('.menu-bolock-ul a[href^="/photo/"]')).find((anchor) => {
var _a2;
return (_a2 = anchor.textContent) == null ? void 0 : _a2.includes("涓婁竴");
});
return prevChapter ? prevChapter.href : null;
}
const materialize = create18ComicBrowserMaterializer((aid, imageId) => {
if (!unsafeWindow.get_num) return void 0;
return unsafeWindow.get_num(btoa(aid), btoa(imageId));
});
const Comic18Adapter = {
name: "18comic",
match(url) {
return url.includes("18comic.vip") || url.includes("18comic.ink");
},
async loadInitialPage(doc, pageUrl) {
return {
pageUrl,
items: extractChapterItems(doc, pageUrl, true, true),
nextUrl: getNextUrl(doc),
prevUrl: getPrevUrl(doc)
};
},
async loadPage(url, signal) {
const response = await fetch(url, { signal });
if (!response.ok) throw new Error(`Failed to fetch page: HTTP ${response.status}`);
const html = await response.text();
const doc = new DOMParser().parseFromString(html, "text/html");
return {
pageUrl: url,
items: extractChapterItems(doc, url, false, false),
nextUrl: getNextUrl(doc),
prevUrl: getPrevUrl(doc)
};
},
async resolveImage(url) {
try {
return resolve18ComicSource(url);
} catch {
return { src: url };
}
},
async materializeImage(resolved, signal) {
return materialize(resolved, signal);
},
getContainer() {
return document.querySelector(".scramble-page") || document.body;
},
hideOriginalElements() {
const pages = Array.from(document.querySelectorAll(".scramble-page"));
for (let index = 1; index < pages.length; index++) pages[index].remove();
document.querySelectorAll(".owl-carousel").forEach((element) => element.remove());
}
};
const adapters = [EHentaiAdapter, FourKHDAdapter, Comic18Adapter];
const SiteManager = {
register(adapter) {
adapters.push(adapter);
},
getAdapter(url, doc = document) {
return adapters.find((a) => a.match(url, doc)) || null;
}
};
class EmptyGalleryPageError extends Error {
constructor(url) {
super(`Gallery page has no items: ${url}`);
this.name = "EmptyGalleryPageError";
}
}
class GalleryPageLoader {
constructor(adapter) {
this.inFlight = new Map();
this.loadedUrls = new Set();
this.adapter = adapter;
}
async loadInitialPage(doc, url) {
const page = this.normalize(url, await this.adapter.loadInitialPage(doc, url));
this.markLoaded(url, page.pageUrl);
return page;
}
loadPage(url, signal) {
if (this.loadedUrls.has(url)) return Promise.resolve(null);
const existing = this.inFlight.get(url);
if (existing) return existing;
const request = this.adapter.loadPage(url, signal).then((page) => {
if (page.items.length === 0) throw new EmptyGalleryPageError(url);
const normalized = this.normalize(url, page);
this.markLoaded(url, normalized.pageUrl);
return normalized;
}).finally(() => {
this.inFlight.delete(url);
});
this.inFlight.set(url, request);
return request;
}
hasLoaded(url) {
return this.loadedUrls.has(url);
}
markLoaded(...urls) {
for (const url of urls) {
if (url) this.loadedUrls.add(url);
}
}
normalize(requestedUrl, page) {
const isLoop = (url) => !!url && (url === requestedUrl || url === page.pageUrl || this.loadedUrls.has(url));
return {
...page,
nextUrl: isLoop(page.nextUrl) ? null : page.nextUrl,
prevUrl: isLoop(page.prevUrl) ? null : page.prevUrl
};
}
}
const defaultPolicy = {
resolveAttempts: 4,
nodeRetries: 3,
plainRetries: 2,
retryDelay: 1e3,
cacheEntries: 80
};
class ImageLoadService {
constructor(deps, policy = {}) {
this.active = new Map();
this.cache = new Map();
this.phases = new Map();
this.listeners = new Map();
this.deps = deps;
this.policy = { ...defaultPolicy, ...policy };
}
acquire(url, options) {
var _a, _b;
const token = Symbol(options.intent);
const cached = this.cache.get(url);
if (cached) {
this.touchCache(url, cached);
cached.leases.add(token);
this.setPhase(url, "ready");
return this.createLease(url, token, Promise.resolve(cached.asset));
}
let load = this.active.get(url);
if (load) {
load.leases.add(token);
if (options.priority > load.priority) {
load.priority = options.priority;
(_b = (_a = this.deps).promote) == null ? void 0 : _b.call(_a, url, options.priority);
}
return this.createLease(url, token, load.result);
}
const controller = new AbortController();
load = {
controller,
leases: new Set([token]),
priority: options.priority,
result: Promise.resolve(null)
};
load.result = this.run(url, load).finally(() => {
if (this.active.get(url) === load) this.active.delete(url);
});
this.active.set(url, load);
return this.createLease(url, token, load.result);
}
getPhase(url) {
return this.phases.get(url) ?? "idle";
}
getCached(url) {
var _a;
return (_a = this.cache.get(url)) == null ? void 0 : _a.asset;
}
getLatestCached() {
let latest;
for (const entry of this.cache.values()) latest = entry.asset;
return latest;
}
getStats() {
let activeLeases = 0;
let cachedLeases = 0;
let leasedCacheEntries = 0;
let ownedObjectUrls = 0;
let listeners = 0;
for (const load of this.active.values()) activeLeases += load.leases.size;
for (const entry of this.cache.values()) {
cachedLeases += entry.leases.size;
if (entry.leases.size > 0) leasedCacheEntries++;
if (entry.asset.ownsObjectUrl) ownedObjectUrls++;
}
for (const subscribers of this.listeners.values()) listeners += subscribers.size;
return {
activeLoads: this.active.size,
cachedEntries: this.cache.size,
activeLeases,
cachedLeases,
leasedCacheEntries,
ownedObjectUrls,
phases: this.phases.size,
listeners
};
}
async run(url, load) {
var _a, _b, _c, _d;
const signal = load.controller.signal;
let resolved = null;
for (let attempt = 0; attempt < this.policy.resolveAttempts && !signal.aborted; attempt++) {
this.setPhase(url, "resolving");
const candidate = await this.resolve(url, void 0, attempt > 0, load.priority);
resolved = await this.materialize(url, candidate, load);
if (resolved == null ? void 0 : resolved.src) break;
if (attempt + 1 < this.policy.resolveAttempts) await this.wait(signal);
}
if (!(resolved == null ? void 0 : resolved.src) || signal.aborted) {
this.discard(url, resolved);
return this.finishWithoutAsset(url, signal);
}
let loaded = await this.tryLoad(url, resolved, load);
let currentNl = resolved.nl;
for (let attempt = 0; !loaded && currentNl && attempt < this.policy.nodeRetries && !signal.aborted; attempt++) {
const candidate = await this.resolve(url, currentNl, true, load.priority);
const next = await this.materialize(url, candidate, load);
if (!(next == null ? void 0 : next.src)) break;
this.discard(url, resolved);
currentNl = next.nl;
resolved = next;
loaded = await this.tryLoad(url, next, load);
}
for (let attempt = 0; !loaded && attempt < this.policy.plainRetries && !signal.aborted; attempt++) {
await this.wait(signal);
if (signal.aborted) break;
const candidate = await this.resolve(url, void 0, true, load.priority);
const next = await this.materialize(url, candidate, load);
if (!(next == null ? void 0 : next.src)) continue;
this.discard(url, resolved);
resolved = next;
loaded = await this.tryLoad(url, next, load);
}
if (!loaded || signal.aborted) {
this.discard(url, resolved);
return this.finishWithoutAsset(url, signal);
}
const asset = {
...resolved,
width: loaded.width,
height: loaded.height
};
this.cache.set(url, { asset, leases: load.leases });
(_b = (_a = this.deps).setResolvedSource) == null ? void 0 : _b.call(_a, url, asset.src);
try {
(_d = (_c = this.deps).onReady) == null ? void 0 : _d.call(_c, url, asset);
} catch {
}
this.touchCache(url, this.cache.get(url));
this.setPhase(url, "ready");
this.trimCache();
return asset;
}
async tryLoad(url, resolved, load) {
if (load.controller.signal.aborted) return null;
this.setPhase(url, "downloading");
try {
return await this.deps.loadBytes(resolved.src, load.controller.signal);
} catch {
return null;
}
}
async resolve(url, nlToken, force, priority) {
try {
return await this.deps.resolve(url, nlToken, force, priority);
} catch {
return null;
}
}
async materialize(url, resolved, load) {
if (!(resolved == null ? void 0 : resolved.src) || load.controller.signal.aborted) return resolved;
if (!this.deps.materialize) return resolved;
try {
const materialized = await this.deps.materialize(
url,
resolved,
load.controller.signal,
load.priority
);
if (!materialized) this.discard(url, resolved);
return materialized;
} catch {
this.discard(url, resolved);
return null;
}
}
discard(url, resolved) {
var _a, _b, _c, _d;
if (!(resolved == null ? void 0 : resolved.ownsObjectUrl)) return;
(_b = (_a = this.deps).invalidateResolved) == null ? void 0 : _b.call(_a, url, resolved.src);
(_d = (_c = this.deps).revokeObjectUrl) == null ? void 0 : _d.call(_c, resolved.src);
}
finishWithoutAsset(url, signal) {
this.setPhase(url, signal.aborted ? "cancelled" : "error");
return null;
}
async wait(signal) {
if (signal.aborted) return;
const delay = this.deps.delay ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
await new Promise((resolve) => {
const finish = () => {
signal.removeEventListener("abort", finish);
resolve();
};
signal.addEventListener("abort", finish, { once: true });
delay(this.policy.retryDelay).then(finish, finish);
});
}
createLease(url, token, result) {
let released = false;
const ownedListeners = new Set();
const service = this;
return {
url,
result,
get phase() {
return service.getPhase(url);
},
subscribe(listener) {
let listeners = service.listeners.get(url);
if (!listeners) {
listeners = new Set();
service.listeners.set(url, listeners);
}
listeners.add(listener);
ownedListeners.add(listener);
listener(service.getPhase(url));
return () => {
listeners.delete(listener);
ownedListeners.delete(listener);
if (listeners.size === 0) service.listeners.delete(url);
};
},
release() {
var _a, _b;
if (released) return;
released = true;
for (const listener of ownedListeners) (_a = service.listeners.get(url)) == null ? void 0 : _a.delete(listener);
ownedListeners.clear();
if (((_b = service.listeners.get(url)) == null ? void 0 : _b.size) === 0) service.listeners.delete(url);
service.release(url, token);
}
};
}
release(url, token) {
const active = this.active.get(url);
if ((active == null ? void 0 : active.leases.delete(token)) && active.leases.size === 0) {
active.controller.abort();
this.active.delete(url);
this.setPhase(url, "cancelled");
return;
}
const cached = this.cache.get(url);
if (cached == null ? void 0 : cached.leases.delete(token)) this.trimCache();
}
setPhase(url, phase) {
var _a;
if (this.phases.get(url) === phase) return;
this.phases.set(url, phase);
(_a = this.listeners.get(url)) == null ? void 0 : _a.forEach((listener) => listener(phase));
}
touchCache(url, entry) {
this.cache.delete(url);
this.cache.set(url, entry);
}
trimCache() {
var _a, _b, _c, _d, _e, _f;
if (this.cache.size <= this.policy.cacheEntries) return;
for (const [url, entry] of this.cache) {
if (this.cache.size <= this.policy.cacheEntries) break;
if (entry.leases.size > 0) continue;
this.cache.delete(url);
(_b = (_a = this.deps).invalidateResolved) == null ? void 0 : _b.call(_a, url, entry.asset.src);
(_d = (_c = this.deps).onEvict) == null ? void 0 : _d.call(_c, url, entry.asset);
if (entry.asset.ownsObjectUrl) {
(_f = (_e = this.deps).revokeObjectUrl) == null ? void 0 : _f.call(_e, entry.asset.src);
}
if (!this.active.has(url)) this.phases.delete(url);
}
}
}
class MaterializeScheduler {
constructor(maxConcurrent) {
this.queue = [];
this.active = 0;
this.sequence = 0;
this.maxConcurrent = maxConcurrent;
}
run(key, task, priority, signal) {
if (signal.aborted) return Promise.reject(new DOMException("Materialization cancelled", "AbortError"));
return new Promise((resolve, reject) => {
const job = {
key,
priority,
sequence: this.sequence++,
signal,
task,
resolve,
reject,
onAbort: () => {
}
};
job.onAbort = () => {
const index = this.queue.indexOf(job);
if (index === -1) return;
this.queue.splice(index, 1);
reject(new DOMException("Materialization cancelled", "AbortError"));
};
signal.addEventListener("abort", job.onAbort, { once: true });
this.queue.push(job);
this.pump();
});
}
promote(key, priority) {
for (const job of this.queue) {
if (job.key === key && priority > job.priority) job.priority = priority;
}
}
pump() {
while (this.active < this.maxConcurrent && this.queue.length > 0) {
let bestIndex = 0;
for (let index = 1; index < this.queue.length; index++) {
const candidate = this.queue[index];
const best = this.queue[bestIndex];
if (candidate.priority > best.priority || candidate.priority === best.priority && candidate.sequence < best.sequence) {
bestIndex = index;
}
}
const job = this.queue.splice(bestIndex, 1)[0];
job.signal.removeEventListener("abort", job.onAbort);
if (job.signal.aborted) {
job.reject(new DOMException("Materialization cancelled", "AbortError"));
continue;
}
this.active++;
Promise.resolve().then(job.task).then(job.resolve, job.reject).finally(() => {
this.active--;
this.pump();
});
}
}
}
const materializeScheduler = new MaterializeScheduler(CFG.maxConcurrent);
function loadImageBytes(src, signal) {
return new Promise((resolve, reject) => {
const img = new Image();
img.decoding = "async";
const cleanup = () => {
signal.removeEventListener("abort", onAbort);
img.onload = null;
img.onerror = null;
};
const onAbort = () => {
cleanup();
img.src = "data:,";
reject(new DOMException("Image load cancelled", "AbortError"));
};
img.onload = () => {
const dimensions = { width: img.naturalWidth, height: img.naturalHeight };
cleanup();
resolve(dimensions);
};
img.onerror = () => {
cleanup();
reject(new Error(`Image byte load failed: ${src}`));
};
signal.addEventListener("abort", onAbort, { once: true });
if (signal.aborted) onAbort();
else img.src = src;
});
}
const imageLoadService = new ImageLoadService({
resolve: async (url, nlToken, _force, priority) => {
var _a;
return ((_a = store.activeAdapter) == null ? void 0 : _a.resolveImage(url, nlToken, priority)) ?? null;
},
materialize: (url, resolved, signal, priority) => {
var _a;
const materialize2 = (_a = store.activeAdapter) == null ? void 0 : _a.materializeImage;
if (!materialize2) return Promise.resolve(resolved);
return materializeScheduler.run(
url,
() => materialize2(resolved, signal),
priority,
signal
);
},
loadBytes: loadImageBytes,
promote: (url, priority) => {
materializeScheduler.promote(url, priority);
},
revokeObjectUrl: (src) => URL.revokeObjectURL(src)
});
function acquireImage(url, options) {
return imageLoadService.acquire(url, options);
}
function getCachedImage(url) {
return imageLoadService.getCached(url);
}
function getLatestCachedImage() {
return imageLoadService.getLatestCached();
}
function cancelImagePrefetch(keepUrls) {
var _a, _b;
(_b = (_a = store.activeAdapter) == null ? void 0 : _a.cancelPrefetch) == null ? void 0 : _b.call(_a, keepUrls);
}
const imageLoadedListeners = new Set();
function notifyScrollImageLoaded(event) {
imageLoadedListeners.forEach((listener) => listener(event));
}
function subscribeScrollImageLoaded(listener) {
imageLoadedListeners.add(listener);
return () => imageLoadedListeners.delete(listener);
}
function validGeometry(geometry) {
return Number.isFinite(geometry.width) && Number.isFinite(geometry.height) && geometry.width > 0 && geometry.height > 0;
}
function findScrollItem(itemKey, fallbackIndex, preferred) {
if ((preferred == null ? void 0 : preferred.isConnected) && (preferred.dataset.itemKey === itemKey || preferred.dataset.viewerUrl === itemKey || preferred.dataset.url === itemKey)) {
return preferred;
}
const items = Array.from(document.querySelectorAll(".r-img, .r-ph"));
return items.find(
(element) => element.dataset.itemKey === itemKey || element.dataset.viewerUrl === itemKey || element.dataset.url === itemKey
) ?? items[fallbackIndex] ?? null;
}
function applyKnownImageGeometry(element, geometry) {
if (!validGeometry(geometry)) return false;
const width = Math.round(geometry.width);
const height = Math.round(geometry.height);
element.style.aspectRatio = `${width} / ${height}`;
element.style.width = "100%";
element.style.maxWidth = `${width}px`;
element.style.height = "auto";
if (element.classList.contains("r-ph")) {
element.style.minHeight = "0";
element.style.marginBottom = "20px";
}
return true;
}
function withInstantDocumentScroll(action) {
const rootBehavior = document.documentElement.style.scrollBehavior;
const bodyBehavior = document.body.style.scrollBehavior;
const alreadyForced = document.documentElement.classList.contains("hr-instant-scroll");
document.documentElement.classList.add("hr-instant-scroll");
document.documentElement.style.scrollBehavior = "auto";
document.body.style.scrollBehavior = "auto";
try {
action();
} finally {
document.documentElement.style.scrollBehavior = rootBehavior;
document.body.style.scrollBehavior = bodyBehavior;
if (!alreadyForced) document.documentElement.classList.remove("hr-instant-scroll");
}
}
function restoreDocumentScroll(top) {
if (!Number.isFinite(top) || Math.abs(window.scrollY - top) < 2) return;
withInstantDocumentScroll(() => window.scrollTo({ top, behavior: "auto" }));
}
function isScrollItemCentered(target, tolerance = 2) {
const rect = target.getBoundingClientRect();
const centeredTop = (window.innerHeight - rect.height) / 2;
return Math.abs(rect.top - centeredTop) < tolerance;
}
function jumpToScrollItem(target) {
if (!target.isConnected) return;
const rect = target.getBoundingClientRect();
const top = window.scrollY + rect.top - (window.innerHeight - rect.height) / 2;
withInstantDocumentScroll(() => window.scrollTo({
top: Math.max(0, top),
behavior: "auto"
}));
}
function restoreReaderScroll(request) {
for (const geometry of request.nearbyGeometry ?? []) {
const element = findScrollItem(geometry.key, geometry.index, geometry.preferred);
if (element) applyKnownImageGeometry(element, geometry);
}
if (!request.target) {
restoreDocumentScroll(request.entryScrollY);
return;
}
const target = findScrollItem(
request.target.key,
request.target.index,
request.target.preferred
);
if (target && !isScrollItemCentered(target)) jumpToScrollItem(target);
}
function setErrorState(placeholder, pIndex, index) {
placeholder.className = "r-ph sp-placeholder error";
placeholder.innerHTML = `
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; transform: translateY(-20px);">
<div style="display: flex; align-items: center; gap: 10px; background: rgba(200, 40, 40, 0.8); border: 1px solid rgba(255, 255, 255, 0.2); padding: 10px 20px; border-radius: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); backdrop-filter: blur(8px); margin-bottom: 16px;">
<svg style="color: #fff; width: 20px; height: 20px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="12"></line>
<line x1="12" y1="16" x2="12.01" y2="16"></line>
</svg>
<div style="font-size: 15px; color: #fff; font-weight: 500; letter-spacing: 0.5px;">Load Failed</div>
</div>
<div style="font-size: 14px; color: rgba(255, 255, 255, 0.5); font-family: monospace; letter-spacing: 1px;">P${pIndex}-${index + 1}</div>
</div>
`;
}
let lazyLoadObserver = null;
const placeholderLeases = new WeakMap();
const imageElementLeases = new WeakMap();
function loadPlaceholderImage(placeholder) {
const url = placeholder.dataset.url;
const adapter = store.activeAdapter;
if (!url || !adapter) return;
if (placeholder.dataset.isFetching === "true") {
return;
}
placeholder.dataset.isFetching = "true";
placeholder.dataset.lazyLoaded = "true";
const pIndex = parseInt(placeholder.dataset.pIndex || "0", 10);
const index = parseInt(placeholder.dataset.index || "0", 10);
const lease = acquireImage(url, { intent: "scroll", priority: LOAD_PRIORITY.scroll });
placeholderLeases.set(placeholder, lease);
lease.result.then((asset) => {
var _a;
if (placeholderLeases.get(placeholder) !== lease) return;
if (!asset) {
setErrorState(placeholder, pIndex, index);
return;
}
const img = document.createElement("img");
img.className = "r-img";
img.decoding = "async";
img.dataset.viewerUrl = url;
if (placeholder.dataset.itemKey) img.dataset.itemKey = placeholder.dataset.itemKey;
img.dataset.realSrc = asset.src;
if (placeholder.dataset.thumb) img.dataset.thumbSrc = placeholder.dataset.thumb;
if (placeholder.dataset.thumbW) img.dataset.thumbW = placeholder.dataset.thumbW;
if (placeholder.dataset.thumbH) img.dataset.thumbH = placeholder.dataset.thumbH;
if (placeholder.dataset.thumbX !== void 0) img.dataset.thumbX = placeholder.dataset.thumbX;
if (placeholder.dataset.thumbY !== void 0) img.dataset.thumbY = placeholder.dataset.thumbY;
applyKnownImageGeometry(img, asset);
img.onload = () => {
if (img.dataset.locked || img.naturalWidth <= 0) return;
applyKnownImageGeometry(img, {
width: img.naturalWidth,
height: img.naturalHeight
});
img.dataset.locked = "true";
};
img.onerror = () => {
var _a2;
(_a2 = imageElementLeases.get(img)) == null ? void 0 : _a2.release();
imageElementLeases.delete(img);
if (img.parentNode) img.parentNode.replaceChild(placeholder, img);
setErrorState(placeholder, pIndex, index);
};
img.src = asset.src;
(_a = placeholder.parentNode) == null ? void 0 : _a.replaceChild(img, placeholder);
if (asset.ownsObjectUrl) {
placeholderLeases.delete(placeholder);
imageElementLeases.set(img, lease);
}
const itemKey = placeholder.dataset.itemKey;
const itemIndex = itemKey ? store.galleryItems.findIndex((item) => item.key === itemKey) : store.galleryItems.findIndex((item) => item.viewerUrl === url);
if (itemIndex !== -1) {
notifyScrollImageLoaded({ index: itemIndex, element: img, viewerUrl: url });
}
}).catch(() => {
setErrorState(placeholder, pIndex, index);
}).finally(() => {
if (placeholderLeases.get(placeholder) === lease) {
placeholderLeases.delete(placeholder);
lease.release();
}
});
}
function initLazyLoad() {
if (lazyLoadObserver) return;
lazyLoadObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const placeholder = entry.target;
if (placeholder.dataset.lazyLoaded) return;
placeholder.dataset.lazyLoaded = "true";
lazyLoadObserver == null ? void 0 : lazyLoadObserver.unobserve(placeholder);
loadPlaceholderImage(placeholder);
}
});
}, { rootMargin: "2000px 0px 2000px 0px" });
}
function pauseLazyLoad() {
lazyLoadObserver == null ? void 0 : lazyLoadObserver.disconnect();
}
function resumeLazyLoad() {
if (!lazyLoadObserver) return;
document.querySelectorAll(".r-ph").forEach((ph) => {
if (!ph.dataset.lazyLoaded) lazyLoadObserver.observe(ph);
});
}
function processBatch(items, pIndex, container, prepend = false, pageUrl) {
var _a;
const batchDiv = document.createElement("div");
batchDiv.className = "hr-page-batch";
if (pageUrl) {
batchDiv.dataset.pageUrl = pageUrl;
}
const fragment = document.createDocumentFragment();
initLazyLoad();
let targetContainer = container;
if (!targetContainer) {
targetContainer = document.querySelector("#gdt-hidden") || ((_a = store.activeAdapter) == null ? void 0 : _a.getContainer()) || document.querySelector(".scroll-mode .entry-content, .scroll-mode .wp-block-post-content, .scroll-mode .post-content") || document.body;
}
items.forEach((item, index) => {
const url = item.viewerUrl;
const placeholder = document.createElement("div");
placeholder.className = "r-ph sp-placeholder loading";
placeholder.dataset.url = url;
placeholder.dataset.pIndex = String(pIndex);
placeholder.dataset.index = String(index);
placeholder.dataset.itemKey = item.key;
if (item.dimensions) {
applyKnownImageGeometry(placeholder, item.dimensions);
}
if (item.preview.kind === "url") {
placeholder.dataset.thumb = item.preview.src;
if (item.preview.size) {
placeholder.dataset.thumbW = String(item.preview.size.width);
placeholder.dataset.thumbH = String(item.preview.size.height);
}
} else if (item.preview.kind === "sprite") {
placeholder.dataset.thumb = item.preview.src;
placeholder.dataset.thumbW = String(item.preview.crop.width);
placeholder.dataset.thumbH = String(item.preview.crop.height);
placeholder.dataset.thumbX = String(item.preview.crop.x);
placeholder.dataset.thumbY = String(item.preview.crop.y);
}
placeholder.innerHTML = `
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; transform: translateY(-20px);">
<div style="display: flex; align-items: center; gap: 10px; background: rgba(20, 20, 20, 0.8); border: 1px solid rgba(255, 255, 255, 0.1); padding: 10px 20px; border-radius: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); backdrop-filter: blur(8px); margin-bottom: 16px;">
<svg class="hr-spinner" style="color: #F596AA; width: 20px; height: 20px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
<path d="M21 12a9 9 0 1 1-6.219-8.56"></path>
</svg>
<div style="font-size: 15px; color: #f3f4f6; font-weight: 500; letter-spacing: 0.5px;">Loading...</div>
</div>
<div style="font-size: 14px; color: rgba(255, 255, 255, 0.5); font-family: monospace; letter-spacing: 1px;">P${pIndex}-${index + 1}</div>
</div>
`;
fragment.appendChild(placeholder);
if (store.settings.scrollMode) {
lazyLoadObserver == null ? void 0 : lazyLoadObserver.observe(placeholder);
}
});
batchDiv.appendChild(fragment);
if (prepend && targetContainer.firstChild) {
store.galleryItems = [...items, ...store.galleryItems];
targetContainer.insertBefore(batchDiv, targetContainer.firstChild);
} else {
store.galleryItems.push(...items);
targetContainer.appendChild(batchDiv);
}
}
function setupAutoScroll(pageLoader) {
const scrollSent = document.createElement("div");
document.body.appendChild(scrollSent);
const pageObs = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && store.nextUrl && !store.isFetching) {
const requestedUrl = store.nextUrl;
store.isFetching = true;
pageLoader.loadPage(requestedUrl).then((page) => {
var _a;
if (!page) {
store.nextUrl = null;
pageObs.disconnect();
return;
}
store.currPage++;
processBatch(page.items, store.currPage, ((_a = store.activeAdapter) == null ? void 0 : _a.getContainer()) || document.querySelector(".scroll-mode .entry-content, .scroll-mode .wp-block-post-content, .scroll-mode .post-content") || document.body, false, page.pageUrl);
store.nextUrl = page.nextUrl;
if (!store.nextUrl) pageObs.disconnect();
}).catch((err) => {
if (err instanceof EmptyGalleryPageError) {
store.nextUrl = null;
pageObs.disconnect();
}
}).finally(() => {
store.isFetching = false;
});
}
}, { rootMargin: CFG.nextPage });
pageObs.observe(scrollSent);
}
const readerCss = '.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;pointer-events:auto}.sp-close-btn{transform:scale(var(--hr-scale, 1));transform-origin:top right}.sp-close-btn:hover{background:#555555e6;transform:scale(calc(1.1 * var(--hr-scale, 1)))}.sp-hidden-by-pswp{opacity:0!important;pointer-events:none!important}.sp-sidebar-track{position:absolute;background:#2828284d;border-radius:6px;z-index:10;pointer-events:none;opacity:0;transition:opacity .3s,background .3s}.sp-sidebar-track.active{opacity:1;pointer-events:auto}.sp-sidebar-track:hover{background:#32323280}.sp-sidebar-track.position-bottom{bottom:12px;left:10%;width:80%;height:12px}.sp-sidebar-track.position-top{top:12px;left:10%;width:80%;height:12px}.sp-sidebar-track.position-left{left:12px;top:10%;height:80%;width:12px}.sp-sidebar-track.position-right{right:12px;top:10%;height:80%;width:12px}.sp-sidebar-thumb{position:absolute;background:#fff6;border-radius:6px;transition:background .3s;cursor:grab;-webkit-user-select:none;user-select:none}.sp-sidebar-thumb:hover{background:#fff9}.sp-sidebar-thumb:active{cursor:grabbing;background:#ffffffb3}.sp-sidebar-track.position-bottom .sp-sidebar-thumb,.sp-sidebar-track.position-top .sp-sidebar-thumb{top:0;height:100%;min-width:60px}.sp-sidebar-track.position-left .sp-sidebar-thumb,.sp-sidebar-track.position-right .sp-sidebar-thumb{left:0;width:100%;min-height:60px}.sp-sidebar-label{position:absolute;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-sidebar-track.position-bottom .sp-sidebar-label{bottom:calc(100% + 16px);left:50%;transform:translate(-50%)}.sp-sidebar-track.position-top .sp-sidebar-label{top:calc(100% + 16px);left:50%;transform:translate(-50%)}.sp-sidebar-track.position-left .sp-sidebar-label{left:calc(100% + 16px);top:50%;transform:translateY(-50%)}.sp-sidebar-track.position-right .sp-sidebar-label{right:calc(100% + 16px);top:50%;transform:translateY(-50%)}.sp-sidebar-track:hover .sp-sidebar-label{opacity:1}.sp-thumb-panel{position:absolute;background:#141414f2;border-radius:8px;border:1px solid rgba(255,255,255,.1);box-shadow:0 -4px 16px #00000080;pointer-events:none;display:flex;z-index:11;opacity:0;transition:opacity .3s,transform .3s cubic-bezier(.2,.8,.2,1);-webkit-user-select:none;user-select:none}.sp-thumb-panel.position-top{top:32px;left:50%;transform:translate(-50%) translateY(-20px);flex-direction:row;max-width:calc(80% / var(--hr-scale, 1));width:fit-content;height:116px}.sp-thumb-panel.position-top{transform-origin:top center;transform:translate(-50%) translateY(-20px) scale(var(--hr-scale, 1))}.sp-thumb-panel.position-top.active{opacity:1;transform:translate(-50%) translateY(0) scale(var(--hr-scale, 1));pointer-events:auto}.sp-thumb-panel.position-bottom{bottom:32px;left:50%;transform-origin:bottom center;transform:translate(-50%) translateY(20px) scale(var(--hr-scale, 1));flex-direction:row;max-width:calc(80% / var(--hr-scale, 1));width:fit-content;height:116px}.sp-thumb-panel.position-bottom.active{opacity:1;transform:translate(-50%) translateY(0) scale(var(--hr-scale, 1));pointer-events:auto}.sp-thumb-panel.position-left{left:32px;top:50%;transform-origin:left center;transform:translateY(-50%) translate(-20px) scale(var(--hr-scale, 1));flex-direction:column;max-height:calc(80% / var(--hr-scale, 1));height:fit-content;width:116px}.sp-thumb-panel.position-left.active{opacity:1;transform:translateY(-50%) translate(0) scale(var(--hr-scale, 1));pointer-events:auto}.sp-thumb-panel.position-right{right:32px;top:50%;transform-origin:right center;transform:translateY(-50%) translate(20px) scale(var(--hr-scale, 1));flex-direction:column;max-height:calc(80% / var(--hr-scale, 1));height:fit-content;width:116px}.sp-thumb-panel.position-right.active{opacity:1;transform:translateY(-50%) translate(0) scale(var(--hr-scale, 1));pointer-events:auto}.sp-thumb-viewport{flex:1;min-width:0;min-height:0;position:relative;border-radius:inherit;overflow:auto;overscroll-behavior:none;-webkit-overflow-scrolling:touch;scrollbar-width:none}.sp-thumb-viewport::-webkit-scrollbar{display:none}.sp-thumb-content{position:relative;width:100%;height:100%}.sp-thumb-item{position:absolute;border-radius:4px;overflow:hidden;cursor:pointer;border:2px solid transparent;transition:border-color .15s;box-sizing:border-box}.sp-thumb-panel.position-bottom .sp-thumb-item,.sp-thumb-panel.position-top .sp-thumb-item{top:8px;left:0;width:72px;height:calc(100% - 16px)}.sp-thumb-panel.position-left .sp-thumb-item,.sp-thumb-panel.position-right .sp-thumb-item{left:8px;top:0;width:calc(100% - 16px);height:72px}.sp-thumb-item:hover{border-color:#fff6}.sp-thumb-item.sp-thumb-active{border-color:var(--hr-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-ph.loading{color:#aaa;gap:6px}.sp-thumb-ph.loading:after{content:"";width:10px;height:10px;border:2px solid rgba(255,255,255,.2);border-top-color:var(--hr-accent);border-radius:50%;animation:hr-spin 1s linear infinite}.sp-thumb-ph.error{color:#ef8080}.sp-thumb-label{position:absolute;bottom:0;right:0;padding:1px 5px;background:#00000080;color:#fff9;font-family:monospace;font-size:11px;border-radius:4px 0 0;pointer-events:none}.sp-thumb-counter{position:absolute;display:flex;align-items:center;justify-content:center;background:#1e1e1ebf;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);color:#fff;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:12px;font-weight:500;letter-spacing:.5px;padding:4px 10px;border-radius:12px;border:1px solid rgba(255,255,255,.15);box-shadow:0 4px 12px #0000004d;pointer-events:none;z-index:20;white-space:nowrap}.sp-thumb-panel.position-bottom .sp-thumb-counter{top:-36px;left:50%;transform:translate(-50%)}.sp-thumb-panel.position-top .sp-thumb-counter{bottom:-36px;left:50%;transform:translate(-50%)}.sp-thumb-panel.position-left .sp-thumb-counter,.sp-thumb-panel.position-right .sp-thumb-counter{bottom:-44px;left:50%;transform:translate(-50%);font-size:14px;padding:6px 16px;border-radius:16px}:root.hr-mobile .sp-thumb-panel.position-bottom{bottom:16px;max-width:calc(90% / var(--hr-scale, 1));height:130px}:root.hr-mobile .sp-thumb-panel.position-top{top:16px;max-width:calc(90% / var(--hr-scale, 1));height:130px}:root.hr-mobile .sp-thumb-panel.position-left{left:16px;max-height:calc(90% / var(--hr-scale, 1));width:130px}:root.hr-mobile .sp-thumb-panel.position-right{right:16px;max-height:calc(90% / var(--hr-scale, 1));width:130px}:root.hr-mobile .sp-thumb-panel.position-bottom .sp-thumb-item,:root.hr-mobile .sp-thumb-panel.position-top .sp-thumb-item{width:86px;height:calc(100% - 10px);top:5px}:root.hr-mobile .sp-thumb-panel.position-left .sp-thumb-item,:root.hr-mobile .sp-thumb-panel.position-right .sp-thumb-item{width:calc(100% - 10px);height:86px;left:5px}.sp-hud-container{position:absolute;top:40px;left:50%;transform:translate(-50%);z-index:100;opacity:0;pointer-events:none;transition:opacity .3s ease,transform .3s ease}.sp-hud-container{transform:translate(-50%) scale(var(--hr-scale, 1))}.sp-hud-container.show{opacity:1;pointer-events:auto;transform:translate(-50%) scale(var(--hr-scale, 1))}.sp-hud-box{display:flex;align-items:center;gap:10px;background:#141414b3;border:1px solid rgba(255,255,255,.1);padding:10px 20px;border-radius:30px;box-shadow:0 4px 16px #00000080;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);transition:all .2s}.sp-hud-box.hud-error{background:#b41e1eb3;border-color:#ff64644d}.sp-hud-box.hud-error:hover{background:#dc2828cc}.sp-hud-text{font-size:14px;color:#f3f4f6;font-family:sans-serif;font-weight:500;letter-spacing:.5px;white-space:nowrap;-webkit-user-select:none;user-select:none}.sp-hud-page{font-size:13px;color:#ffffff80;font-family:monospace;letter-spacing:1px;margin-left:6px;padding-left:10px;border-left:1px solid rgba(255,255,255,.2);-webkit-user-select:none;user-select:none}.pswp__error-msg-container,.pswp__error-msg{display:none!important}';
importCSS(readerCss);
const AHEAD = 5;
const BEHIND = 2;
function createPrefetchController(session) {
const downloads = new Map();
let lastCenter = -1;
function urlAt(index) {
var _a;
return (_a = session.itemAt(index)) == null ? void 0 : _a.viewerUrl;
}
function ensureDownload(url) {
if (downloads.has(url)) return;
const lease = acquireImage(url, { intent: "warmup", priority: LOAD_PRIORITY.warmup });
downloads.set(url, lease);
lease.result.then(() => {
if (downloads.get(url) !== lease) return;
downloads.delete(url);
lease.release();
}).catch(() => {
if (downloads.get(url) !== lease) return;
downloads.delete(url);
lease.release();
});
}
function abort(url) {
var _a;
(_a = downloads.get(url)) == null ? void 0 : _a.release();
downloads.delete(url);
}
function setWindow(center, direction) {
const total = session.imageCount;
if (total === 0) return;
const lo = Math.max(0, center - (direction === 1 ? BEHIND : AHEAD));
const hi = Math.min(total - 1, center + (direction === 1 ? AHEAD : BEHIND));
const wanted = new Set();
for (let i = lo; i <= hi; i++) {
const url = urlAt(i);
if (url) wanted.add(url);
}
for (const url of Array.from(downloads.keys())) {
if (!wanted.has(url)) abort(url);
}
const jumped = lastCenter < 0 || Math.abs(center - lastCenter) > AHEAD + BEHIND;
if (jumped) cancelImagePrefetch(wanted);
lastCenter = center;
for (let d = 0; d <= Math.max(hi - center, center - lo); d++) {
const forward = center + direction * d;
if (forward >= lo && forward <= hi) {
const url = urlAt(forward);
if (url) ensureDownload(url);
}
const back = center - direction * d;
if (d > 0 && back >= lo && back <= hi) {
const url = urlAt(back);
if (url) ensureDownload(url);
}
}
}
function warmup(indices) {
for (const i of indices) {
const url = urlAt(i);
if (url) ensureDownload(url);
}
}
function clear() {
for (const url of Array.from(downloads.keys())) abort(url);
lastCenter = -1;
}
return { setWindow, warmup, clear };
}
const WHEEL_DECAY = 0.82;
const MIN_VELOCITY = 20;
const MAX_VELOCITY = 150;
const TURN_INTERVAL_SLOW = 150;
const TURN_INTERVAL_FAST = 50;
const GESTURE_GAP = 150;
function createWheelPager(config) {
let wheelVelocity = 0;
let scrollRafId = null;
let lastTurnTime = 0;
let gestureConsumed = false;
let lastWheelTime = 0;
function stop() {
if (scrollRafId !== null) {
cancelAnimationFrame(scrollRafId);
scrollRafId = null;
}
wheelVelocity = 0;
}
function scrollTick(now) {
scrollRafId = null;
wheelVelocity *= WHEEL_DECAY;
if (Math.abs(wheelVelocity) < MIN_VELOCITY) {
wheelVelocity = 0;
return;
}
const dir = wheelVelocity > 0 ? 1 : -1;
if (gestureConsumed) {
scrollRafId = requestAnimationFrame(scrollTick);
return;
}
const v = Math.min(MAX_VELOCITY, Math.abs(wheelVelocity));
const t = Math.sqrt((v - MIN_VELOCITY) / (MAX_VELOCITY - MIN_VELOCITY));
const interval = TURN_INTERVAL_SLOW + t * (TURN_INTERVAL_FAST - TURN_INTERVAL_SLOW);
if (now - lastTurnTime >= interval) {
const target = config.getCurrentIndex() + dir;
if (target < 0) {
stop();
config.onEdgeBackward();
return;
}
if (target >= config.getImageCount()) {
stop();
config.onEdgeForward();
return;
}
config.stopMotion();
config.goTo(target);
lastTurnTime = now;
if (config.isPageLoading(target)) {
gestureConsumed = true;
}
}
scrollRafId = requestAnimationFrame(scrollTick);
}
function onWheel(event) {
if (config.isCurrentZoomed()) return;
event.preventDefault();
let delta = event.deltaY;
if (event.deltaMode === 1) delta *= 33;
else if (event.deltaMode === 2) delta *= window.innerHeight;
const now = event.timeStamp;
if (now - lastWheelTime > GESTURE_GAP) {
gestureConsumed = false;
}
lastWheelTime = now;
if (Math.sign(delta) !== Math.sign(wheelVelocity) && wheelVelocity !== 0) {
wheelVelocity = 0;
}
wheelVelocity = Math.max(-MAX_VELOCITY, Math.min(MAX_VELOCITY, wheelVelocity + delta));
if (scrollRafId === null) {
scrollRafId = requestAnimationFrame(scrollTick);
}
}
return { onWheel, stop };
}
function createAutoPlay(nextImageFn, context) {
let timer = null;
function start() {
if (timer) clearInterval(timer);
if (context.isAutoPlayEnabled()) {
timer = setInterval(nextImageFn, context.getAutoPlayInterval());
}
}
function stop() {
if (timer) {
clearInterval(timer);
timer = null;
}
}
function reset() {
if (context.isAutoPlayEnabled()) {
stop();
start();
}
}
function stopAtEnd() {
context.setAutoPlayEnabled(false);
stop();
}
return { start, stop, reset, stopAtEnd };
}
const isZH = navigator.language.toLowerCase().includes("zh");
const i18n = {
readerMode: isZH ? "阅读器模式" : "Reader Mode",
settings: isZH ? "设置" : "Settings",
backToTop: isZH ? "回到顶部" : "Back to Top",
play: isZH ? "播放" : "Play",
pause: isZH ? "暂停" : "Pause",
scrollMode: isZH ? "卷轴模式" : "Scroll Mode",
autoEnter: isZH ? "自动进入阅读器" : "Auto Enter Reader",
clickToEnter: isZH ? "点击图片进入阅读器" : "Click Image to Enter Reader",
playSpeed: isZH ? "自动翻页间隔 (秒)" : "Auto Play Interval (s)",
thumbnailPosition: isZH ? "缩略图位置" : "Thumbnail Position",
posBottom: isZH ? "底部" : "Bottom",
posTop: isZH ? "顶部" : "Top",
posLeft: isZH ? "左侧" : "Left",
posRight: isZH ? "右侧" : "Right",
downloading: isZH ? "下载中..." : "Downloading...",
loadFailed: isZH ? "加载失败" : "Load Failed",
waitImagesToLoad: isZH ? "请等待图片加载" : "Please wait for images to load",
resolvingImage: isZH ? "解析图片真实地址中..." : "Resolving Image URL..."
};
function createReaderPaginationController(session, deps, context) {
function checkNearEnd() {
if (!context.getNextUrl() || context.isPageFetching()) return;
const remainingImages = session.imageCount - session.currentIndex;
if (remainingImages <= 5) loadNext();
}
function loadNext() {
const requestedUrl = context.getNextUrl();
if (!requestedUrl || context.isPageFetching()) return;
let shouldContinue = false;
context.setPageFetching(true);
deps.onLoading("next");
deps.pageLoader.loadPage(requestedUrl).then((page) => {
if (!page) {
context.setNextUrl(null);
deps.onIdle();
return;
}
deps.appendPage(page);
deps.syncImages();
deps.onIdle();
shouldContinue = true;
}).catch((err) => {
if (err instanceof EmptyGalleryPageError) {
context.setNextUrl(null);
deps.onIdle();
return;
}
console.error("[Single Page] Load failed", err);
deps.onError("next", loadNext);
}).finally(() => {
context.setPageFetching(false);
if (shouldContinue) checkNearEnd();
});
}
function loadPrev() {
const requestedUrl = context.getPrevUrl();
if (!requestedUrl || context.isPageFetching()) return;
context.setPageFetching(true);
deps.onLoading("prev");
deps.pageLoader.loadPage(requestedUrl).then((page) => {
if (!page) {
context.setPrevUrl(null);
deps.onIdle();
return;
}
deps.prependPage(page);
deps.syncImages();
deps.afterPrepend(page.items.length);
deps.onIdle();
}).catch((err) => {
if (err instanceof EmptyGalleryPageError) {
context.setPrevUrl(null);
deps.onIdle();
return;
}
console.error("[Single Page] Load prev failed", err);
deps.onError("prev", loadPrev);
}).finally(() => {
context.setPageFetching(false);
});
}
return { loadNext, loadPrev, checkNearEnd };
}
function createReaderImageController(session, deps) {
const byteState = new Map();
const leases = new Map();
let disposed = false;
function urlAt(index) {
var _a;
const itemUrl = (_a = session.itemAt(index)) == null ? void 0 : _a.viewerUrl;
if (itemUrl) return itemUrl;
const el = session.elementAt(index);
return (el == null ? void 0 : el.dataset.url) || (el == null ? void 0 : el.dataset.viewerUrl) || void 0;
}
function indexOfUrl(url) {
return session.indexOfViewerUrl(url);
}
function getPhase(index) {
var _a;
const url = urlAt(index);
if (!url) return "error";
if (byteState.get(url) === "error") return "error";
const servicePhase = (_a = leases.get(url)) == null ? void 0 : _a.lease.phase;
if (servicePhase === "error" || servicePhase === "cancelled") return "error";
if (servicePhase === "resolving") return "resolving";
if (servicePhase === "downloading") return "downloading";
if (byteState.get(url) === "loaded" || deps.getSlideContentState(index) === "loaded") {
return "loaded";
}
return "downloading";
}
function notify(url) {
const index = indexOfUrl(url);
if (index !== -1) deps.onPhaseChange(index, getPhase(index));
}
function ensureLease(index, viewerUrl) {
if (!viewerUrl || leases.has(viewerUrl) || disposed) return;
const distance = Math.abs(index - deps.getCurrentIndex());
const lease = acquireImage(viewerUrl, {
intent: distance === 0 ? "foreground" : "neighbor",
priority: LOAD_PRIORITY.foreground - distance
});
leases.set(viewerUrl, { lease, unsubscribe: () => {
} });
const unsubscribe = lease.subscribe(() => notify(viewerUrl));
leases.set(viewerUrl, { lease, unsubscribe });
lease.result.then((asset) => {
var _a;
if (disposed || ((_a = leases.get(viewerUrl)) == null ? void 0 : _a.lease) !== lease) return;
const liveIndex = indexOfUrl(viewerUrl);
if (!asset || liveIndex === -1) {
byteState.set(viewerUrl, "error");
notify(viewerUrl);
return;
}
byteState.set(viewerUrl, "loaded");
notify(viewerUrl);
deps.refreshSlide(liveIndex);
deps.onAssetReady(liveIndex);
}).catch(() => {
if (disposed) return;
byteState.set(viewerUrl, "error");
notify(viewerUrl);
});
}
function getItemData(index) {
var _a;
const el = session.elementAt(index);
const item = session.itemAt(index);
if (!el) return { src: "", w: 0, h: 0 };
const viewerUrl = (item == null ? void 0 : item.viewerUrl) || el.dataset.url || el.dataset.viewerUrl || "";
const fallbackSrc = el.dataset.realSrc || el.src || "";
const cachedAsset = getCachedImage(viewerUrl);
const resolvedSrc = (cachedAsset == null ? void 0 : cachedAsset.src) || fallbackSrc;
let dim = cachedAsset ? { w: cachedAsset.width, h: cachedAsset.height } : void 0;
if (!dim && (item == null ? void 0 : item.dimensions)) {
dim = { w: item.dimensions.width, h: item.dimensions.height };
}
if (!dim && el.tagName === "IMG") {
const htmlImg = el;
if (htmlImg.complete && htmlImg.naturalWidth > 0) {
dim = { w: htmlImg.naturalWidth, h: htmlImg.naturalHeight };
}
}
if (!dim) {
const fallbackW = window.innerWidth;
const previewSize = (item == null ? void 0 : item.preview.kind) === "url" ? item.preview.size : (item == null ? void 0 : item.preview.kind) === "sprite" ? item.preview.crop : void 0;
let fallbackRatio = 1.414;
if (previewSize && previewSize.width > 0 && previewSize.height > 0) {
fallbackRatio = previewSize.height / previewSize.width;
} else {
const latestCached = getLatestCachedImage();
if (latestCached && latestCached.width > 0 && latestCached.height > 0) {
fallbackRatio = latestCached.height / latestCached.width;
}
}
dim = { w: fallbackW, h: fallbackW * fallbackRatio };
}
const trueDimKnown = !!(item == null ? void 0 : item.dimensions) || !!cachedAsset || el.tagName === "IMG" && el.complete && el.naturalWidth > 0;
const msrc = (item == null ? void 0 : item.preview.kind) === "url" ? item.preview.src : void 0;
if (viewerUrl) ensureLease(index, viewerUrl);
const serviceReady = ((_a = leases.get(viewerUrl)) == null ? void 0 : _a.lease.phase) === "ready";
if (serviceReady && resolvedSrc && !resolvedSrc.includes("x.gif") && trueDimKnown) {
return { src: resolvedSrc, msrc, w: dim.w, h: dim.h };
}
return { src: "", msrc, w: dim.w, h: dim.h };
}
function handleContentLoad(index) {
const url = urlAt(index);
if (!url || byteState.get(url) === "loaded") return;
byteState.set(url, "loading");
notify(url);
}
function handleLoadComplete(index, isError) {
const url = urlAt(index);
if (!url) return;
byteState.set(url, isError ? "error" : "loaded");
notify(url);
}
function releaseOutside(center) {
for (const [url, held] of leases) {
const index = indexOfUrl(url);
if (index !== -1 && Math.abs(index - center) <= 2) continue;
held.unsubscribe();
held.lease.release();
leases.delete(url);
}
}
function dispose() {
disposed = true;
for (const held of leases.values()) {
held.unsubscribe();
held.lease.release();
}
leases.clear();
}
return {
urlAt,
getItemData,
getPhase,
isLoading: (index) => {
const phase = getPhase(index);
return phase === "resolving" || phase === "downloading";
},
handleContentLoad,
handleLoadComplete,
releaseOutside,
dispose
};
}
function createStatusHUD() {
const container = document.createElement("div");
container.className = "sp-hud-container";
function show(config) {
const isError = config.status === "error";
const color = isError ? "#ef4444" : "#F596AA";
const iconSvg = isError ? `<svg style="color: ${color}; width: 18px; height: 18px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>` : `<svg class="hr-spinner" style="color: ${color}; width: 18px; height: 18px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg>`;
container.innerHTML = `
<div class="sp-hud-box ${isError ? "hud-error" : ""}">
${iconSvg}
<div class="sp-hud-text">${config.text}</div>
${config.pageText ? `<div class="sp-hud-page">${config.pageText}</div>` : ""}
</div>
`;
const box = container.querySelector(".sp-hud-box");
if (config.onClick) {
box.style.cursor = "pointer";
box.onclick = config.onClick;
}
container.classList.add("show");
}
function hide() {
container.classList.remove("show");
}
return { show, hide, getElement: () => container };
}
function createThumbnailPlan(item, loadedSource) {
switch (item.preview.kind) {
case "url":
return { src: item.preview.src, requestFullImage: false };
case "sprite":
return { src: item.preview.src, crop: item.preview.crop, requestFullImage: false };
case "derived":
case "none":
return loadedSource ? { src: loadedSource, requestFullImage: false } : { requestFullImage: true };
}
}
const VISIBLE_COUNT = 15;
const BUFFER = 3;
const PRELOAD_SETTLE_MS = 300;
function createThumbnailPanel(onIndexChange, onScrollToBottom, onScrollToTop, options) {
var _a;
const thumbPanel = document.createElement("div");
thumbPanel.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);
thumbPanel.appendChild(viewport);
thumbPanel.appendChild(counter);
let lastCenteredIndex = -1;
let clickedFromPanel = false;
let lastContentSize = "";
let lastContentVertical = null;
const itemPool = [];
const activeItems = new Map();
let hideTimeout;
let isPanelActive = false;
let preloadTimer = null;
let userScrollArmed = false;
function openPanel(keepOpen = false) {
var _a2, _b;
if (options.getImageCount() === 0) return;
clearTimeout(hideTimeout);
isPanelActive = true;
thumbPanel.classList.add("active");
const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 0;
if (keepOpen) {
if (isTouchDevice) {
(_a2 = options.onMobileInteractionStart) == null ? void 0 : _a2.call(options);
}
} else {
if (isTouchDevice) {
(_b = options.onMobileInteractionEnd) == null ? void 0 : _b.call(options);
} else {
hideTimeout = setTimeout(() => {
closePanel();
}, 2e3);
}
}
}
function clearThumbnailPreloadTimer() {
if (preloadTimer) {
clearTimeout(preloadTimer);
preloadTimer = null;
}
}
function cancelThumbnailPreloadWork() {
var _a2;
clearThumbnailPreloadTimer();
(_a2 = options.cancelThumbnailPreloads) == null ? void 0 : _a2.call(options);
}
function closePanel() {
isPanelActive = false;
userScrollArmed = false;
thumbPanel.classList.remove("active");
clearThumbnailPreloadTimer();
}
thumbPanel.addEventListener("pointerenter", (e) => {
if (e.pointerType === "mouse") openPanel(true);
});
thumbPanel.addEventListener("pointerleave", (e) => {
if (e.pointerType === "mouse") openPanel(false);
});
thumbPanel.addEventListener("pointermove", (e) => {
if (e.pointerType === "mouse") openPanel(true);
});
(_a = options.subscribeThumbnailChange) == null ? void 0 : _a.call(options, (idx) => {
if (activeItems.has(idx)) {
renderItemContent(activeItems.get(idx), idx);
}
});
function isVertical() {
const pos = options.getThumbnailPosition();
return pos === "left" || pos === "right";
}
function getItemSize() {
return document.documentElement.classList.contains("hr-mobile") ? 96 : 80;
}
function vpSize() {
if (isVertical()) {
return viewport.offsetHeight || Math.min(VISIBLE_COUNT * getItemSize(), options.getImageCount() * getItemSize());
} else {
return viewport.offsetWidth || Math.min(VISIBLE_COUNT * getItemSize(), options.getImageCount() * getItemSize());
}
}
function maxOffset() {
return Math.max(0, options.getImageCount() * getItemSize() - vpSize());
}
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);
}
const THUMB_CACHE_MAX = 60;
const thumbCache = new Map();
function getCachedThumb(src) {
const c = thumbCache.get(src);
if (c) {
thumbCache.delete(src);
thumbCache.set(src, c);
}
return c;
}
function putCachedThumb(src, canvas) {
thumbCache.set(src, canvas);
if (thumbCache.size > THUMB_CACHE_MAX) {
const oldest = thumbCache.keys().next().value;
if (oldest !== void 0) thumbCache.delete(oldest);
}
}
function makeThumbCanvas(source, natW, natH, crop) {
let w = crop ? crop.w : natW;
let h = crop ? crop.h : natH;
if (w > 300) {
h = h * 300 / w;
w = 300;
}
const c = document.createElement("canvas");
c.width = w;
c.height = h;
const ctx = c.getContext("2d");
if (ctx) {
if (crop) {
ctx.drawImage(source, crop.x, crop.y, crop.w, crop.h, 0, 0, w, h);
} else {
ctx.drawImage(source, 0, 0, w, h);
}
}
return c;
}
function blitToCanvas(dest, cache) {
dest.width = cache.width;
dest.height = cache.height;
const ctx = dest.getContext("2d");
if (ctx) ctx.drawImage(cache, 0, 0);
}
function renderItemContent(el, index) {
var _a2, _b, _c, _d;
el.dataset.index = String(index);
el.classList.toggle("sp-thumb-active", index === options.getCurrentIndex());
const img = options.getImageAt(index);
const item = options.getItemAt(index);
const isLoadedImg = img && img.tagName === "IMG" && img.complete && img.naturalWidth > 0;
const preloadedAsset = (_a2 = options.getPreloadedAsset) == null ? void 0 : _a2.call(options, index);
const loadedSource = (preloadedAsset == null ? void 0 : preloadedAsset.src) || (isLoadedImg ? img.dataset.realSrc || img.src : void 0);
const plan = item ? createThumbnailPlan(item, loadedSource) : {};
const thumbSrc = plan.src || "";
const isDerived = (item == null ? void 0 : item.preview.kind) === "none" || (item == null ? void 0 : item.preview.kind) === "derived";
const crop = plan.crop ? {
x: plan.crop.x,
y: plan.crop.y,
w: plan.crop.width,
h: plan.crop.height
} : void 0;
const cacheKey = isDerived && item ? `derived:${item.key}` : crop ? `${thumbSrc}#${crop.x},${crop.y},${crop.w},${crop.h}` : thumbSrc;
const cached = cacheKey ? getCachedThumb(cacheKey) : void 0;
if (cached || thumbSrc) {
let thumbCanvas = el.querySelector("canvas.sp-thumb-img");
if (!thumbCanvas) {
el.innerHTML = "";
thumbCanvas = document.createElement("canvas");
thumbCanvas.className = "sp-thumb-img";
el.appendChild(thumbCanvas);
const label2 = document.createElement("span");
label2.className = "sp-thumb-label";
el.appendChild(label2);
}
if (thumbCanvas.dataset.src !== cacheKey) {
thumbCanvas.dataset.src = cacheKey;
if (cached) {
blitToCanvas(thumbCanvas, cached);
if (isDerived && preloadedAsset) (_b = options.finishThumbnailPreload) == null ? void 0 : _b.call(options, index);
} else if (isDerived && isLoadedImg && loadedSource === thumbSrc) {
const c = makeThumbCanvas(img, img.naturalWidth, img.naturalHeight);
putCachedThumb(cacheKey, c);
blitToCanvas(thumbCanvas, c);
if (preloadedAsset) (_c = options.finishThumbnailPreload) == null ? void 0 : _c.call(options, index);
} else {
const tempImg = new Image();
tempImg.decoding = "async";
tempImg.onload = () => {
var _a3;
const c = makeThumbCanvas(tempImg, tempImg.naturalWidth, tempImg.naturalHeight, crop);
putCachedThumb(cacheKey, c);
if (thumbCanvas.dataset.src === cacheKey) {
blitToCanvas(thumbCanvas, c);
}
if (isDerived) (_a3 = options.finishThumbnailPreload) == null ? void 0 : _a3.call(options, index);
};
tempImg.onerror = () => {
var _a3;
if (isDerived) (_a3 = options.finishThumbnailPreload) == null ? void 0 : _a3.call(options, index, true);
};
tempImg.src = thumbSrc;
}
}
const label = el.querySelector(".sp-thumb-label");
if (label) label.textContent = String(options.getDisplayNumber(index));
} else {
let ph = el.querySelector(".sp-thumb-ph");
if (!ph) {
el.innerHTML = "";
ph = document.createElement("div");
ph.className = "sp-thumb-ph";
el.appendChild(ph);
}
const phase = ((_d = options.getPreloadPhase) == null ? void 0 : _d.call(options, index)) ?? "idle";
ph.classList.toggle("loading", phase === "loading");
ph.classList.toggle("error", phase === "error");
ph.textContent = phase === "error" ? `${options.getDisplayNumber(index)} !` : String(options.getDisplayNumber(index));
}
}
function getScrollOffset() {
return isVertical() ? viewport.scrollTop : viewport.scrollLeft;
}
function visiblePreloadIndices() {
const total = options.getImageCount();
const itemSize = getItemSize();
const offset = getScrollOffset();
const viewportSize = vpSize();
const first = Math.max(0, Math.floor(offset / itemSize));
const last = Math.min(total - 1, Math.ceil((offset + viewportSize) / itemSize) - 1);
const center = offset + viewportSize / 2;
const indices = [];
for (let index = first; index <= last; index++) {
const item = options.getItemAt(index);
if (!item || item.preview.kind !== "none" && item.preview.kind !== "derived") continue;
if (getCachedThumb(`derived:${item.key}`)) continue;
const element = options.getImageAt(index);
if ((element == null ? void 0 : element.tagName) === "IMG" && element.complete && element.naturalWidth > 0) continue;
indices.push(index);
}
return indices.sort((a, b) => {
const distanceA = Math.abs((a + 0.5) * itemSize - center);
const distanceB = Math.abs((b + 0.5) * itemSize - center);
return distanceA - distanceB || a - b;
});
}
function scheduleThumbnailPreload() {
cancelThumbnailPreloadWork();
preloadTimer = setTimeout(() => {
var _a2;
preloadTimer = null;
userScrollArmed = false;
if (!isPanelActive) return;
(_a2 = options.preloadThumbnails) == null ? void 0 : _a2.call(options, visiblePreloadIndices());
}, PRELOAD_SETTLE_MS);
}
let isProgrammaticScroll = false;
let programmaticScrollTimer;
function setScrollOffset(val) {
const current = getScrollOffset();
if (Math.abs(current - val) < 1) return;
isProgrammaticScroll = true;
if (programmaticScrollTimer) clearTimeout(programmaticScrollTimer);
programmaticScrollTimer = setTimeout(() => {
isProgrammaticScroll = false;
}, 50);
if (isVertical()) {
viewport.scrollTop = val;
} else {
viewport.scrollLeft = val;
}
}
function renderVisibleItems() {
const total = options.getImageCount();
if (total === 0) return;
const vp = vpSize();
const itemSize = getItemSize();
const wantSize = `${total * itemSize + 16}px`;
const vertical = isVertical();
if (lastContentSize !== wantSize || lastContentVertical !== vertical) {
if (vertical) {
content.style.width = "100%";
content.style.height = wantSize;
} else {
content.style.width = wantSize;
content.style.height = "100%";
}
lastContentSize = wantSize;
lastContentVertical = vertical;
}
const scrollOffset = getScrollOffset();
const startIdx = Math.max(0, Math.floor(scrollOffset / itemSize) - BUFFER);
const endIdx = Math.min(total - 1, Math.ceil((scrollOffset + vp) / itemSize) + 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);
}
if (isVertical()) {
el.style.transform = `translateY(${i * itemSize + 8}px)`;
} else {
el.style.transform = `translateX(${i * itemSize + 8}px)`;
}
renderItemContent(el, i);
}
}
function centerOnCurrent() {
const vp = vpSize();
const itemSize = getItemSize();
const target = options.getCurrentIndex() * itemSize - vp / 2 + itemSize / 2;
setScrollOffset(target);
}
function ensureVisible() {
const vp = vpSize();
const itemSize = getItemSize();
const itemStart = options.getCurrentIndex() * itemSize;
const itemEnd = itemStart + itemSize;
const currentScroll = getScrollOffset();
if (itemStart < currentScroll) {
setScrollOffset(itemStart);
} else if (itemEnd > currentScroll + vp) {
setScrollOffset(itemEnd - vp);
}
}
function update() {
const total = options.getImageCount();
if (total === 0) return;
const currentIndex = options.getCurrentIndex();
if (currentIndex !== lastCenteredIndex) {
userScrollArmed = false;
cancelThumbnailPreloadWork();
if (clickedFromPanel) {
ensureVisible();
clickedFromPanel = false;
} else {
centerOnCurrent();
}
lastCenteredIndex = currentIndex;
}
renderVisibleItems();
const displayLabel = `${options.getDisplayNumber(currentIndex)} / ${options.getDisplayNumber(total - 1)}`;
counter.textContent = displayLabel;
}
let lastScrollPos = 0;
let scrollRafPending = false;
viewport.addEventListener("scroll", () => {
if (scrollRafPending) return;
scrollRafPending = true;
requestAnimationFrame(() => {
scrollRafPending = false;
renderVisibleItems();
if (isProgrammaticScroll) {
isProgrammaticScroll = false;
if (programmaticScrollTimer) clearTimeout(programmaticScrollTimer);
lastScrollPos = getScrollOffset();
return;
}
if (!userScrollArmed) return;
scheduleThumbnailPreload();
const currentScroll = getScrollOffset();
const isScrollingUp = currentScroll < lastScrollPos;
const isScrollingDown = currentScroll > lastScrollPos;
if (onScrollToTop && currentScroll <= 5 && isScrollingUp) {
onScrollToTop();
} else if (onScrollToBottom && currentScroll >= maxOffset() - 5 && isScrollingDown) {
onScrollToBottom();
}
lastScrollPos = currentScroll;
openPanel(true);
clearTimeout(hideTimeout);
hideTimeout = setTimeout(() => openPanel(false), 500);
});
}, { passive: true });
thumbPanel.addEventListener("wheel", (e) => {
e.stopPropagation();
}, { passive: true });
viewport.addEventListener("wheel", (e) => {
userScrollArmed = true;
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
const prevScroll = getScrollOffset();
if (!isVertical()) {
e.preventDefault();
viewport.scrollBy({ left: e.deltaY, behavior: "auto" });
}
const mOffset = maxOffset();
if (prevScroll <= 0 && e.deltaY < 0 && onScrollToTop) {
onScrollToTop();
} else if (prevScroll >= mOffset - 1 && e.deltaY > 0 && onScrollToBottom) {
onScrollToBottom();
}
}
}, { passive: false });
let isDragging = false;
let hasDragged = false;
let startX = 0;
let startY = 0;
let startScroll = 0;
let lastTime = 0;
let lastClientX = 0;
let lastClientY = 0;
let velocityX = 0;
let velocityY = 0;
let inertiaRaf = 0;
let dragPointerId = null;
viewport.addEventListener("pointerdown", (e) => {
if (e.pointerType !== "mouse" || e.button !== 0) return;
cancelAnimationFrame(inertiaRaf);
isDragging = true;
dragPointerId = e.pointerId;
hasDragged = false;
startX = e.clientX;
startY = e.clientY;
startScroll = isVertical() ? viewport.scrollTop : viewport.scrollLeft;
lastClientX = e.clientX;
lastClientY = e.clientY;
lastTime = performance.now();
velocityX = 0;
velocityY = 0;
viewport.style.cursor = "grabbing";
});
window.addEventListener("pointermove", (e) => {
if (!isDragging || e.pointerId !== dragPointerId) return;
if (Math.abs(e.clientX - startX) > 5 || Math.abs(e.clientY - startY) > 5) {
hasDragged = true;
userScrollArmed = true;
}
e.preventDefault();
const now = performance.now();
const dt = now - lastTime;
if (dt > 5) {
const dx = e.clientX - lastClientX;
const dy = e.clientY - lastClientY;
velocityX = 0.6 * velocityX + 0.4 * (-dx / dt);
velocityY = 0.6 * velocityY + 0.4 * (-dy / dt);
lastClientX = e.clientX;
lastClientY = e.clientY;
lastTime = now;
}
const mOffset = maxOffset();
if (isVertical()) {
const newScroll = startScroll - (e.clientY - startY);
viewport.scrollTop = newScroll;
if (startScroll <= 0 && newScroll < -30 && onScrollToTop) {
onScrollToTop();
startScroll = 0;
startY = e.clientY;
} else if (startScroll >= mOffset - 1 && newScroll > mOffset + 30 && onScrollToBottom) {
onScrollToBottom();
startScroll = mOffset;
startY = e.clientY;
}
} else {
const newScroll = startScroll - (e.clientX - startX);
viewport.scrollLeft = newScroll;
if (startScroll <= 0 && newScroll < -30 && onScrollToTop) {
onScrollToTop();
startScroll = 0;
startX = e.clientX;
} else if (startScroll >= mOffset - 1 && newScroll > mOffset + 30 && onScrollToBottom) {
onScrollToBottom();
startScroll = mOffset;
startX = e.clientX;
}
}
});
function endMouseDrag(withInertia) {
if (!isDragging) return;
isDragging = false;
dragPointerId = null;
viewport.style.cursor = "";
if (!withInertia) {
velocityX = 0;
velocityY = 0;
return;
}
const timeSinceLastMove = performance.now() - lastTime;
if (timeSinceLastMove > 50) {
velocityX = 0;
velocityY = 0;
}
let virtualScroll = isVertical() ? viewport.scrollTop : viewport.scrollLeft;
const startInertia = () => {
const v = isVertical() ? velocityY : velocityX;
if (Math.abs(v) < 0.05) return;
const mOffset = maxOffset();
virtualScroll += v * 16;
if (isVertical()) {
viewport.scrollTop = virtualScroll;
if (virtualScroll < -30 && onScrollToTop) {
onScrollToTop();
return;
}
if (virtualScroll > mOffset + 30 && onScrollToBottom) {
onScrollToBottom();
return;
}
velocityY *= 0.95;
if (virtualScroll < 0 || virtualScroll > mOffset) velocityY *= 0.8;
} else {
viewport.scrollLeft = virtualScroll;
if (virtualScroll < -30 && onScrollToTop) {
onScrollToTop();
return;
}
if (virtualScroll > mOffset + 30 && onScrollToBottom) {
onScrollToBottom();
return;
}
velocityX *= 0.95;
if (virtualScroll < 0 || virtualScroll > mOffset) velocityX *= 0.8;
}
inertiaRaf = requestAnimationFrame(startInertia);
};
inertiaRaf = requestAnimationFrame(startInertia);
}
window.addEventListener("pointerup", (e) => {
if (e.pointerId === dragPointerId) endMouseDrag(true);
});
window.addEventListener("pointercancel", (e) => {
if (e.pointerId === dragPointerId) endMouseDrag(false);
});
let touchStartX = 0;
let touchStartY = 0;
viewport.addEventListener("touchstart", (e) => {
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
}, { passive: true });
viewport.addEventListener("touchmove", (e) => {
userScrollArmed = true;
const currentScroll = getScrollOffset();
const mOffset = maxOffset();
if (isVertical()) {
const dy = e.touches[0].clientY - touchStartY;
if (currentScroll <= 0 && dy > 40 && onScrollToTop) {
onScrollToTop();
touchStartY = e.touches[0].clientY;
} else if (currentScroll >= mOffset - 1 && dy < -40 && onScrollToBottom) {
onScrollToBottom();
touchStartY = e.touches[0].clientY;
}
} else {
const dx = e.touches[0].clientX - touchStartX;
if (currentScroll <= 0 && dx > 40 && onScrollToTop) {
onScrollToTop();
touchStartX = e.touches[0].clientX;
} else if (currentScroll >= mOffset - 1 && dx < -40 && onScrollToBottom) {
onScrollToBottom();
touchStartX = e.touches[0].clientX;
}
}
}, { passive: true });
content.addEventListener("click", (e) => {
if (hasDragged) {
e.preventDefault();
e.stopPropagation();
return;
}
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 < options.getImageCount()) {
clickedFromPanel = true;
onIndexChange(index);
}
}
});
window.addEventListener("resize", () => {
userScrollArmed = false;
cancelThumbnailPreloadWork();
if (options.getImageCount() > 0) {
renderVisibleItems();
}
}, { passive: true });
options.subscribeSettingsChanged(() => {
userScrollArmed = false;
cancelThumbnailPreloadWork();
centerOnCurrent();
renderVisibleItems();
});
return {
getElement: () => thumbPanel,
update,
openPanel,
closePanel,
isActive: () => isPanelActive,
resetCentering: () => {
lastCenteredIndex = -1;
}
};
}
function createProgressTrack(onIndexChange, panelIsActive, options) {
const progressTrack = document.createElement("div");
progressTrack.className = "sp-sidebar-track";
const progressThumb = document.createElement("div");
progressThumb.className = "sp-sidebar-thumb";
const progressLabel = document.createElement("div");
progressLabel.className = "sp-sidebar-label";
progressTrack.appendChild(progressThumb);
progressTrack.appendChild(progressLabel);
let cachedTrackSize = 0;
let isDragging = false;
let dragStartPos = 0;
let thumbStartPos = 0;
let previousBodyUserSelect = null;
function isVertical() {
const pos = options.getThumbnailPosition();
return pos === "left" || pos === "right";
}
function refreshTrackSize() {
cachedTrackSize = isVertical() ? progressTrack.offsetHeight : progressTrack.offsetWidth;
}
window.addEventListener("resize", refreshTrackSize, { passive: true });
options.subscribeSettingsChanged(() => {
progressThumb.style.width = "";
progressThumb.style.height = "";
progressThumb.style.left = "";
progressThumb.style.top = "";
refreshTrackSize();
});
function update() {
const imageCount = options.getImageCount();
if (imageCount === 0) return;
if (!cachedTrackSize) refreshTrackSize();
const trackSize = cachedTrackSize;
let thumbSize;
if (imageCount <= 10) {
thumbSize = 60;
} else if (imageCount <= 50) {
thumbSize = Math.max(60, trackSize * (10 / imageCount));
} else {
thumbSize = Math.max(60, trackSize * (5 / imageCount));
}
const currentIndex = options.getCurrentIndex();
const scrollProgress = currentIndex / Math.max(1, imageCount - 1);
const maxThumbPos = trackSize - thumbSize;
const thumbPos = scrollProgress * maxThumbPos;
if (isVertical()) {
progressThumb.style.height = `${thumbSize}px`;
progressThumb.style.top = `${thumbPos}px`;
} else {
progressThumb.style.width = `${thumbSize}px`;
progressThumb.style.left = `${thumbPos}px`;
}
const displayLabel = `${options.getDisplayNumber(currentIndex)} / ${options.getDisplayNumber(imageCount - 1)}`;
progressLabel.textContent = displayLabel;
}
progressTrack.onclick = (e) => {
if (e.target === progressThumb) return;
const rect = progressTrack.getBoundingClientRect();
let scrollProgress = 0;
if (isVertical()) {
const clickY = e.clientY - rect.top;
scrollProgress = Math.min(1, Math.max(0, clickY / rect.height));
} else {
const clickX = e.clientX - rect.left;
scrollProgress = Math.min(1, Math.max(0, clickX / rect.width));
}
const imageCount = options.getImageCount();
const targetIndex = Math.round(scrollProgress * (imageCount - 1));
if (targetIndex >= 0 && targetIndex < imageCount) {
onIndexChange(targetIndex);
}
};
progressThumb.onmousedown = (e) => {
e.preventDefault();
e.stopPropagation();
isDragging = true;
if (isVertical()) {
dragStartPos = e.clientY;
thumbStartPos = progressThumb.offsetTop;
} else {
dragStartPos = e.clientX;
thumbStartPos = progressThumb.offsetLeft;
}
previousBodyUserSelect = document.body.style.userSelect;
document.body.style.userSelect = "none";
};
document.addEventListener("mousemove", (e) => {
if (!isDragging) return;
const delta = isVertical() ? e.clientY - dragStartPos : e.clientX - dragStartPos;
const newPos = thumbStartPos + delta;
const trackSize = cachedTrackSize;
const thumbSize = isVertical() ? progressThumb.offsetHeight : progressThumb.offsetWidth;
const maxPos = trackSize - thumbSize;
const clampedPos = Math.max(0, Math.min(maxPos, newPos));
const scrollProgress = maxPos > 0 ? clampedPos / maxPos : 0;
const imageCount = options.getImageCount();
const targetIndex = Math.round(scrollProgress * (imageCount - 1));
if (targetIndex >= 0 && targetIndex < imageCount && targetIndex !== options.getCurrentIndex()) {
onIndexChange(targetIndex);
}
});
function endProgressDrag(wake = true) {
if (!isDragging) return;
isDragging = false;
document.body.style.userSelect = previousBodyUserSelect ?? "";
previousBodyUserSelect = null;
if (wake) wakeUp();
}
document.addEventListener("mouseup", () => endProgressDrag());
window.addEventListener("blur", () => endProgressDrag(false));
progressThumb.onclick = (e) => e.stopPropagation();
let progressWakeTimer = null;
function wakeUp() {
if (panelIsActive()) return;
progressTrack.classList.add("active");
if (progressWakeTimer) clearTimeout(progressWakeTimer);
progressWakeTimer = setTimeout(() => {
progressTrack.classList.remove("active");
}, 1500);
}
function sleep() {
progressTrack.classList.remove("active");
if (progressWakeTimer) clearTimeout(progressWakeTimer);
}
return {
getElement: () => progressTrack,
update,
wakeUp,
sleep
};
}
function createMouseTracker(panel, progress, getThumbnailPosition) {
const isTouchDevice = window.matchMedia("(hover: none)").matches;
if (isTouchDevice) return;
const SENSITIVITY = 140;
document.addEventListener("mousemove", (e) => {
if (!document.querySelector(".pswp")) return;
if (e.clientX < 0 || e.clientY < 0 || e.clientX >= window.innerWidth - 1 || e.clientY >= window.innerHeight - 1) {
panel.closePanel();
return;
}
const pos = getThumbnailPosition();
let distanceToEdge = Infinity;
switch (pos) {
case "bottom":
distanceToEdge = window.innerHeight - e.clientY;
break;
case "top":
distanceToEdge = e.clientY;
break;
case "left":
distanceToEdge = e.clientX;
break;
case "right":
distanceToEdge = window.innerWidth - e.clientX;
break;
}
if (distanceToEdge <= SENSITIVITY) {
if (!panel.isActive()) {
panel.openPanel();
progress.sleep();
}
} else {
if (panel.isActive()) {
panel.closePanel();
}
}
});
document.addEventListener("mouseout", (e) => {
if (!e.relatedTarget) panel.closePanel();
});
document.documentElement.addEventListener("mouseleave", () => {
panel.closePanel();
});
}
function createSidebar(onIndexChange, onScrollToBottom, onScrollToTop, options) {
const panel = createThumbnailPanel(onIndexChange, onScrollToBottom, onScrollToTop, options);
const progress = createProgressTrack(onIndexChange, panel.isActive, options);
createMouseTracker(panel, progress, options.getThumbnailPosition);
function update() {
panel.update();
progress.update();
}
function applyPositionClasses() {
const newPos = options.getThumbnailPosition();
panel.getElement().classList.remove(`position-top`, `position-bottom`, `position-left`, `position-right`);
progress.getElement().classList.remove(`position-top`, `position-bottom`, `position-left`, `position-right`);
panel.getElement().classList.add(`position-${newPos}`);
progress.getElement().classList.add(`position-${newPos}`);
}
applyPositionClasses();
options.subscribeSettingsChanged(applyPositionClasses);
return {
update,
getElements: () => [progress.getElement(), panel.getElement()],
wakeUpProgressBar: progress.wakeUp,
openPanel: panel.openPanel,
closePanel: panel.closePanel,
resetCentering: panel.resetCentering
};
}
function createReaderShell(options) {
const hud = createStatusHUD();
const sidebar = createSidebar(
options.onIndexChange,
options.onScrollToBottom,
options.onScrollToTop,
{
onMobileInteractionStart: options.onMobileInteractionStart,
onMobileInteractionEnd: options.onMobileInteractionEnd,
subscribeThumbnailChange: options.thumbnailController.subscribeChange,
preloadThumbnails: options.thumbnailController.preload,
cancelThumbnailPreloads: options.thumbnailController.cancelPreloads,
finishThumbnailPreload: options.thumbnailController.finishPreload,
getPreloadedAsset: options.thumbnailController.getPreloadedAsset,
getPreloadPhase: options.thumbnailController.getPreloadPhase,
getImageCount: options.getImageCount,
getCurrentIndex: options.getCurrentIndex,
getImageAt: options.getImageAt,
getItemAt: options.getItemAt,
getDisplayNumber: options.getDisplayNumber,
getThumbnailPosition: options.getThumbnailPosition,
subscribeSettingsChanged: options.subscribeSettingsChanged
}
);
function mount(driver, counter) {
driver.registerCounter(counter);
const sidebarElements = sidebar.getElements();
sidebarElements.forEach((element) => {
element.style.pointerEvents = "auto";
element.dataset.readerWheelBlock = "";
});
driver.appendUi([...sidebarElements, hud.getElement()]);
const unsubscribeVisibility = driver.observeUiVisibility((isVisible) => {
sidebarElements.forEach((element) => {
element.classList.toggle("sp-hidden-by-pswp", !isVisible);
});
if (window.matchMedia("(hover: none)").matches) {
if (isVisible) sidebar.openPanel(false);
else sidebar.closePanel();
}
});
return () => {
unsubscribeVisibility();
sidebar.closePanel();
options.thumbnailController.cancelPreloads();
};
}
return {
update: sidebar.update,
resetCentering: sidebar.resetCentering,
showStatus: hud.show,
hideStatus: hud.hide,
mount
};
}
class ReaderSession {
constructor(getItems) {
this.images = [];
this.index = 0;
this.navigated = false;
this.previousIndex = -1;
this.getItems = getItems;
}
syncImages(images) {
const changed = images.length !== this.images.length || images.some((image, index) => image !== this.images[index]);
if (!changed) return false;
this.images = images;
return true;
}
replaceImage(index, image) {
if (index < 0 || index >= this.images.length) return;
this.images[index] = image;
}
setCurrentIndex(index, trackNavigation = true) {
if (trackNavigation && this.previousIndex !== -1 && this.previousIndex !== index) {
this.navigated = true;
}
this.previousIndex = index;
this.index = index;
}
reset(startIndex) {
this.navigated = false;
this.previousIndex = startIndex;
this.index = startIndex;
}
prepend(count) {
this.index += count;
this.previousIndex = this.index;
}
get currentIndex() {
return this.index;
}
get lastIndex() {
return this.previousIndex;
}
get hasNavigated() {
return this.navigated;
}
get imageCount() {
return this.images.length;
}
elementAt(index) {
return this.images[index];
}
itemAt(index) {
return this.getItems()[index];
}
indexOfViewerUrl(url) {
return this.getItems().findIndex((item) => item.viewerUrl === url);
}
snapshotImages() {
return this.images;
}
}
function createThumbnailController(session, scroll, deps) {
const acquire = deps.acquire;
const maxConcurrent = Math.max(1, deps.maxConcurrent ?? 2);
const entries = new Map();
const listeners = new Set();
let pending = [];
let active = 0;
function notify(index) {
listeners.forEach((listener) => listener(index));
}
function releaseEntry(entry) {
var _a, _b;
(_a = entry.releaseSlot) == null ? void 0 : _a.call(entry);
entry.releaseSlot = void 0;
(_b = entry.lease) == null ? void 0 : _b.release();
entry.lease = void 0;
entry.asset = void 0;
}
function pump() {
while (active < maxConcurrent && pending.length > 0) {
const index = pending.shift();
const entry = entries.get(index);
const item = session.itemAt(index);
if (!entry || entry.phase !== "loading" || !item) continue;
active++;
let slotHeld = true;
entry.releaseSlot = () => {
if (!slotHeld) return;
slotHeld = false;
active--;
};
const lease = acquire(item.viewerUrl, {
intent: "thumbnail",
priority: deps.priority
});
entry.lease = lease;
lease.result.then((asset) => {
if (entries.get(index) !== entry) return;
if (!asset) {
releaseEntry(entry);
entry.phase = "error";
} else {
entry.asset = asset;
entry.phase = "ready";
}
notify(index);
}).catch(() => {
if (entries.get(index) !== entry) return;
releaseEntry(entry);
entry.phase = "error";
notify(index);
}).finally(() => {
var _a;
(_a = entry.releaseSlot) == null ? void 0 : _a.call(entry);
entry.releaseSlot = void 0;
pump();
});
}
}
function cancelPreloads() {
pending = [];
const changed = Array.from(entries.keys());
entries.forEach(releaseEntry);
entries.clear();
changed.forEach(notify);
pump();
}
function preload(indices) {
cancelPreloads();
const unique = new Set();
for (const index of indices) {
if (unique.has(index)) continue;
const item = session.itemAt(index);
if (!item || item.preview.kind !== "none" && item.preview.kind !== "derived") continue;
const element = session.elementAt(index);
if ((element == null ? void 0 : element.tagName) === "IMG" && element.complete && element.naturalWidth > 0) continue;
unique.add(index);
entries.set(index, { phase: "loading" });
pending.push(index);
notify(index);
}
pump();
}
function finishPreload(index, failed = false) {
const entry = entries.get(index);
if (!entry) return;
releaseEntry(entry);
if (failed) {
entry.phase = "error";
} else {
entries.delete(index);
}
notify(index);
}
return {
preload,
cancelPreloads,
finishPreload,
getPreloadedAsset(index) {
var _a;
return (_a = entries.get(index)) == null ? void 0 : _a.asset;
},
getPreloadPhase(index) {
var _a;
return ((_a = entries.get(index)) == null ? void 0 : _a.phase) ?? "idle";
},
subscribeChange(listener) {
listeners.add(listener);
const unsubscribeScroll = scroll.subscribeImageLoaded((event) => listener(event.index));
return () => {
listeners.delete(listener);
unsubscribeScroll();
};
}
};
}
const RETURN_GEOMETRY_RADIUS = 5;
function createReaderController(deps) {
let pswp = null;
let isActive = false;
let isReinitializing = false;
let overflowSnapshot = null;
const session = new ReaderSession(deps.context.getGalleryItems);
let onMobileInteractionStart = () => {
};
let onMobileInteractionEnd = () => {
};
let entryScrollY = 0;
let entryItemKey = "";
function itemKeyAt(index) {
var _a;
const element = session.elementAt(index);
return ((_a = session.itemAt(index)) == null ? void 0 : _a.key) || (element == null ? void 0 : element.dataset.itemKey) || (element == null ? void 0 : element.dataset.viewerUrl) || (element == null ? void 0 : element.dataset.url) || "";
}
function syncImages() {
const freshImages = Array.from(document.querySelectorAll(".r-img, .r-ph"));
if (session.syncImages(freshImages)) {
shell.update();
if (pswp) {
const c = pswp.currentIndex;
for (let i = c - 1; i <= c + 1; i++) {
if (i >= 0 && i < session.imageCount) pswp.refreshSlide(i);
}
}
}
}
const prefetch = createPrefetchController(session);
const thumbnailController = createThumbnailController(session, deps.scroll, {
acquire: acquireImage,
priority: LOAD_PRIORITY.thumbnail
});
const autoPlay = createAutoPlay(() => pswp == null ? void 0 : pswp.next(), deps.context);
let pagination;
const shell = createReaderShell({
onIndexChange: (index) => {
session.setCurrentIndex(index);
pswp == null ? void 0 : pswp.goTo(index);
autoPlay.reset();
},
onScrollToBottom: () => pagination.loadNext(),
onScrollToTop: () => pagination.loadPrev(),
onMobileInteractionStart: () => onMobileInteractionStart(),
onMobileInteractionEnd: () => onMobileInteractionEnd(),
thumbnailController,
getImageCount: () => session.imageCount,
getCurrentIndex: () => session.currentIndex,
getImageAt: (index) => session.elementAt(index),
getItemAt: (index) => session.itemAt(index),
getDisplayNumber: (index) => deps.context.getImageOffset() + index + 1,
getThumbnailPosition: deps.context.getThumbnailPosition,
subscribeSettingsChanged: deps.context.subscribeSettingsChanged
});
pagination = createReaderPaginationController(session, {
pageLoader: deps.pageLoader,
appendPage: deps.onLoadNextPage,
prependPage: deps.onLoadPrevPage,
syncImages,
afterPrepend: (itemCount) => {
isReinitializing = true;
session.prepend(itemCount);
deps.context.setImageOffset(Math.max(0, deps.context.getImageOffset() - itemCount));
if (pswp) {
pswp.stopMotion();
pswp.goTo(session.currentIndex);
}
isReinitializing = false;
},
onLoading: () => shell.showStatus({ status: "loading", text: i18n.downloading }),
onIdle: () => shell.hideStatus(),
onError: (_direction, retry) => {
shell.showStatus({ status: "error", text: i18n.loadFailed, onClick: () => {
shell.hideStatus();
retry();
} });
}
}, deps.context);
function open(startIdx) {
if (isActive) return;
session.syncImages(Array.from(document.querySelectorAll(".r-img, .r-ph")));
if (session.imageCount === 0) {
alert(i18n.waitImagesToLoad);
return;
}
let startIndex = startIdx ?? 0;
if (startIdx === void 0) {
if (deps.context.isScrollMode()) {
let minDistance = Infinity;
session.snapshotImages().forEach((img, index) => {
const rect = img.getBoundingClientRect();
const viewportCenter = window.innerHeight / 2;
if (rect.top <= viewportCenter && rect.bottom >= viewportCenter) {
startIndex = index;
minDistance = -1;
} else if (minDistance !== -1) {
const distanceToCenter = rect.bottom < viewportCenter ? viewportCenter - rect.bottom : rect.top - viewportCenter;
if (distanceToCenter < minDistance) {
minDistance = distanceToCenter;
startIndex = index;
}
}
});
} else {
startIndex = session.currentIndex;
}
}
entryScrollY = window.scrollY;
entryItemKey = itemKeyAt(startIndex);
session.reset(startIndex);
isActive = true;
overflowSnapshot = {
documentElement: document.documentElement.style.overflow,
body: document.body.style.overflow
};
document.documentElement.style.overflow = "hidden";
document.body.style.overflow = "hidden";
deps.context.emitReaderModeChanged();
if (deps.context.isScrollMode()) {
deps.scroll.pause();
document.documentElement.classList.add("hr-reader-open");
}
shell.resetCentering();
initPhotoSwipe(startIndex);
if (deps.context.isAutoPlayEnabled()) {
autoPlay.start();
}
}
function initPhotoSwipe(startIndex) {
let mobileUiTimeout;
function triggerMobileUITimeout() {
const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 0;
if (!isTouchDevice) return;
clearTimeout(mobileUiTimeout);
mobileUiTimeout = setTimeout(() => {
pswp == null ? void 0 : pswp.hideUi();
}, 2e3);
}
function cancelMobileUITimeout() {
clearTimeout(mobileUiTimeout);
}
onMobileInteractionStart = cancelMobileUITimeout;
onMobileInteractionEnd = triggerMobileUITimeout;
function handleScreenClick(point, defaultCenterAction) {
const width = window.innerWidth;
if (point.x < width * 0.3) {
if ((pswp == null ? void 0 : pswp.currentIndex) === 0 && deps.context.getPrevUrl() && !deps.context.isPageFetching()) {
pagination.loadPrev();
} else {
pswp == null ? void 0 : pswp.prev();
}
} else if (point.x > width * 0.7) {
if ((pswp == null ? void 0 : pswp.currentIndex) === session.imageCount - 1 && deps.context.getNextUrl() && !deps.context.isPageFetching()) {
pagination.loadNext();
} else {
pswp == null ? void 0 : pswp.next();
}
} else {
if (defaultCenterAction === "zoom" && (pswp == null ? void 0 : pswp.canToggleCurrentZoom())) {
pswp.toggleCurrentZoom(point);
} else {
const isNowVisible = pswp == null ? void 0 : pswp.toggleUi();
if (isNowVisible) triggerMobileUITimeout();
}
}
}
pswp = deps.createDriver({
startIndex,
onBackgroundClick: (point) => handleScreenClick(point, "toggle"),
onImageClick: (point) => handleScreenClick(point, "zoom"),
onTap: (point) => handleScreenClick(point, "toggle")
});
pswp.on("zoomLevelsUpdate", (e) => {
const zl = e.zoomLevels;
if (zl.initial > zl.fit) zl.fit = zl.initial;
});
const unsubscribeImageLoaded = deps.scroll.subscribeImageLoaded(({ index, element }) => {
session.replaceImage(index, element);
if (pswp && index >= 0 && index < session.imageCount) {
pswp.refreshSlide(index);
}
});
pswp.on("destroy", () => {
clearTimeout(mobileUiTimeout);
onMobileInteractionStart = () => {
};
onMobileInteractionEnd = () => {
};
unsubscribeImageLoaded();
});
function showImagePhase(phase) {
switch (phase) {
case "error":
shell.showStatus({ status: "error", text: i18n.loadFailed });
break;
case "resolving":
shell.showStatus({ status: "loading", text: i18n.resolvingImage });
break;
case "downloading":
shell.showStatus({ status: "loading", text: i18n.downloading });
break;
case "loaded":
shell.hideStatus();
break;
}
}
const imageController = createReaderImageController(session, {
getCurrentIndex: () => (pswp == null ? void 0 : pswp.currentIndex) ?? session.currentIndex,
getSlideContentState: (index) => pswp == null ? void 0 : pswp.getSlideContentState(index),
refreshSlide: (index) => pswp == null ? void 0 : pswp.refreshSlide(index),
onPhaseChange: (index, phase) => {
if ((pswp == null ? void 0 : pswp.currentIndex) === index) showImagePhase(phase);
},
onAssetReady: (index) => {
if ((pswp == null ? void 0 : pswp.currentIndex) === index && deps.context.isAutoPlayEnabled()) autoPlay.start();
}
});
function refreshHudForCurrent() {
if (!pswp) return;
showImagePhase(imageController.getPhase(pswp.currentIndex));
}
pswp.on("destroy", () => imageController.dispose());
pswp.on("contentLoadImage", (e) => {
var _a;
if (typeof ((_a = e.content) == null ? void 0 : _a.index) === "number") imageController.handleContentLoad(e.content.index);
});
pswp.on("loadComplete", (e) => {
var _a;
if (typeof ((_a = e.content) == null ? void 0 : _a.index) === "number") {
imageController.handleLoadComplete(e.content.index, e.isError);
}
});
pswp.on("numItems", (e) => {
e.numItems = session.imageCount;
});
pswp.on("itemData", (e) => {
e.itemData = imageController.getItemData(e.index);
});
const wheelPager = createWheelPager({
getCurrentIndex: () => (pswp == null ? void 0 : pswp.currentIndex) ?? session.currentIndex,
isCurrentZoomed: () => (pswp == null ? void 0 : pswp.isCurrentZoomed()) ?? false,
goTo: (index) => pswp == null ? void 0 : pswp.goTo(index),
stopMotion: () => pswp == null ? void 0 : pswp.stopMotion(),
getImageCount: () => session.imageCount,
isPageLoading: (index) => imageController.isLoading(index),
onEdgeForward: () => {
if (deps.context.getNextUrl() && !deps.context.isPageFetching()) pagination.loadNext();
},
onEdgeBackward: () => {
if (deps.context.getPrevUrl() && !deps.context.isPageFetching()) pagination.loadPrev();
}
});
pswp.on("destroy", () => wheelPager.stop());
pswp.on("change", () => {
if (isReinitializing) {
return;
}
if (pswp) {
const previousIndex = session.lastIndex;
const isNavigatingBackwards = pswp.currentIndex < previousIndex;
session.setCurrentIndex(pswp.currentIndex);
shell.update();
pagination.checkNearEnd();
refreshHudForCurrent();
if (session.currentIndex <= 3 && deps.context.getPrevUrl() && session.hasNavigated && isNavigatingBackwards) {
pagination.loadPrev();
}
if (deps.context.isAutoPlayEnabled()) {
autoPlay.stop();
if (pswp.currentIndex >= session.imageCount - 1 && !deps.context.getNextUrl()) {
autoPlay.stopAtEnd();
} else {
if (pswp.isCurrentContentLoaded()) {
autoPlay.start();
}
}
}
prefetch.setWindow(pswp.currentIndex, isNavigatingBackwards ? -1 : 1);
imageController.releaseOutside(pswp.currentIndex);
}
});
pswp.on("uiRegister", () => {
if (!pswp) return;
const unmountShell = shell.mount(
pswp,
(index) => `${deps.context.getImageOffset() + index + 1} / ${deps.context.getImageOffset() + session.imageCount}`
);
const removeWheel = pswp.installWheel((event) => wheelPager.onWheel(event));
const removeEdgeSwipe = pswp.installEdgeSwipe({
onBackward: () => {
if ((pswp == null ? void 0 : pswp.currentIndex) === 0 && deps.context.getPrevUrl() && !deps.context.isPageFetching()) {
pagination.loadPrev();
}
},
onForward: () => {
if ((pswp == null ? void 0 : pswp.currentIndex) === session.imageCount - 1 && deps.context.getNextUrl() && !deps.context.isPageFetching()) {
pagination.loadNext();
}
}
});
pswp.on("destroy", () => {
removeWheel();
removeEdgeSwipe();
unmountShell();
});
triggerMobileUITimeout();
});
pswp.on("close", () => {
close();
});
pswp.init();
prefetch.setWindow(startIndex, 1);
}
function close() {
autoPlay.stop();
deps.context.setAutoPlayEnabled(false);
isActive = false;
prefetch.clear();
if (overflowSnapshot) {
document.documentElement.style.overflow = overflowSnapshot.documentElement;
document.body.style.overflow = overflowSnapshot.body;
overflowSnapshot = null;
}
if (deps.context.isScrollMode()) {
const currentKey = itemKeyAt(session.currentIndex);
const returnedToEntry = !!entryItemKey && currentKey === entryItemKey;
if (session.hasNavigated && !returnedToEntry) {
const item = session.itemAt(session.currentIndex);
const key = (item == null ? void 0 : item.key) || currentKey;
const firstGeometryIndex = Math.max(0, session.currentIndex - RETURN_GEOMETRY_RADIUS);
const lastGeometryIndex = Math.min(
session.imageCount - 1,
session.currentIndex + RETURN_GEOMETRY_RADIUS
);
const nearbyGeometry = [];
for (let index = firstGeometryIndex; index <= lastGeometryIndex; index++) {
const nearbyItem = session.itemAt(index);
const cachedAsset = nearbyItem && getCachedImage(nearbyItem.viewerUrl);
if (!nearbyItem || !cachedAsset) continue;
nearbyGeometry.push({
key: nearbyItem.key,
index,
preferred: session.elementAt(index),
width: cachedAsset.width,
height: cachedAsset.height
});
}
deps.scroll.restore({
entryScrollY,
nearbyGeometry,
target: {
key,
index: session.currentIndex,
preferred: session.elementAt(session.currentIndex)
}
});
} else {
deps.scroll.restore({ entryScrollY });
}
document.documentElement.classList.remove("hr-reader-open");
deps.scroll.resume();
}
if (pswp) {
const p = pswp;
pswp = null;
try {
p.destroy();
} catch (err) {
console.error("[Hentai-Reader] Error destroying PhotoSwipe:", err);
}
}
deps.context.emitReaderModeChanged();
deps.context.onReaderClose(deps.context.getImageOffset() + session.currentIndex);
}
deps.context.subscribeSettingsChanged(() => {
if (!isActive) return;
if (deps.context.isAutoPlayEnabled()) {
autoPlay.start();
} else {
autoPlay.stop();
}
});
deps.context.subscribeReaderModeChanged(() => {
if (!isActive || !pswp) return;
const el = document.querySelector(".pswp__counter");
if (el) {
el.innerHTML = `${deps.context.getImageOffset() + pswp.currentIndex + 1} / ${deps.context.getImageOffset() + session.imageCount}`;
}
});
function warmupInitial(count) {
if (isActive) return;
session.syncImages(Array.from(document.querySelectorAll(".r-img, .r-ph")));
if (session.imageCount === 0) return;
const indices = [];
for (let i = 0; i < Math.min(count, session.imageCount); i++) {
indices.push(i);
}
prefetch.warmup(indices);
}
return {
open,
close,
isActive: () => isActive,
warmupInitial
};
}
function createElement(className, tagName, appendToEl) {
const el = document.createElement(tagName);
if (className) {
el.className = className;
}
if (appendToEl) {
appendToEl.appendChild(el);
}
return el;
}
function equalizePoints(p1, p2) {
p1.x = p2.x;
p1.y = p2.y;
if (p2.id !== void 0) {
p1.id = p2.id;
}
return p1;
}
function roundPoint(p) {
p.x = Math.round(p.x);
p.y = Math.round(p.y);
}
function getDistanceBetween(p1, p2) {
const x = Math.abs(p1.x - p2.x);
const y = Math.abs(p1.y - p2.y);
return Math.sqrt(x * x + y * y);
}
function pointsEqual(p1, p2) {
return p1.x === p2.x && p1.y === p2.y;
}
function clamp(val, min, max) {
return Math.min(Math.max(val, min), max);
}
function toTransformString(x, y, scale) {
let propValue = `translate3d(${x}px,${y || 0}px,0)`;
if (scale !== void 0) {
propValue += ` scale3d(${scale},${scale},1)`;
}
return propValue;
}
function setTransform(el, x, y, scale) {
el.style.transform = toTransformString(x, y, scale);
}
const defaultCSSEasing = "cubic-bezier(.4,0,.22,1)";
function setTransitionStyle(el, prop, duration, ease) {
el.style.transition = prop ? `${prop} ${duration}ms ${ease || defaultCSSEasing}` : "none";
}
function setWidthHeight(el, w, h) {
el.style.width = typeof w === "number" ? `${w}px` : w;
el.style.height = typeof h === "number" ? `${h}px` : h;
}
function removeTransitionStyle(el) {
setTransitionStyle(el);
}
function decodeImage(img) {
if ("decode" in img) {
return img.decode().catch(() => {
});
}
if (img.complete) {
return Promise.resolve(img);
}
return new Promise((resolve, reject) => {
img.onload = () => resolve(img);
img.onerror = reject;
});
}
const LOAD_STATE = {
IDLE: "idle",
LOADING: "loading",
LOADED: "loaded",
ERROR: "error"
};
function specialKeyUsed(e) {
return "button" in e && e.button === 1 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey;
}
function getElementsFromOption(option, legacySelector, parent = document) {
let elements = [];
if (option instanceof Element) {
elements = [option];
} else if (option instanceof NodeList || Array.isArray(option)) {
elements = Array.from(option);
} else {
const selector = typeof option === "string" ? option : legacySelector;
if (selector) {
elements = Array.from(parent.querySelectorAll(selector));
}
}
return elements;
}
function isSafari() {
return !!(navigator.vendor && navigator.vendor.match(/apple/i));
}
let supportsPassive = false;
try {
window.addEventListener("test", null, Object.defineProperty({}, "passive", {
get: () => {
supportsPassive = true;
}
}));
} catch (e) {
}
class DOMEvents {
constructor() {
this._pool = [];
}
add(target, type, listener, passive) {
this._toggleListener(target, type, listener, passive);
}
remove(target, type, listener, passive) {
this._toggleListener(target, type, listener, passive, true);
}
removeAll() {
this._pool.forEach((poolItem) => {
this._toggleListener(poolItem.target, poolItem.type, poolItem.listener, poolItem.passive, true, true);
});
this._pool = [];
}
_toggleListener(target, type, listener, passive, unbind, skipPool) {
if (!target) {
return;
}
const methodName = unbind ? "removeEventListener" : "addEventListener";
const types = type.split(" ");
types.forEach((eType) => {
if (eType) {
if (!skipPool) {
if (unbind) {
this._pool = this._pool.filter((poolItem) => {
return poolItem.type !== eType || poolItem.listener !== listener || poolItem.target !== target;
});
} else {
this._pool.push({
target,
type: eType,
listener,
passive
});
}
}
const eventOptions = supportsPassive ? {
passive: passive || false
} : false;
target[methodName](eType, listener, eventOptions);
}
});
}
}
function getViewportSize(options, pswp) {
if (options.getViewportSizeFn) {
const newViewportSize = options.getViewportSizeFn(options, pswp);
if (newViewportSize) {
return newViewportSize;
}
}
return {
x: document.documentElement.clientWidth,
y: window.innerHeight
};
}
function parsePaddingOption(prop, options, viewportSize, itemData, index) {
let paddingValue = 0;
if (options.paddingFn) {
paddingValue = options.paddingFn(viewportSize, itemData, index)[prop];
} else if (options.padding) {
paddingValue = options.padding[prop];
} else {
const legacyPropName = "padding" + prop[0].toUpperCase() + prop.slice(1);
if (options[legacyPropName]) {
paddingValue = options[legacyPropName];
}
}
return Number(paddingValue) || 0;
}
function getPanAreaSize(options, viewportSize, itemData, index) {
return {
x: viewportSize.x - parsePaddingOption("left", options, viewportSize, itemData, index) - parsePaddingOption("right", options, viewportSize, itemData, index),
y: viewportSize.y - parsePaddingOption("top", options, viewportSize, itemData, index) - parsePaddingOption("bottom", options, viewportSize, itemData, index)
};
}
class PanBounds {
constructor(slide) {
this.slide = slide;
this.currZoomLevel = 1;
this.center =
{
x: 0,
y: 0
};
this.max =
{
x: 0,
y: 0
};
this.min =
{
x: 0,
y: 0
};
}
update(currZoomLevel) {
this.currZoomLevel = currZoomLevel;
if (!this.slide.width) {
this.reset();
} else {
this._updateAxis("x");
this._updateAxis("y");
this.slide.pswp.dispatch("calcBounds", {
slide: this.slide
});
}
}
_updateAxis(axis) {
const {
pswp
} = this.slide;
const elSize = this.slide[axis === "x" ? "width" : "height"] * this.currZoomLevel;
const paddingProp = axis === "x" ? "left" : "top";
const padding = parsePaddingOption(paddingProp, pswp.options, pswp.viewportSize, this.slide.data, this.slide.index);
const panAreaSize = this.slide.panAreaSize[axis];
this.center[axis] = Math.round((panAreaSize - elSize) / 2) + padding;
this.max[axis] = elSize > panAreaSize ? Math.round(panAreaSize - elSize) + padding : this.center[axis];
this.min[axis] = elSize > panAreaSize ? padding : this.center[axis];
}
reset() {
this.center.x = 0;
this.center.y = 0;
this.max.x = 0;
this.max.y = 0;
this.min.x = 0;
this.min.y = 0;
}
correctPan(axis, panOffset) {
return clamp(panOffset, this.max[axis], this.min[axis]);
}
}
const MAX_IMAGE_WIDTH = 4e3;
class ZoomLevel {
constructor(options, itemData, index, pswp) {
this.pswp = pswp;
this.options = options;
this.itemData = itemData;
this.index = index;
this.panAreaSize = null;
this.elementSize = null;
this.fit = 1;
this.fill = 1;
this.vFill = 1;
this.initial = 1;
this.secondary = 1;
this.max = 1;
this.min = 1;
}
update(maxWidth, maxHeight, panAreaSize) {
const elementSize = {
x: maxWidth,
y: maxHeight
};
this.elementSize = elementSize;
this.panAreaSize = panAreaSize;
const hRatio = panAreaSize.x / elementSize.x;
const vRatio = panAreaSize.y / elementSize.y;
this.fit = Math.min(1, hRatio < vRatio ? hRatio : vRatio);
this.fill = Math.min(1, hRatio > vRatio ? hRatio : vRatio);
this.vFill = Math.min(1, vRatio);
this.initial = this._getInitial();
this.secondary = this._getSecondary();
this.max = Math.max(this.initial, this.secondary, this._getMax());
this.min = Math.min(this.fit, this.initial, this.secondary);
if (this.pswp) {
this.pswp.dispatch("zoomLevelsUpdate", {
zoomLevels: this,
slideData: this.itemData
});
}
}
_parseZoomLevelOption(optionPrefix) {
const optionName = (
optionPrefix + "ZoomLevel"
);
const optionValue = this.options[optionName];
if (!optionValue) {
return;
}
if (typeof optionValue === "function") {
return optionValue(this);
}
if (optionValue === "fill") {
return this.fill;
}
if (optionValue === "fit") {
return this.fit;
}
return Number(optionValue);
}
_getSecondary() {
let currZoomLevel = this._parseZoomLevelOption("secondary");
if (currZoomLevel) {
return currZoomLevel;
}
currZoomLevel = Math.min(1, this.fit * 3);
if (this.elementSize && currZoomLevel * this.elementSize.x > MAX_IMAGE_WIDTH) {
currZoomLevel = MAX_IMAGE_WIDTH / this.elementSize.x;
}
return currZoomLevel;
}
_getInitial() {
return this._parseZoomLevelOption("initial") || this.fit;
}
_getMax() {
return this._parseZoomLevelOption("max") || Math.max(1, this.fit * 4);
}
}
class Slide {
constructor(data, index, pswp) {
this.data = data;
this.index = index;
this.pswp = pswp;
this.isActive = index === pswp.currIndex;
this.currentResolution = 0;
this.panAreaSize = {
x: 0,
y: 0
};
this.pan = {
x: 0,
y: 0
};
this.isFirstSlide = this.isActive && !pswp.opener.isOpen;
this.zoomLevels = new ZoomLevel(pswp.options, data, index, pswp);
this.pswp.dispatch("gettingData", {
slide: this,
data: this.data,
index
});
this.content = this.pswp.contentLoader.getContentBySlide(this);
this.container = createElement("pswp__zoom-wrap", "div");
this.holderElement = null;
this.currZoomLevel = 1;
this.width = this.content.width;
this.height = this.content.height;
this.heavyAppended = false;
this.bounds = new PanBounds(this);
this.prevDisplayedWidth = -1;
this.prevDisplayedHeight = -1;
this.pswp.dispatch("slideInit", {
slide: this
});
}
setIsActive(isActive) {
if (isActive && !this.isActive) {
this.activate();
} else if (!isActive && this.isActive) {
this.deactivate();
}
}
append(holderElement) {
this.holderElement = holderElement;
this.container.style.transformOrigin = "0 0";
if (!this.data) {
return;
}
this.calculateSize();
this.load();
this.updateContentSize();
this.appendHeavy();
this.holderElement.appendChild(this.container);
this.zoomAndPanToInitial();
this.pswp.dispatch("firstZoomPan", {
slide: this
});
this.applyCurrentZoomPan();
this.pswp.dispatch("afterSetContent", {
slide: this
});
if (this.isActive) {
this.activate();
}
}
load() {
this.content.load(false);
this.pswp.dispatch("slideLoad", {
slide: this
});
}
appendHeavy() {
const {
pswp
} = this;
if (this.heavyAppended || !pswp.opener.isOpen || pswp.mainScroll.isShifted() || !this.isActive && false) {
return;
}
if (this.pswp.dispatch("appendHeavy", {
slide: this
}).defaultPrevented) {
return;
}
this.heavyAppended = true;
this.content.append();
this.pswp.dispatch("appendHeavyContent", {
slide: this
});
}
activate() {
this.isActive = true;
this.appendHeavy();
this.content.activate();
this.pswp.dispatch("slideActivate", {
slide: this
});
}
deactivate() {
this.isActive = false;
this.content.deactivate();
if (this.currZoomLevel !== this.zoomLevels.initial) {
this.calculateSize();
}
this.currentResolution = 0;
this.zoomAndPanToInitial();
this.applyCurrentZoomPan();
this.updateContentSize();
this.pswp.dispatch("slideDeactivate", {
slide: this
});
}
destroy() {
this.content.hasSlide = false;
this.content.remove();
this.container.remove();
this.pswp.dispatch("slideDestroy", {
slide: this
});
}
resize() {
if (this.currZoomLevel === this.zoomLevels.initial || !this.isActive) {
this.calculateSize();
this.currentResolution = 0;
this.zoomAndPanToInitial();
this.applyCurrentZoomPan();
this.updateContentSize();
} else {
this.calculateSize();
this.bounds.update(this.currZoomLevel);
this.panTo(this.pan.x, this.pan.y);
}
}
updateContentSize(force) {
const scaleMultiplier = this.currentResolution || this.zoomLevels.initial;
if (!scaleMultiplier) {
return;
}
const width = Math.round(this.width * scaleMultiplier) || this.pswp.viewportSize.x;
const height = Math.round(this.height * scaleMultiplier) || this.pswp.viewportSize.y;
if (!this.sizeChanged(width, height) && !force) {
return;
}
this.content.setDisplayedSize(width, height);
}
sizeChanged(width, height) {
if (width !== this.prevDisplayedWidth || height !== this.prevDisplayedHeight) {
this.prevDisplayedWidth = width;
this.prevDisplayedHeight = height;
return true;
}
return false;
}
getPlaceholderElement() {
var _this$content$placeho;
return (_this$content$placeho = this.content.placeholder) === null || _this$content$placeho === void 0 ? void 0 : _this$content$placeho.element;
}
zoomTo(destZoomLevel, centerPoint, transitionDuration, ignoreBounds) {
const {
pswp
} = this;
if (!this.isZoomable() || pswp.mainScroll.isShifted()) {
return;
}
pswp.dispatch("beforeZoomTo", {
destZoomLevel,
centerPoint,
transitionDuration
});
pswp.animations.stopAllPan();
const prevZoomLevel = this.currZoomLevel;
if (!ignoreBounds) {
destZoomLevel = clamp(destZoomLevel, this.zoomLevels.min, this.zoomLevels.max);
}
this.setZoomLevel(destZoomLevel);
this.pan.x = this.calculateZoomToPanOffset("x", centerPoint, prevZoomLevel);
this.pan.y = this.calculateZoomToPanOffset("y", centerPoint, prevZoomLevel);
roundPoint(this.pan);
const finishTransition = () => {
this._setResolution(destZoomLevel);
this.applyCurrentZoomPan();
};
if (!transitionDuration) {
finishTransition();
} else {
pswp.animations.startTransition({
isPan: true,
name: "zoomTo",
target: this.container,
transform: this.getCurrentTransform(),
onComplete: finishTransition,
duration: transitionDuration,
easing: pswp.options.easing
});
}
}
toggleZoom(centerPoint) {
this.zoomTo(this.currZoomLevel === this.zoomLevels.initial ? this.zoomLevels.secondary : this.zoomLevels.initial, centerPoint, this.pswp.options.zoomAnimationDuration);
}
setZoomLevel(currZoomLevel) {
this.currZoomLevel = currZoomLevel;
this.bounds.update(this.currZoomLevel);
}
calculateZoomToPanOffset(axis, point, prevZoomLevel) {
const totalPanDistance = this.bounds.max[axis] - this.bounds.min[axis];
if (totalPanDistance === 0) {
return this.bounds.center[axis];
}
if (!point) {
point = this.pswp.getViewportCenterPoint();
}
if (!prevZoomLevel) {
prevZoomLevel = this.zoomLevels.initial;
}
const zoomFactor = this.currZoomLevel / prevZoomLevel;
return this.bounds.correctPan(axis, (this.pan[axis] - point[axis]) * zoomFactor + point[axis]);
}
panTo(panX, panY) {
this.pan.x = this.bounds.correctPan("x", panX);
this.pan.y = this.bounds.correctPan("y", panY);
this.applyCurrentZoomPan();
}
isPannable() {
return Boolean(this.width) && this.currZoomLevel > this.zoomLevels.fit;
}
isZoomable() {
return Boolean(this.width) && this.content.isZoomable();
}
applyCurrentZoomPan() {
this._applyZoomTransform(this.pan.x, this.pan.y, this.currZoomLevel);
if (this === this.pswp.currSlide) {
this.pswp.dispatch("zoomPanUpdate", {
slide: this
});
}
}
zoomAndPanToInitial() {
this.currZoomLevel = this.zoomLevels.initial;
this.bounds.update(this.currZoomLevel);
equalizePoints(this.pan, this.bounds.center);
this.pswp.dispatch("initialZoomPan", {
slide: this
});
}
_applyZoomTransform(x, y, zoom) {
zoom /= this.currentResolution || this.zoomLevels.initial;
setTransform(this.container, x, y, zoom);
}
calculateSize() {
const {
pswp
} = this;
equalizePoints(this.panAreaSize, getPanAreaSize(pswp.options, pswp.viewportSize, this.data, this.index));
this.zoomLevels.update(this.width, this.height, this.panAreaSize);
pswp.dispatch("calcSlideSize", {
slide: this
});
}
getCurrentTransform() {
const scale = this.currZoomLevel / (this.currentResolution || this.zoomLevels.initial);
return toTransformString(this.pan.x, this.pan.y, scale);
}
_setResolution(newResolution) {
if (newResolution === this.currentResolution) {
return;
}
this.currentResolution = newResolution;
this.updateContentSize();
this.pswp.dispatch("resolutionChanged");
}
}
const PAN_END_FRICTION = 0.35;
const VERTICAL_DRAG_FRICTION = 0.6;
const MIN_RATIO_TO_CLOSE = 0.4;
const MIN_NEXT_SLIDE_SPEED = 0.5;
function project(initialVelocity, decelerationRate) {
return initialVelocity * decelerationRate / (1 - decelerationRate);
}
class DragHandler {
constructor(gestures) {
this.gestures = gestures;
this.pswp = gestures.pswp;
this.startPan = {
x: 0,
y: 0
};
}
start() {
if (this.pswp.currSlide) {
equalizePoints(this.startPan, this.pswp.currSlide.pan);
}
this.pswp.animations.stopAll();
}
change() {
const {
p1,
prevP1,
dragAxis
} = this.gestures;
const {
currSlide
} = this.pswp;
if (dragAxis === "y" && this.pswp.options.closeOnVerticalDrag && currSlide && currSlide.currZoomLevel <= currSlide.zoomLevels.fit && !this.gestures.isMultitouch) {
const panY = currSlide.pan.y + (p1.y - prevP1.y);
if (!this.pswp.dispatch("verticalDrag", {
panY
}).defaultPrevented) {
this._setPanWithFriction("y", panY, VERTICAL_DRAG_FRICTION);
const bgOpacity = 1 - Math.abs(this._getVerticalDragRatio(currSlide.pan.y));
this.pswp.applyBgOpacity(bgOpacity);
currSlide.applyCurrentZoomPan();
}
} else {
const mainScrollChanged = this._panOrMoveMainScroll("x");
if (!mainScrollChanged) {
this._panOrMoveMainScroll("y");
if (currSlide) {
roundPoint(currSlide.pan);
currSlide.applyCurrentZoomPan();
}
}
}
}
end() {
const {
velocity
} = this.gestures;
const {
mainScroll,
currSlide
} = this.pswp;
let indexDiff = 0;
this.pswp.animations.stopAll();
if (mainScroll.isShifted()) {
const mainScrollShiftDiff = mainScroll.x - mainScroll.getCurrSlideX();
const currentSlideVisibilityRatio = mainScrollShiftDiff / this.pswp.viewportSize.x;
if (velocity.x < -MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio < 0 || velocity.x < 0.1 && currentSlideVisibilityRatio < -0.5) {
indexDiff = 1;
velocity.x = Math.min(velocity.x, 0);
} else if (velocity.x > MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio > 0 || velocity.x > -0.1 && currentSlideVisibilityRatio > 0.5) {
indexDiff = -1;
velocity.x = Math.max(velocity.x, 0);
}
mainScroll.moveIndexBy(indexDiff, true, velocity.x);
}
if (currSlide && currSlide.currZoomLevel > currSlide.zoomLevels.max || this.gestures.isMultitouch) {
this.gestures.zoomLevels.correctZoomPan(true);
} else {
this._finishPanGestureForAxis("x");
this._finishPanGestureForAxis("y");
}
}
_finishPanGestureForAxis(axis) {
const {
velocity
} = this.gestures;
const {
currSlide
} = this.pswp;
if (!currSlide) {
return;
}
const {
pan,
bounds
} = currSlide;
const panPos = pan[axis];
const restoreBgOpacity = this.pswp.bgOpacity < 1 && axis === "y";
const decelerationRate = 0.995;
const projectedPosition = panPos + project(velocity[axis], decelerationRate);
if (restoreBgOpacity) {
const vDragRatio = this._getVerticalDragRatio(panPos);
const projectedVDragRatio = this._getVerticalDragRatio(projectedPosition);
if (vDragRatio < 0 && projectedVDragRatio < -MIN_RATIO_TO_CLOSE || vDragRatio > 0 && projectedVDragRatio > MIN_RATIO_TO_CLOSE) {
this.pswp.close();
return;
}
}
const correctedPanPosition = bounds.correctPan(axis, projectedPosition);
if (panPos === correctedPanPosition) {
return;
}
const dampingRatio = correctedPanPosition === projectedPosition ? 1 : 0.82;
const initialBgOpacity = this.pswp.bgOpacity;
const totalPanDist = correctedPanPosition - panPos;
this.pswp.animations.startSpring({
name: "panGesture" + axis,
isPan: true,
start: panPos,
end: correctedPanPosition,
velocity: velocity[axis],
dampingRatio,
onUpdate: (pos) => {
if (restoreBgOpacity && this.pswp.bgOpacity < 1) {
const animationProgressRatio = 1 - (correctedPanPosition - pos) / totalPanDist;
this.pswp.applyBgOpacity(clamp(initialBgOpacity + (1 - initialBgOpacity) * animationProgressRatio, 0, 1));
}
pan[axis] = Math.floor(pos);
currSlide.applyCurrentZoomPan();
}
});
}
_panOrMoveMainScroll(axis) {
const {
p1,
dragAxis,
prevP1,
isMultitouch
} = this.gestures;
const {
currSlide,
mainScroll
} = this.pswp;
const delta = p1[axis] - prevP1[axis];
const newMainScrollX = mainScroll.x + delta;
if (!delta || !currSlide) {
return false;
}
if (axis === "x" && !currSlide.isPannable() && !isMultitouch) {
mainScroll.moveTo(newMainScrollX, true);
return true;
}
const {
bounds
} = currSlide;
const newPan = currSlide.pan[axis] + delta;
if (this.pswp.options.allowPanToNext && dragAxis === "x" && axis === "x" && !isMultitouch) {
const currSlideMainScrollX = mainScroll.getCurrSlideX();
const mainScrollShiftDiff = mainScroll.x - currSlideMainScrollX;
const isLeftToRight = delta > 0;
const isRightToLeft = !isLeftToRight;
if (newPan > bounds.min[axis] && isLeftToRight) {
const wasAtMinPanPosition = bounds.min[axis] <= this.startPan[axis];
if (wasAtMinPanPosition) {
mainScroll.moveTo(newMainScrollX, true);
return true;
} else {
this._setPanWithFriction(axis, newPan);
}
} else if (newPan < bounds.max[axis] && isRightToLeft) {
const wasAtMaxPanPosition = this.startPan[axis] <= bounds.max[axis];
if (wasAtMaxPanPosition) {
mainScroll.moveTo(newMainScrollX, true);
return true;
} else {
this._setPanWithFriction(axis, newPan);
}
} else {
if (mainScrollShiftDiff !== 0) {
if (mainScrollShiftDiff > 0) {
mainScroll.moveTo(Math.max(newMainScrollX, currSlideMainScrollX), true);
return true;
} else if (mainScrollShiftDiff < 0) {
mainScroll.moveTo(Math.min(newMainScrollX, currSlideMainScrollX), true);
return true;
}
} else {
this._setPanWithFriction(axis, newPan);
}
}
} else {
if (axis === "y") {
if (!mainScroll.isShifted() && bounds.min.y !== bounds.max.y) {
this._setPanWithFriction(axis, newPan);
}
} else {
this._setPanWithFriction(axis, newPan);
}
}
return false;
}
_getVerticalDragRatio(panY) {
var _this$pswp$currSlide$, _this$pswp$currSlide;
return (panY - ((_this$pswp$currSlide$ = (_this$pswp$currSlide = this.pswp.currSlide) === null || _this$pswp$currSlide === void 0 ? void 0 : _this$pswp$currSlide.bounds.center.y) !== null && _this$pswp$currSlide$ !== void 0 ? _this$pswp$currSlide$ : 0)) / (this.pswp.viewportSize.y / 3);
}
_setPanWithFriction(axis, potentialPan, customFriction) {
const {
currSlide
} = this.pswp;
if (!currSlide) {
return;
}
const {
pan,
bounds
} = currSlide;
const correctedPan = bounds.correctPan(axis, potentialPan);
if (correctedPan !== potentialPan || customFriction) {
const delta = Math.round(potentialPan - pan[axis]);
pan[axis] += delta * (customFriction || PAN_END_FRICTION);
} else {
pan[axis] = potentialPan;
}
}
}
const UPPER_ZOOM_FRICTION = 0.05;
const LOWER_ZOOM_FRICTION = 0.15;
function getZoomPointsCenter(p, p1, p2) {
p.x = (p1.x + p2.x) / 2;
p.y = (p1.y + p2.y) / 2;
return p;
}
class ZoomHandler {
constructor(gestures) {
this.gestures = gestures;
this._startPan = {
x: 0,
y: 0
};
this._startZoomPoint = {
x: 0,
y: 0
};
this._zoomPoint = {
x: 0,
y: 0
};
this._wasOverFitZoomLevel = false;
this._startZoomLevel = 1;
}
start() {
const {
currSlide
} = this.gestures.pswp;
if (currSlide) {
this._startZoomLevel = currSlide.currZoomLevel;
equalizePoints(this._startPan, currSlide.pan);
}
this.gestures.pswp.animations.stopAllPan();
this._wasOverFitZoomLevel = false;
}
change() {
const {
p1,
startP1,
p2,
startP2,
pswp
} = this.gestures;
const {
currSlide
} = pswp;
if (!currSlide) {
return;
}
const minZoomLevel = currSlide.zoomLevels.min;
const maxZoomLevel = currSlide.zoomLevels.max;
if (!currSlide.isZoomable() || pswp.mainScroll.isShifted()) {
return;
}
getZoomPointsCenter(this._startZoomPoint, startP1, startP2);
getZoomPointsCenter(this._zoomPoint, p1, p2);
let currZoomLevel = 1 / getDistanceBetween(startP1, startP2) * getDistanceBetween(p1, p2) * this._startZoomLevel;
if (currZoomLevel > currSlide.zoomLevels.initial + currSlide.zoomLevels.initial / 15) {
this._wasOverFitZoomLevel = true;
}
if (currZoomLevel < minZoomLevel) {
if (pswp.options.pinchToClose && !this._wasOverFitZoomLevel && this._startZoomLevel <= currSlide.zoomLevels.initial) {
const bgOpacity = 1 - (minZoomLevel - currZoomLevel) / (minZoomLevel / 1.2);
if (!pswp.dispatch("pinchClose", {
bgOpacity
}).defaultPrevented) {
pswp.applyBgOpacity(bgOpacity);
}
} else {
currZoomLevel = minZoomLevel - (minZoomLevel - currZoomLevel) * LOWER_ZOOM_FRICTION;
}
} else if (currZoomLevel > maxZoomLevel) {
currZoomLevel = maxZoomLevel + (currZoomLevel - maxZoomLevel) * UPPER_ZOOM_FRICTION;
}
currSlide.pan.x = this._calculatePanForZoomLevel("x", currZoomLevel);
currSlide.pan.y = this._calculatePanForZoomLevel("y", currZoomLevel);
currSlide.setZoomLevel(currZoomLevel);
currSlide.applyCurrentZoomPan();
}
end() {
const {
pswp
} = this.gestures;
const {
currSlide
} = pswp;
if ((!currSlide || currSlide.currZoomLevel < currSlide.zoomLevels.initial) && !this._wasOverFitZoomLevel && pswp.options.pinchToClose) {
pswp.close();
} else {
this.correctZoomPan();
}
}
_calculatePanForZoomLevel(axis, currZoomLevel) {
const zoomFactor = currZoomLevel / this._startZoomLevel;
return this._zoomPoint[axis] - (this._startZoomPoint[axis] - this._startPan[axis]) * zoomFactor;
}
correctZoomPan(ignoreGesture) {
const {
pswp
} = this.gestures;
const {
currSlide
} = pswp;
if (!(currSlide !== null && currSlide !== void 0 && currSlide.isZoomable())) {
return;
}
if (this._zoomPoint.x === 0) {
ignoreGesture = true;
}
const prevZoomLevel = currSlide.currZoomLevel;
let destinationZoomLevel;
let currZoomLevelNeedsChange = true;
if (prevZoomLevel < currSlide.zoomLevels.initial) {
destinationZoomLevel = currSlide.zoomLevels.initial;
} else if (prevZoomLevel > currSlide.zoomLevels.max) {
destinationZoomLevel = currSlide.zoomLevels.max;
} else {
currZoomLevelNeedsChange = false;
destinationZoomLevel = prevZoomLevel;
}
const initialBgOpacity = pswp.bgOpacity;
const restoreBgOpacity = pswp.bgOpacity < 1;
const initialPan = equalizePoints({
x: 0,
y: 0
}, currSlide.pan);
let destinationPan = equalizePoints({
x: 0,
y: 0
}, initialPan);
if (ignoreGesture) {
this._zoomPoint.x = 0;
this._zoomPoint.y = 0;
this._startZoomPoint.x = 0;
this._startZoomPoint.y = 0;
this._startZoomLevel = prevZoomLevel;
equalizePoints(this._startPan, initialPan);
}
if (currZoomLevelNeedsChange) {
destinationPan = {
x: this._calculatePanForZoomLevel("x", destinationZoomLevel),
y: this._calculatePanForZoomLevel("y", destinationZoomLevel)
};
}
currSlide.setZoomLevel(destinationZoomLevel);
destinationPan = {
x: currSlide.bounds.correctPan("x", destinationPan.x),
y: currSlide.bounds.correctPan("y", destinationPan.y)
};
currSlide.setZoomLevel(prevZoomLevel);
const panNeedsChange = !pointsEqual(destinationPan, initialPan);
if (!panNeedsChange && !currZoomLevelNeedsChange && !restoreBgOpacity) {
currSlide._setResolution(destinationZoomLevel);
currSlide.applyCurrentZoomPan();
return;
}
pswp.animations.stopAllPan();
pswp.animations.startSpring({
isPan: true,
start: 0,
end: 1e3,
velocity: 0,
dampingRatio: 1,
naturalFrequency: 40,
onUpdate: (now) => {
now /= 1e3;
if (panNeedsChange || currZoomLevelNeedsChange) {
if (panNeedsChange) {
currSlide.pan.x = initialPan.x + (destinationPan.x - initialPan.x) * now;
currSlide.pan.y = initialPan.y + (destinationPan.y - initialPan.y) * now;
}
if (currZoomLevelNeedsChange) {
const newZoomLevel = prevZoomLevel + (destinationZoomLevel - prevZoomLevel) * now;
currSlide.setZoomLevel(newZoomLevel);
}
currSlide.applyCurrentZoomPan();
}
if (restoreBgOpacity && pswp.bgOpacity < 1) {
pswp.applyBgOpacity(clamp(initialBgOpacity + (1 - initialBgOpacity) * now, 0, 1));
}
},
onComplete: () => {
currSlide._setResolution(destinationZoomLevel);
currSlide.applyCurrentZoomPan();
}
});
}
}
function didTapOnMainContent(event) {
return !!
event.target.closest(".pswp__container");
}
class TapHandler {
constructor(gestures) {
this.gestures = gestures;
}
click(point, originalEvent) {
const targetClassList = (
originalEvent.target.classList
);
const isImageClick = targetClassList.contains("pswp__img");
const isBackgroundClick = targetClassList.contains("pswp__item") || targetClassList.contains("pswp__zoom-wrap");
if (isImageClick) {
this._doClickOrTapAction("imageClick", point, originalEvent);
} else if (isBackgroundClick) {
this._doClickOrTapAction("bgClick", point, originalEvent);
}
}
tap(point, originalEvent) {
if (didTapOnMainContent(originalEvent)) {
this._doClickOrTapAction("tap", point, originalEvent);
}
}
doubleTap(point, originalEvent) {
if (didTapOnMainContent(originalEvent)) {
this._doClickOrTapAction("doubleTap", point, originalEvent);
}
}
_doClickOrTapAction(actionName, point, originalEvent) {
var _this$gestures$pswp$e;
const {
pswp
} = this.gestures;
const {
currSlide
} = pswp;
const actionFullName = (
actionName + "Action"
);
const optionValue = pswp.options[actionFullName];
if (pswp.dispatch(actionFullName, {
point,
originalEvent
}).defaultPrevented) {
return;
}
if (typeof optionValue === "function") {
optionValue.call(pswp, point, originalEvent);
return;
}
switch (optionValue) {
case "close":
case "next":
pswp[optionValue]();
break;
case "zoom":
currSlide === null || currSlide === void 0 || currSlide.toggleZoom(point);
break;
case "zoom-or-close":
if (currSlide !== null && currSlide !== void 0 && currSlide.isZoomable() && currSlide.zoomLevels.secondary !== currSlide.zoomLevels.initial) {
currSlide.toggleZoom(point);
} else if (pswp.options.clickToCloseNonZoomable) {
pswp.close();
}
break;
case "toggle-controls":
(_this$gestures$pswp$e = this.gestures.pswp.element) === null || _this$gestures$pswp$e === void 0 || _this$gestures$pswp$e.classList.toggle("pswp--ui-visible");
break;
}
}
}
const AXIS_SWIPE_HYSTERISIS = 10;
const DOUBLE_TAP_DELAY = 300;
const MIN_TAP_DISTANCE = 25;
class Gestures {
constructor(pswp) {
this.pswp = pswp;
this.dragAxis = null;
this.p1 = {
x: 0,
y: 0
};
this.p2 = {
x: 0,
y: 0
};
this.prevP1 = {
x: 0,
y: 0
};
this.prevP2 = {
x: 0,
y: 0
};
this.startP1 = {
x: 0,
y: 0
};
this.startP2 = {
x: 0,
y: 0
};
this.velocity = {
x: 0,
y: 0
};
this._lastStartP1 = {
x: 0,
y: 0
};
this._intervalP1 = {
x: 0,
y: 0
};
this._numActivePoints = 0;
this._ongoingPointers = [];
this._touchEventEnabled = "ontouchstart" in window;
this._pointerEventEnabled = !!window.PointerEvent;
this.supportsTouch = this._touchEventEnabled || this._pointerEventEnabled && navigator.maxTouchPoints > 1;
this._numActivePoints = 0;
this._intervalTime = 0;
this._velocityCalculated = false;
this.isMultitouch = false;
this.isDragging = false;
this.isZooming = false;
this.raf = null;
this._tapTimer = null;
if (!this.supportsTouch) {
pswp.options.allowPanToNext = false;
}
this.drag = new DragHandler(this);
this.zoomLevels = new ZoomHandler(this);
this.tapHandler = new TapHandler(this);
pswp.on("bindEvents", () => {
pswp.events.add(
pswp.scrollWrap,
"click",
this._onClick.bind(this)
);
if (this._pointerEventEnabled) {
this._bindEvents("pointer", "down", "up", "cancel");
} else if (this._touchEventEnabled) {
this._bindEvents("touch", "start", "end", "cancel");
if (pswp.scrollWrap) {
pswp.scrollWrap.ontouchmove = () => {
};
pswp.scrollWrap.ontouchend = () => {
};
}
} else {
this._bindEvents("mouse", "down", "up");
}
});
}
_bindEvents(pref, down, up, cancel) {
const {
pswp
} = this;
const {
events
} = pswp;
const cancelEvent = cancel ? pref + cancel : "";
events.add(
pswp.scrollWrap,
pref + down,
this.onPointerDown.bind(this)
);
events.add(
window,
pref + "move",
this.onPointerMove.bind(this)
);
events.add(
window,
pref + up,
this.onPointerUp.bind(this)
);
if (cancelEvent) {
events.add(
pswp.scrollWrap,
cancelEvent,
this.onPointerUp.bind(this)
);
}
}
onPointerDown(e) {
const isMousePointer = e.type === "mousedown" || e.pointerType === "mouse";
if (isMousePointer && e.button > 0) {
return;
}
const {
pswp
} = this;
if (!pswp.opener.isOpen) {
e.preventDefault();
return;
}
if (pswp.dispatch("pointerDown", {
originalEvent: e
}).defaultPrevented) {
return;
}
if (isMousePointer) {
pswp.mouseDetected();
this._preventPointerEventBehaviour(e, "down");
}
pswp.animations.stopAll();
this._updatePoints(e, "down");
if (this._numActivePoints === 1) {
this.dragAxis = null;
equalizePoints(this.startP1, this.p1);
}
if (this._numActivePoints > 1) {
this._clearTapTimer();
this.isMultitouch = true;
} else {
this.isMultitouch = false;
}
}
onPointerMove(e) {
this._preventPointerEventBehaviour(e, "move");
if (!this._numActivePoints) {
return;
}
this._updatePoints(e, "move");
if (this.pswp.dispatch("pointerMove", {
originalEvent: e
}).defaultPrevented) {
return;
}
if (this._numActivePoints === 1 && !this.isDragging) {
if (!this.dragAxis) {
this._calculateDragDirection();
}
if (this.dragAxis && !this.isDragging) {
if (this.isZooming) {
this.isZooming = false;
this.zoomLevels.end();
}
this.isDragging = true;
this._clearTapTimer();
this._updateStartPoints();
this._intervalTime = Date.now();
this._velocityCalculated = false;
equalizePoints(this._intervalP1, this.p1);
this.velocity.x = 0;
this.velocity.y = 0;
this.drag.start();
this._rafStopLoop();
this._rafRenderLoop();
}
} else if (this._numActivePoints > 1 && !this.isZooming) {
this._finishDrag();
this.isZooming = true;
this._updateStartPoints();
this.zoomLevels.start();
this._rafStopLoop();
this._rafRenderLoop();
}
}
_finishDrag() {
if (this.isDragging) {
this.isDragging = false;
if (!this._velocityCalculated) {
this._updateVelocity(true);
}
this.drag.end();
this.dragAxis = null;
}
}
onPointerUp(e) {
if (!this._numActivePoints) {
return;
}
this._updatePoints(e, "up");
if (this.pswp.dispatch("pointerUp", {
originalEvent: e
}).defaultPrevented) {
return;
}
if (this._numActivePoints === 0) {
this._rafStopLoop();
if (this.isDragging) {
this._finishDrag();
} else if (!this.isZooming && !this.isMultitouch) {
this._finishTap(e);
}
}
if (this._numActivePoints < 2 && this.isZooming) {
this.isZooming = false;
this.zoomLevels.end();
if (this._numActivePoints === 1) {
this.dragAxis = null;
this._updateStartPoints();
}
}
}
_rafRenderLoop() {
if (this.isDragging || this.isZooming) {
this._updateVelocity();
if (this.isDragging) {
if (!pointsEqual(this.p1, this.prevP1)) {
this.drag.change();
}
} else {
if (!pointsEqual(this.p1, this.prevP1) || !pointsEqual(this.p2, this.prevP2)) {
this.zoomLevels.change();
}
}
this._updatePrevPoints();
this.raf = requestAnimationFrame(this._rafRenderLoop.bind(this));
}
}
_updateVelocity(force) {
const time = Date.now();
const duration = time - this._intervalTime;
if (duration < 50 && !force) {
return;
}
this.velocity.x = this._getVelocity("x", duration);
this.velocity.y = this._getVelocity("y", duration);
this._intervalTime = time;
equalizePoints(this._intervalP1, this.p1);
this._velocityCalculated = true;
}
_finishTap(e) {
const {
mainScroll
} = this.pswp;
if (mainScroll.isShifted()) {
mainScroll.moveIndexBy(0, true);
return;
}
if (e.type.indexOf("cancel") > 0) {
return;
}
if (e.type === "mouseup" || e.pointerType === "mouse") {
this.tapHandler.click(this.startP1, e);
return;
}
const tapDelay = this.pswp.options.doubleTapAction ? DOUBLE_TAP_DELAY : 0;
if (this._tapTimer) {
this._clearTapTimer();
if (getDistanceBetween(this._lastStartP1, this.startP1) < MIN_TAP_DISTANCE) {
this.tapHandler.doubleTap(this.startP1, e);
}
} else {
equalizePoints(this._lastStartP1, this.startP1);
this._tapTimer = setTimeout(() => {
this.tapHandler.tap(this.startP1, e);
this._clearTapTimer();
}, tapDelay);
}
}
_clearTapTimer() {
if (this._tapTimer) {
clearTimeout(this._tapTimer);
this._tapTimer = null;
}
}
_getVelocity(axis, duration) {
const displacement = this.p1[axis] - this._intervalP1[axis];
if (Math.abs(displacement) > 1 && duration > 5) {
return displacement / duration;
}
return 0;
}
_rafStopLoop() {
if (this.raf) {
cancelAnimationFrame(this.raf);
this.raf = null;
}
}
_preventPointerEventBehaviour(e, pointerType) {
const preventPointerEvent = this.pswp.applyFilters("preventPointerEvent", true, e, pointerType);
if (preventPointerEvent) {
e.preventDefault();
}
}
_updatePoints(e, pointerType) {
if (this._pointerEventEnabled) {
const pointerEvent = (
e
);
const pointerIndex = this._ongoingPointers.findIndex((ongoingPointer) => {
return ongoingPointer.id === pointerEvent.pointerId;
});
if (pointerType === "up" && pointerIndex > -1) {
this._ongoingPointers.splice(pointerIndex, 1);
} else if (pointerType === "down" && pointerIndex === -1) {
this._ongoingPointers.push(this._convertEventPosToPoint(pointerEvent, {
x: 0,
y: 0
}));
} else if (pointerIndex > -1) {
this._convertEventPosToPoint(pointerEvent, this._ongoingPointers[pointerIndex]);
}
this._numActivePoints = this._ongoingPointers.length;
if (this._numActivePoints > 0) {
equalizePoints(this.p1, this._ongoingPointers[0]);
}
if (this._numActivePoints > 1) {
equalizePoints(this.p2, this._ongoingPointers[1]);
}
} else {
const touchEvent = (
e
);
this._numActivePoints = 0;
if (touchEvent.type.indexOf("touch") > -1) {
if (touchEvent.touches && touchEvent.touches.length > 0) {
this._convertEventPosToPoint(touchEvent.touches[0], this.p1);
this._numActivePoints++;
if (touchEvent.touches.length > 1) {
this._convertEventPosToPoint(touchEvent.touches[1], this.p2);
this._numActivePoints++;
}
}
} else {
this._convertEventPosToPoint(
e,
this.p1
);
if (pointerType === "up") {
this._numActivePoints = 0;
} else {
this._numActivePoints++;
}
}
}
}
_updatePrevPoints() {
equalizePoints(this.prevP1, this.p1);
equalizePoints(this.prevP2, this.p2);
}
_updateStartPoints() {
equalizePoints(this.startP1, this.p1);
equalizePoints(this.startP2, this.p2);
this._updatePrevPoints();
}
_calculateDragDirection() {
if (this.pswp.mainScroll.isShifted()) {
this.dragAxis = "x";
} else {
const diff = Math.abs(this.p1.x - this.startP1.x) - Math.abs(this.p1.y - this.startP1.y);
if (diff !== 0) {
const axisToCheck = diff > 0 ? "x" : "y";
if (Math.abs(this.p1[axisToCheck] - this.startP1[axisToCheck]) >= AXIS_SWIPE_HYSTERISIS) {
this.dragAxis = axisToCheck;
}
}
}
}
_convertEventPosToPoint(e, p) {
p.x = e.pageX - this.pswp.offset.x;
p.y = e.pageY - this.pswp.offset.y;
if ("pointerId" in e) {
p.id = e.pointerId;
} else if (e.identifier !== void 0) {
p.id = e.identifier;
}
return p;
}
_onClick(e) {
if (this.pswp.mainScroll.isShifted()) {
e.preventDefault();
e.stopPropagation();
}
}
}
const MAIN_SCROLL_END_FRICTION = 0.35;
class MainScroll {
constructor(pswp) {
this.pswp = pswp;
this.x = 0;
this.slideWidth = 0;
this._currPositionIndex = 0;
this._prevPositionIndex = 0;
this._containerShiftIndex = -1;
this.itemHolders = [];
}
resize(resizeSlides) {
const {
pswp
} = this;
const newSlideWidth = Math.round(pswp.viewportSize.x + pswp.viewportSize.x * pswp.options.spacing);
const slideWidthChanged = newSlideWidth !== this.slideWidth;
if (slideWidthChanged) {
this.slideWidth = newSlideWidth;
this.moveTo(this.getCurrSlideX());
}
this.itemHolders.forEach((itemHolder, index) => {
if (slideWidthChanged) {
setTransform(itemHolder.el, (index + this._containerShiftIndex) * this.slideWidth);
}
if (resizeSlides && itemHolder.slide) {
itemHolder.slide.resize();
}
});
}
resetPosition() {
this._currPositionIndex = 0;
this._prevPositionIndex = 0;
this.slideWidth = 0;
this._containerShiftIndex = -1;
}
appendHolders() {
this.itemHolders = [];
for (let i = 0; i < 3; i++) {
const el = createElement("pswp__item", "div", this.pswp.container);
el.setAttribute("role", "group");
el.setAttribute("aria-roledescription", "slide");
el.setAttribute("aria-hidden", "true");
el.style.display = i === 1 ? "block" : "none";
this.itemHolders.push({
el
});
}
}
canBeSwiped() {
return this.pswp.getNumItems() > 1;
}
moveIndexBy(diff, animate, velocityX) {
const {
pswp
} = this;
let newIndex = pswp.potentialIndex + diff;
const numSlides = pswp.getNumItems();
if (pswp.canLoop()) {
newIndex = pswp.getLoopedIndex(newIndex);
const distance = (diff + numSlides) % numSlides;
if (distance <= numSlides / 2) {
diff = distance;
} else {
diff = distance - numSlides;
}
} else {
if (newIndex < 0) {
newIndex = 0;
} else if (newIndex >= numSlides) {
newIndex = numSlides - 1;
}
diff = newIndex - pswp.potentialIndex;
}
pswp.potentialIndex = newIndex;
this._currPositionIndex -= diff;
pswp.animations.stopMainScroll();
const destinationX = this.getCurrSlideX();
if (!animate) {
this.moveTo(destinationX);
this.updateCurrItem();
} else {
pswp.animations.startSpring({
isMainScroll: true,
start: this.x,
end: destinationX,
velocity: velocityX || 0,
naturalFrequency: 30,
dampingRatio: 1,
onUpdate: (x) => {
this.moveTo(x);
},
onComplete: () => {
this.updateCurrItem();
pswp.appendHeavy();
}
});
let currDiff = pswp.potentialIndex - pswp.currIndex;
if (pswp.canLoop()) {
const currDistance = (currDiff + numSlides) % numSlides;
if (currDistance <= numSlides / 2) {
currDiff = currDistance;
} else {
currDiff = currDistance - numSlides;
}
}
if (Math.abs(currDiff) > 1) {
this.updateCurrItem();
}
}
return Boolean(diff);
}
getCurrSlideX() {
return this.slideWidth * this._currPositionIndex;
}
isShifted() {
return this.x !== this.getCurrSlideX();
}
updateCurrItem() {
var _this$itemHolders$;
const {
pswp
} = this;
const positionDifference = this._prevPositionIndex - this._currPositionIndex;
if (!positionDifference) {
return;
}
this._prevPositionIndex = this._currPositionIndex;
pswp.currIndex = pswp.potentialIndex;
let diffAbs = Math.abs(positionDifference);
let tempHolder;
if (diffAbs >= 3) {
this._containerShiftIndex += positionDifference + (positionDifference > 0 ? -3 : 3);
diffAbs = 3;
this.itemHolders.forEach((itemHolder) => {
var _itemHolder$slide;
(_itemHolder$slide = itemHolder.slide) === null || _itemHolder$slide === void 0 || _itemHolder$slide.destroy();
itemHolder.slide = void 0;
});
}
for (let i = 0; i < diffAbs; i++) {
if (positionDifference > 0) {
tempHolder = this.itemHolders.shift();
if (tempHolder) {
this.itemHolders[2] = tempHolder;
this._containerShiftIndex++;
setTransform(tempHolder.el, (this._containerShiftIndex + 2) * this.slideWidth);
pswp.setContent(tempHolder, pswp.currIndex - diffAbs + i + 2);
}
} else {
tempHolder = this.itemHolders.pop();
if (tempHolder) {
this.itemHolders.unshift(tempHolder);
this._containerShiftIndex--;
setTransform(tempHolder.el, this._containerShiftIndex * this.slideWidth);
pswp.setContent(tempHolder, pswp.currIndex + diffAbs - i - 2);
}
}
}
if (Math.abs(this._containerShiftIndex) > 50 && !this.isShifted()) {
this.resetPosition();
this.resize();
}
pswp.animations.stopAllPan();
this.itemHolders.forEach((itemHolder, i) => {
if (itemHolder.slide) {
itemHolder.slide.setIsActive(i === 1);
}
});
pswp.currSlide = (_this$itemHolders$ = this.itemHolders[1]) === null || _this$itemHolders$ === void 0 ? void 0 : _this$itemHolders$.slide;
pswp.contentLoader.updateLazy(positionDifference);
if (pswp.currSlide) {
pswp.currSlide.applyCurrentZoomPan();
}
pswp.dispatch("change");
}
moveTo(x, dragging) {
if (!this.pswp.canLoop() && dragging) {
let newSlideIndexOffset = (this.slideWidth * this._currPositionIndex - x) / this.slideWidth;
newSlideIndexOffset += this.pswp.currIndex;
const delta = Math.round(x - this.x);
if (newSlideIndexOffset < 0 && delta > 0 || newSlideIndexOffset >= this.pswp.getNumItems() - 1 && delta < 0) {
x = this.x + delta * MAIN_SCROLL_END_FRICTION;
}
}
this.x = x;
if (this.pswp.container) {
setTransform(this.pswp.container, x);
}
this.pswp.dispatch("moveMainScroll", {
x,
dragging: dragging !== null && dragging !== void 0 ? dragging : false
});
}
}
const KeyboardKeyCodesMap = {
Escape: 27,
z: 90,
ArrowLeft: 37,
ArrowUp: 38,
ArrowRight: 39,
ArrowDown: 40,
Tab: 9
};
const getKeyboardEventKey = (key, isKeySupported) => {
return isKeySupported ? key : KeyboardKeyCodesMap[key];
};
class Keyboard {
constructor(pswp) {
this.pswp = pswp;
this._wasFocused = false;
pswp.on("bindEvents", () => {
if (pswp.options.trapFocus) {
if (!pswp.options.initialPointerPos) {
this._focusRoot();
}
pswp.events.add(
document,
"focusin",
this._onFocusIn.bind(this)
);
}
pswp.events.add(
document,
"keydown",
this._onKeyDown.bind(this)
);
});
const lastActiveElement = (
document.activeElement
);
pswp.on("destroy", () => {
if (pswp.options.returnFocus && lastActiveElement && this._wasFocused) {
lastActiveElement.focus();
}
});
}
_focusRoot() {
if (!this._wasFocused && this.pswp.element) {
this.pswp.element.focus();
this._wasFocused = true;
}
}
_onKeyDown(e) {
const {
pswp
} = this;
if (pswp.dispatch("keydown", {
originalEvent: e
}).defaultPrevented) {
return;
}
if (specialKeyUsed(e)) {
return;
}
let keydownAction;
let axis;
let isForward = false;
const isKeySupported = "key" in e;
switch (isKeySupported ? e.key : e.keyCode) {
case getKeyboardEventKey("Escape", isKeySupported):
if (pswp.options.escKey) {
keydownAction = "close";
}
break;
case getKeyboardEventKey("z", isKeySupported):
keydownAction = "toggleZoom";
break;
case getKeyboardEventKey("ArrowLeft", isKeySupported):
axis = "x";
break;
case getKeyboardEventKey("ArrowUp", isKeySupported):
axis = "y";
break;
case getKeyboardEventKey("ArrowRight", isKeySupported):
axis = "x";
isForward = true;
break;
case getKeyboardEventKey("ArrowDown", isKeySupported):
isForward = true;
axis = "y";
break;
case getKeyboardEventKey("Tab", isKeySupported):
this._focusRoot();
break;
}
if (axis) {
e.preventDefault();
const {
currSlide
} = pswp;
if (pswp.options.arrowKeys && axis === "x" && pswp.getNumItems() > 1) {
keydownAction = isForward ? "next" : "prev";
} else if (currSlide && currSlide.currZoomLevel > currSlide.zoomLevels.fit) {
currSlide.pan[axis] += isForward ? -80 : 80;
currSlide.panTo(currSlide.pan.x, currSlide.pan.y);
}
}
if (keydownAction) {
e.preventDefault();
pswp[keydownAction]();
}
}
_onFocusIn(e) {
const {
template
} = this.pswp;
if (template && document !== e.target && template !== e.target && !template.contains(
e.target
)) {
template.focus();
}
}
}
const DEFAULT_EASING = "cubic-bezier(.4,0,.22,1)";
class CSSAnimation {
constructor(props) {
var _props$prop;
this.props = props;
const {
target,
onComplete,
transform,
onFinish = () => {
},
duration = 333,
easing = DEFAULT_EASING
} = props;
this.onFinish = onFinish;
const prop = transform ? "transform" : "opacity";
const propValue = (_props$prop = props[prop]) !== null && _props$prop !== void 0 ? _props$prop : "";
this._target = target;
this._onComplete = onComplete;
this._finished = false;
this._onTransitionEnd = this._onTransitionEnd.bind(this);
this._helperTimeout = setTimeout(() => {
setTransitionStyle(target, prop, duration, easing);
this._helperTimeout = setTimeout(() => {
target.addEventListener("transitionend", this._onTransitionEnd, false);
target.addEventListener("transitioncancel", this._onTransitionEnd, false);
this._helperTimeout = setTimeout(() => {
this._finalizeAnimation();
}, duration + 500);
target.style[prop] = propValue;
}, 30);
}, 0);
}
_onTransitionEnd(e) {
if (e.target === this._target) {
this._finalizeAnimation();
}
}
_finalizeAnimation() {
if (!this._finished) {
this._finished = true;
this.onFinish();
if (this._onComplete) {
this._onComplete();
}
}
}
destroy() {
if (this._helperTimeout) {
clearTimeout(this._helperTimeout);
}
removeTransitionStyle(this._target);
this._target.removeEventListener("transitionend", this._onTransitionEnd, false);
this._target.removeEventListener("transitioncancel", this._onTransitionEnd, false);
if (!this._finished) {
this._finalizeAnimation();
}
}
}
const DEFAULT_NATURAL_FREQUENCY = 12;
const DEFAULT_DAMPING_RATIO = 0.75;
class SpringEaser {
constructor(initialVelocity, dampingRatio, naturalFrequency) {
this.velocity = initialVelocity * 1e3;
this._dampingRatio = dampingRatio || DEFAULT_DAMPING_RATIO;
this._naturalFrequency = naturalFrequency || DEFAULT_NATURAL_FREQUENCY;
this._dampedFrequency = this._naturalFrequency;
if (this._dampingRatio < 1) {
this._dampedFrequency *= Math.sqrt(1 - this._dampingRatio * this._dampingRatio);
}
}
easeFrame(deltaPosition, deltaTime) {
let displacement = 0;
let coeff;
deltaTime /= 1e3;
const naturalDumpingPow = Math.E ** (-this._dampingRatio * this._naturalFrequency * deltaTime);
if (this._dampingRatio === 1) {
coeff = this.velocity + this._naturalFrequency * deltaPosition;
displacement = (deltaPosition + coeff * deltaTime) * naturalDumpingPow;
this.velocity = displacement * -this._naturalFrequency + coeff * naturalDumpingPow;
} else if (this._dampingRatio < 1) {
coeff = 1 / this._dampedFrequency * (this._dampingRatio * this._naturalFrequency * deltaPosition + this.velocity);
const dumpedFCos = Math.cos(this._dampedFrequency * deltaTime);
const dumpedFSin = Math.sin(this._dampedFrequency * deltaTime);
displacement = naturalDumpingPow * (deltaPosition * dumpedFCos + coeff * dumpedFSin);
this.velocity = displacement * -this._naturalFrequency * this._dampingRatio + naturalDumpingPow * (-this._dampedFrequency * deltaPosition * dumpedFSin + this._dampedFrequency * coeff * dumpedFCos);
}
return displacement;
}
}
class SpringAnimation {
constructor(props) {
this.props = props;
this._raf = 0;
const {
start,
end,
velocity,
onUpdate,
onComplete,
onFinish = () => {
},
dampingRatio,
naturalFrequency
} = props;
this.onFinish = onFinish;
const easer = new SpringEaser(velocity, dampingRatio, naturalFrequency);
let prevTime = Date.now();
let deltaPosition = start - end;
const animationLoop = () => {
if (this._raf) {
deltaPosition = easer.easeFrame(deltaPosition, Date.now() - prevTime);
if (Math.abs(deltaPosition) < 1 && Math.abs(easer.velocity) < 50) {
onUpdate(end);
if (onComplete) {
onComplete();
}
this.onFinish();
} else {
prevTime = Date.now();
onUpdate(deltaPosition + end);
this._raf = requestAnimationFrame(animationLoop);
}
}
};
this._raf = requestAnimationFrame(animationLoop);
}
destroy() {
if (this._raf >= 0) {
cancelAnimationFrame(this._raf);
}
this._raf = 0;
}
}
class Animations {
constructor() {
this.activeAnimations = [];
}
startSpring(props) {
this._start(props, true);
}
startTransition(props) {
this._start(props);
}
_start(props, isSpring) {
const animation = isSpring ? new SpringAnimation(
props
) : new CSSAnimation(
props
);
this.activeAnimations.push(animation);
animation.onFinish = () => this.stop(animation);
return animation;
}
stop(animation) {
animation.destroy();
const index = this.activeAnimations.indexOf(animation);
if (index > -1) {
this.activeAnimations.splice(index, 1);
}
}
stopAll() {
this.activeAnimations.forEach((animation) => {
animation.destroy();
});
this.activeAnimations = [];
}
stopAllPan() {
this.activeAnimations = this.activeAnimations.filter((animation) => {
if (animation.props.isPan) {
animation.destroy();
return false;
}
return true;
});
}
stopMainScroll() {
this.activeAnimations = this.activeAnimations.filter((animation) => {
if (animation.props.isMainScroll) {
animation.destroy();
return false;
}
return true;
});
}
isPanRunning() {
return this.activeAnimations.some((animation) => {
return animation.props.isPan;
});
}
}
class ScrollWheel {
constructor(pswp) {
this.pswp = pswp;
pswp.events.add(
pswp.element,
"wheel",
this._onWheel.bind(this)
);
}
_onWheel(e) {
e.preventDefault();
const {
currSlide
} = this.pswp;
let {
deltaX,
deltaY
} = e;
if (!currSlide) {
return;
}
if (this.pswp.dispatch("wheel", {
originalEvent: e
}).defaultPrevented) {
return;
}
if (e.ctrlKey || this.pswp.options.wheelToZoom) {
if (currSlide.isZoomable()) {
let zoomFactor = -deltaY;
if (e.deltaMode === 1) {
zoomFactor *= 0.05;
} else {
zoomFactor *= e.deltaMode ? 1 : 2e-3;
}
zoomFactor = 2 ** zoomFactor;
const destZoomLevel = currSlide.currZoomLevel * zoomFactor;
currSlide.zoomTo(destZoomLevel, {
x: e.clientX,
y: e.clientY
});
}
} else {
if (currSlide.isPannable()) {
if (e.deltaMode === 1) {
deltaX *= 18;
deltaY *= 18;
}
currSlide.panTo(currSlide.pan.x - deltaX, currSlide.pan.y - deltaY);
}
}
}
}
function addElementHTML(htmlData) {
if (typeof htmlData === "string") {
return htmlData;
}
if (!htmlData || !htmlData.isCustomSVG) {
return "";
}
const svgData = htmlData;
let out = '<svg aria-hidden="true" class="pswp__icn" viewBox="0 0 %d %d" width="%d" height="%d">';
out = out.split("%d").join(
svgData.size || 32
);
if (svgData.outlineID) {
out += '<use class="pswp__icn-shadow" xlink:href="#' + svgData.outlineID + '"/>';
}
out += svgData.inner;
out += "</svg>";
return out;
}
class UIElement {
constructor(pswp, data) {
var _container;
const name = data.name || data.className;
let elementHTML = data.html;
if (pswp.options[name] === false) {
return;
}
if (typeof pswp.options[name + "SVG"] === "string") {
elementHTML = pswp.options[name + "SVG"];
}
pswp.dispatch("uiElementCreate", {
data
});
let className = "";
if (data.isButton) {
className += "pswp__button ";
className += data.className || `pswp__button--${data.name}`;
} else {
className += data.className || `pswp__${data.name}`;
}
let tagName = data.isButton ? data.tagName || "button" : data.tagName || "div";
tagName =
tagName.toLowerCase();
const element = createElement(className, tagName);
if (data.isButton) {
if (tagName === "button") {
element.type = "button";
}
let {
title
} = data;
const {
ariaLabel
} = data;
if (typeof pswp.options[name + "Title"] === "string") {
title = pswp.options[name + "Title"];
}
if (title) {
element.title = title;
}
const ariaText = ariaLabel || title;
if (ariaText) {
element.setAttribute("aria-label", ariaText);
}
}
element.innerHTML = addElementHTML(elementHTML);
if (data.onInit) {
data.onInit(element, pswp);
}
if (data.onClick) {
element.onclick = (e) => {
if (typeof data.onClick === "string") {
pswp[data.onClick]();
} else if (typeof data.onClick === "function") {
data.onClick(e, element, pswp);
}
};
}
const appendTo = data.appendTo || "bar";
let container = pswp.element;
if (appendTo === "bar") {
if (!pswp.topBar) {
pswp.topBar = createElement("pswp__top-bar pswp__hide-on-close", "div", pswp.scrollWrap);
}
container = pswp.topBar;
} else {
element.classList.add("pswp__hide-on-close");
if (appendTo === "wrapper") {
container = pswp.scrollWrap;
}
}
(_container = container) === null || _container === void 0 || _container.appendChild(pswp.applyFilters("uiElement", element, data));
}
}
function initArrowButton(element, pswp, isNextButton) {
element.classList.add("pswp__button--arrow");
element.setAttribute("aria-controls", "pswp__items");
pswp.on("change", () => {
if (!pswp.options.loop) {
if (isNextButton) {
element.disabled = !(pswp.currIndex < pswp.getNumItems() - 1);
} else {
element.disabled = !(pswp.currIndex > 0);
}
}
});
}
const arrowPrev = {
name: "arrowPrev",
className: "pswp__button--arrow--prev",
title: "Previous",
order: 10,
isButton: true,
appendTo: "wrapper",
html: {
isCustomSVG: true,
size: 60,
inner: '<path d="M29 43l-3 3-16-16 16-16 3 3-13 13 13 13z" id="pswp__icn-arrow"/>',
outlineID: "pswp__icn-arrow"
},
onClick: "prev",
onInit: initArrowButton
};
const arrowNext = {
name: "arrowNext",
className: "pswp__button--arrow--next",
title: "Next",
order: 11,
isButton: true,
appendTo: "wrapper",
html: {
isCustomSVG: true,
size: 60,
inner: '<use xlink:href="#pswp__icn-arrow"/>',
outlineID: "pswp__icn-arrow"
},
onClick: "next",
onInit: (el, pswp) => {
initArrowButton(el, pswp, true);
}
};
const closeButton = {
name: "close",
title: "Close",
order: 20,
isButton: true,
html: {
isCustomSVG: true,
inner: '<path d="M24 10l-2-2-6 6-6-6-2 2 6 6-6 6 2 2 6-6 6 6 2-2-6-6z" id="pswp__icn-close"/>',
outlineID: "pswp__icn-close"
},
onClick: "close"
};
const zoomButton = {
name: "zoom",
title: "Zoom",
order: 10,
isButton: true,
html: {
isCustomSVG: true,
inner: '<path d="M17.426 19.926a6 6 0 1 1 1.5-1.5L23 22.5 21.5 24l-4.074-4.074z" id="pswp__icn-zoom"/><path fill="currentColor" class="pswp__zoom-icn-bar-h" d="M11 16v-2h6v2z"/><path fill="currentColor" class="pswp__zoom-icn-bar-v" d="M13 12h2v6h-2z"/>',
outlineID: "pswp__icn-zoom"
},
onClick: "toggleZoom"
};
const loadingIndicator = {
name: "preloader",
appendTo: "bar",
order: 7,
html: {
isCustomSVG: true,
inner: '<path fill-rule="evenodd" clip-rule="evenodd" d="M21.2 16a5.2 5.2 0 1 1-5.2-5.2V8a8 8 0 1 0 8 8h-2.8Z" id="pswp__icn-loading"/>',
outlineID: "pswp__icn-loading"
},
onInit: (indicatorElement, pswp) => {
let isVisible;
let delayTimeout = null;
const toggleIndicatorClass = (className, add) => {
indicatorElement.classList.toggle("pswp__preloader--" + className, add);
};
const setIndicatorVisibility = (visible) => {
if (isVisible !== visible) {
isVisible = visible;
toggleIndicatorClass("active", visible);
}
};
const updatePreloaderVisibility = () => {
var _pswp$currSlide;
if (!((_pswp$currSlide = pswp.currSlide) !== null && _pswp$currSlide !== void 0 && _pswp$currSlide.content.isLoading())) {
setIndicatorVisibility(false);
if (delayTimeout) {
clearTimeout(delayTimeout);
delayTimeout = null;
}
return;
}
if (!delayTimeout) {
delayTimeout = setTimeout(() => {
var _pswp$currSlide2;
setIndicatorVisibility(Boolean((_pswp$currSlide2 = pswp.currSlide) === null || _pswp$currSlide2 === void 0 ? void 0 : _pswp$currSlide2.content.isLoading()));
delayTimeout = null;
}, pswp.options.preloaderDelay);
}
};
pswp.on("change", updatePreloaderVisibility);
pswp.on("loadComplete", (e) => {
if (pswp.currSlide === e.slide) {
updatePreloaderVisibility();
}
});
if (pswp.ui) {
pswp.ui.updatePreloaderVisibility = updatePreloaderVisibility;
}
}
};
const counterIndicator = {
name: "counter",
order: 5,
onInit: (counterElement, pswp) => {
pswp.on("change", () => {
counterElement.innerText = pswp.currIndex + 1 + pswp.options.indexIndicatorSep + pswp.getNumItems();
});
}
};
function setZoomedIn(el, isZoomedIn) {
el.classList.toggle("pswp--zoomed-in", isZoomedIn);
}
class UI {
constructor(pswp) {
this.pswp = pswp;
this.isRegistered = false;
this.uiElementsData = [];
this.items = [];
this.updatePreloaderVisibility = () => {
};
this._lastUpdatedZoomLevel = void 0;
}
init() {
const {
pswp
} = this;
this.isRegistered = false;
this.uiElementsData = [closeButton, arrowPrev, arrowNext, zoomButton, loadingIndicator, counterIndicator];
pswp.dispatch("uiRegister");
this.uiElementsData.sort((a, b) => {
return (a.order || 0) - (b.order || 0);
});
this.items = [];
this.isRegistered = true;
this.uiElementsData.forEach((uiElementData) => {
this.registerElement(uiElementData);
});
pswp.on("change", () => {
var _pswp$element;
(_pswp$element = pswp.element) === null || _pswp$element === void 0 || _pswp$element.classList.toggle("pswp--one-slide", pswp.getNumItems() === 1);
});
pswp.on("zoomPanUpdate", () => this._onZoomPanUpdate());
}
registerElement(elementData) {
if (this.isRegistered) {
this.items.push(new UIElement(this.pswp, elementData));
} else {
this.uiElementsData.push(elementData);
}
}
_onZoomPanUpdate() {
const {
template,
currSlide,
options
} = this.pswp;
if (this.pswp.opener.isClosing || !template || !currSlide) {
return;
}
let {
currZoomLevel
} = currSlide;
if (!this.pswp.opener.isOpen) {
currZoomLevel = currSlide.zoomLevels.initial;
}
if (currZoomLevel === this._lastUpdatedZoomLevel) {
return;
}
this._lastUpdatedZoomLevel = currZoomLevel;
const currZoomLevelDiff = currSlide.zoomLevels.initial - currSlide.zoomLevels.secondary;
if (Math.abs(currZoomLevelDiff) < 0.01 || !currSlide.isZoomable()) {
setZoomedIn(template, false);
template.classList.remove("pswp--zoom-allowed");
return;
}
template.classList.add("pswp--zoom-allowed");
const potentialZoomLevel = currZoomLevel === currSlide.zoomLevels.initial ? currSlide.zoomLevels.secondary : currSlide.zoomLevels.initial;
setZoomedIn(template, potentialZoomLevel <= currZoomLevel);
if (options.imageClickAction === "zoom" || options.imageClickAction === "zoom-or-close") {
template.classList.add("pswp--click-to-zoom");
}
}
}
function getBoundsByElement(el) {
const thumbAreaRect = el.getBoundingClientRect();
return {
x: thumbAreaRect.left,
y: thumbAreaRect.top,
w: thumbAreaRect.width
};
}
function getCroppedBoundsByElement(el, imageWidth, imageHeight) {
const thumbAreaRect = el.getBoundingClientRect();
const hRatio = thumbAreaRect.width / imageWidth;
const vRatio = thumbAreaRect.height / imageHeight;
const fillZoomLevel = hRatio > vRatio ? hRatio : vRatio;
const offsetX = (thumbAreaRect.width - imageWidth * fillZoomLevel) / 2;
const offsetY = (thumbAreaRect.height - imageHeight * fillZoomLevel) / 2;
const bounds = {
x: thumbAreaRect.left + offsetX,
y: thumbAreaRect.top + offsetY,
w: imageWidth * fillZoomLevel
};
bounds.innerRect = {
w: thumbAreaRect.width,
h: thumbAreaRect.height,
x: offsetX,
y: offsetY
};
return bounds;
}
function getThumbBounds(index, itemData, instance) {
const event = instance.dispatch("thumbBounds", {
index,
itemData,
instance
});
if (event.thumbBounds) {
return event.thumbBounds;
}
const {
element
} = itemData;
let thumbBounds;
let thumbnail;
if (element && instance.options.thumbSelector !== false) {
const thumbSelector = instance.options.thumbSelector || "img";
thumbnail = element.matches(thumbSelector) ? element : (
element.querySelector(thumbSelector)
);
}
thumbnail = instance.applyFilters("thumbEl", thumbnail, itemData, index);
if (thumbnail) {
if (!itemData.thumbCropped) {
thumbBounds = getBoundsByElement(thumbnail);
} else {
thumbBounds = getCroppedBoundsByElement(thumbnail, itemData.width || itemData.w || 0, itemData.height || itemData.h || 0);
}
}
return instance.applyFilters("thumbBounds", thumbBounds, itemData, index);
}
class PhotoSwipeEvent {
constructor(type, details) {
this.type = type;
this.defaultPrevented = false;
if (details) {
Object.assign(this, details);
}
}
preventDefault() {
this.defaultPrevented = true;
}
}
class Eventable {
constructor() {
this._listeners = {};
this._filters = {};
this.pswp = void 0;
this.options = void 0;
}
addFilter(name, fn, priority = 100) {
var _this$_filters$name, _this$_filters$name2, _this$pswp;
if (!this._filters[name]) {
this._filters[name] = [];
}
(_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.push({
fn,
priority
});
(_this$_filters$name2 = this._filters[name]) === null || _this$_filters$name2 === void 0 || _this$_filters$name2.sort((f1, f2) => f1.priority - f2.priority);
(_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.addFilter(name, fn, priority);
}
removeFilter(name, fn) {
if (this._filters[name]) {
this._filters[name] = this._filters[name].filter((filter) => filter.fn !== fn);
}
if (this.pswp) {
this.pswp.removeFilter(name, fn);
}
}
applyFilters(name, ...args) {
var _this$_filters$name3;
(_this$_filters$name3 = this._filters[name]) === null || _this$_filters$name3 === void 0 || _this$_filters$name3.forEach((filter) => {
args[0] = filter.fn.apply(this, args);
});
return args[0];
}
on(name, fn) {
var _this$_listeners$name, _this$pswp2;
if (!this._listeners[name]) {
this._listeners[name] = [];
}
(_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.push(fn);
(_this$pswp2 = this.pswp) === null || _this$pswp2 === void 0 || _this$pswp2.on(name, fn);
}
off(name, fn) {
var _this$pswp3;
if (this._listeners[name]) {
this._listeners[name] = this._listeners[name].filter((listener) => fn !== listener);
}
(_this$pswp3 = this.pswp) === null || _this$pswp3 === void 0 || _this$pswp3.off(name, fn);
}
dispatch(name, details) {
var _this$_listeners$name2;
if (this.pswp) {
return this.pswp.dispatch(name, details);
}
const event = (
new PhotoSwipeEvent(name, details)
);
(_this$_listeners$name2 = this._listeners[name]) === null || _this$_listeners$name2 === void 0 || _this$_listeners$name2.forEach((listener) => {
listener.call(this, event);
});
return event;
}
}
class Placeholder {
constructor(imageSrc, container) {
this.element = createElement("pswp__img pswp__img--placeholder", imageSrc ? "img" : "div", container);
if (imageSrc) {
const imgEl = (
this.element
);
imgEl.decoding = "async";
imgEl.alt = "";
imgEl.src = imageSrc;
imgEl.setAttribute("role", "presentation");
}
this.element.setAttribute("aria-hidden", "true");
}
setDisplayedSize(width, height) {
if (!this.element) {
return;
}
if (this.element.tagName === "IMG") {
setWidthHeight(this.element, 250, "auto");
this.element.style.transformOrigin = "0 0";
this.element.style.transform = toTransformString(0, 0, width / 250);
} else {
setWidthHeight(this.element, width, height);
}
}
destroy() {
var _this$element;
if ((_this$element = this.element) !== null && _this$element !== void 0 && _this$element.parentNode) {
this.element.remove();
}
this.element = null;
}
}
class Content {
constructor(itemData, instance, index) {
this.instance = instance;
this.data = itemData;
this.index = index;
this.element = void 0;
this.placeholder = void 0;
this.slide = void 0;
this.displayedImageWidth = 0;
this.displayedImageHeight = 0;
this.width = Number(this.data.w) || Number(this.data.width) || 0;
this.height = Number(this.data.h) || Number(this.data.height) || 0;
this.isAttached = false;
this.hasSlide = false;
this.isDecoding = false;
this.state = LOAD_STATE.IDLE;
if (this.data.type) {
this.type = this.data.type;
} else if (this.data.src) {
this.type = "image";
} else {
this.type = "html";
}
this.instance.dispatch("contentInit", {
content: this
});
}
removePlaceholder() {
if (this.placeholder && !this.keepPlaceholder()) {
setTimeout(() => {
if (this.placeholder) {
this.placeholder.destroy();
this.placeholder = void 0;
}
}, 1e3);
}
}
load(isLazy, reload) {
if (this.slide && this.usePlaceholder()) {
if (!this.placeholder) {
const placeholderSrc = this.instance.applyFilters(
"placeholderSrc",
this.data.msrc && this.slide.isFirstSlide ? this.data.msrc : false,
this
);
this.placeholder = new Placeholder(placeholderSrc, this.slide.container);
} else {
const placeholderEl = this.placeholder.element;
if (placeholderEl && !placeholderEl.parentElement) {
this.slide.container.prepend(placeholderEl);
}
}
}
if (this.element && !reload) {
return;
}
if (this.instance.dispatch("contentLoad", {
content: this,
isLazy
}).defaultPrevented) {
return;
}
if (this.isImageContent()) {
this.element = createElement("pswp__img", "img");
if (this.displayedImageWidth) {
this.loadImage(isLazy);
}
} else {
this.element = createElement("pswp__content", "div");
this.element.innerHTML = this.data.html || "";
}
if (reload && this.slide) {
this.slide.updateContentSize(true);
}
}
loadImage(isLazy) {
var _this$data$src, _this$data$alt;
if (!this.isImageContent() || !this.element || this.instance.dispatch("contentLoadImage", {
content: this,
isLazy
}).defaultPrevented) {
return;
}
const imageElement = (
this.element
);
this.updateSrcsetSizes();
if (this.data.srcset) {
imageElement.srcset = this.data.srcset;
}
imageElement.src = (_this$data$src = this.data.src) !== null && _this$data$src !== void 0 ? _this$data$src : "";
imageElement.alt = (_this$data$alt = this.data.alt) !== null && _this$data$alt !== void 0 ? _this$data$alt : "";
this.state = LOAD_STATE.LOADING;
if (imageElement.complete) {
this.onLoaded();
} else {
imageElement.onload = () => {
this.onLoaded();
};
imageElement.onerror = () => {
this.onError();
};
}
}
setSlide(slide) {
this.slide = slide;
this.hasSlide = true;
this.instance = slide.pswp;
}
onLoaded() {
this.state = LOAD_STATE.LOADED;
if (this.slide && this.element) {
this.instance.dispatch("loadComplete", {
slide: this.slide,
content: this
});
if (this.slide.isActive && this.slide.heavyAppended && !this.element.parentNode) {
this.append();
this.slide.updateContentSize(true);
}
if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {
this.removePlaceholder();
}
}
}
onError() {
this.state = LOAD_STATE.ERROR;
if (this.slide) {
this.displayError();
this.instance.dispatch("loadComplete", {
slide: this.slide,
isError: true,
content: this
});
this.instance.dispatch("loadError", {
slide: this.slide,
content: this
});
}
}
isLoading() {
return this.instance.applyFilters("isContentLoading", this.state === LOAD_STATE.LOADING, this);
}
isError() {
return this.state === LOAD_STATE.ERROR;
}
isImageContent() {
return this.type === "image";
}
setDisplayedSize(width, height) {
if (!this.element) {
return;
}
if (this.placeholder) {
this.placeholder.setDisplayedSize(width, height);
}
if (this.instance.dispatch("contentResize", {
content: this,
width,
height
}).defaultPrevented) {
return;
}
setWidthHeight(this.element, width, height);
if (this.isImageContent() && !this.isError()) {
const isInitialSizeUpdate = !this.displayedImageWidth && width;
this.displayedImageWidth = width;
this.displayedImageHeight = height;
if (isInitialSizeUpdate) {
this.loadImage(false);
} else {
this.updateSrcsetSizes();
}
if (this.slide) {
this.instance.dispatch("imageSizeChange", {
slide: this.slide,
width,
height,
content: this
});
}
}
}
isZoomable() {
return this.instance.applyFilters("isContentZoomable", this.isImageContent() && this.state !== LOAD_STATE.ERROR, this);
}
updateSrcsetSizes() {
if (!this.isImageContent() || !this.element || !this.data.srcset) {
return;
}
const image = (
this.element
);
const sizesWidth = this.instance.applyFilters("srcsetSizesWidth", this.displayedImageWidth, this);
if (!image.dataset.largestUsedSize || sizesWidth > parseInt(image.dataset.largestUsedSize, 10)) {
image.sizes = sizesWidth + "px";
image.dataset.largestUsedSize = String(sizesWidth);
}
}
usePlaceholder() {
return this.instance.applyFilters("useContentPlaceholder", this.isImageContent(), this);
}
lazyLoad() {
if (this.instance.dispatch("contentLazyLoad", {
content: this
}).defaultPrevented) {
return;
}
this.load(true);
}
keepPlaceholder() {
return this.instance.applyFilters("isKeepingPlaceholder", this.isLoading(), this);
}
destroy() {
this.hasSlide = false;
this.slide = void 0;
if (this.instance.dispatch("contentDestroy", {
content: this
}).defaultPrevented) {
return;
}
this.remove();
if (this.placeholder) {
this.placeholder.destroy();
this.placeholder = void 0;
}
if (this.isImageContent() && this.element) {
this.element.onload = null;
this.element.onerror = null;
this.element = void 0;
}
}
displayError() {
if (this.slide) {
var _this$instance$option, _this$instance$option2;
let errorMsgEl = createElement("pswp__error-msg", "div");
errorMsgEl.innerText = (_this$instance$option = (_this$instance$option2 = this.instance.options) === null || _this$instance$option2 === void 0 ? void 0 : _this$instance$option2.errorMsg) !== null && _this$instance$option !== void 0 ? _this$instance$option : "";
errorMsgEl =
this.instance.applyFilters("contentErrorElement", errorMsgEl, this);
this.element = createElement("pswp__content pswp__error-msg-container", "div");
this.element.appendChild(errorMsgEl);
this.slide.container.innerText = "";
this.slide.container.appendChild(this.element);
this.slide.updateContentSize(true);
this.removePlaceholder();
}
}
append() {
if (this.isAttached || !this.element) {
return;
}
this.isAttached = true;
if (this.state === LOAD_STATE.ERROR) {
this.displayError();
return;
}
if (this.instance.dispatch("contentAppend", {
content: this
}).defaultPrevented) {
return;
}
const supportsDecode = "decode" in this.element;
if (this.isImageContent()) {
if (supportsDecode && this.slide && (!this.slide.isActive || isSafari())) {
this.isDecoding = true;
this.element.decode().catch(() => {
}).finally(() => {
this.isDecoding = false;
this.appendImage();
});
} else {
this.appendImage();
}
} else if (this.slide && !this.element.parentNode) {
this.slide.container.appendChild(this.element);
}
}
activate() {
if (this.instance.dispatch("contentActivate", {
content: this
}).defaultPrevented || !this.slide) {
return;
}
if (this.isImageContent() && this.isDecoding && !isSafari()) {
this.appendImage();
} else if (this.isError()) {
this.load(false, true);
}
if (this.slide.holderElement) {
this.slide.holderElement.setAttribute("aria-hidden", "false");
}
}
deactivate() {
this.instance.dispatch("contentDeactivate", {
content: this
});
if (this.slide && this.slide.holderElement) {
this.slide.holderElement.setAttribute("aria-hidden", "true");
}
}
remove() {
this.isAttached = false;
if (this.instance.dispatch("contentRemove", {
content: this
}).defaultPrevented) {
return;
}
if (this.element && this.element.parentNode) {
this.element.remove();
}
if (this.placeholder && this.placeholder.element) {
this.placeholder.element.remove();
}
}
appendImage() {
if (!this.isAttached) {
return;
}
if (this.instance.dispatch("contentAppendImage", {
content: this
}).defaultPrevented) {
return;
}
if (this.slide && this.element && !this.element.parentNode) {
this.slide.container.appendChild(this.element);
}
if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {
this.removePlaceholder();
}
}
}
const MIN_SLIDES_TO_CACHE = 5;
function lazyLoadData(itemData, instance, index) {
const content = instance.createContentFromData(itemData, index);
let zoomLevel;
const {
options
} = instance;
if (options) {
zoomLevel = new ZoomLevel(options, itemData, -1);
let viewportSize;
if (instance.pswp) {
viewportSize = instance.pswp.viewportSize;
} else {
viewportSize = getViewportSize(options, instance);
}
const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index);
zoomLevel.update(content.width, content.height, panAreaSize);
}
content.lazyLoad();
if (zoomLevel) {
content.setDisplayedSize(Math.ceil(content.width * zoomLevel.initial), Math.ceil(content.height * zoomLevel.initial));
}
return content;
}
function lazyLoadSlide(index, instance) {
const itemData = instance.getItemData(index);
if (instance.dispatch("lazyLoadSlide", {
index,
itemData
}).defaultPrevented) {
return;
}
return lazyLoadData(itemData, instance, index);
}
class ContentLoader {
constructor(pswp) {
this.pswp = pswp;
this.limit = Math.max(pswp.options.preload[0] + pswp.options.preload[1] + 1, MIN_SLIDES_TO_CACHE);
this._cachedItems = [];
}
updateLazy(diff) {
const {
pswp
} = this;
if (pswp.dispatch("lazyLoad").defaultPrevented) {
return;
}
const {
preload
} = pswp.options;
const isForward = diff === void 0 ? true : diff >= 0;
let i;
for (i = 0; i <= preload[1]; i++) {
this.loadSlideByIndex(pswp.currIndex + (isForward ? i : -i));
}
for (i = 1; i <= preload[0]; i++) {
this.loadSlideByIndex(pswp.currIndex + (isForward ? -i : i));
}
}
loadSlideByIndex(initialIndex) {
const index = this.pswp.getLoopedIndex(initialIndex);
let content = this.getContentByIndex(index);
if (!content) {
content = lazyLoadSlide(index, this.pswp);
if (content) {
this.addToCache(content);
}
}
}
getContentBySlide(slide) {
let content = this.getContentByIndex(slide.index);
if (!content) {
content = this.pswp.createContentFromData(slide.data, slide.index);
this.addToCache(content);
}
content.setSlide(slide);
return content;
}
addToCache(content) {
this.removeByIndex(content.index);
this._cachedItems.push(content);
if (this._cachedItems.length > this.limit) {
const indexToRemove = this._cachedItems.findIndex((item) => {
return !item.isAttached && !item.hasSlide;
});
if (indexToRemove !== -1) {
const removedItem = this._cachedItems.splice(indexToRemove, 1)[0];
removedItem.destroy();
}
}
}
removeByIndex(index) {
const indexToRemove = this._cachedItems.findIndex((item) => item.index === index);
if (indexToRemove !== -1) {
this._cachedItems.splice(indexToRemove, 1);
}
}
getContentByIndex(index) {
return this._cachedItems.find((content) => content.index === index);
}
destroy() {
this._cachedItems.forEach((content) => content.destroy());
this._cachedItems = [];
}
}
class PhotoSwipeBase extends Eventable {
getNumItems() {
var _this$options;
let numItems = 0;
const dataSource = (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.dataSource;
if (dataSource && "length" in dataSource) {
numItems = dataSource.length;
} else if (dataSource && "gallery" in dataSource) {
if (!dataSource.items) {
dataSource.items = this._getGalleryDOMElements(dataSource.gallery);
}
if (dataSource.items) {
numItems = dataSource.items.length;
}
}
const event = this.dispatch("numItems", {
dataSource,
numItems
});
return this.applyFilters("numItems", event.numItems, dataSource);
}
createContentFromData(slideData, index) {
return new Content(slideData, this, index);
}
getItemData(index) {
var _this$options2;
const dataSource = (_this$options2 = this.options) === null || _this$options2 === void 0 ? void 0 : _this$options2.dataSource;
let dataSourceItem = {};
if (Array.isArray(dataSource)) {
dataSourceItem = dataSource[index];
} else if (dataSource && "gallery" in dataSource) {
if (!dataSource.items) {
dataSource.items = this._getGalleryDOMElements(dataSource.gallery);
}
dataSourceItem = dataSource.items[index];
}
let itemData = dataSourceItem;
if (itemData instanceof Element) {
itemData = this._domElementToItemData(itemData);
}
const event = this.dispatch("itemData", {
itemData: itemData || {},
index
});
return this.applyFilters("itemData", event.itemData, index);
}
_getGalleryDOMElements(galleryElement) {
var _this$options3, _this$options4;
if ((_this$options3 = this.options) !== null && _this$options3 !== void 0 && _this$options3.children || (_this$options4 = this.options) !== null && _this$options4 !== void 0 && _this$options4.childSelector) {
return getElementsFromOption(this.options.children, this.options.childSelector, galleryElement) || [];
}
return [galleryElement];
}
_domElementToItemData(element) {
const itemData = {
element
};
const linkEl = (
element.tagName === "A" ? element : element.querySelector("a")
);
if (linkEl) {
itemData.src = linkEl.dataset.pswpSrc || linkEl.href;
if (linkEl.dataset.pswpSrcset) {
itemData.srcset = linkEl.dataset.pswpSrcset;
}
itemData.width = linkEl.dataset.pswpWidth ? parseInt(linkEl.dataset.pswpWidth, 10) : 0;
itemData.height = linkEl.dataset.pswpHeight ? parseInt(linkEl.dataset.pswpHeight, 10) : 0;
itemData.w = itemData.width;
itemData.h = itemData.height;
if (linkEl.dataset.pswpType) {
itemData.type = linkEl.dataset.pswpType;
}
const thumbnailEl = element.querySelector("img");
if (thumbnailEl) {
var _thumbnailEl$getAttri;
itemData.msrc = thumbnailEl.currentSrc || thumbnailEl.src;
itemData.alt = (_thumbnailEl$getAttri = thumbnailEl.getAttribute("alt")) !== null && _thumbnailEl$getAttri !== void 0 ? _thumbnailEl$getAttri : "";
}
if (linkEl.dataset.pswpCropped || linkEl.dataset.cropped) {
itemData.thumbCropped = true;
}
}
return this.applyFilters("domItemData", itemData, element, linkEl);
}
lazyLoadData(itemData, index) {
return lazyLoadData(itemData, this, index);
}
}
const MIN_OPACITY = 3e-3;
class Opener {
constructor(pswp) {
this.pswp = pswp;
this.isClosed = true;
this.isOpen = false;
this.isClosing = false;
this.isOpening = false;
this._duration = void 0;
this._useAnimation = false;
this._croppedZoom = false;
this._animateRootOpacity = false;
this._animateBgOpacity = false;
this._placeholder = void 0;
this._opacityElement = void 0;
this._cropContainer1 = void 0;
this._cropContainer2 = void 0;
this._thumbBounds = void 0;
this._prepareOpen = this._prepareOpen.bind(this);
pswp.on("firstZoomPan", this._prepareOpen);
}
open() {
this._prepareOpen();
this._start();
}
close() {
if (this.isClosed || this.isClosing || this.isOpening) {
return;
}
const slide = this.pswp.currSlide;
this.isOpen = false;
this.isOpening = false;
this.isClosing = true;
this._duration = this.pswp.options.hideAnimationDuration;
if (slide && slide.currZoomLevel * slide.width >= this.pswp.options.maxWidthToAnimate) {
this._duration = 0;
}
this._applyStartProps();
setTimeout(() => {
this._start();
}, this._croppedZoom ? 30 : 0);
}
_prepareOpen() {
this.pswp.off("firstZoomPan", this._prepareOpen);
if (!this.isOpening) {
const slide = this.pswp.currSlide;
this.isOpening = true;
this.isClosing = false;
this._duration = this.pswp.options.showAnimationDuration;
if (slide && slide.zoomLevels.initial * slide.width >= this.pswp.options.maxWidthToAnimate) {
this._duration = 0;
}
this._applyStartProps();
}
}
_applyStartProps() {
const {
pswp
} = this;
const slide = this.pswp.currSlide;
const {
options
} = pswp;
if (options.showHideAnimationType === "fade") {
options.showHideOpacity = true;
this._thumbBounds = void 0;
} else if (options.showHideAnimationType === "none") {
options.showHideOpacity = false;
this._duration = 0;
this._thumbBounds = void 0;
} else if (this.isOpening && pswp._initialThumbBounds) {
this._thumbBounds = pswp._initialThumbBounds;
} else {
this._thumbBounds = this.pswp.getThumbBounds();
}
this._placeholder = slide === null || slide === void 0 ? void 0 : slide.getPlaceholderElement();
pswp.animations.stopAll();
this._useAnimation = Boolean(this._duration && this._duration > 50);
this._animateZoom = Boolean(this._thumbBounds) && (slide === null || slide === void 0 ? void 0 : slide.content.usePlaceholder()) && (!this.isClosing || !pswp.mainScroll.isShifted());
if (!this._animateZoom) {
this._animateRootOpacity = true;
if (this.isOpening && slide) {
slide.zoomAndPanToInitial();
slide.applyCurrentZoomPan();
}
} else {
var _options$showHideOpac;
this._animateRootOpacity = (_options$showHideOpac = options.showHideOpacity) !== null && _options$showHideOpac !== void 0 ? _options$showHideOpac : false;
}
this._animateBgOpacity = !this._animateRootOpacity && this.pswp.options.bgOpacity > MIN_OPACITY;
this._opacityElement = this._animateRootOpacity ? pswp.element : pswp.bg;
if (!this._useAnimation) {
this._duration = 0;
this._animateZoom = false;
this._animateBgOpacity = false;
this._animateRootOpacity = true;
if (this.isOpening) {
if (pswp.element) {
pswp.element.style.opacity = String(MIN_OPACITY);
}
pswp.applyBgOpacity(1);
}
return;
}
if (this._animateZoom && this._thumbBounds && this._thumbBounds.innerRect) {
var _this$pswp$currSlide;
this._croppedZoom = true;
this._cropContainer1 = this.pswp.container;
this._cropContainer2 = (_this$pswp$currSlide = this.pswp.currSlide) === null || _this$pswp$currSlide === void 0 ? void 0 : _this$pswp$currSlide.holderElement;
if (pswp.container) {
pswp.container.style.overflow = "hidden";
pswp.container.style.width = pswp.viewportSize.x + "px";
}
} else {
this._croppedZoom = false;
}
if (this.isOpening) {
if (this._animateRootOpacity) {
if (pswp.element) {
pswp.element.style.opacity = String(MIN_OPACITY);
}
pswp.applyBgOpacity(1);
} else {
if (this._animateBgOpacity && pswp.bg) {
pswp.bg.style.opacity = String(MIN_OPACITY);
}
if (pswp.element) {
pswp.element.style.opacity = "1";
}
}
if (this._animateZoom) {
this._setClosedStateZoomPan();
if (this._placeholder) {
this._placeholder.style.willChange = "transform";
this._placeholder.style.opacity = String(MIN_OPACITY);
}
}
} else if (this.isClosing) {
if (pswp.mainScroll.itemHolders[0]) {
pswp.mainScroll.itemHolders[0].el.style.display = "none";
}
if (pswp.mainScroll.itemHolders[2]) {
pswp.mainScroll.itemHolders[2].el.style.display = "none";
}
if (this._croppedZoom) {
if (pswp.mainScroll.x !== 0) {
pswp.mainScroll.resetPosition();
pswp.mainScroll.resize();
}
}
}
}
_start() {
if (this.isOpening && this._useAnimation && this._placeholder && this._placeholder.tagName === "IMG") {
new Promise((resolve) => {
let decoded = false;
let isDelaying = true;
decodeImage(
this._placeholder
).finally(() => {
decoded = true;
if (!isDelaying) {
resolve(true);
}
});
setTimeout(() => {
isDelaying = false;
if (decoded) {
resolve(true);
}
}, 50);
setTimeout(resolve, 250);
}).finally(() => this._initiate());
} else {
this._initiate();
}
}
_initiate() {
var _this$pswp$element, _this$pswp$element2;
(_this$pswp$element = this.pswp.element) === null || _this$pswp$element === void 0 || _this$pswp$element.style.setProperty("--pswp-transition-duration", this._duration + "ms");
this.pswp.dispatch(this.isOpening ? "openingAnimationStart" : "closingAnimationStart");
this.pswp.dispatch(
"initialZoom" + (this.isOpening ? "In" : "Out")
);
(_this$pswp$element2 = this.pswp.element) === null || _this$pswp$element2 === void 0 || _this$pswp$element2.classList.toggle("pswp--ui-visible", this.isOpening);
if (this.isOpening) {
if (this._placeholder) {
this._placeholder.style.opacity = "1";
}
this._animateToOpenState();
} else if (this.isClosing) {
this._animateToClosedState();
}
if (!this._useAnimation) {
this._onAnimationComplete();
}
}
_onAnimationComplete() {
const {
pswp
} = this;
this.isOpen = this.isOpening;
this.isClosed = this.isClosing;
this.isOpening = false;
this.isClosing = false;
pswp.dispatch(this.isOpen ? "openingAnimationEnd" : "closingAnimationEnd");
pswp.dispatch(
"initialZoom" + (this.isOpen ? "InEnd" : "OutEnd")
);
if (this.isClosed) {
pswp.destroy();
} else if (this.isOpen) {
var _pswp$currSlide;
if (this._animateZoom && pswp.container) {
pswp.container.style.overflow = "visible";
pswp.container.style.width = "100%";
}
(_pswp$currSlide = pswp.currSlide) === null || _pswp$currSlide === void 0 || _pswp$currSlide.applyCurrentZoomPan();
}
}
_animateToOpenState() {
const {
pswp
} = this;
if (this._animateZoom) {
if (this._croppedZoom && this._cropContainer1 && this._cropContainer2) {
this._animateTo(this._cropContainer1, "transform", "translate3d(0,0,0)");
this._animateTo(this._cropContainer2, "transform", "none");
}
if (pswp.currSlide) {
pswp.currSlide.zoomAndPanToInitial();
this._animateTo(pswp.currSlide.container, "transform", pswp.currSlide.getCurrentTransform());
}
}
if (this._animateBgOpacity && pswp.bg) {
this._animateTo(pswp.bg, "opacity", String(pswp.options.bgOpacity));
}
if (this._animateRootOpacity && pswp.element) {
this._animateTo(pswp.element, "opacity", "1");
}
}
_animateToClosedState() {
const {
pswp
} = this;
if (this._animateZoom) {
this._setClosedStateZoomPan(true);
}
if (this._animateBgOpacity && pswp.bgOpacity > 0.01 && pswp.bg) {
this._animateTo(pswp.bg, "opacity", "0");
}
if (this._animateRootOpacity && pswp.element) {
this._animateTo(pswp.element, "opacity", "0");
}
}
_setClosedStateZoomPan(animate) {
if (!this._thumbBounds) return;
const {
pswp
} = this;
const {
innerRect
} = this._thumbBounds;
const {
currSlide,
viewportSize
} = pswp;
if (this._croppedZoom && innerRect && this._cropContainer1 && this._cropContainer2) {
const containerOnePanX = -viewportSize.x + (this._thumbBounds.x - innerRect.x) + innerRect.w;
const containerOnePanY = -viewportSize.y + (this._thumbBounds.y - innerRect.y) + innerRect.h;
const containerTwoPanX = viewportSize.x - innerRect.w;
const containerTwoPanY = viewportSize.y - innerRect.h;
if (animate) {
this._animateTo(this._cropContainer1, "transform", toTransformString(containerOnePanX, containerOnePanY));
this._animateTo(this._cropContainer2, "transform", toTransformString(containerTwoPanX, containerTwoPanY));
} else {
setTransform(this._cropContainer1, containerOnePanX, containerOnePanY);
setTransform(this._cropContainer2, containerTwoPanX, containerTwoPanY);
}
}
if (currSlide) {
equalizePoints(currSlide.pan, innerRect || this._thumbBounds);
currSlide.currZoomLevel = this._thumbBounds.w / currSlide.width;
if (animate) {
this._animateTo(currSlide.container, "transform", currSlide.getCurrentTransform());
} else {
currSlide.applyCurrentZoomPan();
}
}
}
_animateTo(target, prop, propValue) {
if (!this._duration) {
target.style[prop] = propValue;
return;
}
const {
animations
} = this.pswp;
const animProps = {
duration: this._duration,
easing: this.pswp.options.easing,
onComplete: () => {
if (!animations.activeAnimations.length) {
this._onAnimationComplete();
}
},
target
};
animProps[prop] = propValue;
animations.startTransition(animProps);
}
}
const defaultOptions = {
allowPanToNext: true,
spacing: 0.1,
loop: true,
pinchToClose: true,
closeOnVerticalDrag: true,
hideAnimationDuration: 333,
showAnimationDuration: 333,
zoomAnimationDuration: 333,
escKey: true,
arrowKeys: true,
trapFocus: true,
returnFocus: true,
maxWidthToAnimate: 4e3,
clickToCloseNonZoomable: true,
imageClickAction: "zoom-or-close",
bgClickAction: "close",
tapAction: "toggle-controls",
doubleTapAction: "zoom",
indexIndicatorSep: " / ",
preloaderDelay: 2e3,
bgOpacity: 0.8,
index: 0,
errorMsg: "The image cannot be loaded",
preload: [1, 2],
easing: "cubic-bezier(.4,0,.22,1)"
};
class PhotoSwipe extends PhotoSwipeBase {
constructor(options) {
super();
this.options = this._prepareOptions(options || {});
this.offset = {
x: 0,
y: 0
};
this._prevViewportSize = {
x: 0,
y: 0
};
this.viewportSize = {
x: 0,
y: 0
};
this.bgOpacity = 1;
this.currIndex = 0;
this.potentialIndex = 0;
this.isOpen = false;
this.isDestroying = false;
this.hasMouse = false;
this._initialItemData = {};
this._initialThumbBounds = void 0;
this.topBar = void 0;
this.element = void 0;
this.template = void 0;
this.container = void 0;
this.scrollWrap = void 0;
this.currSlide = void 0;
this.events = new DOMEvents();
this.animations = new Animations();
this.mainScroll = new MainScroll(this);
this.gestures = new Gestures(this);
this.opener = new Opener(this);
this.keyboard = new Keyboard(this);
this.contentLoader = new ContentLoader(this);
}
init() {
if (this.isOpen || this.isDestroying) {
return false;
}
this.isOpen = true;
this.dispatch("init");
this.dispatch("beforeOpen");
this._createMainStructure();
let rootClasses = "pswp--open";
if (this.gestures.supportsTouch) {
rootClasses += " pswp--touch";
}
if (this.options.mainClass) {
rootClasses += " " + this.options.mainClass;
}
if (this.element) {
this.element.className += " " + rootClasses;
}
this.currIndex = this.options.index || 0;
this.potentialIndex = this.currIndex;
this.dispatch("firstUpdate");
this.scrollWheel = new ScrollWheel(this);
if (Number.isNaN(this.currIndex) || this.currIndex < 0 || this.currIndex >= this.getNumItems()) {
this.currIndex = 0;
}
if (!this.gestures.supportsTouch) {
this.mouseDetected();
}
this.updateSize();
this.offset.y = window.pageYOffset;
this._initialItemData = this.getItemData(this.currIndex);
this.dispatch("gettingData", {
index: this.currIndex,
data: this._initialItemData,
slide: void 0
});
this._initialThumbBounds = this.getThumbBounds();
this.dispatch("initialLayout");
this.on("openingAnimationEnd", () => {
const {
itemHolders
} = this.mainScroll;
if (itemHolders[0]) {
itemHolders[0].el.style.display = "block";
this.setContent(itemHolders[0], this.currIndex - 1);
}
if (itemHolders[2]) {
itemHolders[2].el.style.display = "block";
this.setContent(itemHolders[2], this.currIndex + 1);
}
this.appendHeavy();
this.contentLoader.updateLazy();
this.events.add(window, "resize", this._handlePageResize.bind(this));
this.events.add(window, "scroll", this._updatePageScrollOffset.bind(this));
this.dispatch("bindEvents");
});
if (this.mainScroll.itemHolders[1]) {
this.setContent(this.mainScroll.itemHolders[1], this.currIndex);
}
this.dispatch("change");
this.opener.open();
this.dispatch("afterInit");
return true;
}
getLoopedIndex(index) {
const numSlides = this.getNumItems();
if (this.options.loop) {
if (index > numSlides - 1) {
index -= numSlides;
}
if (index < 0) {
index += numSlides;
}
}
return clamp(index, 0, numSlides - 1);
}
appendHeavy() {
this.mainScroll.itemHolders.forEach((itemHolder) => {
var _itemHolder$slide;
(_itemHolder$slide = itemHolder.slide) === null || _itemHolder$slide === void 0 || _itemHolder$slide.appendHeavy();
});
}
goTo(index) {
this.mainScroll.moveIndexBy(this.getLoopedIndex(index) - this.potentialIndex);
}
next() {
this.goTo(this.potentialIndex + 1);
}
prev() {
this.goTo(this.potentialIndex - 1);
}
zoomTo(...args) {
var _this$currSlide;
(_this$currSlide = this.currSlide) === null || _this$currSlide === void 0 || _this$currSlide.zoomTo(...args);
}
toggleZoom() {
var _this$currSlide2;
(_this$currSlide2 = this.currSlide) === null || _this$currSlide2 === void 0 || _this$currSlide2.toggleZoom();
}
close() {
if (!this.opener.isOpen || this.isDestroying) {
return;
}
this.isDestroying = true;
this.dispatch("close");
this.events.removeAll();
this.opener.close();
}
destroy() {
var _this$element;
if (!this.isDestroying) {
this.options.showHideAnimationType = "none";
this.close();
return;
}
this.dispatch("destroy");
this._listeners = {};
if (this.scrollWrap) {
this.scrollWrap.ontouchmove = null;
this.scrollWrap.ontouchend = null;
}
(_this$element = this.element) === null || _this$element === void 0 || _this$element.remove();
this.mainScroll.itemHolders.forEach((itemHolder) => {
var _itemHolder$slide2;
(_itemHolder$slide2 = itemHolder.slide) === null || _itemHolder$slide2 === void 0 || _itemHolder$slide2.destroy();
});
this.contentLoader.destroy();
this.events.removeAll();
}
refreshSlideContent(slideIndex) {
this.contentLoader.removeByIndex(slideIndex);
this.mainScroll.itemHolders.forEach((itemHolder, i) => {
var _this$currSlide$index, _this$currSlide3;
let potentialHolderIndex = ((_this$currSlide$index = (_this$currSlide3 = this.currSlide) === null || _this$currSlide3 === void 0 ? void 0 : _this$currSlide3.index) !== null && _this$currSlide$index !== void 0 ? _this$currSlide$index : 0) - 1 + i;
if (this.canLoop()) {
potentialHolderIndex = this.getLoopedIndex(potentialHolderIndex);
}
if (potentialHolderIndex === slideIndex) {
this.setContent(itemHolder, slideIndex, true);
if (i === 1) {
var _itemHolder$slide3;
this.currSlide = itemHolder.slide;
(_itemHolder$slide3 = itemHolder.slide) === null || _itemHolder$slide3 === void 0 || _itemHolder$slide3.setIsActive(true);
}
}
});
this.dispatch("change");
}
setContent(holder, index, force) {
if (this.canLoop()) {
index = this.getLoopedIndex(index);
}
if (holder.slide) {
if (holder.slide.index === index && !force) {
return;
}
holder.slide.destroy();
holder.slide = void 0;
}
if (!this.canLoop() && (index < 0 || index >= this.getNumItems())) {
return;
}
const itemData = this.getItemData(index);
holder.slide = new Slide(itemData, index, this);
if (index === this.currIndex) {
this.currSlide = holder.slide;
}
holder.slide.append(holder.el);
}
getViewportCenterPoint() {
return {
x: this.viewportSize.x / 2,
y: this.viewportSize.y / 2
};
}
updateSize(force) {
if (this.isDestroying) {
return;
}
const newViewportSize = getViewportSize(this.options, this);
if (!force && pointsEqual(newViewportSize, this._prevViewportSize)) {
return;
}
equalizePoints(this._prevViewportSize, newViewportSize);
this.dispatch("beforeResize");
equalizePoints(this.viewportSize, this._prevViewportSize);
this._updatePageScrollOffset();
this.dispatch("viewportSize");
this.mainScroll.resize(this.opener.isOpen);
if (!this.hasMouse && window.matchMedia("(any-hover: hover)").matches) {
this.mouseDetected();
}
this.dispatch("resize");
}
applyBgOpacity(opacity) {
this.bgOpacity = Math.max(opacity, 0);
if (this.bg) {
this.bg.style.opacity = String(this.bgOpacity * this.options.bgOpacity);
}
}
mouseDetected() {
if (!this.hasMouse) {
var _this$element2;
this.hasMouse = true;
(_this$element2 = this.element) === null || _this$element2 === void 0 || _this$element2.classList.add("pswp--has_mouse");
}
}
_handlePageResize() {
this.updateSize();
if (/iPhone|iPad|iPod/i.test(window.navigator.userAgent)) {
setTimeout(() => {
this.updateSize();
}, 500);
}
}
_updatePageScrollOffset() {
this.setScrollOffset(0, window.pageYOffset);
}
setScrollOffset(x, y) {
this.offset.x = x;
this.offset.y = y;
this.dispatch("updateScrollOffset");
}
_createMainStructure() {
this.element = createElement("pswp", "div");
this.element.setAttribute("tabindex", "-1");
this.element.setAttribute("role", "dialog");
this.template = this.element;
this.bg = createElement("pswp__bg", "div", this.element);
this.scrollWrap = createElement("pswp__scroll-wrap", "section", this.element);
this.container = createElement("pswp__container", "div", this.scrollWrap);
this.scrollWrap.setAttribute("aria-roledescription", "carousel");
this.container.setAttribute("aria-live", "off");
this.container.setAttribute("id", "pswp__items");
this.mainScroll.appendHolders();
this.ui = new UI(this);
this.ui.init();
(this.options.appendToEl || document.body).appendChild(this.element);
}
getThumbBounds() {
return getThumbBounds(this.currIndex, this.currSlide ? this.currSlide.data : this._initialItemData, this);
}
canLoop() {
return this.options.loop && this.getNumItems() > 2;
}
_prepareOptions(options) {
if (window.matchMedia("(prefers-reduced-motion), (update: slow)").matches) {
options.showHideAnimationType = "none";
options.zoomAnimationDuration = 0;
}
return {
...defaultOptions,
...options
};
}
}
class PhotoSwipeDriver {
constructor(options) {
this.instance = new PhotoSwipe({
index: options.startIndex,
counter: false,
bgOpacity: 1,
spacing: 0.1,
loop: false,
wheelToZoom: false,
preload: [1, 1],
closeOnVerticalDrag: true,
arrowPrev: false,
arrowNext: false,
doubleTapAction: false,
initialZoomLevel: (zoomLevelObject) => {
if (!zoomLevelObject.panAreaSize || !zoomLevelObject.elementSize) return zoomLevelObject.fit;
const hRatio = zoomLevelObject.panAreaSize.x / zoomLevelObject.elementSize.x;
const vRatio = zoomLevelObject.panAreaSize.y / zoomLevelObject.elementSize.y;
return hRatio < vRatio ? hRatio : vRatio;
},
secondaryZoomLevel: (zoomLevelObject) => {
if (!zoomLevelObject.panAreaSize || !zoomLevelObject.elementSize) return zoomLevelObject.fit * 1.5;
const hRatio = zoomLevelObject.panAreaSize.x / zoomLevelObject.elementSize.x;
return hRatio * 1.5;
},
maxZoomLevel: (zoomLevelObject) => {
if (!zoomLevelObject.panAreaSize || !zoomLevelObject.elementSize) return 4;
const hRatio = zoomLevelObject.panAreaSize.x / zoomLevelObject.elementSize.x;
return Math.max(hRatio * 3, 4);
},
bgClickAction: (point) => options.onBackgroundClick(point),
imageClickAction: (point) => options.onImageClick(point),
tapAction: (point) => options.onTap(point)
});
}
get currentIndex() {
return this.instance.currIndex;
}
on(event, listener) {
this.instance.on(event, listener);
}
init() {
this.instance.init();
}
destroy() {
this.instance.destroy();
}
next() {
this.instance.next();
}
prev() {
this.instance.prev();
}
goTo(index) {
this.instance.goTo(index);
}
refreshSlide(index) {
this.instance.refreshSlideContent(index);
}
stopMotion() {
var _a, _b;
(_b = (_a = this.instance.mainScroll) == null ? void 0 : _a.stop) == null ? void 0 : _b.call(_a);
}
getSlideContentState(index) {
var _a, _b, _c;
const instance = this.instance;
const slide = ((_a = instance.slides) == null ? void 0 : _a[index]) ?? ((_b = instance.getSlideByIndex) == null ? void 0 : _b.call(instance, index));
return (_c = slide == null ? void 0 : slide.content) == null ? void 0 : _c.state;
}
isCurrentContentLoaded() {
var _a, _b;
return ((_b = (_a = this.instance.currSlide) == null ? void 0 : _a.content) == null ? void 0 : _b.state) === "loaded";
}
isCurrentZoomed() {
const slide = this.instance.currSlide;
return !!slide && slide.currZoomLevel > slide.zoomLevels.initial;
}
isCurrentAtInitialZoom() {
const slide = this.instance.currSlide;
return !!slide && slide.currZoomLevel <= slide.zoomLevels.initial;
}
canToggleCurrentZoom() {
const slide = this.instance.currSlide;
return !!slide && slide.isZoomable() && slide.zoomLevels.secondary !== slide.zoomLevels.initial;
}
toggleCurrentZoom(point) {
var _a;
(_a = this.instance.currSlide) == null ? void 0 : _a.toggleZoom(point);
}
hideUi() {
var _a;
(_a = this.instance.element) == null ? void 0 : _a.classList.remove("pswp--ui-visible");
}
toggleUi() {
var _a;
return ((_a = this.instance.element) == null ? void 0 : _a.classList.toggle("pswp--ui-visible")) ?? false;
}
appendUi(elements) {
const root = this.instance.element;
if (!root) return;
for (const element of elements) root.appendChild(element);
}
registerCounter(render) {
var _a;
(_a = this.instance.ui) == null ? void 0 : _a.registerElement({
name: "custom-counter",
order: 5,
onInit: (element, instance) => {
element.className = "pswp__counter";
const update = () => {
element.innerHTML = render(instance.currIndex);
};
update();
instance.on("change", update);
}
});
}
observeUiVisibility(listener) {
const root = this.instance.element;
if (!root) return () => {
};
const notify = () => listener(root.classList.contains("pswp--ui-visible"));
const observer = new MutationObserver(notify);
observer.observe(root, { attributes: true, attributeFilter: ["class"] });
notify();
return () => observer.disconnect();
}
installWheel(listener) {
const root = this.instance.element;
if (!root) return () => {
};
const onWheel = (event) => {
const target = event.target;
if (target == null ? void 0 : target.closest("[data-reader-wheel-block]")) return;
listener(event);
};
document.addEventListener("wheel", onWheel, { passive: false, capture: true });
return () => document.removeEventListener("wheel", onWheel, { capture: true });
}
installEdgeSwipe(options) {
const root = this.instance.element;
if (!root) return () => {
};
let touchStartX = 0;
let touchStartY = 0;
const onStart = (event) => {
if (event.touches.length !== 1) return;
touchStartX = event.touches[0].clientX;
touchStartY = event.touches[0].clientY;
};
const onEnd = (event) => {
if (event.changedTouches.length !== 1 || !this.isCurrentAtInitialZoom()) return;
const deltaX = event.changedTouches[0].clientX - touchStartX;
const deltaY = event.changedTouches[0].clientY - touchStartY;
if (Math.abs(deltaX) <= 50 || Math.abs(deltaY) >= Math.abs(deltaX)) return;
if (deltaX > 0) options.onBackward();
else options.onForward();
};
root.addEventListener("touchstart", onStart, { passive: true });
root.addEventListener("touchend", onEnd, { passive: true });
return () => {
root.removeEventListener("touchstart", onStart);
root.removeEventListener("touchend", onEnd);
};
}
}
function createPhotoSwipeDriver(options) {
return new PhotoSwipeDriver(options);
}
function createReader(pageLoader) {
const reader = createReaderController({
pageLoader,
createDriver: createPhotoSwipeDriver,
scroll: {
pause: pauseLazyLoad,
resume: resumeLazyLoad,
requestImage: loadPlaceholderImage,
subscribeImageLoaded: subscribeScrollImageLoaded,
restore: restoreReaderScroll
},
context: {
getGalleryItems: () => store.galleryItems,
isScrollMode: () => store.settings.scrollMode,
isAutoPlayEnabled: () => store.autoPlay,
setAutoPlayEnabled: (enabled) => {
if (store.autoPlay === enabled) return;
store.autoPlay = enabled;
store.emit("settingsChanged");
},
getAutoPlayInterval: () => store.settings.autoPlayInterval,
getThumbnailPosition: () => store.settings.thumbnailPosition,
getImageOffset: () => store.imageOffset,
setImageOffset: (offset) => {
store.imageOffset = offset;
},
getNextUrl: () => store.nextUrl,
setNextUrl: (url) => {
store.nextUrl = url;
},
getPrevUrl: () => store.prevUrl,
setPrevUrl: (url) => {
store.prevUrl = url;
},
isPageFetching: () => store.isFetching,
setPageFetching: (fetching) => {
store.isFetching = fetching;
},
subscribeSettingsChanged: (listener) => store.on("settingsChanged", listener),
subscribeReaderModeChanged: (listener) => store.on("readerModeChanged", listener),
emitReaderModeChanged: () => store.emit("readerModeChanged"),
onReaderClose: (globalIndex) => {
var _a, _b;
return (_b = (_a = store.activeAdapter) == null ? void 0 : _a.onReaderClose) == null ? void 0 : _b.call(_a, globalIndex, {
scrollMode: store.settings.scrollMode,
pageSize: store.perPage
});
}
},
onLoadNextPage: (page) => {
store.currPage++;
processBatch(page.items, store.currPage, void 0, false, page.pageUrl);
store.nextUrl = page.nextUrl;
},
onLoadPrevPage: (page) => {
store.currPage--;
processBatch(page.items, store.currPage, void 0, true, page.pageUrl);
store.prevUrl = page.prevUrl;
}
});
return reader;
}
const floatControlCss = ".bookmark-control{position:fixed;z-index:2147483647;display:flex;flex-direction:column;align-items:center;background:#1e1e1e99;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.1);border-radius:24px;padding:16px 8px;gap:16px;transition:opacity .3s,left .3s cubic-bezier(.2,.8,.2,1),right .3s cubic-bezier(.2,.8,.2,1);box-shadow:0 4px 16px #0000004d;-webkit-user-select:none;user-select:none;touch-action:none;transform:translateY(-50%) scale(var(--hr-scale, 1))}.bookmark-control.hidden{opacity:0;pointer-events:none}.bookmark-control:not(.dragging){opacity:.4}.bookmark-control:not(.dragging):active{opacity:1}@media(hover:hover){.bookmark-control:not(.dragging):hover{opacity:1}}.bookmark-control.left-side{left:0;border-radius:0 28px 28px 0;border-left:none;padding:16px 8px 16px 4px;transform-origin:left center}.bookmark-control.right-side{right:0;border-radius:28px 0 0 28px;border-right:none;padding:16px 4px 16px 8px;transform-origin:right center}.bm-btn{width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .2s;background:transparent;color:#fff}.bm-btn:hover{background:#ffffff1a;transform:scale(1.1)}.bookmark-control .bm-btn svg{width:24px;height:24px;opacity:.8}.bookmark-control .bm-btn svg:not([fill=none]),.bookmark-control .bm-btn svg:not([fill=none]) *{fill:currentColor!important;stroke:none!important}.bookmark-control .bm-btn svg[fill=none],.bookmark-control .bm-btn svg[fill=none] *{fill:none!important;stroke:currentColor!important}.bm-btn:hover svg{opacity:1}.bm-mode-btn{background:var(--hr-accent);width:48px;height:48px;box-shadow:0 2px 8px #f596aa66}.bm-mode-btn:hover{background:var(--hr-accent-hover);box-shadow:0 4px 12px #f596aa99;transform:scale(1.05)}.bm-mode-btn svg{width:26px;height:26px;opacity:1}:root.hr-mobile .bookmark-control{padding:12px 8px;gap:12px;border-radius:24px}:root.hr-mobile .bookmark-control.left-side{padding:12px 8px 12px 6px;border-radius:0 24px 24px 0}:root.hr-mobile .bookmark-control.right-side{padding:12px 6px 12px 8px;border-radius:24px 0 0 24px}:root.hr-mobile .bm-btn{width:46px;height:46px}:root.hr-mobile .bm-btn svg{width:26px;height:26px}:root.hr-mobile .bm-mode-btn{width:52px;height:52px}:root.hr-mobile .bm-mode-btn svg{width:28px;height:28px}";
importCSS(floatControlCss);
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" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path></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 19h4V5H6v14zm8-14v14h4V5h-4z"/></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 svgScroll = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="2" width="18" height="20" rx="2" ry="2"></rect><line x1="3" y1="15" x2="21" y2="15"></line><line x1="12" y1="2" x2="10" y2="15"></line><path d="M9 19h.01"></path><path d="M15 19h.01"></path><path d="M11 19.5a1.5 1.5 0 0 0 2 0"></path></svg>`;
const svgClose = `<svg class="pswp__icn" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`;
const settingsPanelCss = ".settings-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background:#00000080;z-index:2147483648;opacity:0;pointer-events:none;transition:opacity .3s;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}.settings-backdrop{width:calc(100% / var(--hr-scale, 1));height:calc(100% / var(--hr-scale, 1));transform:scale(var(--hr-scale, 1));transform-origin:top left}.settings-backdrop.show{opacity:1;pointer-events:auto}.settings-bottom-sheet{position:absolute;bottom:-100%;left:0;width:100%;background:#1a1a1a;border-radius:24px 24px 0 0;padding:0 20px 30px;box-sizing:border-box;transition:bottom .3s cubic-bezier(.2,.8,.2,1);box-shadow:0 -4px 20px #00000080;border-top:1px solid rgba(255,255,255,.05)}.settings-backdrop.show .settings-bottom-sheet{bottom:0}:root:not(.hr-mobile) .settings-bottom-sheet{max-width:480px;left:50%;transform:translate(-50%);border-radius:16px 16px 0 0;border:1px solid rgba(255,255,255,.05);border-bottom:none}@media(hover:hover)and (pointer:fine){:root:not(.hr-mobile) .settings-backdrop{background:none;backdrop-filter:none;-webkit-backdrop-filter:none}:root:not(.hr-mobile) .settings-bottom-sheet{position:absolute;width:340px;max-width:none;border-radius:16px;border:1px solid rgba(255,255,255,.1);box-shadow:0 10px 40px #00000080;padding:0 20px 20px;transition:opacity .2s,transform .2s;opacity:0;pointer-events:none;bottom:auto!important;left:auto;transform:scale(.95)}:root:not(.hr-mobile) .settings-backdrop.show .settings-bottom-sheet{opacity:1;pointer-events:auto;transform:scale(1)}}.settings-sheet-header{position:relative;width:100%;height:48px;display:flex;justify-content:center;align-items:center;margin-bottom:20px;border-bottom:1px solid rgba(255,255,255,.05)}.settings-title{font-size:18px;font-weight:600;color:#fff;margin:0}.settings-close-btn{position:absolute;right:0;top:0;bottom:0;margin:auto;width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .2s;color:#fff9}.settings-close-btn:hover{background:#ffffff1a;color:#fff}.settings-close-btn svg{width:22px;height:22px;stroke:currentColor}.settings-item{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;font-size:15px;color:#eee}.settings-item:last-child{margin-bottom:0}.settings-label{margin-right:10px;white-space:nowrap;font-weight:500}.toggle-switch{width:44px;height:24px;background:#333;border-radius:12px;position:relative;cursor:pointer;transition:background .3s}.toggle-switch.on{background:var(--hr-accent)}.toggle-slider{width:20px;height:20px;background:#fff;border-radius:50%;position:absolute;top:2px;left:2px;transition:left .3s;box-shadow:0 1px 3px #0000004d}.toggle-switch.on .toggle-slider{left:22px}.stepper-control{display:flex;align-items:center;background:#ffffff14;border-radius:8px;overflow:hidden}.stepper-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;color:#fff;cursor:pointer;-webkit-user-select:none;user-select:none;font-size:22px;transition:background .2s}.stepper-btn:hover{background:#ffffff26}.stepper-btn:active{background:#ffffff40}.interval-input{width:50px;background:transparent;border:none;border-left:1px solid rgba(255,255,255,.1);border-right:1px solid rgba(255,255,255,.1);border-radius:0;color:#fff;padding:6px 0;font-size:15px;text-align:center;height:36px;box-sizing:border-box;outline:none}.interval-input::-webkit-outer-spin-button,.interval-input::-webkit-inner-spin-button{-webkit-appearance:none;-moz-appearance:none;appearance:none;margin:0}.interval-input[type=number]{-moz-appearance:textfield;-webkit-appearance:textfield;appearance:textfield}.interval-input:focus{background:#ffffff0d}.segment-control{display:flex;background:#ffffff14;border-radius:8px;padding:4px;gap:4px}.segment-item{padding:6px 8px;border-radius:6px;font-size:13px;color:#fff9;cursor:pointer;transition:all .2s ease;-webkit-user-select:none;user-select:none;font-weight:500;white-space:nowrap}.segment-item:hover{color:#ffffffe6}.segment-item.active{background:#ffffff26;color:#fff;box-shadow:0 2px 6px #0003}:root.hr-mobile .settings-sheet-header{height:40px;margin-bottom:10px}:root.hr-mobile .settings-title{font-size:17px}:root.hr-mobile .settings-close-btn{width:36px;height:36px}:root.hr-mobile .settings-close-btn svg{width:20px;height:20px}:root.hr-mobile .settings-item{margin-bottom:14px;font-size:15px;font-weight:600;gap:12px}:root.hr-mobile .settings-item:last-child{margin-bottom:0}:root.hr-mobile .toggle-switch{width:48px;height:28px;border-radius:14px;flex-shrink:0}:root.hr-mobile .toggle-slider{width:24px;height:24px;top:2px;left:2px}:root.hr-mobile .toggle-switch.on .toggle-slider{left:22px}:root.hr-mobile .stepper-control{border-radius:10px}:root.hr-mobile .stepper-btn{width:44px;height:34px;font-size:20px;font-weight:400}:root.hr-mobile .interval-input{width:52px;height:34px;font-size:17px;font-weight:600}:root.hr-mobile .segment-control{padding:3px;gap:3px;border-radius:10px;display:flex;flex:1;min-width:180px}:root.hr-mobile .segment-item{flex:1;padding:7px 6px;font-size:14px;border-radius:8px;font-weight:600;text-align:center;display:flex;justify-content:center;align-items:center;white-space:nowrap}:root.hr-mobile .settings-bottom-sheet{padding:0 20px 20px}";
importCSS(settingsPanelCss);
const SETTINGS = [
{ label: i18n.scrollMode, key: "scrollMode" },
{ label: i18n.autoEnter, key: "autoEnterSinglePage" },
{ label: i18n.clickToEnter, key: "clickToEnterReader" }
];
function createSettingsPanel(anchorElement) {
const backdrop = document.createElement("div");
backdrop.className = "settings-backdrop";
const bottomSheet = document.createElement("div");
bottomSheet.className = "settings-bottom-sheet";
const sheetHeader = document.createElement("div");
sheetHeader.className = "settings-sheet-header";
const title = document.createElement("div");
title.className = "settings-title";
title.textContent = i18n.settings;
const closeBtn = document.createElement("div");
closeBtn.className = "settings-close-btn";
closeBtn.innerHTML = svgClose.replace('class="pswp__icn"', "");
closeBtn.onclick = (e) => {
e.stopPropagation();
hide();
};
sheetHeader.appendChild(title);
sheetHeader.appendChild(closeBtn);
bottomSheet.appendChild(sheetHeader);
SETTINGS.forEach(({ label, key }) => {
if (key === "scrollMode" && store.activeAdapter && ["18comic", "4KHD"].includes(store.activeAdapter.name)) {
return;
}
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 = (e) => {
e.stopPropagation();
const newValue = !store.settings[key];
store.updateSetting(key, newValue);
toggle.classList.toggle("on", newValue);
if (key === "scrollMode") {
window.location.reload();
}
};
item.appendChild(labelEl);
item.appendChild(toggle);
bottomSheet.appendChild(item);
});
const intervalItem = document.createElement("div");
intervalItem.className = "settings-item";
const intervalLabel = document.createElement("span");
intervalLabel.className = "settings-label";
intervalLabel.textContent = i18n.playSpeed;
const intervalRight = document.createElement("div");
intervalRight.className = "stepper-control";
const minusBtn = document.createElement("div");
minusBtn.className = "stepper-btn";
minusBtn.textContent = "−";
const intervalInput = document.createElement("input");
intervalInput.type = "number";
intervalInput.className = "interval-input";
intervalInput.min = "1";
intervalInput.max = "60";
intervalInput.step = "1";
intervalInput.value = String(store.settings.autoPlayInterval / 1e3);
intervalInput.onclick = (e) => e.stopPropagation();
const plusBtn = document.createElement("div");
plusBtn.className = "stepper-btn";
plusBtn.textContent = "+";
const updateInterval = (val) => {
if (!isNaN(val) && val >= 1 && val <= 60) {
intervalInput.value = String(val);
store.updateSetting("autoPlayInterval", val * 1e3);
}
};
intervalInput.onchange = (e) => {
updateInterval(parseFloat(e.target.value));
};
minusBtn.onclick = (e) => {
e.stopPropagation();
let current = parseFloat(intervalInput.value);
if (isNaN(current)) current = 5;
updateInterval(Math.max(5, current - 5));
};
plusBtn.onclick = (e) => {
e.stopPropagation();
let current = parseFloat(intervalInput.value);
if (isNaN(current)) current = 5;
updateInterval(Math.min(60, current + 5));
};
intervalRight.appendChild(minusBtn);
intervalRight.appendChild(intervalInput);
intervalRight.appendChild(plusBtn);
intervalItem.appendChild(intervalLabel);
intervalItem.appendChild(intervalRight);
bottomSheet.appendChild(intervalItem);
const posItem = document.createElement("div");
posItem.className = "settings-item";
const posLabel = document.createElement("span");
posLabel.className = "settings-label";
posLabel.textContent = i18n.thumbnailPosition;
const segmentControl = document.createElement("div");
segmentControl.className = "segment-control";
const posOptions = [
{ value: "top", label: i18n.posTop },
{ value: "bottom", label: i18n.posBottom },
{ value: "left", label: i18n.posLeft },
{ value: "right", label: i18n.posRight }
];
const segmentItems = [];
posOptions.forEach((opt) => {
const item = document.createElement("div");
item.className = `segment-item${store.settings.thumbnailPosition === opt.value ? " active" : ""}`;
item.textContent = opt.label;
item.onclick = (e) => {
e.stopPropagation();
if (store.settings.thumbnailPosition === opt.value) return;
store.updateSetting("thumbnailPosition", opt.value);
segmentItems.forEach((el) => el.classList.remove("active"));
item.classList.add("active");
};
segmentItems.push(item);
segmentControl.appendChild(item);
});
posItem.appendChild(posLabel);
posItem.appendChild(segmentControl);
bottomSheet.appendChild(posItem);
backdrop.appendChild(bottomSheet);
const show = () => {
if (window.matchMedia("(hover: hover) and (pointer: fine)").matches && !document.documentElement.classList.contains("hr-mobile")) {
const panelWidth = 340;
const rect = anchorElement.getBoundingClientRect();
const panelRect = bottomSheet.getBoundingClientRect();
const panelHeight = panelRect.height || 300;
let top = rect.top + rect.height / 2 - panelHeight / 2;
top = Math.max(16, Math.min(window.innerHeight - panelHeight - 16, top));
bottomSheet.style.top = `${top}px`;
bottomSheet.style.bottom = "auto";
if (rect.left < window.innerWidth / 2) {
bottomSheet.style.left = `${rect.right + 20}px`;
bottomSheet.style.transformOrigin = "left center";
} else {
bottomSheet.style.left = `${rect.left - panelWidth - 20}px`;
bottomSheet.style.transformOrigin = "right center";
}
void bottomSheet.offsetHeight;
} else {
bottomSheet.style.cssText = "";
}
backdrop.classList.add("show");
};
const hide = () => {
backdrop.classList.remove("show");
};
backdrop.onclick = (e) => {
if (e.target === backdrop) {
hide();
}
};
const isOpen = () => backdrop.classList.contains("show");
return {
getContainerElement: () => backdrop,
show,
hide,
isOpen
};
}
function createFloatControl(readerHandle) {
const floatControl = document.createElement("div");
floatControl.className = `bookmark-control${store.settings.showControl ? "" : " hidden"}`;
let savedPos = { side: "right", topPercent: 50 };
try {
const raw = localStorage.getItem("hentai-reader-bookmark-pos");
if (raw) savedPos = JSON.parse(raw);
} catch (e) {
}
if (savedPos.side === "left") {
floatControl.classList.add("left-side");
} else {
floatControl.classList.add("right-side");
}
floatControl.style.top = `${savedPos.topPercent}%`;
const topBtn = document.createElement("div");
topBtn.className = "bm-btn bm-top-btn";
const updateTopBtn = () => {
if (readerHandle.isActive()) {
topBtn.innerHTML = store.autoPlay ? svgPause : svgPlay;
topBtn.title = store.autoPlay ? i18n.pause : i18n.play;
} else {
topBtn.innerHTML = svgTop;
topBtn.title = i18n.backToTop;
}
};
updateTopBtn();
topBtn.onclick = (e) => {
e.stopPropagation();
if (readerHandle.isActive()) {
store.autoPlay = !store.autoPlay;
store.emit("settingsChanged");
} else {
const firstImg = document.querySelector(".r-img, .r-ph");
if (firstImg) {
firstImg.scrollIntoView({ behavior: "smooth", block: "start" });
} else {
window.scrollTo({ top: 0, behavior: "smooth" });
}
}
};
const modeBtn = document.createElement("div");
modeBtn.className = "bm-btn bm-mode-btn";
modeBtn.innerHTML = svgReader;
modeBtn.title = i18n.readerMode;
modeBtn.onclick = (e) => {
e.stopPropagation();
if (readerHandle.isActive()) {
readerHandle.close();
} else {
readerHandle.open();
}
};
store.on("readerModeChanged", () => {
modeBtn.innerHTML = readerHandle.isActive() ? svgScroll : svgReader;
updateTopBtn();
});
store.on("settingsChanged", () => {
updateTopBtn();
});
const settings = createSettingsPanel(floatControl);
document.body.appendChild(settings.getContainerElement());
const settingsBtn = document.createElement("div");
settingsBtn.className = "bm-btn bm-settings-btn";
settingsBtn.innerHTML = svgSettings;
settingsBtn.title = i18n.settings;
settingsBtn.onclick = (e) => {
e.stopPropagation();
if (settings.isOpen()) {
settings.hide();
} else {
settings.show();
}
};
floatControl.appendChild(topBtn);
floatControl.appendChild(modeBtn);
floatControl.appendChild(settingsBtn);
document.body.appendChild(floatControl);
let isDragging = false;
let activePointerId = null;
let startY = 0;
let startTop = 0;
let dragMoved = false;
function endDrag(commit, e) {
if (!isDragging) return;
isDragging = false;
floatControl.classList.remove("dragging");
if (activePointerId !== null) {
try {
floatControl.releasePointerCapture(activePointerId);
} catch (err) {
}
activePointerId = null;
}
if (commit && dragMoved) {
const rect = floatControl.getBoundingClientRect();
const centerPercent = (rect.top + rect.height / 2) / window.innerHeight * 100;
const centerX = e ? e.clientX : rect.left + rect.width / 2;
const side = centerX < window.innerWidth / 2 ? "left" : "right";
floatControl.style.top = `${centerPercent}%`;
localStorage.setItem("hentai-reader-bookmark-pos", JSON.stringify({ side, topPercent: centerPercent }));
}
}
floatControl.addEventListener("pointerdown", (e) => {
if (e.button !== 0 && e.pointerType === "mouse") return;
isDragging = true;
dragMoved = false;
activePointerId = e.pointerId;
startY = e.clientY;
const rect = floatControl.getBoundingClientRect();
startTop = rect.top + rect.height / 2;
floatControl.classList.add("dragging");
});
floatControl.addEventListener("pointermove", (e) => {
if (!isDragging || e.pointerId !== activePointerId) return;
const deltaY = e.clientY - startY;
if (Math.abs(deltaY) > 5) {
if (!dragMoved) {
try {
floatControl.setPointerCapture(e.pointerId);
} catch (err) {
}
if (settings.isOpen()) settings.hide();
}
dragMoved = true;
}
const newCenterY = startTop + deltaY;
const maxCenterY = window.innerHeight - floatControl.offsetHeight / 2;
const minCenterY = floatControl.offsetHeight / 2;
const clampedY = Math.max(minCenterY, Math.min(maxCenterY, newCenterY));
floatControl.style.top = `${clampedY}px`;
if (e.clientX < window.innerWidth / 2) {
floatControl.classList.remove("right-side");
floatControl.classList.add("left-side");
} else {
floatControl.classList.remove("left-side");
floatControl.classList.add("right-side");
}
});
floatControl.addEventListener("pointerup", (e) => {
if (e.pointerId !== activePointerId) return;
endDrag(true, e);
});
floatControl.addEventListener("pointercancel", (e) => {
if (e.pointerId !== activePointerId) return;
endDrag(false, e);
});
floatControl.addEventListener("click", (e) => {
if (dragMoved) {
e.stopPropagation();
e.preventDefault();
}
}, { capture: true });
}
const isMobileBrowser = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
function initViewportScale() {
const updateScale = () => {
if (isMobileBrowser) {
const screenWidth = Math.min(window.screen.width, window.screen.height);
const layoutWidth = window.innerWidth;
if (screenWidth < 768) {
document.documentElement.classList.add("hr-mobile");
} else {
document.documentElement.classList.remove("hr-mobile");
}
let scale = 1;
if (layoutWidth > screenWidth && screenWidth > 0) {
scale = layoutWidth / screenWidth;
}
document.documentElement.style.setProperty("--hr-scale", scale.toString());
} else {
document.documentElement.classList.remove("hr-mobile");
document.documentElement.style.setProperty("--hr-scale", "1");
}
};
updateScale();
window.addEventListener("resize", updateScale);
window.addEventListener("orientationchange", () => setTimeout(updateScale, 100));
}
(async function main() {
var _a, _b, _c;
const adapter = SiteManager.getAdapter(window.location.href);
if (!adapter) {
return;
}
store.activeAdapter = adapter;
const pageLoader = new GalleryPageLoader(adapter);
store.reloadSettings();
if (adapter.name === "18comic" || adapter.name === "4KHD") {
store.settings.scrollMode = true;
}
const initialPage = await pageLoader.loadInitialPage(document, window.location.href);
if (initialPage.items.length === 0) return;
initViewportScale();
store.imageOffset = ((_a = initialPage.position) == null ? void 0 : _a.startIndex) ?? 0;
store.perPage = ((_b = initialPage.position) == null ? void 0 : _b.pageSize) ?? initialPage.items.length ?? 20;
store.nextUrl = initialPage.nextUrl;
store.prevUrl = initialPage.prevUrl;
if (store.imageOffset > 0 && store.perPage > 0) {
store.currPage = Math.floor(store.imageOffset / store.perPage) + 1;
}
let container = adapter.getContainer();
if (store.settings.scrollMode) {
document.documentElement.classList.add("scroll-mode");
(_c = adapter.hideOriginalElements) == null ? void 0 : _c.call(adapter);
if (!container) {
container = document.createElement("div");
container.id = "gdt";
document.body.appendChild(container);
}
container.innerHTML = "";
processBatch(initialPage.items, store.currPage, container, false, initialPage.pageUrl);
setupAutoScroll(pageLoader);
} else {
const hiddenBox = document.createElement("div");
hiddenBox.id = "gdt-hidden";
hiddenBox.style.display = "none";
document.body.appendChild(hiddenBox);
processBatch(initialPage.items, store.currPage, hiddenBox, false, initialPage.pageUrl);
}
const readerHandle = createReader(pageLoader);
createFloatControl(readerHandle);
if (!store.settings.scrollMode) {
setTimeout(() => {
if (!readerHandle.isActive()) readerHandle.warmupInitial(3);
}, 1500);
}
if (store.settings.autoEnterSinglePage) {
setTimeout(() => readerHandle.open(0), 1e3);
}
document.body.addEventListener("click", (e) => {
if (!store.settings.clickToEnterReader || !store.settings.scrollMode || readerHandle.isActive()) return;
const target = e.target;
const imgTarget = target.closest(".r-img, .r-ph");
if (imgTarget) {
e.preventDefault();
e.stopPropagation();
const allImages = Array.from(document.querySelectorAll(".r-img, .r-ph"));
const index = allImages.indexOf(imgTarget);
if (index !== -1) {
readerHandle.open(index);
}
}
}, true);
})();
})();