A touch-optimized E-H/ExH viewer
// ==UserScript==
// @name EhPeek
// @version 260723.1506
// @description A touch-optimized E-H/ExH viewer
// @icon https://raw.githubusercontent.com/yamipot/ehpeek/master/icon.svg
// @icon64 https://raw.githubusercontent.com/yamipot/ehpeek/master/icon.svg
// @license MIT
// @namespace https://github.com/yamipot/ehpeek
// @homepage https://github.com/yamipot/ehpeek
// @supportURL https://github.com/yamipot/ehpeek/issues
// @match *://exhentai.org/*
// @match *://exhentai55ld2wyap5juskbm67czulomrouspdacjamjeloj7ugjbsad.onion/*
// @match *://e-hentai.org/*
// @match *://*.exhentai.org/*
// @match *://*.exhentai55ld2wyap5juskbm67czulomrouspdacjamjeloj7ugjbsad.onion/*
// @match *://*.e-hentai.org/*
// @match *://*.hath.network/*
// @exclude *://forums.e-hentai.org/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_registerMenuCommand
// @grant GM_download
// @run-at document-start
// ==/UserScript==
"use strict";
(() => {
var __defProp = Object.defineProperty;
var __typeError = (msg) => {
throw TypeError(msg);
};
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: !0, configurable: !0, writable: !0, value }) : obj[key] = value;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: !0 });
};
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key != "symbol" ? key + "" : key, value), __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)), __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value), __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
// node_modules/.pnpm/[email protected]/node_modules/solid-js/dist/solid.js
var sharedConfig = {
context: void 0,
registry: void 0,
effects: void 0,
done: !1,
getContextId() {
return getContextId(this.context.count);
},
getNextContextId() {
return getContextId(this.context.count++);
}
};
function getContextId(count) {
let num = String(count), len = num.length - 1;
return sharedConfig.context.id + (len ? String.fromCharCode(96 + len) : "") + num;
}
function setHydrateContext(context) {
sharedConfig.context = context;
}
function nextHydrateContext() {
return {
...sharedConfig.context,
id: sharedConfig.getNextContextId(),
count: 0
};
}
var IS_DEV = !1, equalFn = (a, b) => a === b, $PROXY = /* @__PURE__ */ Symbol("solid-proxy"), SUPPORTS_PROXY = typeof Proxy == "function", $TRACK = /* @__PURE__ */ Symbol("solid-track");
var signalOptions = {
equals: equalFn
}, ERROR = null, runEffects = runQueue, STALE = 1, PENDING = 2, UNOWNED = {
owned: null,
cleanups: null,
context: null,
owner: null
};
var Owner = null, Transition = null, Scheduler = null, ExternalSourceConfig = null, Listener = null, Updates = null, Effects = null, ExecCount = 0;
function createRoot(fn, detachedOwner) {
let listener = Listener, owner = Owner, unowned = fn.length === 0, current = detachedOwner === void 0 ? owner : detachedOwner, root = unowned ? UNOWNED : {
owned: null,
cleanups: null,
context: current ? current.context : null,
owner: current
}, updateFn = unowned ? fn : () => fn(() => untrack(() => cleanNode(root)));
Owner = root, Listener = null;
try {
return runUpdates(updateFn, !0);
} finally {
Listener = listener, Owner = owner;
}
}
function createSignal(value, options) {
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
let s = {
value,
observers: null,
observerSlots: null,
comparator: options.equals || void 0
}, setter = (value2) => (typeof value2 == "function" && (Transition && Transition.running && Transition.sources.has(s) ? value2 = value2(s.tValue) : value2 = value2(s.value)), writeSignal(s, value2));
return [readSignal.bind(s), setter];
}
function createRenderEffect(fn, value, options) {
let c = createComputation(fn, value, !1, STALE);
Scheduler && Transition && Transition.running ? Updates.push(c) : updateComputation(c);
}
function createEffect(fn, value, options) {
runEffects = runUserEffects;
let c = createComputation(fn, value, !1, STALE), s = SuspenseContext && useContext(SuspenseContext);
s && (c.suspense = s), (!options || !options.render) && (c.user = !0), Effects ? Effects.push(c) : updateComputation(c);
}
function createMemo(fn, value, options) {
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
let c = createComputation(fn, value, !0, 0);
return c.observers = null, c.observerSlots = null, c.comparator = options.equals || void 0, Scheduler && Transition && Transition.running ? (c.tState = STALE, Updates.push(c)) : updateComputation(c), readSignal.bind(c);
}
function batch(fn) {
return runUpdates(fn, !1);
}
function untrack(fn) {
if (!ExternalSourceConfig && Listener === null) return fn();
let listener = Listener;
Listener = null;
try {
return ExternalSourceConfig ? ExternalSourceConfig.untrack(fn) : fn();
} finally {
Listener = listener;
}
}
function onMount(fn) {
createEffect(() => untrack(fn));
}
function onCleanup(fn) {
return Owner === null || (Owner.cleanups === null ? Owner.cleanups = [fn] : Owner.cleanups.push(fn)), fn;
}
function getListener() {
return Listener;
}
function getOwner() {
return Owner;
}
function runWithOwner(o, fn) {
let prev = Owner, prevListener = Listener;
Owner = o, Listener = null;
try {
return runUpdates(fn, !0);
} catch (err) {
handleError(err);
} finally {
Owner = prev, Listener = prevListener;
}
}
function startTransition(fn) {
if (Transition && Transition.running)
return fn(), Transition.done;
let l = Listener, o = Owner;
return Promise.resolve().then(() => {
Listener = l, Owner = o;
let t;
return (Scheduler || SuspenseContext) && (t = Transition || (Transition = {
sources: /* @__PURE__ */ new Set(),
effects: [],
promises: /* @__PURE__ */ new Set(),
disposed: /* @__PURE__ */ new Set(),
queue: /* @__PURE__ */ new Set(),
running: !0
}), t.done || (t.done = new Promise((res) => t.resolve = res)), t.running = !0), runUpdates(fn, !1), Listener = Owner = null, t ? t.done : void 0;
});
}
var [transPending, setTransPending] = /* @__PURE__ */ createSignal(!1);
function useContext(context) {
let value;
return Owner && Owner.context && (value = Owner.context[context.id]) !== void 0 ? value : context.defaultValue;
}
var SuspenseContext;
function readSignal() {
let runningTransition = Transition && Transition.running;
if (this.sources && (runningTransition ? this.tState : this.state))
if ((runningTransition ? this.tState : this.state) === STALE) updateComputation(this);
else {
let updates = Updates;
Updates = null, runUpdates(() => lookUpstream(this), !1), Updates = updates;
}
if (Listener) {
let observers = this.observers;
if (!observers || observers[observers.length - 1] !== Listener) {
let sSlot = observers ? observers.length : 0;
Listener.sources ? (Listener.sources.push(this), Listener.sourceSlots.push(sSlot)) : (Listener.sources = [this], Listener.sourceSlots = [sSlot]), observers ? (observers.push(Listener), this.observerSlots.push(Listener.sources.length - 1)) : (this.observers = [Listener], this.observerSlots = [Listener.sources.length - 1]);
}
}
return runningTransition && Transition.sources.has(this) ? this.tValue : this.value;
}
function writeSignal(node, value, isComp) {
let current = Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value;
if (!node.comparator || !node.comparator(current, value)) {
if (Transition) {
let TransitionRunning = Transition.running;
(TransitionRunning || !isComp && Transition.sources.has(node)) && (Transition.sources.add(node), node.tValue = value), TransitionRunning || (node.value = value);
} else node.value = value;
node.observers && node.observers.length && runUpdates(() => {
for (let i = 0; i < node.observers.length; i += 1) {
let o = node.observers[i], TransitionRunning = Transition && Transition.running;
TransitionRunning && Transition.disposed.has(o) || ((TransitionRunning ? !o.tState : !o.state) && (o.pure ? Updates.push(o) : Effects.push(o), o.observers && markDownstream(o)), TransitionRunning ? o.tState = STALE : o.state = STALE);
}
if (Updates.length > 1e6)
throw Updates = [], new Error();
}, !1);
}
return value;
}
function updateComputation(node) {
if (!node.fn) return;
cleanNode(node);
let time = ExecCount;
runComputation(node, Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value, time), Transition && !Transition.running && Transition.sources.has(node) && queueMicrotask(() => {
runUpdates(() => {
Transition && (Transition.running = !0), Listener = Owner = node, runComputation(node, node.tValue, time), Listener = Owner = null;
}, !1);
});
}
function runComputation(node, value, time) {
let nextValue, owner = Owner, listener = Listener;
Listener = Owner = node;
try {
nextValue = node.fn(value);
} catch (err) {
return node.pure && (Transition && Transition.running ? (node.tState = STALE, node.tOwned && node.tOwned.forEach(cleanNode), node.tOwned = void 0) : (node.state = STALE, node.owned && node.owned.forEach(cleanNode), node.owned = null)), node.updatedAt = time + 1, handleError(err);
} finally {
Listener = listener, Owner = owner;
}
(!node.updatedAt || node.updatedAt <= time) && (node.updatedAt != null && "observers" in node ? writeSignal(node, nextValue, !0) : Transition && Transition.running && node.pure ? (Transition.sources.has(node) || (node.value = nextValue), Transition.sources.add(node), node.tValue = nextValue) : node.value = nextValue, node.updatedAt = time);
}
function createComputation(fn, init, pure, state2 = STALE, options) {
let c = {
fn,
state: state2,
updatedAt: null,
owned: null,
sources: null,
sourceSlots: null,
cleanups: null,
value: init,
owner: Owner,
context: Owner ? Owner.context : null,
pure
};
if (Transition && Transition.running && (c.state = 0, c.tState = state2), Owner === null || Owner !== UNOWNED && (Transition && Transition.running && Owner.pure ? Owner.tOwned ? Owner.tOwned.push(c) : Owner.tOwned = [c] : Owner.owned ? Owner.owned.push(c) : Owner.owned = [c]), ExternalSourceConfig && c.fn) {
let sourceFn = c.fn, [track, trigger] = createSignal(void 0, {
equals: !1
}), ordinary = ExternalSourceConfig.factory(sourceFn, trigger);
onCleanup(() => ordinary.dispose());
let inTransition, triggerInTransition = () => startTransition(trigger).then(() => {
inTransition && (inTransition.dispose(), inTransition = void 0);
});
c.fn = (x) => (track(), Transition && Transition.running ? (inTransition || (inTransition = ExternalSourceConfig.factory(sourceFn, triggerInTransition)), inTransition.track(x)) : ordinary.track(x));
}
return c;
}
function runTop(node) {
let runningTransition = Transition && Transition.running;
if ((runningTransition ? node.tState : node.state) === 0) return;
if ((runningTransition ? node.tState : node.state) === PENDING) return lookUpstream(node);
if (node.suspense && untrack(node.suspense.inFallback)) return node.suspense.effects.push(node);
let ancestors = [node];
for (; (node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount); ) {
if (runningTransition && Transition.disposed.has(node)) return;
(runningTransition ? node.tState : node.state) && ancestors.push(node);
}
for (let i = ancestors.length - 1; i >= 0; i--) {
if (node = ancestors[i], runningTransition) {
let top = node, prev = ancestors[i + 1];
for (; (top = top.owner) && top !== prev; )
if (Transition.disposed.has(top)) return;
}
if ((runningTransition ? node.tState : node.state) === STALE)
updateComputation(node);
else if ((runningTransition ? node.tState : node.state) === PENDING) {
let updates = Updates;
Updates = null, runUpdates(() => lookUpstream(node, ancestors[0]), !1), Updates = updates;
}
}
}
function runUpdates(fn, init) {
if (Updates) return fn();
let wait = !1;
init || (Updates = []), Effects ? wait = !0 : Effects = [], ExecCount++;
try {
let res = fn();
return completeUpdates(wait), res;
} catch (err) {
wait || (Effects = null), Updates = null, handleError(err);
}
}
function completeUpdates(wait) {
if (Updates && (Scheduler && Transition && Transition.running ? scheduleQueue(Updates) : runQueue(Updates), Updates = null), wait) return;
let res;
if (Transition) {
if (!Transition.promises.size && !Transition.queue.size) {
let sources = Transition.sources, disposed = Transition.disposed;
Effects.push.apply(Effects, Transition.effects), res = Transition.resolve;
for (let e2 of Effects)
"tState" in e2 && (e2.state = e2.tState), delete e2.tState;
Transition = null, runUpdates(() => {
for (let d of disposed) cleanNode(d);
for (let v of sources) {
if (v.value = v.tValue, v.owned)
for (let i = 0, len = v.owned.length; i < len; i++) cleanNode(v.owned[i]);
v.tOwned && (v.owned = v.tOwned), delete v.tValue, delete v.tOwned, v.tState = 0;
}
setTransPending(!1);
}, !1);
} else if (Transition.running) {
Transition.running = !1, Transition.effects.push.apply(Transition.effects, Effects), Effects = null, setTransPending(!0);
return;
}
}
let e = Effects;
Effects = null, e.length && runUpdates(() => runEffects(e), !1), res && res();
}
function runQueue(queue) {
for (let i = 0; i < queue.length; i++) runTop(queue[i]);
}
function scheduleQueue(queue) {
for (let i = 0; i < queue.length; i++) {
let item = queue[i], tasks = Transition.queue;
tasks.has(item) || (tasks.add(item), Scheduler(() => {
tasks.delete(item), runUpdates(() => {
Transition.running = !0, runTop(item);
}, !1), Transition && (Transition.running = !1);
}));
}
}
function runUserEffects(queue) {
let i, userLength = 0;
for (i = 0; i < queue.length; i++) {
let e = queue[i];
e.user ? queue[userLength++] = e : runTop(e);
}
if (sharedConfig.context) {
if (sharedConfig.count) {
sharedConfig.effects || (sharedConfig.effects = []), sharedConfig.effects.push(...queue.slice(0, userLength));
return;
}
setHydrateContext();
}
for (sharedConfig.effects && (sharedConfig.done || !sharedConfig.count) && (queue = [...sharedConfig.effects, ...queue], userLength += sharedConfig.effects.length, delete sharedConfig.effects), i = 0; i < userLength; i++) runTop(queue[i]);
}
function lookUpstream(node, ignore) {
let runningTransition = Transition && Transition.running;
runningTransition ? node.tState = 0 : node.state = 0;
for (let i = 0; i < node.sources.length; i += 1) {
let source = node.sources[i];
if (source.sources) {
let state2 = runningTransition ? source.tState : source.state;
state2 === STALE ? source !== ignore && (!source.updatedAt || source.updatedAt < ExecCount) && runTop(source) : state2 === PENDING && lookUpstream(source, ignore);
}
}
}
function markDownstream(node) {
let runningTransition = Transition && Transition.running;
for (let i = 0; i < node.observers.length; i += 1) {
let o = node.observers[i];
(runningTransition ? !o.tState : !o.state) && (runningTransition ? o.tState = PENDING : o.state = PENDING, o.pure ? Updates.push(o) : Effects.push(o), o.observers && markDownstream(o));
}
}
function cleanNode(node) {
let i;
if (node.sources)
for (; node.sources.length; ) {
let source = node.sources.pop(), index = node.sourceSlots.pop(), obs = source.observers;
if (obs && obs.length) {
let n = obs.pop(), s = source.observerSlots.pop();
index < obs.length && (n.sourceSlots[s] = index, obs[index] = n, source.observerSlots[index] = s);
}
}
if (node.tOwned) {
for (i = node.tOwned.length - 1; i >= 0; i--) cleanNode(node.tOwned[i]);
delete node.tOwned;
}
if (Transition && Transition.running && node.pure)
reset(node, !0);
else if (node.owned) {
for (i = node.owned.length - 1; i >= 0; i--) cleanNode(node.owned[i]);
node.owned = null;
}
if (node.cleanups) {
for (i = node.cleanups.length - 1; i >= 0; i--) node.cleanups[i]();
node.cleanups = null;
}
Transition && Transition.running ? node.tState = 0 : node.state = 0;
}
function reset(node, top) {
if (top || (node.tState = 0, Transition.disposed.add(node)), node.owned)
for (let i = 0; i < node.owned.length; i++) reset(node.owned[i]);
}
function castError(err) {
return err instanceof Error ? err : new Error(typeof err == "string" ? err : "Unknown error", {
cause: err
});
}
function runErrors(err, fns, owner) {
try {
for (let f of fns) f(err);
} catch (e) {
handleError(e, owner && owner.owner || null);
}
}
function handleError(err, owner = Owner) {
let fns = ERROR && owner && owner.context && owner.context[ERROR], error = castError(err);
if (!fns) throw error;
Effects ? Effects.push({
fn() {
runErrors(error, fns, owner);
},
state: STALE
}) : runErrors(error, fns, owner);
}
var FALLBACK = /* @__PURE__ */ Symbol("fallback");
function dispose(d) {
for (let i = 0; i < d.length; i++) d[i]();
}
function mapArray(list, mapFn, options = {}) {
let items = [], mapped = [], disposers = [], len = 0, indexes = mapFn.length > 1 ? [] : null;
return onCleanup(() => dispose(disposers)), () => {
let newItems = list() || [], newLen = newItems.length, i, j;
return newItems[$TRACK], untrack(() => {
let newIndices, newIndicesNext, temp, tempdisposers, tempIndexes, start, end, newEnd, item;
if (newLen === 0)
len !== 0 && (dispose(disposers), disposers = [], items = [], mapped = [], len = 0, indexes && (indexes = [])), options.fallback && (items = [FALLBACK], mapped[0] = createRoot((disposer) => (disposers[0] = disposer, options.fallback())), len = 1);
else if (len === 0) {
for (mapped = new Array(newLen), j = 0; j < newLen; j++)
items[j] = newItems[j], mapped[j] = createRoot(mapper);
len = newLen;
} else {
for (temp = new Array(newLen), tempdisposers = new Array(newLen), indexes && (tempIndexes = new Array(newLen)), start = 0, end = Math.min(len, newLen); start < end && items[start] === newItems[start]; start++) ;
for (end = len - 1, newEnd = newLen - 1; end >= start && newEnd >= start && items[end] === newItems[newEnd]; end--, newEnd--)
temp[newEnd] = mapped[end], tempdisposers[newEnd] = disposers[end], indexes && (tempIndexes[newEnd] = indexes[end]);
for (newIndices = /* @__PURE__ */ new Map(), newIndicesNext = new Array(newEnd + 1), j = newEnd; j >= start; j--)
item = newItems[j], i = newIndices.get(item), newIndicesNext[j] = i === void 0 ? -1 : i, newIndices.set(item, j);
for (i = start; i <= end; i++)
item = items[i], j = newIndices.get(item), j !== void 0 && j !== -1 ? (temp[j] = mapped[i], tempdisposers[j] = disposers[i], indexes && (tempIndexes[j] = indexes[i]), j = newIndicesNext[j], newIndices.set(item, j)) : disposers[i]();
for (j = start; j < newLen; j++)
j in temp ? (mapped[j] = temp[j], disposers[j] = tempdisposers[j], indexes && (indexes[j] = tempIndexes[j], indexes[j](j))) : mapped[j] = createRoot(mapper);
mapped = mapped.slice(0, len = newLen), items = newItems.slice(0);
}
return mapped;
});
function mapper(disposer) {
if (disposers[j] = disposer, indexes) {
let [s, set] = createSignal(j);
return indexes[j] = set, mapFn(newItems[j], s);
}
return mapFn(newItems[j]);
}
};
}
var hydrationEnabled = !1;
function createComponent(Comp, props) {
if (hydrationEnabled && sharedConfig.context) {
let c = sharedConfig.context;
setHydrateContext(nextHydrateContext());
let r = untrack(() => Comp(props || {}));
return setHydrateContext(c), r;
}
return untrack(() => Comp(props || {}));
}
function trueFn() {
return !0;
}
var propTraps = {
get(_, property, receiver) {
return property === $PROXY ? receiver : _.get(property);
},
has(_, property) {
return property === $PROXY ? !0 : _.has(property);
},
set: trueFn,
deleteProperty: trueFn,
getOwnPropertyDescriptor(_, property) {
return {
configurable: !0,
enumerable: !0,
get() {
return _.get(property);
},
set: trueFn,
deleteProperty: trueFn
};
},
ownKeys(_) {
return _.keys();
}
};
function splitProps(props, ...keys) {
let len = keys.length;
if (SUPPORTS_PROXY && $PROXY in props) {
let blocked = len > 1 ? keys.flat() : keys[0], res = keys.map((k) => new Proxy({
get(property) {
return k.includes(property) ? props[property] : void 0;
},
has(property) {
return k.includes(property) && property in props;
},
keys() {
return k.filter((property) => property in props);
}
}, propTraps));
return res.push(new Proxy({
get(property) {
return blocked.includes(property) ? void 0 : props[property];
},
has(property) {
return blocked.includes(property) ? !1 : property in props;
},
keys() {
return Object.keys(props).filter((k) => !blocked.includes(k));
}
}, propTraps)), res;
}
let objects = [];
for (let i = 0; i <= len; i++)
objects[i] = {};
for (let propName of Object.getOwnPropertyNames(props)) {
let keyIndex = len;
for (let i = 0; i < keys.length; i++)
if (keys[i].includes(propName)) {
keyIndex = i;
break;
}
let desc = Object.getOwnPropertyDescriptor(props, propName);
!desc.get && !desc.set && desc.enumerable && desc.writable && desc.configurable ? objects[keyIndex][propName] = desc.value : Object.defineProperty(objects[keyIndex], propName, desc);
}
return objects;
}
var narrowedError = (name) => `Stale read from <${name}>.`;
function For(props) {
let fallback = "fallback" in props && {
fallback: () => props.fallback
};
return createMemo(mapArray(() => props.each, props.children, fallback || void 0));
}
function Show(props) {
let keyed = props.keyed, conditionValue = createMemo(() => props.when, void 0, void 0), condition = keyed ? conditionValue : createMemo(conditionValue, void 0, {
equals: (a, b) => !a == !b
});
return createMemo(() => {
let c = condition();
if (c) {
let child = props.children;
return typeof child == "function" && child.length > 0 ? untrack(() => child(keyed ? c : () => {
if (!untrack(condition)) throw narrowedError("Show");
return conditionValue();
})) : child;
}
return props.fallback;
}, void 0, void 0);
}
// node_modules/.pnpm/[email protected]/node_modules/solid-js/web/dist/web.js
var booleans = [
"allowfullscreen",
"async",
"alpha",
"autofocus",
"autoplay",
"checked",
"controls",
"default",
"disabled",
"formnovalidate",
"hidden",
"indeterminate",
"inert",
"ismap",
"loop",
"multiple",
"muted",
"nomodule",
"novalidate",
"open",
"playsinline",
"readonly",
"required",
"reversed",
"seamless",
"selected",
"adauctionheaders",
"browsingtopics",
"credentialless",
"defaultchecked",
"defaultmuted",
"defaultselected",
"defer",
"disablepictureinpicture",
"disableremoteplayback",
"preservespitch",
"shadowrootclonable",
"shadowrootcustomelementregistry",
"shadowrootdelegatesfocus",
"shadowrootserializable",
"sharedstoragewritable"
], Properties = /* @__PURE__ */ new Set([
"className",
"value",
"readOnly",
"noValidate",
"formNoValidate",
"isMap",
"noModule",
"playsInline",
"adAuctionHeaders",
"allowFullscreen",
"browsingTopics",
"defaultChecked",
"defaultMuted",
"defaultSelected",
"disablePictureInPicture",
"disableRemotePlayback",
"preservesPitch",
"shadowRootClonable",
"shadowRootCustomElementRegistry",
"shadowRootDelegatesFocus",
"shadowRootSerializable",
"sharedStorageWritable",
...booleans
]), ChildProperties = /* @__PURE__ */ new Set(["innerHTML", "textContent", "innerText", "children"]), Aliases = /* @__PURE__ */ Object.assign(/* @__PURE__ */ Object.create(null), {
className: "class",
htmlFor: "for"
}), PropAliases = /* @__PURE__ */ Object.assign(/* @__PURE__ */ Object.create(null), {
class: "className",
novalidate: {
$: "noValidate",
FORM: 1
},
formnovalidate: {
$: "formNoValidate",
BUTTON: 1,
INPUT: 1
},
ismap: {
$: "isMap",
IMG: 1
},
nomodule: {
$: "noModule",
SCRIPT: 1
},
playsinline: {
$: "playsInline",
VIDEO: 1
},
readonly: {
$: "readOnly",
INPUT: 1,
TEXTAREA: 1
},
adauctionheaders: {
$: "adAuctionHeaders",
IFRAME: 1
},
allowfullscreen: {
$: "allowFullscreen",
IFRAME: 1
},
browsingtopics: {
$: "browsingTopics",
IMG: 1
},
defaultchecked: {
$: "defaultChecked",
INPUT: 1
},
defaultmuted: {
$: "defaultMuted",
AUDIO: 1,
VIDEO: 1
},
defaultselected: {
$: "defaultSelected",
OPTION: 1
},
disablepictureinpicture: {
$: "disablePictureInPicture",
VIDEO: 1
},
disableremoteplayback: {
$: "disableRemotePlayback",
AUDIO: 1,
VIDEO: 1
},
preservespitch: {
$: "preservesPitch",
AUDIO: 1,
VIDEO: 1
},
shadowrootclonable: {
$: "shadowRootClonable",
TEMPLATE: 1
},
shadowrootdelegatesfocus: {
$: "shadowRootDelegatesFocus",
TEMPLATE: 1
},
shadowrootserializable: {
$: "shadowRootSerializable",
TEMPLATE: 1
},
sharedstoragewritable: {
$: "sharedStorageWritable",
IFRAME: 1,
IMG: 1
}
});
function getPropAlias(prop, tagName) {
let a = PropAliases[prop];
return typeof a == "object" ? a[tagName] ? a.$ : void 0 : a;
}
var DelegatedEvents = /* @__PURE__ */ new Set(["beforeinput", "click", "dblclick", "contextmenu", "focusin", "focusout", "input", "keydown", "keyup", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup", "pointerdown", "pointermove", "pointerout", "pointerover", "pointerup", "touchend", "touchmove", "touchstart"]), SVGElements = /* @__PURE__ */ new Set([
"altGlyph",
"altGlyphDef",
"altGlyphItem",
"animate",
"animateColor",
"animateMotion",
"animateTransform",
"circle",
"clipPath",
"color-profile",
"cursor",
"defs",
"desc",
"ellipse",
"feBlend",
"feColorMatrix",
"feComponentTransfer",
"feComposite",
"feConvolveMatrix",
"feDiffuseLighting",
"feDisplacementMap",
"feDistantLight",
"feDropShadow",
"feFlood",
"feFuncA",
"feFuncB",
"feFuncG",
"feFuncR",
"feGaussianBlur",
"feImage",
"feMerge",
"feMergeNode",
"feMorphology",
"feOffset",
"fePointLight",
"feSpecularLighting",
"feSpotLight",
"feTile",
"feTurbulence",
"filter",
"font",
"font-face",
"font-face-format",
"font-face-name",
"font-face-src",
"font-face-uri",
"foreignObject",
"g",
"glyph",
"glyphRef",
"hkern",
"image",
"line",
"linearGradient",
"marker",
"mask",
"metadata",
"missing-glyph",
"mpath",
"path",
"pattern",
"polygon",
"polyline",
"radialGradient",
"rect",
"set",
"stop",
"svg",
"switch",
"symbol",
"text",
"textPath",
"tref",
"tspan",
"use",
"view",
"vkern"
]), SVGNamespace = {
xlink: "http://www.w3.org/1999/xlink",
xml: "http://www.w3.org/XML/1998/namespace"
};
var memo = (fn) => createMemo(() => fn());
function reconcileArrays(parentNode, a, b) {
let bLength = b.length, aEnd = a.length, bEnd = bLength, aStart = 0, bStart = 0, after = a[aEnd - 1].nextSibling, map = null;
for (; aStart < aEnd || bStart < bEnd; ) {
if (a[aStart] === b[bStart]) {
aStart++, bStart++;
continue;
}
for (; a[aEnd - 1] === b[bEnd - 1]; )
aEnd--, bEnd--;
if (aEnd === aStart) {
let node = bEnd < bLength ? bStart ? b[bStart - 1].nextSibling : b[bEnd - bStart] : after;
for (; bStart < bEnd; ) parentNode.insertBefore(b[bStart++], node);
} else if (bEnd === bStart)
for (; aStart < aEnd; )
(!map || !map.has(a[aStart])) && a[aStart].remove(), aStart++;
else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {
let node = a[--aEnd].nextSibling;
parentNode.insertBefore(b[bStart++], a[aStart++].nextSibling), parentNode.insertBefore(b[--bEnd], node), a[aEnd] = b[bEnd];
} else {
if (!map) {
map = /* @__PURE__ */ new Map();
let i = bStart;
for (; i < bEnd; ) map.set(b[i], i++);
}
let index = map.get(a[aStart]);
if (index != null)
if (bStart < index && index < bEnd) {
let i = aStart, sequence = 1, t;
for (; ++i < aEnd && i < bEnd && !((t = map.get(a[i])) == null || t !== index + sequence); )
sequence++;
if (sequence > index - bStart) {
let node = a[aStart];
for (; bStart < index; ) parentNode.insertBefore(b[bStart++], node);
} else parentNode.replaceChild(b[bStart++], a[aStart++]);
} else aStart++;
else a[aStart++].remove();
}
}
}
var $$EVENTS = "_$DX_DELEGATE";
function render(code, element, init, options = {}) {
let disposer;
return createRoot((dispose2) => {
disposer = dispose2, element === document ? code() : insert(element, code(), element.firstChild ? null : void 0, init);
}, options.owner), () => {
disposer(), element.textContent = "";
};
}
function template(html, isImportNode, isSVG, isMathML) {
let node, create = () => {
let t = isMathML ? document.createElementNS("http://www.w3.org/1998/Math/MathML", "template") : document.createElement("template");
return t.innerHTML = html, isSVG ? t.content.firstChild.firstChild : isMathML ? t.firstChild : t.content.firstChild;
}, fn = isImportNode ? () => untrack(() => document.importNode(node || (node = create()), !0)) : () => (node || (node = create())).cloneNode(!0);
return fn.cloneNode = fn, fn;
}
function delegateEvents(eventNames, document2 = window.document) {
let e = document2[$$EVENTS] || (document2[$$EVENTS] = /* @__PURE__ */ new Set());
for (let i = 0, l = eventNames.length; i < l; i++) {
let name = eventNames[i];
e.has(name) || (e.add(name), document2.addEventListener(name, eventHandler));
}
}
function setAttribute(node, name, value) {
isHydrating(node) || (value == null ? node.removeAttribute(name) : node.setAttribute(name, value));
}
function setAttributeNS(node, namespace, name, value) {
isHydrating(node) || (value == null ? node.removeAttributeNS(namespace, name) : node.setAttributeNS(namespace, name, value));
}
function setBoolAttribute(node, name, value) {
isHydrating(node) || (value ? node.setAttribute(name, "") : node.removeAttribute(name));
}
function className(node, value) {
isHydrating(node) || (value == null ? node.removeAttribute("class") : node.className = value);
}
function addEventListener(node, name, handler, delegate) {
if (delegate)
Array.isArray(handler) ? (node[`$$${name}`] = handler[0], node[`$$${name}Data`] = handler[1]) : node[`$$${name}`] = handler;
else if (Array.isArray(handler)) {
let handlerFn = handler[0];
node.addEventListener(name, handler[0] = (e) => handlerFn.call(node, handler[1], e));
} else node.addEventListener(name, handler, typeof handler != "function" && handler);
}
function classList(node, value, prev = {}) {
let classKeys = Object.keys(value || {}), prevKeys = Object.keys(prev), i, len;
for (i = 0, len = prevKeys.length; i < len; i++) {
let key = prevKeys[i];
!key || key === "undefined" || value[key] || (toggleClassKey(node, key, !1), delete prev[key]);
}
for (i = 0, len = classKeys.length; i < len; i++) {
let key = classKeys[i], classValue = !!value[key];
!key || key === "undefined" || prev[key] === classValue || !classValue || (toggleClassKey(node, key, !0), prev[key] = classValue);
}
return prev;
}
function style(node, value, prev) {
if (!value) return prev ? setAttribute(node, "style") : value;
let nodeStyle = node.style;
if (typeof value == "string") return nodeStyle.cssText = value;
typeof prev == "string" && (nodeStyle.cssText = prev = void 0), prev || (prev = {}), value || (value = {});
let v, s;
for (s in prev)
value[s] == null && nodeStyle.removeProperty(s), delete prev[s];
for (s in value)
v = value[s], v !== prev[s] && (nodeStyle.setProperty(s, v), prev[s] = v);
return prev;
}
function setStyleProperty(node, name, value) {
value != null ? node.style.setProperty(name, value) : node.style.removeProperty(name);
}
function spread(node, props = {}, isSVG, skipChildren) {
let prevProps = {};
return skipChildren || createRenderEffect(() => prevProps.children = insertExpression(node, props.children, prevProps.children)), createRenderEffect(() => typeof props.ref == "function" && use(props.ref, node)), createRenderEffect(() => assign(node, props, isSVG, !0, prevProps, !0)), prevProps;
}
function use(fn, element, arg) {
return untrack(() => fn(element, arg));
}
function insert(parent, accessor, marker, initial) {
if (marker !== void 0 && !initial && (initial = []), typeof accessor != "function") return insertExpression(parent, accessor, initial, marker);
createRenderEffect((current) => insertExpression(parent, accessor(), current, marker), initial);
}
function assign(node, props, isSVG, skipChildren, prevProps = {}, skipRef = !1) {
props || (props = {});
for (let prop in prevProps)
if (!(prop in props)) {
if (prop === "children") continue;
prevProps[prop] = assignProp(node, prop, null, prevProps[prop], isSVG, skipRef, props);
}
for (let prop in props) {
if (prop === "children") {
skipChildren || insertExpression(node, props.children);
continue;
}
let value = props[prop];
prevProps[prop] = assignProp(node, prop, value, prevProps[prop], isSVG, skipRef, props);
}
}
function getNextElement(template2) {
let node, key;
return !isHydrating() || !(node = sharedConfig.registry.get(key = getHydrationKey())) ? template2() : (sharedConfig.completed && sharedConfig.completed.add(node), sharedConfig.registry.delete(key), node);
}
function isHydrating(node) {
return !!sharedConfig.context && !sharedConfig.done && (!node || node.isConnected);
}
function toPropertyName(name) {
return name.toLowerCase().replace(/-([a-z])/g, (_, w) => w.toUpperCase());
}
function toggleClassKey(node, key, value) {
let classNames = key.trim().split(/\s+/);
for (let i = 0, nameLen = classNames.length; i < nameLen; i++) node.classList.toggle(classNames[i], value);
}
function assignProp(node, prop, value, prev, isSVG, skipRef, props) {
let isCE, isProp, isChildProp, propAlias, forceProp;
if (prop === "style") return style(node, value, prev);
if (prop === "classList") return classList(node, value, prev);
if (value === prev) return prev;
if (prop === "ref")
skipRef || value(node);
else if (prop.slice(0, 3) === "on:") {
let e = prop.slice(3);
prev && node.removeEventListener(e, prev, typeof prev != "function" && prev), value && node.addEventListener(e, value, typeof value != "function" && value);
} else if (prop.slice(0, 10) === "oncapture:") {
let e = prop.slice(10);
prev && node.removeEventListener(e, prev, !0), value && node.addEventListener(e, value, !0);
} else if (prop.slice(0, 2) === "on") {
let name = prop.slice(2).toLowerCase(), delegate = DelegatedEvents.has(name);
if (!delegate && prev) {
let h = Array.isArray(prev) ? prev[0] : prev;
node.removeEventListener(name, h);
}
(delegate || value) && (addEventListener(node, name, value, delegate), delegate && delegateEvents([name]));
} else if (prop.slice(0, 5) === "attr:")
setAttribute(node, prop.slice(5), value);
else if (prop.slice(0, 5) === "bool:")
setBoolAttribute(node, prop.slice(5), value);
else if ((forceProp = prop.slice(0, 5) === "prop:") || (isChildProp = ChildProperties.has(prop)) || !isSVG && ((propAlias = getPropAlias(prop, node.tagName)) || (isProp = Properties.has(prop))) || (isCE = node.nodeName.includes("-") || "is" in props)) {
if (forceProp)
prop = prop.slice(5), isProp = !0;
else if (isHydrating(node)) return value;
prop === "class" || prop === "className" ? className(node, value) : isCE && !isProp && !isChildProp ? node[toPropertyName(prop)] = value : node[propAlias || prop] = value;
} else {
let ns = isSVG && prop.indexOf(":") > -1 && SVGNamespace[prop.split(":")[0]];
ns ? setAttributeNS(node, ns, prop, value) : setAttribute(node, Aliases[prop] || prop, value);
}
return value;
}
function eventHandler(e) {
if (sharedConfig.registry && sharedConfig.events && sharedConfig.events.find(([el, ev]) => ev === e))
return;
let node = e.target, key = `$$${e.type}`, oriTarget = e.target, oriCurrentTarget = e.currentTarget, retarget = (value) => Object.defineProperty(e, "target", {
configurable: !0,
value
}), handleNode = () => {
let handler = node[key];
if (handler && !node.disabled) {
let data = node[`${key}Data`];
if (data !== void 0 ? handler.call(node, data, e) : handler.call(node, e), e.cancelBubble) return;
}
return node.host && typeof node.host != "string" && !node.host._$host && node.contains(e.target) && retarget(node.host), !0;
}, walkUpTree = () => {
for (; handleNode() && (node = node._$host || node.parentNode || node.host); ) ;
};
if (Object.defineProperty(e, "currentTarget", {
configurable: !0,
get() {
return node || document;
}
}), sharedConfig.registry && !sharedConfig.done && (sharedConfig.done = _$HY.done = !0), e.composedPath) {
let path = e.composedPath();
retarget(path[0]);
for (let i = 0; i < path.length - 2 && (node = path[i], !!handleNode()); i++) {
if (node._$host) {
node = node._$host, walkUpTree();
break;
}
if (node.parentNode === oriCurrentTarget)
break;
}
} else walkUpTree();
retarget(oriTarget);
}
function insertExpression(parent, value, current, marker, unwrapArray) {
let hydrating = isHydrating(parent);
if (hydrating) {
!current && (current = [...parent.childNodes]);
let cleaned = [];
for (let i = 0; i < current.length; i++) {
let node = current[i];
node.nodeType === 8 && node.data.slice(0, 2) === "!$" ? node.remove() : cleaned.push(node);
}
current = cleaned;
}
for (; typeof current == "function"; ) current = current();
if (value === current) return current;
let t = typeof value, multi = marker !== void 0;
if (parent = multi && current[0] && current[0].parentNode || parent, t === "string" || t === "number") {
if (hydrating || t === "number" && (value = value.toString(), value === current))
return current;
if (multi) {
let node = current[0];
node && node.nodeType === 3 ? node.data !== value && (node.data = value) : node = document.createTextNode(value), current = cleanChildren(parent, current, marker, node);
} else
current !== "" && typeof current == "string" ? current = parent.firstChild.data = value : current = parent.textContent = value;
} else if (value == null || t === "boolean") {
if (hydrating) return current;
current = cleanChildren(parent, current, marker);
} else {
if (t === "function")
return createRenderEffect(() => {
let v = value();
for (; typeof v == "function"; ) v = v();
current = insertExpression(parent, v, current, marker);
}), () => current;
if (Array.isArray(value)) {
let array = [], currentArray = current && Array.isArray(current);
if (normalizeIncomingArray(array, value, current, unwrapArray))
return createRenderEffect(() => current = insertExpression(parent, array, current, marker, !0)), () => current;
if (hydrating) {
if (!array.length) return current;
if (marker === void 0) return current = [...parent.childNodes];
let node = array[0];
if (node.parentNode !== parent) return current;
let nodes = [node];
for (; (node = node.nextSibling) !== marker; ) nodes.push(node);
return current = nodes;
}
if (array.length === 0) {
if (current = cleanChildren(parent, current, marker), multi) return current;
} else currentArray ? current.length === 0 ? appendNodes(parent, array, marker) : reconcileArrays(parent, current, array) : (current && cleanChildren(parent), appendNodes(parent, array));
current = array;
} else if (value.nodeType) {
if (hydrating && value.parentNode) return current = multi ? [value] : value;
if (Array.isArray(current)) {
if (multi) return current = cleanChildren(parent, current, marker, value);
cleanChildren(parent, current, null, value);
} else current == null || current === "" || !parent.firstChild ? parent.appendChild(value) : parent.replaceChild(value, parent.firstChild);
current = value;
}
}
return current;
}
function normalizeIncomingArray(normalized, array, current, unwrap2) {
let dynamic = !1;
for (let i = 0, len = array.length; i < len; i++) {
let item = array[i], prev = current && current[normalized.length], t;
if (!(item == null || item === !0 || item === !1)) if ((t = typeof item) == "object" && item.nodeType)
normalized.push(item);
else if (Array.isArray(item))
dynamic = normalizeIncomingArray(normalized, item, prev) || dynamic;
else if (t === "function")
if (unwrap2) {
for (; typeof item == "function"; ) item = item();
dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item], Array.isArray(prev) ? prev : [prev]) || dynamic;
} else
normalized.push(item), dynamic = !0;
else {
let value = String(item);
prev && prev.nodeType === 3 && prev.data === value ? normalized.push(prev) : normalized.push(document.createTextNode(value));
}
}
return dynamic;
}
function appendNodes(parent, array, marker = null) {
for (let i = 0, len = array.length; i < len; i++) parent.insertBefore(array[i], marker);
}
function cleanChildren(parent, current, marker, replacement) {
if (marker === void 0) return parent.textContent = "";
let node = replacement || document.createTextNode("");
if (current.length) {
let inserted = !1;
for (let i = current.length - 1; i >= 0; i--) {
let el = current[i];
if (node !== el) {
let isParent = el.parentNode === parent;
!inserted && !i ? isParent ? parent.replaceChild(node, el) : parent.insertBefore(node, marker) : isParent && el.remove();
} else inserted = !0;
}
} else parent.insertBefore(node, marker);
return [node];
}
function getHydrationKey() {
return sharedConfig.getNextContextId();
}
var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
function createElement(tagName, isSVG = !1, is = void 0) {
return isSVG ? document.createElementNS(SVG_NAMESPACE, tagName) : document.createElement(tagName, {
is
});
}
function Portal(props) {
let {
useShadow
} = props, marker = document.createTextNode(""), mount = () => props.mount || document.body, owner = getOwner(), content, hydrating = !!sharedConfig.context;
return createEffect(() => {
hydrating && (getOwner().user = hydrating = !1), content || (content = runWithOwner(owner, () => createMemo(() => props.children)));
let el = mount();
if (el instanceof HTMLHeadElement) {
let [clean, setClean] = createSignal(!1), cleanup = () => setClean(!0);
createRoot((dispose2) => insert(el, () => clean() ? dispose2() : content(), null)), onCleanup(cleanup);
} else {
let container = createElement(props.isSVG ? "g" : "div", props.isSVG), renderRoot = useShadow && container.attachShadow ? container.attachShadow({
mode: "open"
}) : container;
Object.defineProperty(container, "_$host", {
get() {
return marker.parentNode;
},
configurable: !0
}), insert(renderRoot, content), el.appendChild(container), props.ref && props.ref(container), onCleanup(() => el.contains(container) && el.removeChild(container));
}
}, void 0, {
render: !hydrating
}), marker;
}
function createDynamic(component, props) {
let cached = createMemo(component);
return createMemo(() => {
let component2 = cached();
switch (typeof component2) {
case "function":
return untrack(() => component2(props));
case "string":
let isSvg = SVGElements.has(component2), el = sharedConfig.context ? getNextElement() : createElement(component2, isSvg, untrack(() => props.is));
return spread(el, props, isSvg), el;
}
});
}
function Dynamic(props) {
let [, others] = splitProps(props, ["component"]);
return createDynamic(() => props.component, others);
}
// src/eh/url.ts
var EXHENTAI_HOST = "exhentai.org", EXHENTAI_ONION_HOST = "exhentai55ld2wyap5juskbm67czulomrouspdacjamjeloj7ugjbsad.onion";
function ehSiteTheme(url = window.location.href) {
let hostname = new URL(url, window.location.href).hostname;
return hostname === EXHENTAI_HOST || hostname.endsWith(`.${EXHENTAI_HOST}`) || hostname === EXHENTAI_ONION_HOST || hostname.endsWith(`.${EXHENTAI_ONION_HOST}`) ? "exhentai" : "e-hentai";
}
function galleryIdentityFromUrl(url = window.location.href) {
try {
let match = new URL(url, window.location.href).pathname.match(/^\/g\/(\d+)\/([^/]+)/i), galleryId = Number(match?.[1]), token = match?.[2];
return token && Number.isSafeInteger(galleryId) && galleryId > 0 ? { galleryId, token } : null;
} catch {
return null;
}
}
function urlPath(url) {
try {
return new URL(url, window.location.href).pathname.toLowerCase();
} catch {
return "";
}
}
function galleryTagNameFromUrl(url) {
let encodedName = urlPath(url).match(/^\/tag\/(.+?)\/?$/i)?.[1];
try {
return encodedName ? decodeURIComponent(encodedName.replace(/\+/g, " ")) : null;
} catch {
return null;
}
}
function isFullImageUrl(url) {
return urlPath(url).includes("/fullimg");
}
function extractPageType(url = window.location.href) {
try {
let parsed = new URL(url, window.location.href), hash = new URLSearchParams(parsed.hash.replace(/^#/, ""));
if (parsed.pathname === "/popular" && hash.has("ehpeek_history")) {
let requestedPage = Number(hash.get("page") ?? "0");
return {
type: "readHistory",
url: parsed.href,
pageIndex: Number.isSafeInteger(requestedPage) && requestedPage >= 0 ? requestedPage : 0
};
}
let galleryMatch = parsed.pathname.match(/^\/g\/(\d+)\/([^/]+)\/?$/i);
if (galleryMatch) {
let galleryId = Number(galleryMatch[1]), token = galleryMatch[2];
if (token && Number.isFinite(galleryId) && galleryId > 0)
return {
type: "gallery",
url: parsed.href,
galleryId,
token,
previewIndex: previewPageIndex(parsed.href),
peekPage: peekPageFromHash(parsed.hash)
};
}
let imageMatch = parsed.pathname.match(/^\/s\/[^/]+\/(\d+)-(\d+)\/?$/i);
if (imageMatch) {
let galleryId = Number(imageMatch[1]), pageNum = Number(imageMatch[2]);
if (Number.isFinite(galleryId) && galleryId > 0 && Number.isFinite(pageNum) && pageNum > 0)
return {
type: "image",
url: parsed.href,
galleryId,
pageNum
};
}
return parsed.pathname === "/favorites.php" ? {
type: "favorites",
url: parsed.href
} : /^\/mytags\/?$/.test(parsed.pathname) ? {
type: "myTags",
url: parsed.href
} : parsed.pathname === "/uconfig.php" ? {
type: "settings",
url: parsed.href
} : parsed.pathname === "/" || parsed.pathname.startsWith("/tag/") || parsed.pathname.startsWith("/uploader/") || /^\/(?:popular|watched)\/?$/.test(parsed.pathname) ? {
type: "search",
url: parsed.href
} : {
type: "other",
url: parsed.href
};
} catch {
return {
type: "other",
url
};
}
}
function readHistoryUrl(pageIndex = 0) {
let url = new URL("/popular", window.location.href);
return url.hash = pageIndex > 0 ? `ehpeek_history&page=${pageIndex}` : "ehpeek_history", url.href;
}
function galleryPageNumber(url) {
let page2 = extractPageType(url);
return page2.type === "image" ? page2.pageNum : void 0;
}
function previewPageIndex(url = window.location.href) {
try {
let value = Number(new URL(url).searchParams.get("p") || "0");
return Number.isFinite(value) && value >= 0 ? value : 0;
} catch {
return 0;
}
}
function previewUrlForIndex(previewIndex, pageUrl = window.location.href) {
let url = new URL(pageUrl);
return previewIndex <= 0 ? url.searchParams.delete("p") : url.searchParams.set("p", String(previewIndex)), url.hash = "", url.href;
}
function previewPageIndexForGalleryPage(galleryPage, pageSize, maxPreviewIndex) {
let previewIndex = Math.max(0, Math.floor((galleryPage - 1) / pageSize));
return Math.min(previewIndex, maxPreviewIndex);
}
function peekPageFromHash(hash = window.location.hash) {
let params = new URLSearchParams(hash.replace(/^#/, "")), page2 = Number(params.get("peek_page") || "");
return Number.isFinite(page2) && page2 > 0 ? page2 : null;
}
function clearPeekLocation() {
let url = new URL(window.location.href), params = new URLSearchParams(url.hash.replace(/^#/, ""));
params.has("peek_page") && (params.delete("peek_page"), url.hash = params.toString(), window.history.replaceState(window.history.state, "", url.href));
}
function updatePeekLocation(pageNumber, pageSize, maxPreviewIndex) {
if (!pageNumber || pageNumber <= 0)
return;
let url = new URL(window.location.href), params = new URLSearchParams(window.location.hash.replace(/^#/, "")), nextValue = String(pageNumber), nextPreviewIndex = previewPageIndexForGalleryPage(pageNumber, pageSize, maxPreviewIndex), changed = !1;
nextPreviewIndex === 0 ? url.searchParams.has("p") && (url.searchParams.delete("p"), changed = !0) : url.searchParams.get("p") !== String(nextPreviewIndex) && (url.searchParams.set("p", String(nextPreviewIndex)), changed = !0), params.get("peek_page") !== nextValue && (params.set("peek_page", nextValue), changed = !0), changed && (url.hash = params.toString(), window.history.replaceState(window.history.state, "", url.href));
}
// src/eh/request.ts
async function requestPage(url, options = {}) {
let controller = new AbortController(), abort = () => controller.abort(), timeoutMs = options.timeoutMs === void 0 ? 3e4 : options.timeoutMs, timeout = timeoutMs === null ? null : window.setTimeout(abort, timeoutMs);
options.signal?.aborted ? controller.abort() : options.signal?.addEventListener("abort", abort, { once: !0 });
try {
let response = await fetch(url, {
method: options.method ?? "GET",
body: options.body,
credentials: "include",
headers: options.headers,
signal: controller.signal
});
if (!response.ok)
throw new Error(`HTTP ${response.status}`);
let html = await response.text();
return {
document: new DOMParser().parseFromString(html, "text/html"),
url: response.url || url
};
} finally {
timeout !== null && window.clearTimeout(timeout), options.signal?.removeEventListener("abort", abort);
}
}
async function updateGalleryFavorite(actionUrl, value) {
let body = new URLSearchParams();
body.set("favcat", value), body.set("favnote", ""), body.set("apply", "Apply Changes"), body.set("update", "1"), await requestPage(actionUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body
});
}
async function addMyTag(tagName, tagSet, mode) {
let body = new URLSearchParams();
body.set("usertag_action", "add"), body.set("tagname_new", tagName), body.set("tagcolor_new", ""), body.set("tagweight_new", "10"), mode === "watched" ? body.set("tagwatch_new", "on") : mode === "hidden" && body.set("taghide_new", "on");
let url = new URL("/mytags", window.location.origin);
return url.searchParams.set("tagset", tagSet), requestPage(url.href, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body
});
}
async function deleteMyTag(tagId, tagSet) {
let body = new URLSearchParams();
body.set("usertag_action", "mass"), body.set("usertag_target", "0"), body.append("modify_usertags[]", tagId);
let url = new URL("/mytags", window.location.origin);
return url.searchParams.set("tagset", tagSet), requestPage(url.href, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body
});
}
// src/eh/dom/core.ts
var EHPEEK_ANCHOR_ATTRIBUTE = "data-ehpeek-anchor", EH_SYRINGE_IGNORE_SELECTOR = ".eh-syringe-ignore", LONG_PRESS_DELAY_MS = 600, LONG_PRESS_MOVE_TOLERANCE_PX = 10, mountedNodes = /* @__PURE__ */ new WeakMap();
var managedBody = null, emptyDomApply = {}, emptyDomChilds = {};
function defineDomNode() {
return (selector, options = {}) => {
let childs = options.childs ?? emptyDomChilds;
return Object.assign({
apply: options.apply ?? emptyDomApply,
childs,
kind: "node",
selector
}, childs);
};
}
var query = defineDomNode(), anchor = defineDomNode(), area = defineDomNode(), button = defineDomNode(), cell = defineDomNode(), control = defineDomNode(), form = defineDomNode(), image = defineDomNode(), input = defineDomNode(), option = defineDomNode(), row = defineDomNode(), script = defineDomNode(), select = defineDomNode(), table = defineDomNode();
function cls(name, options = {}) {
return query(`.${name}`, options);
}
function id(name, options = {}) {
return query(`#${name}`, options);
}
function tag(name, options = {}) {
return defineDomNode()(name, options);
}
function domSelector(source) {
return typeof source == "string" ? source : source.selector;
}
function originalPageNode(node) {
return node.closest(EH_SYRINGE_IGNORE_SELECTOR) === null;
}
function anyDomNode() {
return !0;
}
function createAnchor(name) {
let selector = `[${EHPEEK_ANCHOR_ATTRIBUTE}="${CSS.escape(name)}"]`;
if (document.querySelector(selector))
return null;
let anchor2 = document.createElement("div");
return anchor2.setAttribute(EHPEEK_ANCHOR_ATTRIBUTE, name), DomNode.from(anchor2).inplace();
}
function createManagedElement(tagName, apply = emptyDomApply) {
return ManagedDomNode.from(document.createElement(tagName), apply);
}
function documentBody() {
return managedBody ?? (managedBody = DomNode.from(document.body).inplace()), managedBody;
}
var _node, _DomNode = class _DomNode {
constructor(node) {
__privateAdd(this, _node);
__privateSet(this, _node, node);
}
static from(node) {
return new _DomNode(node);
}
use(description) {
return bindDom(description, () => [this]).bound;
}
one(source, filter = originalPageNode) {
return Array.from(
__privateGet(this, _node).querySelectorAll(domSelector(source)),
_DomNode.from
).find(filter) ?? null;
}
all(source, filter = originalPageNode) {
return Array.from(__privateGet(this, _node).querySelectorAll(domSelector(source))).map(_DomNode.from).filter(filter);
}
parent() {
let parent = __privateGet(this, _node).parentElement;
return parent ? _DomNode.from(parent) : null;
}
children() {
return Array.from(__privateGet(this, _node).children, (child) => _DomNode.from(child));
}
closest(source) {
let element = __privateGet(this, _node).closest(domSelector(source));
return element ? _DomNode.from(element) : null;
}
matches(source) {
return __privateGet(this, _node).matches(domSelector(source));
}
previous() {
let previous = __privateGet(this, _node).previousElementSibling;
return previous instanceof HTMLElement ? _DomNode.from(previous) : null;
}
form() {
return __privateGet(this, _node).form ? _DomNode.from(__privateGet(this, _node).form) : null;
}
childElementCount() {
return __privateGet(this, _node).childElementCount;
}
text() {
return __privateGet(this, _node).textContent?.trim() ?? "";
}
attribute(name) {
return __privateGet(this, _node).getAttribute(name);
}
hasAttribute(name) {
return __privateGet(this, _node).hasAttribute(name);
}
attributeNames() {
return __privateGet(this, _node).getAttributeNames();
}
hasClass(className2) {
return __privateGet(this, _node).classList.contains(className2);
}
computedStyle() {
return window.getComputedStyle(__privateGet(this, _node));
}
imageSize() {
return {
height: __privateGet(this, _node).naturalHeight || __privateGet(this, _node).height || Number(__privateGet(this, _node).getAttribute("height") || ""),
width: __privateGet(this, _node).naturalWidth || __privateGet(this, _node).width || Number(__privateGet(this, _node).getAttribute("width") || "")
};
}
inputValue() {
return __privateGet(this, _node).value;
}
checked() {
return __privateGet(this, _node).checked;
}
selected() {
return __privateGet(this, _node).selected;
}
sameNode(other) {
return __privateGet(this, _node) === __privateGet(other, _node);
}
observe(source, acquire, onManaged, options = { childList: !0, subtree: !0 }) {
let seen = [], cleanups = [], scan = () => {
for (let node of this.all(domSelector(source))) {
if (seen.some((candidate) => candidate.sameNode(node)))
continue;
seen.push(node);
let managed = acquire(node);
if (!managed)
continue;
let cleanup = onManaged(managed);
cleanup && cleanups.push(cleanup);
}
}, observer = new MutationObserver(scan);
return scan(), observer.observe(__privateGet(this, _node), options), () => {
observer.disconnect(), cleanups.forEach((cleanup) => cleanup());
};
}
inplace(apply = emptyDomApply) {
return ManagedDomNode.from(__privateGet(this, _node), apply);
}
move(apply = emptyDomApply) {
let managed = this.inplace(apply);
return managed.remove(), managed;
}
clone(applyOrDeep = emptyDomApply, deep = !0) {
let apply = typeof applyOrDeep == "boolean" ? emptyDomApply : applyOrDeep, cloneDeep = typeof applyOrDeep == "boolean" ? applyOrDeep : deep;
return ManagedDomNode.from(
__privateGet(this, _node).cloneNode(cloneDeep),
apply
);
}
};
_node = new WeakMap();
var DomNode = _DomNode;
function bindDom(description, scopes) {
let children = [], invalidateChildren = () => {
for (let child of children)
child.invalidate();
}, definition = isDomDefinition(description) ? description : null, nodeController = definition ? bindDomNode(definition, scopes, invalidateChildren) : null, bound = nodeController?.bound ?? {}, childScopes = definition ? () => nodeController?.bound.all() ?? [] : scopes, childs = definition?.childs ?? description;
for (let [name, child] of Object.entries(childs)) {
if (name in bound)
throw new Error(`Original DOM child name is reserved: ${name}`);
let childController = bindDom(child, childScopes);
children.push(childController), Object.assign(bound, { [name]: childController.bound });
}
return {
bound,
invalidate: nodeController?.invalidate ?? invalidateChildren
};
}
function isDomDefinition(description) {
return "kind" in description && description.kind === "node";
}
function bindDomNode(description, scopes, invalidateChildren) {
let cached, queryNodes = () => scopes().flatMap((scope) => scope.all(description.selector)), resolve = () => (cached ?? (cached = queryNodes()), cached);
return {
bound: {
all: () => [...resolve()],
clone: () => resolve()[0]?.clone(description.apply) ?? null,
cloneAll: () => resolve().map((node) => node.clone(description.apply)),
inplace: () => resolve()[0]?.inplace(description.apply) ?? null,
inplaceAll: () => resolve().map((node) => node.inplace(description.apply)),
move: () => resolve()[0]?.move(description.apply) ?? null,
moveAll: () => resolve().map((node) => node.move(description.apply)),
one: () => resolve()[0] ?? null,
requery: () => (cached = queryNodes(), invalidateChildren(), [...cached])
},
invalidate: () => {
cached = void 0, invalidateChildren();
}
};
}
var _apply, _node2, _ManagedDomNode = class _ManagedDomNode {
constructor(element, apply) {
__privateAdd(this, _apply);
__privateAdd(this, _node2);
__privateSet(this, _apply, apply), __privateSet(this, _node2, element), this.Component = () => __privateGet(this, _node2);
}
static from(element, apply = emptyDomApply) {
return new _ManagedDomNode(element, apply);
}
apply(...names) {
let classes = names.map((name) => {
let className2 = __privateGet(this, _apply)[name];
if (!className2)
throw new Error(`Unknown original DOM application: ${name}`);
return className2;
});
return __privateGet(this, _node2).classList.add(...classes), this;
}
all(source) {
let apply = typeof source == "string" ? emptyDomApply : source.apply;
return Array.from(
__privateGet(this, _node2).querySelectorAll(domSelector(source)),
(node) => _ManagedDomNode.from(node, apply)
);
}
rect() {
return __privateGet(this, _node2).getBoundingClientRect();
}
readAttribute(name) {
return __privateGet(this, _node2).getAttribute(name);
}
setAttributes(values) {
for (let [name, value] of Object.entries(values))
__privateGet(this, _node2).setAttribute(name, value);
return this;
}
removeAttributes(...names) {
for (let name of names)
__privateGet(this, _node2).removeAttribute(name);
return this;
}
addClasses(...names) {
return __privateGet(this, _node2).classList.add(...names), this;
}
removeClasses(...names) {
return __privateGet(this, _node2).classList.remove(...names), this;
}
replaceClasses(value) {
return __privateGet(this, _node2).className = value, this;
}
styles(values, priority = "") {
for (let [property, value] of Object.entries(values))
__privateGet(this, _node2).style.setProperty(property, value, priority);
return this;
}
removeStyles(...properties) {
for (let property of properties)
__privateGet(this, _node2).style.removeProperty(property);
return this;
}
removeAllStyles() {
return __privateGet(this, _node2).removeAttribute("style"), this;
}
attribute(name, value) {
return __privateGet(this, _node2).setAttribute(name, value), this;
}
click() {
__privateGet(this, _node2).click();
}
mount(view) {
mountedNodes.get(__privateGet(this, _node2))?.(), __privateGet(this, _node2).replaceChildren(), mountedNodes.set(__privateGet(this, _node2), render(view, __privateGet(this, _node2)));
}
remove() {
mountedNodes.get(__privateGet(this, _node2))?.(), mountedNodes.delete(__privateGet(this, _node2)), __privateGet(this, _node2).remove();
}
replaceWith(replacement) {
__privateGet(this, _node2).replaceWith(
replacement instanceof _ManagedDomNode ? __privateGet(replacement, _node2) : replacement
);
}
before(sibling) {
__privateGet(this, _node2).before(sibling instanceof _ManagedDomNode ? __privateGet(sibling, _node2) : sibling);
}
after(sibling) {
__privateGet(this, _node2).after(sibling instanceof _ManagedDomNode ? __privateGet(sibling, _node2) : sibling);
}
append(...children) {
return __privateGet(this, _node2).append(...children.map((child) => __privateGet(child, _node2))), this;
}
prepend(child) {
__privateGet(this, _node2).prepend(child instanceof _ManagedDomNode ? __privateGet(child, _node2) : child);
}
setTextUnlessInput(text) {
__privateGet(this, _node2) instanceof HTMLInputElement || (__privateGet(this, _node2).textContent = text);
}
setHidden(hidden) {
return __privateGet(this, _node2).hidden = hidden, this;
}
replaceChildren(...children) {
__privateGet(this, _node2).replaceChildren(...children.map((child) => child instanceof _ManagedDomNode ? __privateGet(child, _node2) : child));
}
listen(type, listener, options) {
return __privateGet(this, _node2).addEventListener(type, listener, options), () => __privateGet(this, _node2).removeEventListener(type, listener, options);
}
listenLongPress(listener, shouldStart = () => !0) {
let timer = null, press = null, suppressClick = !1, suppressClickTimer = null, cancel = () => {
timer !== null && (window.clearTimeout(timer), timer = null), press = null;
}, onPointerDown = (event) => {
!event.isPrimary || event.button !== 0 || !shouldStart(event) || (cancel(), press = { event, x: event.clientX, y: event.clientY }, timer = window.setTimeout(() => {
let completed = press;
cancel(), completed && (suppressClick = !0, listener(completed.event), suppressClickTimer = window.setTimeout(() => {
suppressClick = !1, suppressClickTimer = null;
}, 1e3));
}, LONG_PRESS_DELAY_MS));
}, onPointerMove = (event) => {
press?.event.pointerId === event.pointerId && Math.hypot(event.clientX - press.x, event.clientY - press.y) > LONG_PRESS_MOVE_TOLERANCE_PX && cancel();
}, onPointerEnd = (event) => {
press?.event.pointerId === event.pointerId && cancel();
}, onContextMenu = (event) => {
(press || suppressClick) && event.preventDefault();
}, onClick = (event) => {
suppressClick && (suppressClick = !1, event.preventDefault(), event.stopImmediatePropagation());
}, cleanups = [
this.listen("pointerdown", onPointerDown),
this.listen("pointermove", onPointerMove),
this.listen("pointerup", onPointerEnd),
this.listen("pointercancel", onPointerEnd),
this.listen("contextmenu", onContextMenu),
this.listen("click", onClick, !0)
];
return () => {
cancel(), suppressClickTimer !== null && window.clearTimeout(suppressClickTimer), cleanups.forEach((cleanup) => cleanup());
};
}
observe(onChange, options = { childList: !0, subtree: !0 }) {
let observer = new MutationObserver(onChange);
return observer.observe(__privateGet(this, _node2), options), () => observer.disconnect();
}
focus() {
__privateGet(this, _node2).focus();
}
scrollIntoView(options) {
__privateGet(this, _node2).scrollIntoView(options);
}
isNode(node) {
return __privateGet(this, _node2) === node;
}
contains(node) {
return __privateGet(this, _node2).contains(node);
}
matches(source) {
return __privateGet(this, _node2).matches(domSelector(source));
}
copyAttributesTo(target) {
for (let attribute of Array.from(__privateGet(this, _node2).attributes))
__privateGet(target, _node2).setAttribute(attribute.name, attribute.value);
}
setInputValue(value) {
__privateGet(this, _node2).value = value;
}
inputValue() {
return __privateGet(this, _node2).value;
}
setSelected(selected) {
__privateGet(this, _node2).selected = selected;
}
dispatchInput() {
__privateGet(this, _node2).dispatchEvent(new Event("input", { bubbles: !0 }));
}
mirrorContentTo(target) {
let update = () => {
target.replaceChildren(
...Array.from(__privateGet(this, _node2).childNodes, (node) => node.cloneNode(!0))
);
let language = __privateGet(this, _node2).getAttribute("lang");
language ? target.setAttribute("lang", language) : target.removeAttribute("lang");
};
return update(), this.observe(update, {
attributes: !0,
attributeFilter: ["lang"],
characterData: !0,
childList: !0,
subtree: !0
});
}
};
_apply = new WeakMap(), _node2 = new WeakMap();
var ManagedDomNode = _ManagedDomNode;
// src/eh/dom/domClass.ts
var sharedApply = {
coverlessSearchGrid: "ehpeek-expand-coverless-search-grid",
constrainResultsToViewport: "ehpeek-constrain-results-to-viewport",
galleryTagMenuItem: "ehpeek-layout-gallery-tag-menu-item",
hideOriginalSearchAction: "ehpeek-hide-original-search-action",
searchGrid: "ehpeek-layout-search-grid",
searchResultColumns: "ehpeek-search-result-columns",
stackSearchGridTags: "ehpeek-stack-search-grid-tags"
}, page = {
footer: query("body > .dp"),
html: tag("html", {
apply: {
constrainResults: sharedApply.constrainResultsToViewport,
galleryTouchLayout: "ehpeek-touch-gallery-page",
galleryWideLayout: "ehpeek-gallery-wide-layout-root"
}
}),
body: tag("body", {
apply: {
constrainFavoritesNavigation: "ehpeek-constrain-favorites-navigation",
constrainResults: sharedApply.constrainResultsToViewport,
galleryTouchLayout: "ehpeek-touch-gallery-page",
galleryWideLayout: "ehpeek-gallery-wide-layout-root",
hidePreviewPageBars: "ehpeek-hide-original-preview-page-bars"
}
})
}, common = {
descendants: query("*"),
galleryLink: anchor('a[href*="/g/"]'),
image: image("img"),
interactive: query(
"a[href], button, input, select, textarea, label, [onclick]"
),
links: anchor("a[href]"),
scripts: script("script")
}, ehSyringe = {
root: cls("ehs-injected")
}, myTags = {
tags: id("usertags_outer", {
childs: {
items: query(":scope > [id^='usertag_']", {
childs: {
color: input("input[id^='tagcolor_']"),
preview: query("[id^='tagpreview_'][title]")
}
})
}
}),
options: option("#tagset_outer select option"),
defaultColor: input("#tagcolor"),
enabled: input("#tagset_enable"),
favoriteOptions: input("input[name='favcat']"),
favoriteOptionRow: query("div[style*='height']")
}, gallery = {
actions: id("gd5", {
apply: {
expand: "ehpeek-expand-gallery-actions"
},
childs: {
items: query("a, button, input[type='button'], input[type='submit']", {
apply: {
layout: "ehpeek-layout-gallery-action"
}
})
}
}),
comments: id("cdiv", {
apply: {
touchScore: "ehpeek-enable-touch-comment-score"
},
childs: {
score: cls("c5"),
scoreComment: cls("c1", {
childs: {
details: query(".c7[id^='cvotes_']")
}
})
}
}),
commentsAnchor: anchor('a[name="comments"]'),
imagePage: {
image: image("img#img"),
links: anchor("a[href]")
},
info: {
category: id("gdc", {
childs: {
appearance: query("[class*='ct']")
}
}),
cover: id("gd1", {
childs: {
image: image("img", {
apply: {
fit: "ehpeek-fit-gallery-cover"
}
}),
descendants: query("*")
}
}),
details: id("gdd", {
childs: {
rows: row("tr", {
childs: {
cells: cell("td, th")
}
})
}
}),
favorite: id("fav", {
childs: {
link: id("favoritelink"),
titled: query("[title]")
}
}),
hostFallback: id("gleft"),
original: id("gmid"),
rating: {
actions: area('map[name="rating"] area'),
count: id("rating_count"),
image: id("rating_image"),
label: id("rating_label"),
rated: query(".irb, .irg, .irr")
},
tagMenu: id("tagmenu_act", {
apply: {
layout: "ehpeek-layout-gallery-tag-menu"
},
childs: {
actions: anchor("a")
}
}),
newTag: id("tagmenu_new", {
apply: {
layout: "ehpeek-layout-new-tag-form"
},
childs: {
button: control("#newtagbutton"),
field: input("#newtagfield"),
form: form("form")
}
}),
titleMain: id("gn"),
titleSub: id("gj"),
uploader: query("#gdn a, #gdn")
},
preview: {
description: cls("gpc"),
imageLinks: anchor(
"#gdt a[href], .gdtm a[href], .gdtl a[href], a[href*='/s/']"
),
imageLinkHost: query("#gdt, .gdtm, .gdtl"),
pageBarBottom: cls("ptb"),
pageBarTop: cls("ptt"),
thumbs: id("gdt", {
apply: {
suppressTapHighlight: "ehpeek-suppress-thumbnail-tap-highlight",
swipe: "ehpeek-enable-preview-swipe-input"
},
childs: {
images: image("img"),
links: anchor("a[href]")
}
})
},
tagContainer: query("div.gt, div.gtl, div.gtw", {
apply: {
myTag: "ehpeek-color-my-tag"
}
}),
tags: id("taglist", {
childs: {
links: anchor("a"),
rows: row("tr", {
childs: {
namespace: query(".tc, td:first-child"),
links: anchor("a")
}
})
}
})
}, search = {
controls: query("#toppane, .searchtext, .searchwarn, .searchnav, .ptt, .ptb"),
displayMode: select("select[onchange*='inline_set=dm_']", {
childs: {
options: option("option")
}
}),
favorites: {
categories: query(".ido > .nosel", {
apply: {
hide: "ehpeek-hide-original-favorites-categories"
},
childs: {
items: query(":scope > .fp, :scope > .fps", {
childs: {
indicator: cls("i")
}
})
}
}),
input: input("input[name='f_search']"),
selectedCategory: cls("fps")
},
input: input("#f_search, input[name='f_search']", {
apply: {
expand: "ehpeek-expand-search-input"
}
}),
navigation: cls("searchnav", {
childs: {
first: anchor("a[id$='first'][href]"),
previous: anchor("a[id$='prev'][href]"),
next: anchor("a[id$='next'][href]"),
last: anchor("a[id$='last'][href]"),
links: anchor("a[href]")
}
}),
navigationLink: anchor(
".searchnav a[id$='first'][href], .searchnav a[id$='prev'][href], .searchnav a[id$='next'][href], .searchnav a[id$='last'][href]"
),
panel: {
box: id("searchbox", {
apply: {
reset: "ehpeek-reset-search-box-layout"
},
childs: {
advanced: id("advdiv", {
apply: {
expand: "ehpeek-expand-search-advanced-options"
}
}),
categories: table("form > table", {
apply: {
layout: "ehpeek-layout-search-categories"
}
}),
form: form("form", {
apply: {
stack: "ehpeek-stack-search-form"
}
})
}
}),
clear: control("input[name='f_clear'], button[name='f_clear']", {
apply: {
hide: sharedApply.hideOriginalSearchAction
}
}),
clearFallback: control("input[type='button'], button[type='button']", {
apply: {
hide: sharedApply.hideOriginalSearchAction
}
}),
fileSearch: id("fsdiv", {
apply: {
expand: "ehpeek-expand-file-search"
}
}),
optionLinks: anchor("a"),
submit: control("input[name='f_apply'], button[name='f_apply']", {
apply: {
hide: sharedApply.hideOriginalSearchAction
}
}),
submitFallback: control("input[type='submit'], button[type='submit']", {
apply: {
hide: sharedApply.hideOriginalSearchAction
}
})
},
rangeBar: id("rangebar"),
removeHistory: control("[data-ehpeek-remove-history]"),
results: cls("itg", {
apply: {
compactFavorites: "ehpeek-compact-all-favorites-results",
containFavorites: "ehpeek-contain-favorites-results",
containSearch: "ehpeek-contain-search-results",
columns: sharedApply.searchResultColumns,
grid: sharedApply.searchGrid,
swipe: "ehpeek-enable-search-swipe-input"
},
childs: {
body: query(":scope > tbody"),
rows: row("tbody > tr", {
apply: {
coverless: sharedApply.coverlessSearchGrid
},
childs: {
cover: query(":scope > .gl1e"),
content: query(":scope > .gl2e", {
childs: {
detail: cls("gl4e", {
childs: {
tags: query(":scope > *", {
apply: {
stack: sharedApply.stackSearchGridTags
}
}),
title: query(":scope > .glink", {
apply: {
history: "ehpeek-prefix-read-history-label"
}
})
}
}),
metadata: cls("gl3e")
}
})
}
}),
galleryLinks: anchor('a[href*="/g/"]'),
links: anchor("a[href]"),
titles: cls("glink", {
apply: {
history: "ehpeek-prefix-read-history-label"
}
})
}
}),
resultText: cls("searchtext"),
submit: control("input[name='f_apply'], button[name='f_apply']"),
submitFallback: control("input[type='submit'], button[type='submit']")
}, settings = {
titleDefault: input("#tl_r"),
titleJapanese: input("#tl_j")
}, topBar = {
galleryTitle: query("#gd2, h1"),
navigation: id("nb", {
childs: {
links: anchor("a[href]", {
apply: {
layout: "ehpeek-layout-top-bar-menu-item"
}
})
}
})
}, domClass = {
common,
ehSyringe,
gallery,
myTags,
page,
search,
settings,
topBar
};
// src/eh/dom/ehSyringe.ts
var ehSyringe_exports = {};
__export(ehSyringe_exports, {
initialize: () => initialize
});
// src/state/index.ts
var touchUiDefault = window.matchMedia("(pointer: coarse)").matches, portraitUiScaleDefault = touchUiDefault ? "large" : "small", landscapeUiScaleDefault = touchUiDefault && Math.min(window.screen.width, window.screen.height) >= 600 ? "medium" : portraitUiScaleDefault, state = {
app: {
ehSyringeDetected: persisted("ehpeek:ehsyringe:detected", !1),
openGalleryInNewTab: persisted("ehpeek:open-gallery-in-new-tab", !1),
portraitUiScale: persisted("ehpeek:ui-scale:portrait", portraitUiScaleDefault),
landscapeUiScale: persisted("ehpeek:ui-scale:landscape", landscapeUiScaleDefault)
},
reader: {
enabled: persisted("ehpeek:reader:enabled", !0),
fullscreen: persisted("ehpeek:reader:fullscreen", prefersTouchFullscreen()),
navigationMode: persisted("ehpeek:reader:navigation-mode", "scroll"),
scrollDirection: persisted("ehpeek:reader:scroll-direction", "ttb"),
pagedDirection: persisted("ehpeek:reader:paged-direction", "rtl"),
pageLayout: persisted("ehpeek:reader:page-layout", "single"),
rightTapAction: persisted("ehpeek:reader:right-tap-action", "previous"),
scrollTtbScale: persisted("ehpeek:reader:scroll-ttb-scale", null),
scrollHorizontalScale: persisted("ehpeek:reader:scroll-horizontal-scale", null)
},
gallery: {
enhanceThumbs: persisted("ehpeek:enhance-thumbs:enabled", !0),
myTags: persisted("ehpeek:my-tags:enabled", !0),
myTagAppearances: localJson("ehpeek:my-tags", [], isMyTagAppearance),
myTagSets: localJson("ehpeek:my-tag-sets", [], isMyTagSetOption),
readHistory: persisted("ehpeek:read-history:enabled", !0),
includeUnreadHistory: persisted("ehpeek:read-history:include-unread", !0),
readHistoryCount: persisted("ehpeek:history-count", 0),
titlePreference: localEnum(
"ehpeek:gallery-title-preference",
"main",
["main", "sub"]
)
},
search: {
enhance: persisted("ehpeek:enhance-search:enabled", !0),
grid: localSelection("ehpeek:search-grid", "ehpeek"),
history: persisted("ehpeek:search-history:enabled", !0),
searchHistory: persisted("ehpeek:search:history", [])
},
touch: {
enabled: persisted("ehpeek:touch-ui:enabled", touchUiDefault),
portraitColumns: persisted("ehpeek:touch-ui:portrait-columns", !1),
landscapeColumns: persisted("ehpeek:touch-ui:landscape-columns", !0)
}
};
function loadSearchHistory() {
let history = state.search.searchHistory.reload();
return Array.isArray(history) ? history.filter((item) => typeof item == "string") : [];
}
function addSearchHistory(value) {
let normalized = value.trim();
if (!normalized)
return loadSearchHistory();
let history = [normalized, ...loadSearchHistory().filter((item) => item !== normalized)];
return state.search.searchHistory.set(history), history;
}
function removeSearchHistory(value) {
let history = loadSearchHistory().filter((item) => item !== value);
return state.search.searchHistory.set(history), history;
}
function prefersTouchFullscreen() {
return window.matchMedia("(pointer: coarse)").matches || navigator.maxTouchPoints > 0;
}
function persisted(key, defaultValue) {
let item = {
defaultValue,
value: GM_getValue(key, defaultValue),
set(value) {
item.value = value, GM_setValue(key, value);
},
reload() {
return item.value = GM_getValue(key, defaultValue), item.value;
}
};
return item;
}
function normalizeReaderScrollSizeScale(scale) {
return Number.isFinite(scale) ? Math.min(100, Math.max(1e-3, scale)) : 1;
}
function localSelection(key, selectedValue) {
let read = () => window.localStorage.getItem(key) === selectedValue, item = {
defaultValue: !1,
value: read(),
set(value) {
item.value = value, value ? window.localStorage.setItem(key, selectedValue) : window.localStorage.removeItem(key);
},
reload() {
return item.value = read(), item.value;
}
};
return item;
}
function localEnum(key, defaultValue, values) {
let read = () => {
let value = window.localStorage.getItem(key);
return values.includes(value) ? value : defaultValue;
}, item = {
defaultValue,
value: read(),
set(value) {
item.value = value, window.localStorage.setItem(key, value);
},
reload() {
return item.value = read(), item.value;
}
};
return item;
}
function localJson(key, defaultValue, valid) {
let read = () => {
try {
let value = JSON.parse(window.localStorage.getItem(key) ?? "null");
return Array.isArray(value) ? value.filter(valid) : defaultValue;
} catch {
return defaultValue;
}
}, item = {
defaultValue,
value: read(),
set(value) {
item.value = value, window.localStorage.setItem(key, JSON.stringify(value));
},
reload() {
return item.value = read(), item.value;
},
clear() {
item.value = defaultValue, window.localStorage.removeItem(key);
},
stored() {
return window.localStorage.getItem(key) !== null;
}
};
return item;
}
function isMyTagAppearance(value) {
if (!value || typeof value != "object" || Array.isArray(value))
return !1;
let item = value;
return typeof item.name == "string" && typeof item.backgroundColor == "string" && typeof item.color == "string" && typeof item.id == "string" && typeof item.tagSet == "string";
}
function isMyTagSetOption(value) {
if (!value || typeof value != "object" || Array.isArray(value))
return !1;
let item = value;
return typeof item.label == "string" && typeof item.selected == "boolean" && typeof item.value == "string";
}
// src/eh/dom/ehSyringe.ts
var INJECTION_TIMEOUT_MS = 3e3;
function initialize() {
detect();
}
async function detect() {
document.readyState === "loading" && await new Promise((resolve) => {
document.addEventListener("DOMContentLoaded", () => resolve(), { once: !0 });
});
let detected = isInjected();
return !detected && state.app.ehSyringeDetected.value && (await new Promise((resolve) => {
window.setTimeout(resolve, INJECTION_TIMEOUT_MS);
}), detected = isInjected()), state.app.ehSyringeDetected.value !== detected && state.app.ehSyringeDetected.set(detected), detected;
}
function isInjected() {
return DomNode.from(document.documentElement).matches(domClass.ehSyringe.root);
}
// src/texts.json
var texts_default = {
description: "A touch-optimized E-H/ExH viewer",
button: {
apply: "Apply",
clearHistory: "Clear All History",
close: "Close",
confirm: "Confirm",
current: "Current",
default: "Default",
removeHistory: "Delete History"
},
help: {
title: "Help",
content: {
Reader: [
"**Tap** the center to show or hide the toolbar.",
"**Swipe or scroll** in the reading direction; tap either side or use the **Arrow keys** to turn pages.",
"**Pinch** to zoom the image. Or **double-tap** to enter zoom mode and use the **mouse wheel** to adjust the scale."
],
Browse: [
"When **Enhance > Search Grids** is enabled, **swipe** horizontally to change search result pages."
],
Gallery: [
"When **Enhance > Thumbs Grids** is enabled, **swipe** horizontally to change preview pages.",
"The Page Bar in Gallery can be **dragged** to adjust the range."
]
}
},
reader: {
readingOptions: "Reading options",
fullscreen: "Fullscreen",
exitFullscreen: "Exit fullscreen",
adjustScrollViewport: "Adjust Scroll viewport size",
resizeScrollViewport: "Resize Scroll viewport",
fit: "Fit",
applyGlobally: "Set Default",
pagedMode: "Paged mode",
singlePageMode: "Single-page layout",
doublePageMode: "Double-page layout",
scrollMode: "Scroll mode",
rightTapPrevious: `Right side action:
Previous page`,
rightTapNext: `Right side action:
Next page`,
directionRtl: `Reading direction:
Right to left`,
directionLtr: `Reading direction:
Left to right`,
directionTtb: `Reading direction:
Top to bottom`,
download: "Download",
downloadDisplayedImage: "Displayed image",
downloadOriginalImage: "Original image",
originalImageSource: "Original source provided by E-Hentai",
originalImageUnavailable: "Original image unavailable",
startReading: "Read",
continueReading: "Continue",
loading: "Loading...",
pages: "Pages",
endPage: "End",
end: "End of gallery. Tap to exit.",
failedPrefix: "Failed",
reloadPage: "Reload page"
},
favorites: {
all: "All"
},
history: {
clearConfirm: "Clear all reading history?",
empty: "No reading history",
limit: "(max {limit})",
visitedLabel: "[VISITED]",
range: "{start}-{end} in {total} histories",
readingLabel: "[READING]",
removeConfirm: "Remove this gallery from Read History?",
unread: "Unread"
},
gallery: {
scrollPreview: "Scroll Preview",
favoriteTag: "Add My Tag",
removeFavoriteTag: "Remove My Tag",
tagCollection: "Collection",
tagBehavior: "Behavior",
markTag: "Mark",
watchTag: "Watch",
hideTag: "Hide"
},
settings: {
openSettings: "Settings",
menuLabel: "Ehpeek",
on: "On",
off: "Off",
discardChanges: "Discard unsaved settings changes?",
general: "General",
options: "Options",
about: "About",
readerLabel: "Reader",
readerHelp: "Opens gallery images in Ehpeek's reader",
readerFullscreenLabel: "Reader in Fullscreen",
readerFullscreenHelp: "Enters fullscreen when the Reader opens",
readerOptions: "Options",
openGalleryInNewTabLabel: "Gallery in New Tab",
openGalleryInNewTabHelp: "Opens Gallery links in a new browser tab",
uiControlsLabel: "UI Layout",
uiScaleLabel: "UI Scale",
columnsLabel: "Two Columns",
enhance: "Enhance",
enhanceSearchLabel: "Search Grids",
enhanceSearchHelp: "Adds swipe navigation to search pages",
enhanceThumbsLabel: "Thumbs Grids",
enhanceThumbsHelp: "Adds swipe navigation and scrollable pages bar for gallery preview",
myTagsLabel: "My Tag in Gallery",
myTagsHelp: "Highlights your saved tags with colors in gallery",
historyLabel: "History",
readHistoryLabel: "Read History",
readHistoryHelp: "Remembers reading progress",
includeUnreadHistoryLabel: "Include Unread History",
includeUnreadHistoryHelp: "Adds opened galleries to Read History before reading starts",
searchHistoryLabel: "Search History",
searchHistoryHelp: "Remembers previous searches",
touchUiLabel: "Touch UI",
touchUiHelp: "Uses touch-friendly navigation UI."
},
search: {
advancedOptions: "Advanced Options",
categories: "Categories",
fileSearch: "File Search"
},
errors: {
imageNotFound: "Image not found",
downloadFailed: "Download failed",
loadFailed: "Load failed",
imageLoadFailed: "Image load failed",
previewPageSizeUnknown: "Cannot determine gallery preview page size",
searchPageContentNotFound: "Cannot find search page content"
}
};
// src/utils.ts
function clamp(value, min, max) {
return max < min ? min : Math.min(max, Math.max(min, value));
}
function normalizeUrl(url, baseUrl = window.location.href) {
try {
return new URL(url, baseUrl).href;
} catch {
return "";
}
}
function normalizedAspectRatio(value, fallback) {
return value && Number.isFinite(value) && value > 0 ? value : fallback;
}
function positiveNumber(value) {
return value && Number.isFinite(value) && value > 0 ? value : null;
}
function stopEvent(event) {
event.stopPropagation();
}
function registerGlobalStyle(id2, css) {
if (!css || document.getElementById(id2))
return;
let style2 = document.createElement("style");
style2.id = id2, style2.textContent = css, (document.head ?? document.documentElement).append(style2);
}
// src/eh/dom/gallery.ts
function extractMyTagsPageData(root = document, tagSet) {
let source = DomNode.from(root).use(domClass.myTags), tags = source.tags.one();
if (!tags)
throw new Error("The My Tags page could not be read.");
let options = source.options.all().map((option2) => ({
label: option2.text() || option2.inputValue(),
selected: option2.selected(),
value: option2.inputValue()
})), activeTagSet = tagSet ?? options.find((option2) => option2.selected)?.value ?? "1", defaultColor = source.defaultColor.one()?.inputValue().trim() ?? "", output = [];
for (let item of tags.all(domClass.myTags.tags.items)) {
let preview = item.one(domClass.myTags.tags.items.preview), name = normalizeTagName(preview?.attribute("title") ?? "");
if (!preview || !name)
continue;
let itemColor = item.one(domClass.myTags.tags.items.color)?.inputValue() ?? "", backgroundColor = normalizeTagColor(itemColor) || normalizeTagColor(defaultColor), id2 = item.attribute("id")?.match(/^usertag_(\d+)$/)?.[1] ?? "";
id2 && output.push({
name,
backgroundColor,
color: readableTagColor(backgroundColor),
id: id2,
tagSet: activeTagSet
});
}
return {
appearances: output,
enabled: source.enabled.one()?.checked() ?? !0,
options
};
}
function normalizeTagName(value) {
return value.trim().replace(/\s+/g, " ").toLowerCase();
}
function normalizeTagColor(value) {
let color = value.trim();
return /^#[\da-f]{6}$/i.test(color) ? color : "";
}
function readableTagColor(backgroundColor) {
let red = Number.parseInt(backgroundColor.slice(1, 3), 16) / 255, green = Number.parseInt(backgroundColor.slice(3, 5), 16) / 255, blue = Number.parseInt(backgroundColor.slice(5, 7), 16) / 255, linear = (channel) => channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
return 0.2126 * linear(red) + 0.7152 * linear(green) + 0.0722 * linear(blue) > 0.179 ? "#000000" : "#ffffff";
}
function mutateGalleryMyTags(appearances) {
let byName = new Map(appearances.map((appearance) => [appearance.name, appearance])), source = DomNode.from(document).use(domClass.gallery), apply = () => {
for (let tag2 of source.tags.links.requery()) {
let name = galleryTagNameFromUrl(tag2.attribute("href") ?? ""), appearance = name ? byName.get(normalizeTagName(name)) : void 0;
if (!appearance?.backgroundColor)
continue;
let container = tag2.closest(domClass.gallery.tagContainer);
container && (container.inplace(domClass.gallery.tagContainer.apply).styles({
"--ehpeek-my-tag-background": appearance.backgroundColor,
"--ehpeek-my-tag-color": appearance.color
}).apply("myTag"), tag2.inplace().setAttributes({
"data-ehpeek-my-tag-id": appearance.id,
"data-ehpeek-my-tag-set": appearance.tagSet
}));
}
};
return apply(), source.tags.inplace()?.observe(apply) ?? (() => {
});
}
function manageGalleryContinueReadingButtonMount() {
let managedHost = createManagedElement("div").replaceClasses("box-border w-full px-sm pt-sm pb-sm"), viewerOptions = DomNode.from(document).use(domClass.gallery).actions.inplace();
return viewerOptions ? (viewerOptions.apply("expand").append(managedHost), managedHost) : (documentBody().append(managedHost), managedHost);
}
function manageGalleryPreview(root = document, baseUrl = window.location.href) {
let source = DomNode.from(root).use(domClass.gallery.preview), currentUrl = new URL(baseUrl, window.location.href).href, currentIndex = previewPageIndex(currentUrl), pageDescriptionSource = source.description.one(), rangeText = pageDescriptionSource?.text() ?? "", rangeValues = rangeText.match(/([\d,]+)\s*-\s*([\d,]+)\D+([\d,]+)/)?.slice(1).map((value) => Number(value.replace(/,/g, ""))) ?? [], startImage = rangeValues[0], endImage = rangeValues[1], totalImages = rangeValues[2];
if (startImage === void 0 || endImage === void 0 || totalImages === void 0 || !Number.isSafeInteger(startImage) || !Number.isSafeInteger(endImage) || !Number.isSafeInteger(totalImages) || startImage <= 0 || endImage <= 0 || totalImages <= 0 || endImage < startImage || totalImages < endImage)
throw new Error("Cannot read the gallery preview image range.");
let currentPageSize = endImage - startImage + 1, inferredFullPageSize = endImage === totalImages && currentIndex > 0 ? (totalImages - currentPageSize) / currentIndex : currentPageSize;
if (!Number.isInteger(inferredFullPageSize) || inferredFullPageSize <= 0)
throw new Error("Cannot determine the gallery preview page size.");
let pageSize = inferredFullPageSize, maxIndex = Math.max(currentIndex, Math.ceil(totalImages / pageSize) - 1), seen = /* @__PURE__ */ new Set(), previewItems = source.imageLinks.all().flatMap((link) => {
let url = normalizeUrl(link.attribute("href") || "", currentUrl), imagePage = extractPageType(url);
if (imagePage.type !== "image" || seen.has(url))
return [];
seen.add(url);
let image2 = link.one(domClass.common.image), size = image2?.imageSize(), backgroundStyle = (link.one("[style*='url(']") ?? link.closest("[style*='url(']"))?.attribute("style") ?? "", backgroundUrl = cssBackgroundUrl(backgroundStyle), imageSrc = image2?.attribute("src") || "", lazyImageSrc = image2?.attribute("data-src") || "", imageSource = imageSrc && !/blank\.gif(?:$|\?)/i.test(imageSrc) ? imageSrc : lazyImageSrc || imageSrc, thumbnail = backgroundUrl ? backgroundThumbnail(backgroundStyle, backgroundUrl, currentUrl, size) : imageThumbnail(imageSource, currentUrl, size);
return [{
aspectRatio: thumbnail.height / thumbnail.width,
pageNum: imagePage.pageNum,
pageUrl: url,
thumbnail
}];
}).sort((left, right) => left.pageNum - right.pageNum), pages = previewItems.map((item) => ({
aspectRatio: item.aspectRatio,
pageNum: item.pageNum,
url: item.pageUrl
})), data = {
currentIndex,
currentUrl,
descriptionText: rangeText,
endImage,
maxIndex,
pageSize,
pages,
previewItems,
startImage,
totalImages
}, thumbsSource = source.thumbs.one(), pageBarTopSource = source.pageBarTop.one(), pageBarBottomSource = source.pageBarBottom.one(), createPageBarMount = (position) => createManagedElement("div").replaceClasses(
`w-max max-w-full mx-auto overflow-x-auto touch-pan-y [-webkit-overflow-scrolling:touch] [&[data-dragging=true]]:select-none ${position === "top" ? "mt-2px mb-0" : "mt-0 mb-10px"}`
), elems = {
mount: root === document && thumbsSource ? createManagedElement("div").replaceClasses("contents") : null,
originalPageBarBottom: pageBarBottomSource?.inplace() ?? null,
originalPageBarTop: pageBarTopSource?.inplace() ?? null,
originalPageDescription: pageDescriptionSource?.inplace() ?? null,
pageBarBottom: pageBarBottomSource ? createPageBarMount("bottom") : null,
pageBarDescription: pageDescriptionSource && pageBarTopSource ? createManagedElement("div") : null,
pageBarTop: pageBarTopSource ? createPageBarMount("top") : null,
thumbImages: source.thumbs.images.inplaceAll(),
thumbItems: thumbsSource?.children().map(
(item) => root === document ? item.inplace() : item.move()
) ?? [],
thumbs: root === document ? source.thumbs.inplace() : null
};
return elems.mount && elems.thumbs && elems.thumbs.before(elems.mount), { data, elems, handle: {
/** Opens thumbnail image links in EhPeek Reader instead of original navigation. */
interceptPreviewImageOpen(onOpen) {
elems.thumbs?.apply("suppressTapHighlight");
let handleClick = (event) => {
let link = event.target instanceof Element ? DomNode.from(event.target).closest(domClass.common.links) : null, href = link?.attribute("href") ?? "";
!link || extractPageType(href).type !== "image" || !link.one(domClass.common.image) && !link.closest(domClass.gallery.preview.imageLinkHost) || (event.preventDefault(), event.stopPropagation(), onOpen(normalizeUrl(href, currentUrl)));
};
return elems.thumbs?.listen("click", handleClick) ?? (() => {
});
},
/** Makes thumbnail dragging available to the horizontal preview-page gesture. */
ensurePreviewSwipeInput() {
elems.thumbs?.apply("swipe");
for (let image2 of elems.thumbImages)
image2.setAttributes({ draggable: "false" });
},
/** Installs a fetched preview page into the currently visible thumbnail host. */
replacePreviewThumbs(items) {
elems.thumbs?.replaceChildren(...items);
},
/** Marks preview loading while retaining the currently visible thumbnails. */
updatePreviewLoading(loading) {
elems.thumbs?.attribute("aria-busy", String(loading));
},
/** Replaces both original page bars with mounts owned by EhPeek pagination. */
installPreviewPageBars() {
DomNode.from(document).use(domClass.page).body.inplace()?.apply("hidePreviewPageBars"), elems.originalPageBarTop && elems.pageBarTop && elems.originalPageBarTop.after(elems.pageBarTop), elems.originalPageBarBottom && elems.pageBarBottom && elems.originalPageBarBottom.after(elems.pageBarBottom), elems.originalPageDescription && elems.pageBarDescription && elems.pageBarTop && elems.pageBarTop.before(elems.pageBarDescription);
},
/** Brings the requested EhPeek page bar into view after preview navigation. */
scrollPreviewPageBarIntoView(position) {
(position === "top" ? elems.pageBarTop : elems.pageBarBottom)?.scrollIntoView({
behavior: "smooth",
block: position === "top" ? "start" : "end"
});
}
} };
}
function cssBackgroundUrl(style2) {
return style2.match(/url\(\s*(['"]?)(.*?)\1\s*\)/i)?.[2] ?? "";
}
function backgroundThumbnail(style2, url, baseUrl, fallbackSize) {
let declaration = document.createElement("div").style;
return declaration.cssText = style2, {
backgroundPosition: declaration.backgroundPosition || "0 0",
backgroundRepeat: declaration.backgroundRepeat || "no-repeat",
backgroundSize: declaration.backgroundSize || "auto",
height: cssPixelSize(declaration.height) ?? validThumbnailSize(fallbackSize?.height),
kind: "background",
url: normalizeUrl(url, baseUrl),
width: cssPixelSize(declaration.width) ?? validThumbnailSize(fallbackSize?.width)
};
}
function imageThumbnail(url, baseUrl, size) {
return {
backgroundPosition: "0 0",
backgroundRepeat: "no-repeat",
backgroundSize: "auto",
height: validThumbnailSize(size?.height),
kind: "image",
url: url ? normalizeUrl(url, baseUrl) : "",
width: validThumbnailSize(size?.width)
};
}
function cssPixelSize(value) {
if (!value.endsWith("px"))
return null;
let parsed = Number.parseFloat(value);
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
}
function validThumbnailSize(value) {
return value && Number.isFinite(value) && value > 0 ? value : 100;
}
async function loadGalleryPreviewPage(previewIndex, pageUrl) {
let url = previewUrlForIndex(previewIndex, pageUrl), response = await requestPage(url);
return manageGalleryPreview(response.document, response.url);
}
function extractImageGalleryPage(root = document) {
for (let link of DomNode.from(root).use(domClass.common).links.all()) {
let page2 = extractPageType(normalizeUrl(link.attribute("href") || ""));
if (page2.type === "gallery")
return page2;
}
return null;
}
async function loadEhImagePage(page2) {
let response = await requestPage(page2.url), imagePage = DomNode.from(response.document).use(domClass.gallery.imagePage), image2 = imagePage.image.one(), imageUrl = normalizeUrl(
image2?.attribute("src") || image2?.attribute("data-src") || "",
page2.url
);
if (!imageUrl)
throw new Error(texts_default.errors.imageNotFound);
let numberAttribute = (name) => {
let value = Number(image2?.attribute(name));
return Number.isFinite(value) && value > 0 ? value : null;
};
return {
height: numberAttribute("height"),
imageUrl,
originalImageUrl: imagePage.links.all().map((link) => normalizeUrl(link.attribute("href") || "", page2.url)).find(isFullImageUrl) ?? null,
width: numberAttribute("width")
};
}
// src/eh/dom/galleryInfo.ts
function extractGalleryHistoryInfo() {
let page2 = DomNode.from(document), source = page2.use(domClass.gallery.info), category = source.category.one(), categoryClass = (category?.one(domClass.gallery.info.category.appearance)?.attribute("class") ?? category?.attribute("class") ?? "").split(/\s+/).find((className2) => /^ct[1-9a]$/.test(className2)), rows = source.details.rows.all().map((detailRow) => detailRow.all(domClass.gallery.info.details.rows.cells).slice(1).map((detailCell) => detailCell.text()).filter(Boolean).join(" ")), ratingMatch = (page2.all(domClass.common.scripts).map((pageScript) => pageScript.text()).find((script2) => script2.includes("display_rating")) ?? "").match(/\bdisplay_rating\s*=\s*(-?\d+(?:\.\d+)?)/), rating = Number(ratingMatch?.[1]), cover = source.cover.one(), coverUrl = source.cover.image.one()?.attribute("src") ?? "";
if (!coverUrl && cover)
for (let node of [cover, ...cover.all(domClass.common.descendants)]) {
let match = node.computedStyle().backgroundImage.match(/url\(["']?(.+?)["']?\)/);
if (match?.[1]) {
coverUrl = match[1];
break;
}
}
return {
category: category?.text() || void 0,
categoryClass,
coverUrl: coverUrl || void 0,
language: rows[3] || void 0,
posted: rows[0] || void 0,
rating: ratingMatch && Number.isFinite(rating) ? rating : void 0,
title: source.titleMain.one()?.text() || void 0,
titleSub: source.titleSub.one()?.text() || void 0,
uploader: source.uploader.one()?.text() || void 0
};
}
function manageGalleryInfo(preview) {
let mount = createAnchor("gallery-info");
if (!mount)
return null;
let page2 = DomNode.from(document), gallery2 = page2.use(domClass.gallery), source = gallery2.info, original = source.original.one(), host = original?.parent() ?? source.hostFallback.one()?.parent() ?? null;
if (!original || !host)
return null;
let readMeta = () => {
let rows = source.details.rows.all().map((detailRow) => {
let cells = detailRow.all(domClass.gallery.info.details.rows.cells);
return {
label: cells[0]?.text() ?? "",
value: cells.slice(1).map((cell2) => cell2.text()).filter(Boolean).join(" ")
};
});
return {
favorited: rows[6]?.value ? [rows[6].label, rows[6].value].filter(Boolean).join(" ") : void 0,
fileSize: rows[4]?.value,
language: rows[3]?.value,
parent: rows[1]?.value,
posted: rows[0]?.value
};
}, readCategory = (node) => {
let style2 = node?.computedStyle();
return {
"background-color": style2?.backgroundColor ?? "",
"background-image": style2?.backgroundImage ?? "",
"border-color": style2?.borderColor ?? "",
color: style2?.color ?? ""
};
}, readCoverUrl = (cover2, source2) => {
let direct = source2?.attribute("src") ?? "";
if (direct)
return direct;
for (let node of cover2 ? [cover2, ...cover2.all(domClass.common.descendants)] : []) {
let match = node.computedStyle().backgroundImage.match(/url\(["']?(.+?)["']?\)/);
if (match?.[1])
return match[1];
}
return "";
}, readFavorite = (element, scripts2) => {
let displayed = element?.one(domClass.gallery.info.favorite.link)?.text() || element?.one(domClass.gallery.info.favorite.titled)?.attribute("title")?.trim() || "", slot = displayed.match(/(?:^|\D)([0-9])(?:\D|$)/)?.[1], favorited = slot !== void 0 || /^favorited$/i.test(displayed), match = (scripts2.find(
(item) => item.includes("popbase") && item.includes("addfav")
) ?? "").match(
/popbase\s*=\s*base_url\s*\+\s*"gallerypopups\.php\?gid=(\d+)&t=([^"]+)&act="/
);
return {
actionUrl: match ? `/gallerypopups.php?gid=${match[1]}&t=${match[2]}&act=addfav` : "",
color: slot === void 0 ? null : `var(--color-site-favorite-${slot})`,
favorited,
label: favorited ? displayed : "Not Favorited"
};
}, readRating = (count, image2, labelNode, scripts2) => {
let label = labelNode?.text() ?? "", match = (scripts2.find((item) => item.includes("display_rating")) ?? "").match(/\bdisplay_rating\s*=\s*(-?\d+(?:\.\d+)?)/), scriptValue = Number(match?.[1]), value = match && Number.isFinite(scriptValue) ? scriptValue : null;
return label && value !== null ? {
count: count?.text() ?? "",
label,
rated: image2?.matches(domClass.gallery.info.rating.rated) ?? !1,
value
} : null;
}, readActions = () => gallery2.actions.items.all().filter((node) => {
let href = node.attribute("href")?.trim() ?? "";
return node.hasAttribute("onclick") || !!(href && href !== "#" && !/^javascript:/i.test(href));
}).slice(0, 6), readTag = (tag2) => {
let label = tag2.text() || tag2.attribute("ehs-tag")?.trim() || tag2.attribute("title")?.trim() || "", href = tag2.attribute("href") ?? "", name = galleryTagNameFromUrl(href);
if (!label || !name || !href)
return null;
let container = tag2.closest(domClass.gallery.tagContainer) ?? tag2, tagStyle = tag2.computedStyle(), containerStyle = container.computedStyle(), myTagId = tag2.attribute("data-ehpeek-my-tag-id"), myTagSet = tag2.attribute("data-ehpeek-my-tag-set");
return {
data: {
appearance: {
backgroundColor: containerStyle.backgroundColor,
borderColor: containerStyle.borderColor,
color: tagStyle.color
},
label,
myTag: myTagId && myTagSet ? { id: myTagId, tagSet: myTagSet } : null,
name,
url: href
},
source: tag2
};
}, readTagGroups = () => {
let rows = gallery2.tags.rows.all();
return rows.length > 0 ? rows.map((row2) => ({
namespace: row2.one(domClass.gallery.tags.rows.namespace)?.text().replace(/:$/, "") || "tag",
tags: row2.all(domClass.gallery.tags.rows.links).map(readTag).filter((tag2) => tag2 !== null).slice(0, 30)
})).filter((group) => group.tags.length > 0) : [{
namespace: "tag",
tags: gallery2.tags.links.all().map(readTag).filter((tag2) => tag2 !== null).slice(0, 60)
}].filter((group) => group.tags.length > 0);
}, favoriteColor = (value) => {
let slot = value.match(/^(?:fav)?([0-9])$/i)?.[1] ?? value.match(/^favorites?\s+([0-9])$/i)?.[1];
return slot === void 0 ? null : `var(--color-site-favorite-${slot})`;
}, readFavoriteOptions = (doc, favorited) => DomNode.from(doc).use(domClass.myTags).favoriteOptions.all().map((favoriteInput) => {
let row2 = favoriteInput.closest(domClass.myTags.favoriteOptionRow), value = favoriteInput.inputValue();
return {
color: favoriteColor(value),
label: row2?.text().replace(/\s+/g, " ") || value,
selected: favorited && favoriteInput.checked(),
value
};
}), manageTagGroups = () => readTagGroups().map((group) => ({
namespace: group.namespace,
tags: group.tags.map(({ data: tag2, source: source2 }) => ({
...tag2,
contentSource: source2.inplace()
}))
})), meta = readMeta(), category = source.category.one(), categoryStyle = source.category.appearance.one() ?? category, cover = source.cover.one(), coverSource = source.cover.image.one(), favorite = source.favorite.one(), ratingCount = source.rating.count.one(), ratingImage = source.rating.image.one(), ratingLabel = source.rating.label.one(), ratingActions = source.rating.actions.all(), newTagButton = source.newTag.button.one(), newTagField = source.newTag.field.one(), newTagForm = source.newTag.form.one(), scripts = page2.all(domClass.common.scripts).map((pageScript) => pageScript.text()), actionSources = readActions(), tagContentSources = [], tagGroups = readTagGroups().map((group) => ({
namespace: group.namespace,
tags: group.tags.map(({ data: tag2, source: source2 }) => {
let contentSourceIndex = tagContentSources.push(source2) - 1;
return { ...tag2, contentSourceIndex };
})
})), data = {
category: category?.text() ?? "",
categoryAppearance: readCategory(categoryStyle),
favorite: readFavorite(favorite, scripts),
rating: readRating(ratingCount, ratingImage, ratingLabel, scripts),
summary: [
meta.language,
preview?.totalImages ? `${preview.totalImages} ${texts_default.reader.pages.toLowerCase()}` : void 0,
meta.fileSize,
meta.favorited,
meta.posted ?? meta.parent
].filter((value) => !!value).slice(0, 6).map((value) => ({ value })),
tagGroups,
titleMain: source.titleMain.one()?.text() ?? "",
titleSub: source.titleSub.one()?.text() ?? ""
}, coverUrl = readCoverUrl(cover, coverSource), managedCover = coverUrl ? source.cover.image.clone()?.replaceClasses("").apply("fit") ?? createManagedElement("img", { fit: "ehpeek-fit-gallery-cover" }).apply("fit") : null;
managedCover?.removeAttributes("id", "style", "width", "height").setAttributes({ alt: "", decoding: "async", loading: "eager", src: coverUrl });
let managedNewTag = newTagButton && newTagField && newTagForm ? source.newTag.move()?.apply("layout") ?? null : null, hostApply = { hide: "ehpeek-hide-original-gallery-info" };
managedNewTag?.setHidden(!1).removeStyles("display");
let elems = {
actionItems: actionSources.map((item) => item.move(domClass.gallery.actions.items.apply).apply("layout")),
cover: managedCover,
host: host.inplace(hostApply).apply("hide"),
mount,
newTag: managedNewTag,
ratingActions: ratingActions.map((action) => action.inplace()),
tagContents: tagContentSources.map((source2) => source2.inplace()),
tagList: gallery2.tags.inplace(),
tagMenuAction: source.tagMenu.inplace()
}, selectedTagSource = null, activateTagMenu = (source2) => {
let stopNavigation = source2.listen("click", (event) => {
event.preventDefault();
}, { once: !0 });
source2.click(), stopNavigation();
};
return { data, elems, handle: {
/** Normalizes the original cover for GalleryInfoPanel's responsive layout. */
/** Hides original GalleryInfo children and installs the component mount. */
installGalleryInfoPanel() {
elems.host.prepend(elems.mount);
},
/** Loads the original favorite dialog choices for EhPeek's favorite modal. */
async loadGalleryFavoriteOptions(actionUrl, favorited) {
let response = await requestPage(actionUrl);
return readFavoriteOptions(response.document, favorited);
},
/** Submits a tag to the chosen My Tags collection and validates the response. */
async submitFavoriteTag(tag2, tagSet, mode) {
let response = await addMyTag(tag2.name, tagSet, mode);
return extractMyTagsPageData(response.document, tagSet);
},
/** Keeps component tag groups synchronized with original-page tag updates. */
observeGalleryTagGroups(onChange) {
return elems.tagList?.observe(() => {
gallery2.tags.rows.requery(), gallery2.tags.links.requery(), onChange(manageTagGroups());
}) ?? (() => {
});
},
/** Activates E-H's original rating area and lets its page script submit the vote. */
submitGalleryRating(value) {
let rating = Math.round(value * 2);
if (rating < 1 || rating > 10)
throw new RangeError("Gallery rating must be between 0.5 and 5 stars.");
let action = elems.ratingActions[rating - 1];
if (!action)
throw new Error("Gallery rating action is unavailable.");
action.click();
},
/** Removes the selected tag from its stored My Tags collection. */
async removeFavoriteTag(tag2) {
if (!tag2.myTag)
throw new Error("The tag is not in My Tags.");
let response = await deleteMyTag(tag2.myTag.id, tag2.myTag.tagSet);
return extractMyTagsPageData(response.document, tag2.myTag.tagSet);
},
/** Opens E-H's original tag actions and only adapts their presentation. */
openGalleryTagMenu(tag2) {
if (!elems.tagMenuAction)
throw new Error("Gallery tag actions are unavailable.");
activateTagMenu(tag2.contentSource), elems.newTag?.setHidden(!1).removeStyles("display");
let actions = elems.tagMenuAction.all(domClass.gallery.info.tagMenu.actions);
if (actions.length === 0)
throw activateTagMenu(tag2.contentSource), elems.newTag?.setHidden(!1).removeStyles("display"), new Error("Gallery tag actions could not be opened.");
selectedTagSource = tag2.contentSource, elems.tagMenuAction.apply("layout"), actions.find((action) => action.readAttribute("onclick")?.includes("toggle_tagmenu"))?.remove();
},
/** Closes E-H's selected tag without replacing its action DOM. */
closeGalleryTagMenu() {
selectedTagSource && activateTagMenu(selectedTagSource), elems.newTag?.setHidden(!1).removeStyles("display"), selectedTagSource = null;
},
/** Updates the Gallery favorite state through the original site endpoint. */
updateGalleryFavorite
} };
}
function mutateGalleryTouchLayout() {
let page2 = DomNode.from(document).use(domClass.page), html = page2.html.inplace(), body = page2.body.inplace();
if (!html || !body)
throw new Error("Gallery page layout is unavailable.");
html.apply("galleryTouchLayout"), body.apply("galleryTouchLayout");
}
function mutateGalleryWideLayout(info, preview, initiallyEnabled) {
let page2 = DomNode.from(document).use(domClass.page), source = DomNode.from(document).use(domClass.gallery), html = page2.html.inplace(), body = page2.body.inplace(), footer = page2.footer.inplace(), comments = source.comments.inplace(), commentsAnchor = source.commentsAnchor.inplace(), pageBarTopHost = source.preview.pageBarTop.one()?.parent()?.inplace() ?? null, pageBarBottomHost = source.preview.pageBarBottom.one()?.parent()?.inplace() ?? null, previewMount = preview.elems.mount, thumbs = preview.elems.thumbs;
if (!html || !body || !comments || !previewMount || !thumbs)
return null;
let leftNodes = [info.elems.host, commentsAnchor, comments].filter((node) => node !== null), rightNodes = [pageBarTopHost, previewMount, thumbs, pageBarBottomHost].filter((node) => node !== null), layout = null, positions = [], enabled = initiallyEnabled, update = () => {
if (enabled && !layout) {
if (layout = createAnchor("gallery-wide-layout")?.replaceClasses("ehpeek-touch-gallery-layout") ?? null, !layout)
return;
window.scrollTo(0, 0), html.apply("galleryWideLayout"), body.apply("galleryWideLayout");
let left = createManagedElement("div").replaceClasses("ehpeek-touch-gallery-layout-left"), right = createManagedElement("div").replaceClasses("ehpeek-touch-gallery-layout-right");
positions = [...leftNodes, ...rightNodes, footer].filter((node) => node !== null).map((node) => {
let marker = createManagedElement("span").setHidden(!0);
return node.before(marker), { marker, node };
}), info.elems.host.before(layout), layout.append(left, right, ...footer ? [footer] : []), left.append(...leftNodes), right.append(...rightNodes);
return;
}
if (!enabled && layout) {
for (let { marker, node } of positions)
marker.after(node), marker.remove();
positions = [], layout.remove(), layout = null, html.removeClasses("ehpeek-gallery-wide-layout-root"), body.removeClasses("ehpeek-gallery-wide-layout-root");
}
};
return update(), {
updateEnabled(value) {
enabled = value, update();
}
};
}
function mutateGalleryCommentsTouch() {
let source = DomNode.from(document).use(domClass.gallery.comments);
source.inplace()?.apply("touchScore");
let items = source.score.all().filter((trigger) => trigger.attribute("data-ehpeek-touch-comment-score") !== "true").map((trigger) => ({
trigger,
details: trigger.closest(domClass.gallery.comments.scoreComment)?.one(domClass.gallery.comments.scoreComment.details) ?? null
})).filter((item) => item.details !== null).map(({ trigger, details }) => ({
details: details.inplace(),
detailsId: details.attribute("id") ?? "",
expanded: !1,
trigger: trigger.inplace()
})), setExpanded = (item, expanded) => {
item.expanded = expanded, item.trigger.attribute("aria-expanded", String(expanded)), item.details.attribute("aria-hidden", String(!expanded));
};
for (let item of items) {
item.trigger.removeAttributes("onmouseover", "onmouseout", "onclick").setAttributes({
"data-ehpeek-touch-comment-score": "true",
role: "button",
tabindex: "0",
"aria-controls": item.detailsId
}), setExpanded(item, !1);
let toggle = (event) => {
event.preventDefault(), event.stopImmediatePropagation();
let shouldExpand = !item.expanded;
for (let candidate of items)
setExpanded(candidate, candidate === item && shouldExpand);
};
item.trigger.listen("click", toggle), item.trigger.listen("keydown", (event) => {
(event.key === "Enter" || event.key === " ") && toggle(event);
});
}
}
// src/eh/dom/search.ts
function createReadHistoryGridRow(item, titlePreference) {
let info = item.info, metadataItems = [], appendMetadata = (value) => {
if (!value)
return;
let element = createManagedElement("div");
element.setTextUnlessInput(value), metadataItems.push(element);
};
if (info?.category && info.categoryClass) {
let category = createManagedElement("div").replaceClasses(`cn ${info.categoryClass}`);
category.setTextUnlessInput(info.category), metadataItems.push(category);
}
if (appendMetadata(info?.posted), info?.rating !== void 0) {
let rounded = Math.round(info.rating * 2) / 2, rating = createManagedElement("div").replaceClasses("ir").styles({
"background-position": `${-16 * (5 - Math.ceil(rounded))}px ${Number.isInteger(rounded) ? -1 : -21}px`,
opacity: "1"
});
metadataItems.push(rating);
}
appendMetadata(info?.uploader);
let progress = item.currentPage > 0 ? item.totalPages ? `${item.currentPage}/${item.totalPages}` : String(item.currentPage) : texts_default.history.unread, updatedAt = new Date(item.updatedAt), pad = (value) => String(value).padStart(2, "0"), historyStatus = createManagedElement("div").replaceClasses("textsize-sm font-600 leading-[1.3]");
historyStatus.setTextUnlessInput(
`${progress} · ${updatedAt.getFullYear()}-${pad(updatedAt.getMonth() + 1)}-${pad(updatedAt.getDate())} ${pad(updatedAt.getHours())}:${pad(updatedAt.getMinutes())}`
);
let removeButton = createManagedElement("button").setAttributes({ type: "button", "data-ehpeek-remove-history": "true" }).replaceClasses(
"relative z-2 min-h-lg py-xs px-md rounded-md border border-[var(--color-site-border-subtle)] bg-[var(--color-site-surface)] ehp-color-site-text font-inherit textsize-md font-700 text-center cursor-pointer [touch-action:manipulation] hover:bg-[var(--color-site-item-hover)]"
);
removeButton.setTextUnlessInput(texts_default.button.removeHistory);
let historyActions = createManagedElement("div").replaceClasses("ehpeek-read-history-actions flex flex-col items-start gap-xs").append(historyStatus, removeButton), titleText = titlePreference === "sub" ? info?.titleSub || info?.title : info?.title || info?.titleSub, galleryHref = new URL(
`/g/${item.galleryId}/${item.token}/`,
window.location.href
).href, row2 = createManagedElement("tr"), thumbnailCell = createManagedElement("td").replaceClasses("gl1e"), thumbnail = createManagedElement("div"), image2 = info?.coverUrl ? createManagedElement("img").setAttributes({
alt: titleText ?? "",
loading: "lazy",
src: info.coverUrl
}) : null;
image2 ? thumbnail.append(
createManagedElement("a").attribute("href", galleryHref).append(image2)
) : thumbnailCell.setHidden(!0), thumbnailCell.append(thumbnail);
let contentCell = createManagedElement("td").replaceClasses("gl2e"), galleryLink = createManagedElement("a").attribute("href", galleryHref), detail = createManagedElement("div").replaceClasses("gl4e"), title = createManagedElement("div").replaceClasses("glink");
title.setTextUnlessInput(titleText ?? ""), title.setHidden(!titleText);
let metadata = createManagedElement("div").replaceClasses("gl3e");
return metadata.append(...metadataItems), galleryLink.append(detail.append(title, historyStatus)), contentCell.append(createManagedElement("div").append(metadata, galleryLink)), row2.append(thumbnailCell, contentCell), {
detail,
galleryHref,
galleryLink,
metadata,
row: row2,
tags: [historyActions],
title,
titleText: titleText ?? "",
withoutCover: !image2
};
}
function manageReadHistoryPage(items, titlePreference) {
let page2 = DomNode.from(document), resultList = page2.use(domClass.search).results.one(), navigationTopMount = createAnchor("read-history-navigation-top"), navigationBottomMount = createAnchor("read-history-navigation-bottom");
if (!resultList || !navigationTopMount || !navigationBottomMount)
return null;
let grids = manageReadHistoryGrids({
items,
source: resultList,
titlePreference
});
grids.elems.resultList.before(navigationTopMount), grids.elems.resultList.after(navigationBottomMount);
for (let control2 of page2.all(domClass.search.controls, anyDomNode))
control2.inplace().remove();
let handle = {
/** Replaces the visible History rows without navigating away from the current document. */
updateReadHistoryItems: grids.handle.updateItems,
/** Reports explicit and long-press removal requests without exposing History rows. */
listenForReadHistoryRemoval: grids.handle.listenForItemRemoval,
/** Keeps navigation anchored to the corresponding edge after an in-page page change. */
scrollReadHistoryPage(position) {
(position === "bottom" ? navigationBottomMount : navigationTopMount).scrollIntoView({
behavior: "smooth",
block: position === "bottom" ? "end" : "start"
});
},
/** Switches the History result list between one and two result columns. */
updateResultColumns(enabled) {
enabled ? grids.elems.resultList.addClasses(sharedApply.searchResultColumns) : grids.elems.resultList.removeClasses(sharedApply.searchResultColumns);
}
};
return {
elems: {
navigationBottomMount,
navigationTopMount,
resultList: grids.elems.resultList
},
handle
};
}
function manageSearchResults() {
let source = DomNode.from(document).use(domClass.search), resultSource = source.results.one();
if (!resultSource)
return null;
let data = {
nextUrl: source.navigation.next.one()?.attribute("href") ?? null,
previousUrl: source.navigation.previous.one()?.attribute("href") ?? null
}, elems = {
resultList: resultSource.inplace(domClass.search.results.apply),
searchInput: source.input.inplace()
};
return { data, elems, handle: {
/** Routes the original pagination controls through the active page owner. */
interceptSearchNavigation(onNavigate) {
let handleClick = (event) => {
let url = (event.target instanceof Element ? DomNode.from(event.target).closest(domClass.search.navigationLink) : null)?.attribute("href") ?? null;
url && (event.preventDefault(), event.stopPropagation(), onNavigate(url));
};
return document.addEventListener("click", handleClick, !0), () => document.removeEventListener("click", handleClick, !0);
},
/** Replaces the current result page for enhanced swipe navigation. */
async loadSearchPage(url) {
let response = await requestPage(url);
if (!replaceSearchPageContent(response.document))
throw new Error(texts_default.errors.searchPageContentNotFound);
window.history.pushState(window.history.state, "", url);
},
/** Returns enhanced Search navigation to its input, or the page top when absent. */
scrollSearchPageToInput() {
elems.searchInput ? elems.searchInput.scrollIntoView({ block: "start", behavior: "auto" }) : window.scrollTo({ top: 0, behavior: "auto" });
},
/** Exposes result loading state without removing the current result list. */
updateSearchLoading(busy) {
busy ? elems.resultList.setAttributes({ "aria-busy": "true" }) : elems.resultList.removeAttributes("aria-busy");
},
/** Switches the EhPeek result list between one and two result columns. */
updateResultColumns(enabled) {
enabled ? elems.resultList.apply("columns") : elems.resultList.removeClasses(sharedApply.searchResultColumns);
},
/** Prevents result content from stealing a horizontal swipe gesture. */
ensureSearchSwipeInput() {
elems.resultList.apply("swipe");
},
/** Applies the user setting to gallery links already owned by the result list. */
ensureGalleryLinksOpenInNewTab() {
for (let link of elems.resultList.all(domClass.search.results.links))
extractPageType(link.readAttribute("href") ?? "").type === "gallery" && link.setAttributes({ target: "_blank", rel: "noopener noreferrer" });
}
} };
}
function manageSearchTextInput() {
let inputSource = DomNode.from(document).use(domClass.search).input.one(), formSource = inputSource?.form() ?? null, submitSource = formSource?.one(domClass.search.submit) ?? inputSource?.parent()?.one(domClass.search.submitFallback) ?? null;
if (!inputSource || !submitSource)
return null;
let elems = {
form: formSource?.inplace() ?? null,
input: inputSource.inplace(),
submit: submitSource.inplace()
};
return { data: { value: elems.input.inputValue() }, elems, handle: {
/** Connects the original input to EhPeek's history and suggestion overlay. */
listenSearchHistoryOverlay(callbacks, overlay) {
let update = () => callbacks.onInput(
elems.input.inputValue(),
document.activeElement === elems.input.Component()
), submitValue = () => callbacks.onSubmit(elems.input.inputValue()), outsidePointer = (event) => {
let target = event.target;
target instanceof Node && (elems.input.isNode(target) || overlay()?.contains(target)) || callbacks.onOutsidePointer();
}, disconnect = [
elems.input.listen("input", update),
elems.input.listen("focus", callbacks.onFocus),
elems.input.listen("pointerdown", callbacks.onFocus),
elems.input.listen("keydown", callbacks.onKeyDown),
elems.submit.listen("click", submitValue),
...elems.form ? [elems.form.listen("submit", submitValue)] : []
];
return document.addEventListener("pointerdown", outsidePointer, !0), document.addEventListener("scroll", callbacks.onPositionChange, !0), window.addEventListener("resize", callbacks.onPositionChange), () => {
disconnect.forEach((cleanup) => cleanup()), document.removeEventListener("pointerdown", outsidePointer, !0), document.removeEventListener("scroll", callbacks.onPositionChange, !0), window.removeEventListener("resize", callbacks.onPositionChange);
};
},
/** Locates the overlay directly below the original search input. */
readSearchOverlayPosition() {
let rect = elems.input.rect();
return { left: rect.left, top: rect.bottom, width: rect.width };
},
/** Commits a history or suggestion choice through the original input events. */
applySearchSelection(value) {
elems.input.setInputValue(value), elems.input.dispatchInput(), elems.input.focus(), elems.input.Component().setSelectionRange(value.length, value.length);
}
} };
}
function manageReadHistoryGrids(options) {
let resultList = createManagedElement("table").replaceClasses("itg"), body = createManagedElement("tbody"), visibleRows = [];
resultList.append(body).addClasses(
"overscroll-x-contain",
"touch-pan-y",
"[&[data-dragging=true]]:select-none"
), options.source.inplace().replaceWith(resultList);
let updateItems = (items) => {
visibleRows = items.map((item) => ({
item,
row: createReadHistoryGridRow(item, options.titlePreference)
})), body.replaceChildren(...visibleRows.map(({ row: row2 }) => row2.row)), manageEhPeekGrid(resultList, visibleRows.map(({ row: row2 }) => row2));
};
return updateItems(options.items), {
elems: { resultList },
handle: {
updateItems,
listenForItemRemoval(callback) {
let itemForTarget = (target) => target instanceof Node ? visibleRows.find(({ row: row2 }) => row2.row.contains(target))?.item ?? null : null, stopLongPress = resultList.listenLongPress((event) => {
let item = itemForTarget(event.target);
item && callback(item);
}, (event) => itemForTarget(event.target) !== null), stopButton = resultList.listen("click", (event) => {
let item = (event.target instanceof Element ? DomNode.from(event.target).closest(domClass.search.removeHistory) : null) ? itemForTarget(event.target) : null;
item && (event.preventDefault(), event.stopPropagation(), callback(item));
});
return () => {
stopLongPress(), stopButton();
};
}
}
};
}
function manageSearchGrids() {
let source = DomNode.from(document).use(domClass.search), resultList = source.results.one();
if (!resultList)
return;
let rows = source.results.rows.all().map(manageSearchGridRow).filter((row2) => row2 !== null);
manageEhPeekGrid(
resultList.inplace(),
rows
);
function manageSearchGridRow(row2) {
let thumbnailCell = row2.one(domClass.search.results.rows.cover), contentCell = row2.one(domClass.search.results.rows.content), detail = contentCell?.one(domClass.search.results.rows.content.detail), metadata = contentCell?.one(domClass.search.results.rows.content.metadata);
if (!thumbnailCell || !contentCell || !detail || !metadata)
return null;
let title = detail.one(domClass.search.results.rows.content.detail.title), parent = detail.parent(), galleryLink = parent?.matches(domClass.common.links) ? parent : null, tags = detail.children().filter((element) => !title?.sameNode(element));
return {
detail: detail.inplace(),
galleryHref: galleryLink?.attribute("href") ?? null,
galleryLink: galleryLink?.inplace() ?? null,
metadata: metadata.inplace(),
row: row2.inplace(),
tags: tags.map((item) => item.inplace()),
title: title?.inplace() ?? null,
titleText: title?.text() ?? "",
withoutCover: !1
};
}
}
function manageEhPeekGrid(resultList, rows) {
resultList.addClasses(sharedApply.searchGrid);
for (let row2 of rows)
row2.row.addClasses(...row2.withoutCover ? [sharedApply.coverlessSearchGrid] : []), manageEhPeekGridContent(row2);
function manageEhPeekGridContent(source) {
let { detail, galleryLink, metadata, row: row2, tags, title } = source;
if (galleryLink && title && source.galleryHref) {
let titleLink = createManagedElement("a").attribute("href", source.galleryHref).replaceClasses("block min-w-0 ehp-color-site-text no-underline");
titleLink.append(title), galleryLink.before(detail), galleryLink.remove(), detail.replaceChildren(titleLink, metadata, ...tags), ensureEhPeekGridRowNavigation(
row2,
titleLink,
source.galleryHref,
source.titleText
);
} else title && title.after(metadata);
for (let tag2 of tags)
tag2.addClasses(sharedApply.stackSearchGridTags);
}
function ensureEhPeekGridRowNavigation(row2, galleryLink, galleryHref, title) {
let overlay = createManagedElement("a", {
cover: "ehpeek-cover-search-grid-row"
}).attribute("href", galleryHref).attribute("aria-label", title || "Open gallery").replaceClasses("hidden coarse:block absolute inset-0 z-1").apply("cover");
row2.append(overlay).listen("click", (event) => {
(event.target instanceof Element ? DomNode.from(event.target) : null)?.closest(domClass.common.interactive) || galleryLink.click();
});
}
}
function mutateSearchReadHistoryAppearance(readPageForGallery) {
let resultList = DomNode.from(document).use(domClass.search).results.one();
if (!resultList)
return;
let items = (resultList.one(domClass.search.results.body) ?? resultList).children();
for (let item of items) {
let galleryLinks = item.all(domClass.search.results.galleryLinks), galleryLink = galleryLinks.find((link) => !!link.text()) ?? galleryLinks[0];
if (!galleryLink)
continue;
let identity = galleryIdentityFromUrl(galleryLink.attribute("href") ?? "");
if (!identity)
continue;
let pageNum = readPageForGallery(identity.galleryId, identity.token);
if (pageNum === null)
continue;
(item.one(domClass.search.results.titles) ?? galleryLink).inplace(domClass.search.results.titles.apply).setAttributes({
"data-ehpeek-history-label": pageNum > 0 ? texts_default.history.readingLabel : texts_default.history.visitedLabel
}).apply("history"), item.inplace().setAttributes({
"data-ehpeek-read-history": pageNum > 0 ? "reading" : "visited"
});
}
}
function mutateSearchGridModeSelect(selected, onEhPeekSelect, onOriginalSelect) {
let selects = DomNode.from(document).use(domClass.search).displayMode.all();
for (let source of selects) {
let select2 = source.inplace(), option2 = source.all(domClass.search.displayMode.options).find((item) => item.inputValue() === "ehpeek")?.inplace() ?? null;
option2 || (option2 = createManagedElement("option").attribute("value", "ehpeek"), option2.setTextUnlessInput("EhPeek"), select2.append(option2)), option2.setSelected(selected), source.attribute("data-ehpeek-grid-mode") !== "true" && (select2.attribute("data-ehpeek-grid-mode", "true"), select2.listen("change", (event) => {
if (select2.inputValue() !== "ehpeek") {
onOriginalSelect();
return;
}
event.preventDefault(), event.stopImmediatePropagation(), onEhPeekSelect();
}, !0));
}
}
function replaceSearchPageContent(doc) {
let currentList = DomNode.from(document).use(domClass.search).results.one(), incomingList = DomNode.from(doc).use(domClass.search).results.one();
if (!currentList || !incomingList || !refreshSearchRangeBar(doc))
return !1;
replaceSearchResultText(doc), replaceSearchNavigationBars(doc);
let current = currentList.inplace(), importedList = incomingList.clone();
return current.replaceWith(importedList), !0;
}
function refreshSearchRangeBar(doc) {
let current = DomNode.from(document).use(domClass.search).rangeBar.one(), incomingPage = DomNode.from(doc), incoming = incomingPage.use(domClass.search).rangeBar.one();
if (!current && !incoming)
return !0;
if (!current || !incoming)
return !1;
let script2 = incomingPage.all(domClass.common.scripts).map((item) => item.text()).find((item) => item.includes("build_rangebar()")), rangeUrl = script2?.match(/\brangeurl\s*=\s*["']([^"']*)["']/)?.[1], rangeMin = Number(script2?.match(/\brangemin\s*=\s*(-?\d+)/)?.[1]), rangeMax = Number(script2?.match(/\brangemax\s*=\s*(-?\d+)/)?.[1]), rangeSpan = Number(script2?.match(/\brangespan\s*=\s*(-?\d+)/)?.[1]);
if (rangeUrl === void 0 || !Number.isFinite(rangeMin) || !Number.isFinite(rangeMax) || !Number.isFinite(rangeSpan))
return !1;
let items = [];
if (rangeSpan > 0)
for (let index = 0; index < 99; index += rangeSpan) {
let marker = createManagedElement("div");
if ((index === 98 && rangeMin === 99 || index >= rangeMin && index <= rangeMax) && marker.attribute("data-inrange", "1"), !rangeUrl) {
items.push(marker);
continue;
}
let href = index === 0 ? rangeUrl : `${rangeUrl}${rangeUrl.includes("?") ? "&" : "?"}range=${index}`;
items.push(createManagedElement("a").attribute("href", href).append(marker));
}
return current.inplace().replaceChildren(...items), !0;
}
function replaceSearchNavigationBars(doc) {
let currentBars = DomNode.from(document).use(domClass.search).navigation.all(), incomingBars = DomNode.from(doc).use(domClass.search).navigation.all(), count = Math.min(currentBars.length, incomingBars.length);
for (let index = 0; index < count; index += 1) {
let currentSource = currentBars[index], incomingSource = incomingBars[index];
if (!currentSource || !incomingSource)
continue;
let current = currentSource.inplace(), incoming = incomingSource.clone();
current.replaceWith(incoming);
}
}
function replaceSearchResultText(doc) {
let current = DomNode.from(document).use(domClass.search).resultText.one(), incoming = DomNode.from(doc).use(domClass.search).resultText.one();
if (!current || !incoming)
return;
let currentElement = current.inplace(), incomingElement = incoming.clone();
currentElement.replaceWith(incomingElement);
}
function favoritesPageTouch() {
let page2 = DomNode.from(document), pageSource = page2.use(domClass.page), source = page2.use(domClass.search);
pageSource.html.inplace()?.apply("constrainResults"), pageSource.body.inplace()?.apply(
"constrainResults",
"constrainFavoritesNavigation"
);
let categories = source.favorites.categories.one(), categorySelect = categories ? manageFavoritesCategories(categories) : null, searchHostApply = { expand: "ehpeek-expand-favorites-search" };
source.favorites.input.one()?.form()?.parent()?.inplace(searchHostApply).apply("expand");
let resultSource = source.results.one();
if (!resultSource)
return categorySelect;
let allSelected = categorySelect?.info.categories[0]?.selected === !0, resultList = resultSource.inplace(domClass.search.results.apply).apply("containFavorites");
return allSelected && resultList.apply("compactFavorites"), categorySelect;
}
function manageFavoritesCategories(container) {
let nodes = container.all(domClass.search.favorites.categories.items);
if (nodes.length === 0)
return null;
let parsed = nodes.map((node) => {
let children = node.children(), countText = children[0]?.text() ?? "0", label = children[children.length - 1]?.text() || node.text(), count = Number(countText.replace(/,/g, "")), indicatorStyle = node.one(domClass.search.favorites.categories.items.indicator)?.computedStyle() ?? null;
return {
appearance: indicatorStyle ? {
backgroundImage: indicatorStyle.backgroundImage,
backgroundPosition: indicatorStyle.backgroundPosition,
backgroundSize: indicatorStyle.backgroundSize
} : null,
count: Number.isFinite(count) ? count : 0,
label,
selected: node.matches(domClass.search.favorites.selectedCategory),
source: node
};
}), all = parsed.find((category) => category.source.childElementCount() === 0), favorites = parsed.filter((category) => category !== all), total = favorites.reduce((sum, category) => sum + category.count, 0);
container.inplace(domClass.search.favorites.categories.apply).apply("hide");
let categories = [
...all ? [{ ...all, count: total, label: texts_default.favorites.all }] : [],
...favorites
];
return {
info: {
categories: categories.map(({ appearance, count, label, selected }) => ({
appearance,
count,
label,
selected
}))
},
items: categories.map(({ source }) => source.inplace())
};
}
function searchResultsPageTouch() {
let page2 = DomNode.from(document), pageSource = page2.use(domClass.page), source = page2.use(domClass.search);
pageSource.html.inplace()?.apply("constrainResults"), pageSource.body.inplace()?.apply("constrainResults");
let resultSource = source.results.one();
resultSource && resultSource.inplace(domClass.search.results.apply).apply("containSearch");
}
function manageTouchResultsPage(page2) {
let apply = () => page2.type === "favorites" ? favoritesPageTouch() : ((page2.type === "search" || page2.type === "readHistory") && searchResultsPageTouch(), null), favoritesCategory = apply(), data = { favoritesCategory: favoritesCategory?.info ?? null }, elems = {
favoriteCategoryItems: favoritesCategory?.items ?? []
};
return { data, elems, handle: {
/** Activates E-H's original Favorites collection control. */
activateFavoriteCategory(index) {
elems.favoriteCategoryItems[index]?.click();
},
/** Reapplies TouchUI layout after the result list is replaced in place. */
updateTouchResultsLayout() {
let updated = apply();
updated && elems.favoriteCategoryItems.splice(
0,
elems.favoriteCategoryItems.length,
...updated.items
);
}
} };
}
// src/eh/dom/searchPanel.ts
function manageSearchPanel() {
let search2 = DomNode.from(document).use(domClass.search), source = search2.panel, searchInput = search2.input.one(), form2 = searchInput?.form() ?? null, standardSearchBox = source.box.one(), categories = source.box.categories.one(), advancedPanel = source.box.advanced.one(), optionLinks = advancedPanel?.previous() ?? null, fileSearch = source.fileSearch.one(), searchSubmit = form2?.one(domClass.search.submit) ?? searchInput?.parent()?.one(domClass.search.submitFallback) ?? null, clearButton = form2?.one(domClass.search.panel.clear) ?? searchInput?.parent()?.one(domClass.search.panel.clearFallback) ?? null;
if (!searchInput || !form2 || !searchSubmit)
return null;
let mount = createAnchor("search-panel"), categoryToggleMount = categories && optionLinks ? createAnchor("search-category-toggle") : null, searchActionMount = createAnchor("search-action"), clearActionMount = clearButton ? createAnchor("search-clear-action") : null;
if (!mount || !searchActionMount || clearButton && !clearActionMount)
return null;
let optionLinkItems = optionLinks?.all(domClass.search.panel.optionLinks) ?? [], advancedToggle = advancedPanel ? optionLinkItems[0] ?? null : null, fileSearchToggle = fileSearch ? optionLinkItems[advancedToggle ? 1 : 0] ?? null : null, advancedToggleMount = advancedToggle ? createAnchor("search-advanced-toggle") : null, fileSearchToggleMount = fileSearchToggle ? createAnchor("search-file-toggle") : null, searchControls = createManagedElement("div", {
overlay: "ehpeek-overlay-search-actions"
}).apply("overlay"), optionLinksApply = { wrap: "ehpeek-wrap-search-options" }, elems = {
advancedPanel: source.box.advanced.inplace()?.apply("expand") ?? null,
advancedToggle: advancedToggle?.inplace() ?? null,
advancedToggleMount,
categories: source.box.categories.inplace()?.apply("layout") ?? null,
categoryToggleMount,
clearActionMount,
clearButton: clearButton?.inplace(domClass.search.panel.clear.apply).apply("hide") ?? null,
fileSearch: source.fileSearch.inplace()?.apply("expand") ?? null,
fileSearchToggle: fileSearchToggle?.inplace() ?? null,
fileSearchToggleMount,
form: form2.inplace(domClass.search.panel.box.form.apply),
mount,
optionLinks: optionLinks?.inplace(optionLinksApply).apply("wrap") ?? null,
searchActionMount,
searchBox: source.box.inplace()?.apply("reset") ?? searchControls,
searchControls,
searchInput: searchInput.inplace(domClass.search.input.apply).apply("expand"),
searchSubmit: searchSubmit.inplace(domClass.search.panel.submit.apply).apply("hide")
};
(standardSearchBox ? elems.searchBox : elems.form).before(elems.mount), standardSearchBox && elems.searchBox.remove(), elems.searchInput.replaceWith(elems.searchControls), elems.searchControls.append(elems.searchInput), elems.searchSubmit.remove(), elems.clearButton && elems.clearActionMount && (elems.clearButton.remove(), elems.searchControls.append(elems.clearActionMount)), elems.searchControls.append(elems.searchActionMount), elems.categories && elems.optionLinks && elems.categoryToggleMount && (elems.optionLinks.after(elems.categories), elems.optionLinks.prepend(elems.categoryToggleMount)), elems.optionLinks && elems.advancedToggle && elems.advancedToggleMount && (elems.advancedToggle.after(elems.advancedToggleMount), elems.advancedToggle.remove()), elems.optionLinks && elems.fileSearchToggle && elems.fileSearchToggleMount && (elems.fileSearchToggle.after(elems.fileSearchToggleMount), elems.fileSearchToggle.remove()), elems.fileSearch?.remove();
let formInsideSearchBox = source.box.form.one()?.sameNode(form2) ?? !1, formId = form2.attribute("id") || "ehpeek-search-form", data = {
clearLabel: clearButton ? actionLabel(clearButton) : null,
hasClear: elems.clearButton !== null && elems.clearActionMount !== null,
searchLabel: actionLabel(searchSubmit)
};
elems.searchActionMount.addClasses("contents"), elems.clearActionMount?.addClasses("contents"), elems.form.apply("stack"), elems.searchControls.setAttributes({ "data-ehpeek-has-clear": String(data.hasClear) }), elems.categories?.setAttributes({ "aria-hidden": "true" });
let handle = {
/** Controls the original category table from EhPeek's category toggle. */
updateCategoryVisibility(open) {
elems.categories?.setAttributes({ "aria-hidden": String(!open) });
},
/** Activates E-H's original Search submit control. */
activateSearch() {
elems.searchSubmit.click();
},
/** Clears only the Search text without invoking E-H's page-navigation reset. */
clearSearchText() {
elems.searchInput.setInputValue(""), elems.searchInput.dispatchInput(), elems.searchInput.focus();
},
toggleAdvancedOptions() {
elems.advancedToggle?.click();
},
toggleFileSearch() {
elems.fileSearchToggle?.click();
}
};
return formInsideSearchBox || (elems.form.setAttributes({ id: formId }), elems.searchInput.setAttributes({ form: formId }), elems.searchSubmit.setAttributes({ form: formId }), elems.clearButton?.setAttributes({ form: formId })), { data, elems, handle };
}
function actionLabel(element) {
return element.attribute("value") ?? element.text();
}
// src/eh/dom/settings.ts
function extractGalleryTitlePreference() {
let source = DomNode.from(document).use(domClass.settings), japaneseTitle = source.titleJapanese.one(), defaultTitle = source.titleDefault.one();
return japaneseTitle?.checked() ? "sub" : defaultTitle?.checked() ? "main" : null;
}
// src/eh/dom/topBar.ts
function manageSettingsMenuMount() {
let page2 = DomNode.from(document), source = page2.use(domClass.topBar), thumbnailContainer = page2.use(domClass.gallery).preview.thumbs.one(), titleContainer = source.galleryTitle.one(), topNav = source.navigation.one(), anchor2 = thumbnailContainer ?? titleContainer;
if (topNav) {
let item2 = createManagedElement("div");
return topNav.inplace().append(item2), item2;
}
if (!anchor2?.parent())
return null;
let item = createManagedElement("div").replaceClasses("text-right"), managedAnchor = anchor2.inplace();
return thumbnailContainer ? managedAnchor.before(item) : managedAnchor.after(item), item;
}
function manageTopBar() {
let mount = createAnchor("top-bar");
if (!mount)
return null;
let source = DomNode.from(document).use(domClass.topBar), original = source.navigation.one(), links = source.navigation.links.all();
if (!original || links.length === 0)
return null;
let data = {
favoritesHref: new URL("/favorites.php", window.location.href).href,
homeHref: links[0]?.attribute("href") ?? "/"
}, elems = {
mount,
navItems: source.navigation.links.moveAll().map((link) => link.apply("layout"))
};
return original.inplace().replaceWith(elems.mount), {
data,
elems
};
}
// src/components/Widgets/Icon.tsx
var _tmpl$ = /* @__PURE__ */ template('<svg class="ehpeek-icon block flex-none"viewBox="0 0 24 24"stroke-linecap=round stroke-linejoin=round aria-hidden=true>'), _tmpl$2 = /* @__PURE__ */ template("<svg><path fill=currentColor stroke=none></svg>", !1, !0, !1), _tmpl$3 = /* @__PURE__ */ template("<svg><path></svg>", !1, !0, !1);
function Icon(props) {
let definition = createMemo(() => ICON_DEFINITIONS[props.name]), filled = createMemo(() => definition().solid || definition().fillable && props.filled), size = createMemo(() => typeof props.size == "number" ? `${props.size}px` : props.size ?? "24px");
return (() => {
var _el$ = _tmpl$();
return insert(_el$, createComponent(For, {
get each() {
return definition().filledPaths;
},
children: (path) => (() => {
var _el$2 = _tmpl$2();
return setAttribute(_el$2, "d", path), _el$2;
})()
}), null), insert(_el$, createComponent(For, {
get each() {
return definition().paths;
},
children: (path) => (() => {
var _el$3 = _tmpl$3();
return setAttribute(_el$3, "d", path), _el$3;
})()
}), null), createRenderEffect((_p$) => {
var _v$ = size(), _v$2 = size(), _v$3 = filled() ? "currentColor" : "none", _v$4 = filled() ? "none" : "currentColor", _v$5 = props.strokeWidth ?? 2, _v$6 = props.name;
return _v$ !== _p$.e && setStyleProperty(_el$, "width", _p$.e = _v$), _v$2 !== _p$.t && setStyleProperty(_el$, "height", _p$.t = _v$2), _v$3 !== _p$.a && setAttribute(_el$, "fill", _p$.a = _v$3), _v$4 !== _p$.o && setAttribute(_el$, "stroke", _p$.o = _v$4), _v$5 !== _p$.i && setAttribute(_el$, "stroke-width", _p$.i = _v$5), _v$6 !== _p$.n && setAttribute(_el$, "data-icon-name", _p$.n = _v$6), _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0
}), _el$;
})();
}
var ICON_DEFINITIONS = {
"arrow-left": {
paths: ["M19 12H5", "m12 19-7-7 7-7"]
},
"arrow-right": {
paths: ["M5 12h14", "m12 5 7 7-7 7"]
},
"arrow-down": {
paths: ["M12 5v14", "m5 12 7 7 7-7"]
},
"arrow-up": {
paths: ["m5 12 7-7 7 7", "M12 5v14"]
},
"arrows-horizontal": {
paths: ["M3 12h18", "m7 8-4 4 4 4", "m17 8 4 4-4 4"]
},
"arrows-vertical": {
paths: ["M12 3v18", "m8 7 4-4 4 4", "m8 17 4 4 4-4"]
},
"book-open": {
paths: ["M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2Z", "M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7Z"]
},
check: {
paths: ["m5 12.5 4.25 4.25L19.5 6.5"]
},
"chevron-left": {
paths: ["m15 18-6-6 6-6"]
},
"chevron-right": {
paths: ["m9 18 6-6-6-6"]
},
close: {
paths: ["M6 6l12 12", "M18 6 6 18"]
},
download: {
paths: ["M12 3v12", "m7 10 5 5 5-5", "M5 21h14"]
},
"external-link": {
paths: ["M14 4h6v6", "m20 4-9 9", "M20 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5"]
},
fullscreen: {
paths: ["M8 3H3v5", "M16 3h5v5", "M3 16v5h5", "M21 16v5h-5"]
},
"fullscreen-exit": {
paths: ["M8 3v5H3", "M16 3v5h5", "M3 16h5v5", "M21 16h-5v5"]
},
grid: {
paths: ["M3 3h8v8H3z", "M13 3h8v8h-8z", "M3 13h8v8H3z", "M13 13h8v8h-8z"]
},
heart: {
fillable: !0,
paths: ["M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78Z"]
},
history: {
paths: ["M3 12a9 9 0 1 0 3-6.7", "M3 4v5h5", "M12 7v5l3 2"]
},
home: {
paths: ["m3 10.5 9-7.5 9 7.5", "M5.5 9v11h13V9", "M9.5 20v-6h5v6"]
},
menu: {
paths: ["M12 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm0 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm0 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"],
solid: !0
},
page: {
paths: ["M5 3h14v18H5z"]
},
palette: {
paths: ["M12 22a10 10 0 1 1 10-10c0 2.76-2.24 5-5 5h-2.25a1.75 1.75 0 0 0-1.4 2.8l.3.4a1.75 1.75 0 0 1-1.4 2.8Z", "M7.5 10.5h.01", "M10.5 7.5h.01", "M14.5 7.5h.01", "M16.5 10.5h.01"]
},
"panda-peek": {
filledPaths: ["M7.2 3.2a2.4 2.4 0 1 0 0 4.8 2.4 2.4 0 0 0 0-4.8Z", "M16.8 3.2a2.4 2.4 0 1 0 0 4.8 2.4 2.4 0 0 0 0-4.8Z", "M7.6 9.8c.5-1.2 1.6-1.8 2.6-1.3s1.3 1.8.8 3-1.6 1.8-2.6 1.3-1.3-1.8-.8-3Z", "M13.8 8.5c1-.5 2.1.1 2.6 1.3s.2 2.5-.8 3-2.1-.1-2.6-1.3-.2-2.5.8-3Z", "M10.9 13.6c0-.6.5-.9 1.1-.9s1.1.3 1.1.9-.5 1-1.1 1-1.1-.4-1.1-1Z", "M5.2 13.7a2.8 1.9 0 1 0 0 3.8 2.8 1.9 0 0 0 0-3.8Z", "M18.8 14.1a2.8 1.9 0 1 0 0 3.8 2.8 1.9 0 0 0 0-3.8Z"],
paths: ["M5 17c-.8-6.4 2.1-10.8 7-10.8s7.8 4.4 7 10.8", "M12 14.6v.7c0 .7-.6 1.2-1.3 1.2m1.3-1.2c0 .7.6 1.2 1.3 1.2", "M2 17h20"]
},
pages: {
paths: ["M3 5h8v14H3z", "M13 5h8v14h-8z"]
},
refresh: {
paths: ["M3 12a9 9 0 1 0 3-6.7L3 8", "M3 3v5h5"]
},
search: {
paths: ["M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14Z", "m16.2 16.2 4.3 4.3"]
},
"scroll-continuous": {
paths: ["M5 3h14v5H5z", "M5 9.5h14v5H5z", "M5 16h14v5H5z"]
},
settings: {
paths: ["M12.2 2h-.4a2 2 0 0 0-2 2v.2a2 2 0 0 1-1 1.7l-.4.3a2 2 0 0 1-2 0l-.2-.1a2 2 0 0 0-2.7.7l-.2.4A2 2 0 0 0 4 9.9l.2.1a2 2 0 0 1 1 1.7v.6a2 2 0 0 1-1 1.7l-.2.1a2 2 0 0 0-.7 2.7l.2.4a2 2 0 0 0 2.7.7l.2-.1a2 2 0 0 1 2 0l.4.3a2 2 0 0 1 1 1.7v.2a2 2 0 0 0 2 2h.4a2 2 0 0 0 2-2v-.2a2 2 0 0 1 1-1.7l.4-.3a2 2 0 0 1 2 0l.2.1a2 2 0 0 0 2.7-.7l.2-.4a2 2 0 0 0-.7-2.7l-.2-.1a2 2 0 0 1-1-1.7v-.6a2 2 0 0 1 1-1.7l.2-.1a2 2 0 0 0 .7-2.7l-.2-.4a2 2 0 0 0-2.7-.7l-.2.1a2 2 0 0 1-2 0l-.4-.3a2 2 0 0 1-1-1.7V4a2 2 0 0 0-2-2Z", "M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"]
},
star: {
fillable: !0,
paths: ["m12 2.75 2.85 5.77 6.37.93-4.61 4.49 1.09 6.34L12 17.24 6.3 20.23l1.09-6.34-4.61-4.49 6.37-.93Z"]
},
viewport: {
paths: ["M8 4H4v4", "M16 4h4v4", "M20 16v4h-4", "M8 20H4v-4", "M8 12h8"]
}
};
// src/components/WelcomeIcon.tsx
var _tmpl$4 = /* @__PURE__ */ template('<div role=status aria-live=polite><span class="inline-flex items-center justify-center gap-md ehp-color-site-text"><span class="inline-block box-border w-sm h-sm animate-spin rounded-full border-4 border-solid ehp-color-spinner"aria-hidden=true></span><span>');
function WelcomeIcon(props) {
let label = () => props.label ?? texts_default.reader.loading, placementClass = () => props.embedded ? "relative w-full border-0 bg-transparent px-lg py-md" : "fixed left-1/2 top-1/2 z-[1200] -translate-x-1/2 -translate-y-1/2 rounded-lg border ehp-color-site-border bg-[var(--color-loading)] px-xl py-lg shadow-[0_6px_20px_var(--color-shadow-floating)]";
return (() => {
var _el$ = _tmpl$4(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling;
return insert(_el$, (() => {
var _c$ = memo(() => props.showIcon !== !1);
return () => _c$() ? createComponent(Icon, {
name: "panda-peek",
size: 80,
strokeWidth: 1.6
}) : null;
})(), _el$2), insert(_el$4, label), createRenderEffect((_p$) => {
var _v$ = `${placementClass()} flex select-none flex-col items-center gap-lg ehp-color-site-accent pointer-events-none`, _v$2 = label();
return _v$ !== _p$.e && className(_el$, _p$.e = _v$), _v$2 !== _p$.t && setAttribute(_el$, "aria-label", _p$.t = _v$2), _p$;
}, {
e: void 0,
t: void 0
}), _el$;
})();
}
// src/components/Widgets/Loading.tsx
function LoadingOverlay(props) {
return createComponent(Show, {
get when() {
return props.visible;
},
get children() {
return createComponent(WelcomeIcon, {
get label() {
return props.label;
}
});
}
});
}
// src/components/PointerGesture.tsx
var DEFAULT_TAP_MOVE_THRESHOLD_PX = 8, DEFAULT_DRAG_START_THRESHOLD_PX = 8, DEFAULT_DRAG_INTENT_RATIO = 1, MOUSE_POINTER_ID = -1, PointerGesture = class {
constructor(target, callbacks) {
__publicField(this, "pinchPointers", /* @__PURE__ */ new Map());
__publicField(this, "drag", null);
__publicField(this, "suppressClick", !1);
__publicField(this, "suppressClickPoint", null);
__publicField(this, "suppressClickTimer", null);
__publicField(this, "pinch", null);
__publicField(this, "onDragStart", (event) => {
this.drag?.canDrag && event.preventDefault();
});
__publicField(this, "onClick", (event) => {
let point = this.suppressClickPoint, targetInside = event.target instanceof Node && this.target.contains(event.target), nearReleasePoint = point !== null && Math.hypot(event.clientX - point.clientX, event.clientY - point.clientY) <= 24;
!this.suppressClick || !targetInside && !nearReleasePoint || (this.clearClickSuppression(), event.preventDefault(), event.stopImmediatePropagation());
});
__publicField(this, "onClickSuppressionPointerDown", () => {
this.clearClickSuppression();
});
__publicField(this, "onContextMenu", () => {
this.drag?.active || (this.cancel(), this.clearPinch());
});
__publicField(this, "onPointerDown", (event) => {
if (event.pointerType === "mouse" && event.button !== 0 || this.trackPinchPointerDown(event) || this.pinch || this.drag)
return;
let callbacks = this.callbacks(), canDrag = callbacks.shouldCaptureDrag?.(event) ?? !0;
(canDrag || (callbacks.shouldObserveTap?.(event) ?? !1)) && (this.start(event.pointerId, event.pointerType, event.clientX, event.clientY, event, canDrag), event.pointerType === "mouse" && this.addMouseListeners());
});
__publicField(this, "onMouseDown", (event) => {
event.button !== 0 || typeof PointerEvent < "u" || this.drag || !(this.callbacks().shouldCaptureDrag?.(event) ?? !0) || (this.start(MOUSE_POINTER_ID, "mouse", event.clientX, event.clientY, event, !0), this.addMouseListeners());
});
__publicField(this, "onPointerMove", (event) => {
!this.drag || event.pointerId !== this.drag.pointerId || this.drag.pointerType === "mouse" || this.move(event.clientX, event.clientY, event);
});
__publicField(this, "onPointerUp", (event) => {
!this.drag || event.pointerId !== this.drag.pointerId || (this.finish(event.clientX, event.clientY, event), this.releasePinchPointer(event));
});
__publicField(this, "onPointerCancel", (event) => {
!this.drag || event.pointerId !== this.drag.pointerId || (this.finish(event.clientX, event.clientY, event, !0), this.releasePinchPointer(event));
});
__publicField(this, "onMouseMove", (event) => {
!this.drag || this.drag.pointerType !== "mouse" || this.move(event.clientX, event.clientY, event);
});
__publicField(this, "onMouseUp", (event) => {
!this.drag || this.drag.pointerType !== "mouse" || this.finish(event.clientX, event.clientY, event);
});
__publicField(this, "onPinchPointerMove", (event) => {
if (!this.pinch || !this.pinchPointers.has(event.pointerId))
return;
this.pinchPointers.set(event.pointerId, {
clientX: event.clientX,
clientY: event.clientY
});
let snapshot = this.pinchSnapshot();
snapshot && (this.callbacks().onPinchMove?.({
...snapshot,
scale: snapshot.distance / this.pinch.startDistance
}, event), event.preventDefault());
});
__publicField(this, "onPinchPointerEnd", (event) => {
this.pinchPointers.has(event.pointerId) && (this.pinchPointers.delete(event.pointerId), !(!this.pinch || this.pinchPointers.size >= 2) && (this.callbacks().onPinchEnd?.(), this.clearPinch(), event.preventDefault()));
});
this.target = target, this.callbacks = callbacks, this.setDragging(!1), target.addEventListener("pointerdown", this.onPointerDown), target.addEventListener("mousedown", this.onMouseDown), target.addEventListener("dragstart", this.onDragStart), target.addEventListener("contextmenu", this.onContextMenu);
}
dispose() {
this.drag && this.releaseCapture(this.drag), this.drag = null, this.setDragging(!1), this.clearPinch(), this.removePointerListeners(), this.removeMouseListeners(), this.target.removeEventListener("pointerdown", this.onPointerDown), this.target.removeEventListener("mousedown", this.onMouseDown), this.target.removeEventListener("dragstart", this.onDragStart), this.target.removeEventListener("contextmenu", this.onContextMenu), this.clearClickSuppression();
}
isDragging() {
return this.drag?.active === !0;
}
cancel(preservePinchPointers = !1) {
let drag = this.drag;
drag && (this.releaseCapture(drag), preservePinchPointers || this.pinchPointers.delete(drag.pointerId), this.drag = null, this.setDragging(!1), this.removePointerListeners(), this.removeMouseListeners());
}
start(pointerId, pointerType, clientX, clientY, event, canDrag) {
this.drag = {
active: !1,
canDrag,
captureTarget: null,
pointerId,
pointerType,
startClientX: clientX,
startClientY: clientY,
lastClientX: clientX,
lastClientY: clientY,
lastMoveTime: event.timeStamp,
startTarget: event.target,
tapCancelled: !1,
velocityX: 0,
velocityY: 0
};
let captureTarget = event.target;
canDrag && "pointerId" in event && typeof captureTarget?.setPointerCapture == "function" && (captureTarget.setPointerCapture(pointerId), this.drag.captureTarget = captureTarget), this.addPointerListeners();
}
move(clientX, clientY, event) {
let drag = this.drag;
if (!drag)
return;
let dx = clientX - drag.startClientX, dy = clientY - drag.startClientY, tapMoveThreshold = this.tapMoveThreshold();
if ((Math.abs(dx) >= tapMoveThreshold || Math.abs(dy) >= tapMoveThreshold) && (drag.tapCancelled = !0), !drag.canDrag) {
this.updateLastMove(drag, clientX, clientY, event);
return;
}
let intent = this.dragIntent(dx, dy);
if (!drag.active && intent === "cancel") {
this.cancel();
return;
}
if (!drag.active && intent !== "start") {
this.updateLastMove(drag, clientX, clientY, event);
return;
}
drag.active || this.activateDrag(drag, event);
let elapsed = Math.max(1, event.timeStamp - drag.lastMoveTime);
drag.velocityX = (clientX - drag.lastClientX) / elapsed, drag.velocityY = (clientY - drag.lastClientY) / elapsed, drag.lastClientX = clientX, drag.lastClientY = clientY, drag.lastMoveTime = event.timeStamp, this.callbacks().onMove?.({
pointerId: drag.pointerId,
clientX,
clientY,
dx: clientX - drag.startClientX,
dy: clientY - drag.startClientY,
velocityX: drag.velocityX,
velocityY: drag.velocityY
}, event), event.preventDefault();
}
finish(clientX, clientY, event, cancelled = !1) {
let drag = this.drag;
if (!drag)
return;
this.drag = null, this.setDragging(!1), this.releaseCapture(drag), this.removePointerListeners(), this.removeMouseListeners();
let info = {
pointerId: drag.pointerId,
clientX,
clientY,
dx: clientX - drag.startClientX,
dy: clientY - drag.startClientY,
velocityX: drag.velocityX,
velocityY: drag.velocityY
}, tapMoveThreshold = this.tapMoveThreshold(), isTap = !drag.tapCancelled && Math.abs(info.dx) < tapMoveThreshold && Math.abs(info.dy) < tapMoveThreshold;
if (!cancelled && !drag.active && isTap && this.callbacks().onTap?.({
...info,
startTarget: drag.startTarget
}, event), drag.active) {
if (cancelled) {
this.callbacks().onEnd?.({
...info,
dx: 0,
dy: 0,
velocityX: 0,
velocityY: 0
}, event);
return;
}
this.suppressNextClick(info.clientX, info.clientY), this.callbacks().onEnd?.(info, event);
}
}
addPointerListeners() {
document.addEventListener("pointermove", this.onPointerMove, !0), document.addEventListener("pointerup", this.onPointerUp, !0), document.addEventListener("pointercancel", this.onPointerCancel, !0);
}
removePointerListeners() {
document.removeEventListener("pointermove", this.onPointerMove, !0), document.removeEventListener("pointerup", this.onPointerUp, !0), document.removeEventListener("pointercancel", this.onPointerCancel, !0);
}
addMouseListeners() {
window.addEventListener("mousemove", this.onMouseMove, !0), window.addEventListener("mouseup", this.onMouseUp, !0);
}
removeMouseListeners() {
window.removeEventListener("mousemove", this.onMouseMove, !0), window.removeEventListener("mouseup", this.onMouseUp, !0);
}
trackPinchPointerDown(event) {
let callbacks = this.callbacks();
if (!callbacks.onPinchStart || event.pointerType === "mouse" || (this.pinchPointers.set(event.pointerId, {
clientX: event.clientX,
clientY: event.clientY
}), this.pinch || this.pinchPointers.size !== 2))
return !1;
let snapshot = this.pinchSnapshot();
return snapshot ? callbacks.onPinchStart(snapshot, event) ? (this.cancel(!0), this.pinch = {
startDistance: snapshot.distance
}, this.addPinchListeners(), event.preventDefault(), event.stopPropagation(), !0) : (this.pinchPointers.delete(event.pointerId), !1) : !1;
}
addPinchListeners() {
document.addEventListener("pointermove", this.onPinchPointerMove, !0), document.addEventListener("pointerup", this.onPinchPointerEnd, !0), document.addEventListener("pointercancel", this.onPinchPointerEnd, !0);
}
removePinchListeners() {
document.removeEventListener("pointermove", this.onPinchPointerMove, !0), document.removeEventListener("pointerup", this.onPinchPointerEnd, !0), document.removeEventListener("pointercancel", this.onPinchPointerEnd, !0);
}
clearPinch() {
this.pinch = null, this.pinchPointers.clear(), this.removePinchListeners();
}
releasePinchPointer(event) {
this.pinch || this.pinchPointers.delete(event.pointerId);
}
pinchSnapshot() {
let points = Array.from(this.pinchPointers.values()), first = points[0], second = points[1];
if (!first || !second)
return null;
let dx = second.clientX - first.clientX, dy = second.clientY - first.clientY;
return {
clientX: (first.clientX + second.clientX) / 2,
clientY: (first.clientY + second.clientY) / 2,
distance: Math.hypot(dx, dy)
};
}
tapMoveThreshold() {
return this.callbacks().tapMoveThreshold ?? DEFAULT_TAP_MOVE_THRESHOLD_PX;
}
dragStartThreshold() {
return this.callbacks().dragStartThreshold ?? DEFAULT_DRAG_START_THRESHOLD_PX;
}
dragIntentRatio() {
return this.callbacks().dragIntentRatio ?? DEFAULT_DRAG_INTENT_RATIO;
}
dragAxis() {
return this.callbacks().dragAxis ?? "any";
}
dragIntent(dx, dy) {
let absX = Math.abs(dx), absY = Math.abs(dy), threshold = this.dragStartThreshold(), ratio = this.dragIntentRatio();
return this.dragAxis() === "x" ? absY >= threshold && absY > absX ? "cancel" : absX >= threshold && absX >= absY * ratio ? "start" : "pending" : this.dragAxis() === "y" ? absX >= threshold && absX > absY ? "cancel" : absY >= threshold && absY >= absX * ratio ? "start" : "pending" : Math.hypot(dx, dy) >= threshold ? "start" : "pending";
}
activateDrag(drag, event) {
drag.active = !0, this.setDragging(!0), drag.pointerType === "mouse" && window.getSelection()?.removeAllRanges(), this.callbacks().onStart?.({
pointerId: drag.pointerId,
clientX: drag.startClientX,
clientY: drag.startClientY
}, event), event.preventDefault();
}
updateLastMove(drag, clientX, clientY, event) {
let elapsed = Math.max(1, event.timeStamp - drag.lastMoveTime);
drag.velocityX = (clientX - drag.lastClientX) / elapsed, drag.velocityY = (clientY - drag.lastClientY) / elapsed, drag.lastClientX = clientX, drag.lastClientY = clientY, drag.lastMoveTime = event.timeStamp;
}
suppressNextClick(clientX, clientY) {
this.suppressClick = !0, this.suppressClickPoint = {
clientX,
clientY
}, window.addEventListener("click", this.onClick, !0), window.addEventListener("mousedown", this.onClickSuppressionPointerDown, !0), window.addEventListener("pointerdown", this.onClickSuppressionPointerDown, !0), this.suppressClickTimer !== null && window.clearTimeout(this.suppressClickTimer), this.suppressClickTimer = window.setTimeout(() => {
this.clearClickSuppression();
}, 400);
}
clearClickSuppression() {
this.suppressClick = !1, this.suppressClickPoint = null, window.removeEventListener("click", this.onClick, !0), window.removeEventListener("mousedown", this.onClickSuppressionPointerDown, !0), window.removeEventListener("pointerdown", this.onClickSuppressionPointerDown, !0), this.suppressClickTimer !== null && (window.clearTimeout(this.suppressClickTimer), this.suppressClickTimer = null);
}
setDragging(dragging) {
this.target.dataset.dragging = String(dragging);
}
releaseCapture(drag) {
drag.captureTarget?.hasPointerCapture(drag.pointerId) && drag.captureTarget.releasePointerCapture(drag.pointerId);
}
};
function createPointerGestureElement(target, callbacks) {
let gesture = null;
return createEffect(() => {
let element = target();
element && (gesture = new PointerGesture(element, callbacks), onCleanup(() => {
gesture?.dispose(), gesture = null;
}));
}), () => gesture?.isDragging() ?? !1;
}
// src/components/Widgets/SwipeIndicator.tsx
var _tmpl$5 = /* @__PURE__ */ template('<div class="ehpeek-swipe-indicator fixed top-1/2 z-overlay flex w-42px h-108px items-center justify-center border border-[var(--color-site-swipe-border)] rounded-full bg-[var(--color-site-swipe-background)] text-[var(--color-site-text)] shadow-[0_6px_20px_var(--color-shadow-floating)] pointer-events-none select-none transition-opacity duration-120 ease-in-out"style=backdrop-filter:blur(8px)>'), HIDE_PROGRESS = 1e-3;
function SwipeIndicator(props) {
let progress = createMemo(() => Math.min(1, Math.max(0, props.state.progress))), hidden = createMemo(() => progress() <= HIDE_PROGRESS), pull = createMemo(() => Math.round(48 * progress())), offset = createMemo(() => props.state.direction === "left" ? 42 - pull() : pull() - 42), iconName = createMemo(() => props.state.blocked ? "close" : props.state.direction === "left" ? "chevron-left" : "chevron-right");
return (() => {
var _el$ = _tmpl$5();
return insert(_el$, createComponent(Icon, {
get name() {
return iconName();
},
size: 36
})), createRenderEffect((_p$) => {
var _v$ = hidden() ? "true" : "false", _v$2 = hidden() ? "none" : "flex", _v$3 = props.state.direction === "right" ? "6px" : "", _v$4 = hidden() ? "0" : String(0.35 + progress() * 0.65), _v$5 = props.state.direction === "left" ? "6px" : "", _v$6 = `translate(${offset()}px, -50%)`;
return _v$ !== _p$.e && setAttribute(_el$, "aria-hidden", _p$.e = _v$), _v$2 !== _p$.t && setStyleProperty(_el$, "display", _p$.t = _v$2), _v$3 !== _p$.a && setStyleProperty(_el$, "left", _p$.a = _v$3), _v$4 !== _p$.o && setStyleProperty(_el$, "opacity", _p$.o = _v$4), _v$5 !== _p$.i && setStyleProperty(_el$, "right", _p$.i = _v$5), _v$6 !== _p$.n && setStyleProperty(_el$, "transform", _p$.n = _v$6), _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0
}), _el$;
})();
}
// src/components/Enhance/PageSwipe.tsx
var SWIPE_MIN_DISTANCE = 96, SWIPE_INTENT_DISTANCE = 28, HORIZONTAL_INTENT_RATIO = 2.2, SWIPE_MAX_VERTICAL_RATIO = 0.38;
function PageSwipe(props) {
let [indicator, setIndicator] = createSignal({
blocked: !1,
direction: "left",
progress: 0
}), directionFor = (dx) => dx < 0 ? "next" : "previous", reset2 = () => setIndicator((current) => ({
...current,
blocked: !1,
progress: 0
}));
return createPointerGestureElement(() => props.target(), () => ({
onStart: reset2,
onMove: (info) => {
let direction = directionFor(info.dx);
setIndicator({
blocked: !props.canNavigate(direction),
direction: direction === "next" ? "left" : "right",
progress: Math.min(1, Math.max(0, (Math.abs(info.dx) - SWIPE_INTENT_DISTANCE) / (SWIPE_MIN_DISTANCE - SWIPE_INTENT_DISTANCE)))
});
},
onEnd: (info, event) => {
navigate(info, event), reset2();
},
dragAxis: "x",
dragIntentRatio: HORIZONTAL_INTENT_RATIO,
dragStartThreshold: SWIPE_INTENT_DISTANCE
})), createComponent(SwipeIndicator, {
get state() {
return indicator();
}
});
function navigate(info, event) {
let absX = Math.abs(info.dx), absY = Math.abs(info.dy), direction = directionFor(info.dx);
absX < SWIPE_MIN_DISTANCE || absY > absX * SWIPE_MAX_VERTICAL_RATIO || !props.canNavigate(direction) || (event.preventDefault(), props.onNavigate(direction));
}
}
// src/components/Enhance/EnhanceSearchGrids.tsx
function EnhanceSearchGrids(props) {
let [gestureTarget, setGestureTarget] = createSignal(null), [loading, setLoading] = createSignal(!1), source = untrack(() => props.source), navigationLoading = !1, swipeUrl = (direction) => direction === "next" ? source.data.nextUrl : source.data.previousUrl, navigate = async (url) => {
if (!navigationLoading) {
navigationLoading = !0, setLoading(!0), source.handle.updateSearchLoading(!0);
try {
await source.handle.loadSearchPage(url);
let nextSource = manageSearchResults();
if (!nextSource)
throw new Error(texts_default.errors.searchPageContentNotFound);
source = nextSource, props.onPageChange(source), source.handle.ensureSearchSwipeInput(), setGestureTarget(source.elems.resultList.Component()), source.handle.scrollSearchPageToInput();
} catch (error) {
console.error("[ehpeek]", error);
} finally {
navigationLoading = !1, setLoading(!1), source.handle.updateSearchLoading(!1);
}
}
}, onNavigation = (url) => {
navigate(url);
};
return onMount(() => {
source.handle.ensureSearchSwipeInput(), setGestureTarget(source.elems.resultList.Component()), onCleanup(source.handle.interceptSearchNavigation(onNavigation));
}), [createComponent(PageSwipe, {
canNavigate: (direction) => !!swipeUrl(direction),
onNavigate: (direction) => {
let url = swipeUrl(direction);
url && navigate(url);
},
target: gestureTarget
}), createComponent(LoadingOverlay, {
get label() {
return texts_default.reader.loading;
},
get visible() {
return loading();
}
})];
}
// src/components/Enhance/ScrollPageBar.tsx
var _tmpl$6 = /* @__PURE__ */ template('<div class="w-full mb-xs text-center textsize-sm">'), _tmpl$22 = /* @__PURE__ */ template('<a class="flex !w-sm !h-sm coarse:!w-md coarse:!h-md items-center justify-center box-border !p-0 rounded-sm coarse:rounded-md !border textsize-sm font-inherit no-underline hover:no-underline active:no-underline !border-transparent !bg-transparent !text-[var(--color-site-text)] visited:!text-[var(--color-site-text)] hover:!bg-[var(--color-site-item-hover)] hover:!text-[var(--color-site-text)] active:!text-[var(--color-site-text)]">'), _tmpl$32 = /* @__PURE__ */ template('<td class="!w-sm !h-sm coarse:!w-md coarse:!h-md !p-0 rounded-sm coarse:rounded-md cursor-pointer text-center align-middle select-none">'), _tmpl$42 = /* @__PURE__ */ template("<span>"), _tmpl$52 = /* @__PURE__ */ template('<td class="!w-sm !h-sm coarse:!w-md coarse:!h-md !p-0 rounded-sm coarse:rounded-md cursor-pointer text-center align-middle select-none cursor-default"><span class="flex !w-sm !h-sm coarse:!w-md coarse:!h-md items-center justify-center box-border !p-0 rounded-sm coarse:rounded-md !border textsize-sm font-inherit no-underline hover:no-underline active:no-underline !border-transparent !bg-transparent !text-[var(--color-site-text)] visited:!text-[var(--color-site-text)] hover:!bg-[var(--color-site-item-hover)] hover:!text-[var(--color-site-text)] active:!text-[var(--color-site-text)] invisible">'), _tmpl$62 = /* @__PURE__ */ template('<div><table class="border-separate border-spacing-4px coarse:border-spacing-6px"><tbody><tr>'), _tmpl$7 = /* @__PURE__ */ template('<span class="flex !w-sm !h-sm coarse:!w-md coarse:!h-md items-center justify-center box-border !p-0 rounded-sm coarse:rounded-md !border textsize-sm font-inherit no-underline hover:no-underline active:no-underline !border-transparent !bg-[color-mix(in_srgb,var(--color-site-page)_82%,black)] !text-[var(--color-site-text)]"aria-current=page>'), DRAG_PIXEL_STEP = 18;
var PAGE_BAR_LINK_CLASS = "flex !w-sm !h-sm coarse:!w-md coarse:!h-md items-center justify-center box-border !p-0 rounded-sm coarse:rounded-md !border textsize-sm font-inherit no-underline hover:no-underline active:no-underline";
var PAGE_BAR_CURRENT_COLOR_CLASS = "!border-transparent !bg-[color-mix(in_srgb,var(--color-site-page)_82%,black)] !text-[var(--color-site-text)]", PAGE_BAR_DISABLED_COLOR_CLASS = "!border-transparent !bg-[color-mix(in_srgb,var(--color-site-page)_82%,black)] !text-[var(--color-site-text)] opacity-40 cursor-default";
function GalleryPageDescription(props) {
return (() => {
var _el$ = _tmpl$6();
return insert(_el$, () => props.text), _el$;
})();
}
function ScrollPageBar(props) {
let maxIndex = createMemo(() => Math.max(0, props.maxIndex)), currentIndex = createMemo(() => clamp(props.currentIndex, 0, maxIndex())), [visibleCenterIndex, setVisibleCenterIndex] = createSignal(untrack(currentIndex)), gestureHost, dragStartVisibleCenterIndex = untrack(visibleCenterIndex), draggable = () => maxIndex() + 1 > 7, slots = createMemo(() => pageSlots(visibleCenterIndex(), maxIndex())), firstSlotIndex = createMemo(() => slots()[0] ?? currentIndex()), lastSlotIndex = createMemo(() => slots()[slots().length - 1] ?? currentIndex()), currentBeforeWindow = () => currentIndex() < firstSlotIndex(), currentAfterWindow = () => currentIndex() > lastSlotIndex(), scrollTargetForIndex = (pageIndex) => pageIndex === currentIndex() - 1 || pageIndex === maxIndex() ? "bottom" : "top";
createEffect(() => {
setVisibleCenterIndex(currentIndex());
});
let linkCell = (text, pageIndex, itemState = () => "link") => {
let resolvedText = () => typeof text == "function" ? text() : text, resolvedPageIndex = () => typeof pageIndex == "function" ? pageIndex() : pageIndex;
return (() => {
var _el$2 = _tmpl$32();
return insert(_el$2, createComponent(Show, {
get when() {
return itemState() === "link";
},
get fallback() {
return (() => {
var _el$4 = _tmpl$42();
return insert(_el$4, resolvedText), createRenderEffect((_p$) => {
var _v$ = `${PAGE_BAR_LINK_CLASS} ${itemState() === "current" ? PAGE_BAR_CURRENT_COLOR_CLASS : PAGE_BAR_DISABLED_COLOR_CLASS}`, _v$2 = itemState() === "current" ? "page" : void 0, _v$3 = itemState() === "disabled" ? "true" : void 0;
return _v$ !== _p$.e && className(_el$4, _p$.e = _v$), _v$2 !== _p$.t && setAttribute(_el$4, "aria-current", _p$.t = _v$2), _v$3 !== _p$.a && setAttribute(_el$4, "aria-disabled", _p$.a = _v$3), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$4;
})();
},
get children() {
var _el$3 = _tmpl$22();
return _el$3.$$click = (event) => {
event.preventDefault(), event.stopPropagation(), props.onNavigate(resolvedPageIndex(), scrollTargetForIndex(resolvedPageIndex()));
}, setAttribute(_el$3, "draggable", !1), insert(_el$3, resolvedText), createRenderEffect(() => setAttribute(_el$3, "href", props.urlForIndex(resolvedPageIndex()))), _el$3;
}
})), _el$2;
})();
}, emptyCell = () => (() => {
var _el$5 = _tmpl$52(), _el$6 = _el$5.firstChild;
return _el$5;
})();
return createPointerGestureElement(() => gestureHost, () => ({
shouldCaptureDrag: draggable,
dragAxis: "x",
onStart: () => {
dragStartVisibleCenterIndex = visibleCenterIndex();
},
onMove: (info) => {
if (Math.abs(info.dx) < Math.abs(info.dy))
return;
let nextIndex = clamp(dragStartVisibleCenterIndex - acceleratedPageOffset(info.dx), 0, maxIndex());
nextIndex !== visibleCenterIndex() && setVisibleCenterIndex(nextIndex);
}
})), (() => {
var _el$7 = _tmpl$62(), _el$8 = _el$7.firstChild, _el$9 = _el$8.firstChild, _el$0 = _el$9.firstChild, _ref$ = gestureHost;
return typeof _ref$ == "function" ? use(_ref$, _el$7) : gestureHost = _el$7, insert(_el$0, () => linkCell("<<", 0, () => currentIndex() === 0 ? "disabled" : "link"), null), insert(_el$0, createComponent(Show, {
get when() {
return currentBeforeWindow();
},
get fallback() {
return emptyCell();
},
get children() {
return linkCell(() => String(currentIndex() + 1), currentIndex, () => "current");
}
}), null), insert(_el$0, () => linkCell("<", () => Math.max(0, currentIndex() - 1), () => currentIndex() === 0 ? "disabled" : "link"), null), insert(_el$0, createComponent(For, {
get each() {
return slots();
},
children: (pageIndex) => {
let itemState = createMemo(() => pageIndex === currentIndex() ? "current" : "link");
return pageIndex !== null ? (() => {
var _el$1 = _tmpl$32();
return insert(_el$1, createComponent(Show, {
get when() {
return itemState() === "link";
},
get fallback() {
return (() => {
var _el$11 = _tmpl$7();
return insert(_el$11, pageIndex + 1), _el$11;
})();
},
get children() {
var _el$10 = _tmpl$22();
return _el$10.$$click = (event) => {
event.preventDefault(), event.stopPropagation(), props.onNavigate(pageIndex, scrollTargetForIndex(pageIndex));
}, setAttribute(_el$10, "draggable", !1), insert(_el$10, pageIndex + 1), createRenderEffect(() => setAttribute(_el$10, "href", props.urlForIndex(pageIndex))), _el$10;
}
})), _el$1;
})() : emptyCell();
}
}), null), insert(_el$0, () => linkCell(">", () => Math.min(maxIndex(), currentIndex() + 1), () => currentIndex() === maxIndex() ? "disabled" : "link"), null), insert(_el$0, createComponent(Show, {
get when() {
return currentAfterWindow();
},
get fallback() {
return emptyCell();
},
get children() {
return linkCell(() => String(currentIndex() + 1), currentIndex, () => "current");
}
}), null), insert(_el$0, () => linkCell(">>", maxIndex, () => currentIndex() === maxIndex() ? "disabled" : "link"), null), _el$7;
})();
}
function pageSlots(visibleCenterIndex, maxIndex) {
if (maxIndex + 1 <= 7)
return range(0, maxIndex);
let visibleStartIndex = clamp(visibleCenterIndex - 3, -1, maxIndex - 5);
return range(visibleStartIndex, visibleStartIndex + 6).map((pageIndex) => pageIndex >= 0 && pageIndex <= maxIndex ? pageIndex : null);
}
function range(start, end) {
let output = [];
for (let index = start; index <= end; index += 1)
output.push(index);
return output;
}
function acceleratedPageOffset(dx) {
let distance = Math.abs(dx), direction = dx > 0 ? 1 : -1, pages = Math.floor((distance / DRAG_PIXEL_STEP) ** 1.35);
return direction * pages;
}
delegateEvents(["click"]);
// src/components/Enhance/EnhanceThumbsGrids.tsx
var SWIPE_MIN_DISTANCE2 = 96, SWIPE_INTENT_DISTANCE2 = 28, HORIZONTAL_INTENT_RATIO2 = 2.2, SWIPE_MAX_VERTICAL_RATIO2 = 0.38;
function ThumbsGrids(props) {
let pageBarSource = untrack(() => props.previewCache.current()), [pageBarCurrentIndex, setPageBarCurrentIndex] = createSignal(pageBarSource.data.currentIndex), [swipeIndicatorState, setSwipeIndicatorState] = createSignal({
blocked: !1,
direction: "left",
progress: 0
}), pageBarMaxIndex = () => props.previewCache.current().data.maxIndex, requestPreviewPage = (previewIndex, scrollToPageBar) => {
let current = props.previewCache.current(), onLoadError = props.onLoadError;
pageBarSource.handle.scrollPreviewPageBarIntoView(scrollToPageBar), previewIndex !== current.data.currentIndex && (setPageBarCurrentIndex(previewIndex), props.previewCache.select(previewIndex).then((next) => {
untrack(() => props.previewCache.current()) === next && pageBarSource.handle.scrollPreviewPageBarIntoView(scrollToPageBar);
}, (error) => {
let cacheIndex = untrack(() => props.previewCache.current().data.currentIndex);
setPageBarCurrentIndex((currentIndex) => currentIndex === previewIndex ? cacheIndex : currentIndex), onLoadError(error);
}));
}, swipeIndexForDelta = (dx) => {
let current = props.previewCache.current().data, nextIndex = dx < 0 ? current.currentIndex + 1 : current.currentIndex - 1;
return nextIndex < 0 || nextIndex > current.maxIndex ? null : nextIndex;
}, hideSwipeIndicator = () => {
setSwipeIndicatorState((current) => ({
...current,
blocked: !1,
progress: 0
}));
}, updateSwipeIndicator = (info) => {
setSwipeIndicatorState({
blocked: swipeIndexForDelta(info.dx) === null,
direction: info.dx < 0 ? "left" : "right",
progress: Math.min(1, Math.max(0, (Math.abs(info.dx) - SWIPE_INTENT_DISTANCE2) / (SWIPE_MIN_DISTANCE2 - SWIPE_INTENT_DISTANCE2)))
});
}, navigateBySwipe = (info, event) => {
let absX = Math.abs(info.dx), absY = Math.abs(info.dy);
if (absX < SWIPE_MIN_DISTANCE2 || absY > absX * SWIPE_MAX_VERTICAL_RATIO2)
return;
let previewIndex = swipeIndexForDelta(info.dx);
previewIndex !== null && (event.preventDefault(), requestPreviewPage(previewIndex, info.dx < 0 ? "top" : "bottom"));
}, actions = {
gotoPreview: setPageBarCurrentIndex
};
createEffect(() => {
props.actionsRef(actions);
}), createEffect((previous) => {
let current = props.previewCache.current();
return setPageBarCurrentIndex(current.data.currentIndex), current.handle.ensurePreviewSwipeInput(), current !== previous && pageBarSource.handle.replacePreviewThumbs(current.elems.thumbItems), current;
}, pageBarSource), createEffect(() => {
pageBarSource.handle.updatePreviewLoading(props.previewCache.loading());
}), onCleanup(() => {
pageBarSource.handle.updatePreviewLoading(!1);
}), createPointerGestureElement(() => pageBarSource.elems.thumbs?.Component() ?? null, () => ({
onStart: hideSwipeIndicator,
onMove: updateSwipeIndicator,
onEnd: (info, event) => {
navigateBySwipe(info, event), hideSwipeIndicator();
},
dragAxis: "x",
dragIntentRatio: HORIZONTAL_INTENT_RATIO2,
dragStartThreshold: SWIPE_INTENT_DISTANCE2
})), pageBarSource.handle.installPreviewPageBars(), pageBarSource.elems.pageBarDescription?.mount(() => createComponent(GalleryPageDescription, {
get text() {
return props.previewCache.current().data.descriptionText;
}
}));
for (let element of [pageBarSource.elems.pageBarTop, pageBarSource.elems.pageBarBottom])
element && element.mount(() => createComponent(ScrollPageBar, {
get currentIndex() {
return pageBarCurrentIndex();
},
get maxIndex() {
return pageBarMaxIndex();
},
onNavigate: requestPreviewPage,
urlForIndex: (index) => previewUrlForIndex(index, props.previewCache.current().data.currentUrl)
}));
return onCleanup(() => {
pageBarSource.elems.pageBarDescription?.remove(), pageBarSource.elems.pageBarTop?.remove(), pageBarSource.elems.pageBarBottom?.remove();
}), [createComponent(LoadingOverlay, {
get label() {
return texts_default.reader.loading;
},
get visible() {
return props.previewCache.loading();
}
}), createComponent(SwipeIndicator, {
get state() {
return swipeIndicatorState();
}
})];
}
// src/components/animation.ts
var SCROLL_ANIMATION_MS = 180, SCROLL_EASING_POWER = 3, ANIMATION_FRAME_MIN_DELTA_MS = 1, ANIMATION_FRAME_MAX_DELTA_MS = 32, SCROLL_FLING_MIN_VELOCITY = 0.35, SCROLL_FLING_STOP_VELOCITY = 0.02, SCROLL_FLING_DECAY = 45e-4, ScrollAnimator = class {
constructor(axis) {
this.axis = axis;
this.frame = null;
}
scrollTo(scroller, target, motion = "instant", onComplete) {
if (this.cancel(), motion !== "animated") {
this.setScrollPosition(scroller, target), onComplete?.();
return;
}
this.scrollWithRaf(scroller, target, onComplete);
}
cancel() {
this.frame !== null && (window.cancelAnimationFrame(this.frame), this.frame = null);
}
scrollWithRaf(scroller, target, onComplete) {
let start = this.scrollPosition(scroller), delta = target - start, lastFrameTime = performance.now(), animationTime = 0, step = (time) => {
let elapsed = clamp(time - lastFrameTime, ANIMATION_FRAME_MIN_DELTA_MS, ANIMATION_FRAME_MAX_DELTA_MS);
lastFrameTime = time, animationTime += elapsed;
let progress = clamp(animationTime / SCROLL_ANIMATION_MS, 0, 1), eased = 1 - Math.pow(1 - progress, SCROLL_EASING_POWER);
if (this.setScrollPosition(scroller, start + delta * eased), progress >= 1) {
this.frame = null, onComplete?.();
return;
}
this.frame = window.requestAnimationFrame(step);
};
this.frame = window.requestAnimationFrame(step);
}
scrollPosition(scroller) {
return this.axis === "x" ? scroller.scrollLeft : scroller.scrollTop;
}
setScrollPosition(scroller, value) {
this.axis === "x" ? scroller.scrollLeft = value : scroller.scrollTop = value;
}
}, ScrollFlingAnimator = class {
constructor() {
this.frame = null;
this.velocity = 0;
this.lastFrameTime = 0;
}
start(options) {
this.cancel();
let initialVelocity = options.maxVelocity ? clamp(options.initialVelocity, -options.maxVelocity, options.maxVelocity) : options.initialVelocity;
if (Math.abs(initialVelocity) < SCROLL_FLING_MIN_VELOCITY)
return;
this.velocity = initialVelocity, this.lastFrameTime = performance.now();
let step = (time) => {
if (!options.canRun()) {
this.cancel();
return;
}
let elapsed = clamp(time - this.lastFrameTime, ANIMATION_FRAME_MIN_DELTA_MS, ANIMATION_FRAME_MAX_DELTA_MS);
this.lastFrameTime = time;
let previousPosition = options.axis === "x" ? options.scroller.scrollLeft : options.scroller.scrollTop;
if (options.setScrollPosition(previousPosition + this.velocity * elapsed), (options.axis === "x" ? options.scroller.scrollLeft : options.scroller.scrollTop) === previousPosition) {
this.cancel(), options.onStop();
return;
}
if (this.velocity *= Math.exp(-SCROLL_FLING_DECAY * elapsed), Math.abs(this.velocity) < SCROLL_FLING_STOP_VELOCITY) {
this.cancel(), options.onStop();
return;
}
this.frame = window.requestAnimationFrame(step);
};
this.frame = window.requestAnimationFrame(step);
}
cancel() {
this.frame !== null && (window.cancelAnimationFrame(this.frame), this.frame = null), this.velocity = 0;
}
};
// src/components/Widgets/ProgressBar.css
var ProgressBar_default = `.ehpeek-progress-bar::-webkit-slider-runnable-track {
height: 0.4em;
border-radius: 9999px;
background: linear-gradient(
var(--progress-bar-track-direction),
var(--color-accent) 0 var(--progress-bar-fill),
var(--color-track) var(--progress-bar-fill) 100%
);
}
.ehpeek-progress-bar::-webkit-slider-thumb {
width: 1.4em;
height: 1.4em;
margin-top: -0.5em;
border: 2px solid var(--color-border);
border-radius: 9999px;
background: var(--color-text);
box-shadow: 0 2px 10px var(--color-shadow-control);
appearance: none;
-webkit-appearance: none;
}
.ehpeek-progress-bar::-moz-range-track,
.ehpeek-progress-bar::-moz-range-progress {
height: 0.4em;
border-radius: 9999px;
}
.ehpeek-progress-bar::-moz-range-track {
background: var(--color-track);
}
.ehpeek-progress-bar::-moz-range-progress {
background: var(--color-accent);
}
.ehpeek-progress-bar::-moz-range-thumb {
width: 1.4em;
height: 1.4em;
border: 2px solid var(--color-border);
border-radius: 9999px;
background: var(--color-text);
box-shadow: 0 2px 10px var(--color-shadow-control);
}
`;
// src/components/Widgets/ProgressBar.tsx
var _tmpl$8 = /* @__PURE__ */ template("<input type=range>"), PROGRESS_BAR_CLASS = "ehpeek-progress-bar", PROGRESS_BAR_CLASS_NAME = [PROGRESS_BAR_CLASS, "w-full h-[2.4em] px-[0.6em] py-0 m-0", "bg-transparent", "cursor-grab active:cursor-grabbing touch-none select-none", "[-webkit-appearance:none] [appearance:none]", "[--progress-bar-fill:0%] [--progress-bar-track-direction:to_right]", "[accent-color:var(--color-text)]"].join(" ");
registerGlobalStyle(PROGRESS_BAR_CLASS, ProgressBar_default);
function ProgressBar(props) {
let input2;
createEffect(() => {
let direction = props.direction ?? "ltr";
input2.min = String(props.min), input2.max = String(Math.max(1, props.max ?? props.min)), input2.step = String(props.step), input2.dir = direction, input2.style.setProperty("--progress-bar-track-direction", direction === "rtl" ? "to left" : "to right"), input2.style.setProperty("--progress-bar-fill", `${Math.min(100, Math.max(0, props.fillPercent ?? 0))}%`), !props.keepInputValue && props.value !== void 0 && (input2.value = String(props.value));
});
let currentValue = (event) => Number(event.currentTarget.value || "");
return (() => {
var _el$ = _tmpl$8();
return _el$.addEventListener("pointercancel", (event) => {
props.onCommit?.(currentValue(event));
}), _el$.$$pointerup = (event) => {
props.onCommit?.(currentValue(event));
}, _el$.addEventListener("change", (event) => {
props.onCommit?.(currentValue(event));
}), _el$.$$input = (event) => {
props.onInput?.(currentValue(event));
}, _el$.$$pointerdown = (event) => {
props.onPointerDown?.(event);
}, use((element) => {
input2 = element, element.min = String(props.min), element.max = String(Math.max(1, props.max ?? props.min)), element.step = String(props.step), element.value = String(props.value ?? props.min);
}, _el$), createRenderEffect((_p$) => {
var _v$ = `${PROGRESS_BAR_CLASS_NAME}${props.class ? ` ${props.class}` : ""}`, _v$2 = String(props.min), _v$3 = String(Math.max(1, props.max ?? props.min)), _v$4 = String(props.step), _v$5 = props.direction ?? "ltr";
return _v$ !== _p$.e && className(_el$, _p$.e = _v$), _v$2 !== _p$.t && setAttribute(_el$, "min", _p$.t = _v$2), _v$3 !== _p$.a && setAttribute(_el$, "max", _p$.a = _v$3), _v$4 !== _p$.o && setAttribute(_el$, "step", _p$.o = _v$4), _v$5 !== _p$.i && setAttribute(_el$, "dir", _p$.i = _v$5), _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0
}), _el$;
})();
}
delegateEvents(["pointerdown", "input", "pointerup"]);
// src/components/InteractionHelp.tsx
var _tmpl$9 = /* @__PURE__ */ template('<div class="fixed inset-0 z-overlay flex items-center justify-center p-lg bg-black/65 pointer-events-auto"role=dialog aria-modal=true><div><div class="flex items-center justify-between gap-md mb-lg"><h2 class="m-0 font-sans textsize-lg font-700"></h2><button type=button>×</button></div><div class="grid gap-lg text-left font-sans textsize-md leading-[1.45]">'), _tmpl$23 = /* @__PURE__ */ template('<section><h3 class="m-0 mb-sm textsize-md font-700"></h3><ul class="m-0 pl-xl">'), _tmpl$33 = /* @__PURE__ */ template('<li class="mb-xs last:mb-0">'), _tmpl$43 = /* @__PURE__ */ template("<strong>"), SECTIONS = Object.entries(texts_default.help.content);
function InteractionHelp(props) {
let reader = untrack(() => props.variant === "reader");
return onMount(() => {
let closeOnEscape = (event) => {
event.key === "Escape" && (event.preventDefault(), event.stopImmediatePropagation(), props.onClose());
};
window.addEventListener("keydown", closeOnEscape, !0), onCleanup(() => window.removeEventListener("keydown", closeOnEscape, !0));
}), (() => {
var _el$ = _tmpl$9(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.firstChild, _el$5 = _el$4.nextSibling, _el$6 = _el$3.nextSibling;
return _el$.addEventListener("wheel", (event) => event.stopPropagation()), _el$.$$pointerdown = (event) => event.stopPropagation(), _el$.$$click = (event) => {
event.stopPropagation(), event.target === event.currentTarget && props.onClose();
}, className(_el$2, `box-border w-full max-w-520px max-h-[min(720px,calc(100dvh-32px))] overflow-y-auto overscroll-contain p-xl coarse:p-lg rounded-lg border shadow-xl ${reader ? "ehpeek-reader-help-panel border-[var(--color-border)] bg-[var(--color-background)] text-[var(--color-text)]" : "ehp-color-site-border ehp-color-site-elevated ehp-color-site-text"}`), insert(_el$4, () => texts_default.help.title), _el$5.$$click = () => props.onClose(), className(_el$5, `inline-flex w-40px h-40px flex-none items-center justify-center p-0 rounded-md border bg-transparent font-inherit textsize-xl cursor-pointer ${reader ? "border-[var(--color-border)] text-[var(--color-text)]" : "ehp-color-site-border ehp-color-site-text"}`), insert(_el$6, createComponent(For, {
each: SECTIONS,
children: ([title, items]) => (() => {
var _el$7 = _tmpl$23(), _el$8 = _el$7.firstChild, _el$9 = _el$8.nextSibling;
return insert(_el$8, title), insert(_el$9, createComponent(For, {
each: items,
children: (item) => (() => {
var _el$0 = _tmpl$33();
return insert(_el$0, createComponent(HelpText, {
text: item
})), _el$0;
})()
})), _el$7;
})()
})), createRenderEffect((_p$) => {
var _v$ = texts_default.help.title, _v$2 = texts_default.button.close, _v$3 = texts_default.button.close;
return _v$ !== _p$.e && setAttribute(_el$, "aria-label", _p$.e = _v$), _v$2 !== _p$.t && setAttribute(_el$5, "aria-label", _p$.t = _v$2), _v$3 !== _p$.a && setAttribute(_el$5, "title", _p$.a = _v$3), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$;
})();
}
function HelpText(props) {
return createComponent(For, {
get each() {
return props.text.split(/(\*\*[^*]+\*\*)/g);
},
children: (part) => part.startsWith("**") && part.endsWith("**") ? (() => {
var _el$1 = _tmpl$43();
return insert(_el$1, () => part.slice(2, -2)), _el$1;
})() : part
});
}
delegateEvents(["click", "pointerdown"]);
// src/components/Reader/Toolbar.tsx
var _tmpl$10 = /* @__PURE__ */ template('<div class="flex flex-row gap-md coarse:gap-lg"><button type=button></button><button type=button></button><button type=button></button><button type=button></button><button type=button>'), _tmpl$24 = /* @__PURE__ */ template('<div class="ehpeek-reader-fullscreen-status fixed z-3 flex items-center gap-sm pointer-events-none top-[calc(10px+env(safe-area-inset-top,0px))] left-[max(10px,env(safe-area-inset-left,0px))] py-xs px-md rounded-md bg-[var(--color-badge)] ehp-color-text font-sans textsize-md font-600 leading-[1.4] whitespace-nowrap"role=status><span>'), _tmpl$34 = /* @__PURE__ */ template('<div class="fixed inset-0 z-overlay flex items-center justify-center p-lg bg-black/65 pointer-events-auto"role=dialog aria-modal=true><div class="ehpeek-reader-download-dialog-panel w-full max-w-480px p-lg rounded-lg border border-[var(--color-border)] bg-[var(--color-background)] text-[var(--color-text)] shadow-xl"><div class="flex items-center justify-between gap-md mb-lg"><div class="font-sans textsize-md font-700"></div><button type=button></button></div><div class="grid gap-md font-sans textsize-md">'), _tmpl$44 = /* @__PURE__ */ template('<div class=contents><div class="fixed z-2 flex justify-end transition-[opacity,transform] duration-160 ease-in-out right-[max(12px,env(safe-area-inset-right,0px))] bottom-[calc(var(--ui-control-size-lg)*2+44px+env(safe-area-inset-bottom,0px))] [&[data-open=false]]:opacity-0 [&[data-open=false]]:translate-y-[calc(100%+16px)] [&[data-open=false]]:pointer-events-none"><div class="ehpeek-reader-floating-actions flex flex-col gap-sm"><button type=button></button><button type=button></button><button type=button></button></div></div><div class="ehpeek-reader-toolbar fixed z-3 flex justify-end pointer-events-none top-[calc(10px+env(safe-area-inset-top,0px))] right-10px coarse:top-[calc(8px+env(safe-area-inset-top,0px))] coarse:right-8px"><div><div class="flex flex-row gap-md coarse:gap-lg"><button type=button></button><button type=button></button><button type=button>?</button><button type=button></button></div></div></div><div class="ehpeek-reader-page-number fixed z-3 pointer-events-none top-[calc(10px+env(safe-area-inset-top,0px))] left-[max(10px,env(safe-area-inset-left,0px))] right-auto min-w-0 max-w-[calc(100vw-20px)] py-xs px-md rounded-md bg-[var(--color-badge)] ehp-color-text font-sans textsize-md font-600 leading-[1.4] whitespace-nowrap text-left"></div><div class="fixed z-2 flex items-center p-0 transition-[opacity,transform] duration-160 ease-in-out right-[max(12px,env(safe-area-inset-right,0px))] bottom-[calc(12px+env(safe-area-inset-bottom,0px))] left-[max(12px,env(safe-area-inset-left,0px))] [&[data-open=false]]:opacity-0 [&[data-open=false]]:translate-y-[calc(100%+16px)] [&[data-open=false]]:pointer-events-none">'), _tmpl$53 = /* @__PURE__ */ template('<div class="ehpeek-reader-control-change fixed z-overlay top-1/2 left-1/2 w-max max-w-[calc(100vw-32px)] -translate-x-1/2 -translate-y-1/2 pointer-events-none rounded-lg bg-[var(--color-badge)] ehp-color-text px-xl py-lg font-sans textsize-lg font-700 leading-[1.3] whitespace-pre-line text-center shadow-xl">'), _tmpl$63 = /* @__PURE__ */ template('<div class="grid gap-md"><button type=button><span class="textsize-md font-700"></span><span class="max-w-full overflow-hidden text-ellipsis whitespace-nowrap textsize-sm opacity-75"></span></button><button type=button><span class="textsize-md font-700"></span><span class="textsize-sm opacity-75">'), READER_BUTTON_CLASS = ["inline-flex min-w-[var(--ui-control-size-md)] h-[var(--ui-control-size-md)] items-center justify-center px-md py-0 rounded-md large:px-lg large:rounded-lg", "border border-[var(--color-border)] bg-[var(--color-control)] text-[var(--color-text)] cursor-pointer font-sans textsize-md font-700 leading-1 disabled:opacity-40 disabled:cursor-default"].join(" "), READER_FLOATING_ACTION_CLASS = [READER_BUTTON_CLASS, "!min-w-[var(--ui-control-size-lg)] !h-[var(--ui-control-size-lg)] opacity-85 hover:opacity-100 focus-visible:opacity-100 transition-opacity duration-160"].join(" "), READER_FLOATING_ICON_ACTION_CLASS = `${READER_FLOATING_ACTION_CLASS} !w-[calc(var(--ui-control-size-lg)*2)] px-0`, READER_ICON_SIZE = "var(--ui-icon-size-md)", TIME_FORMATTER = new Intl.DateTimeFormat(void 0, {
hour: "2-digit",
minute: "2-digit"
}), DOWNLOAD_OPTION_CLASS = ["flex w-full min-h-lg flex-col items-start justify-center gap-xs px-lg py-md rounded-md", "border border-[var(--color-border)] bg-[var(--color-control)] text-[var(--color-text)] cursor-pointer text-left", "hover:bg-[var(--color-badge)] disabled:opacity-40 disabled:cursor-default"].join(" ");
function Toolbar(props) {
let [downloadDialogPageNum, setDownloadDialogPageNum] = createSignal(null), [helpOpen, setHelpOpen] = createSignal(!1), [moreOpen, setMoreOpen] = createSignal(!1), [controlChange, setControlChange] = createSignal(null), controlChangeTimer = null, fullscreenTime = createFullscreenTime(() => props.fullscreenActive), showControlChange = (message) => {
controlChangeTimer !== null && window.clearTimeout(controlChangeTimer), setControlChange(message), controlChangeTimer = window.setTimeout(() => {
setControlChange(null), controlChangeTimer = null;
}, 1200);
};
return onCleanup(() => {
controlChangeTimer !== null && window.clearTimeout(controlChangeTimer);
}), createEffect(() => {
props.open || setMoreOpen(!1);
}), createEffect(() => {
let pageNum = downloadDialogPageNum();
pageNum !== null && pageNum !== props.progress.pageNum && setDownloadDialogPageNum(null);
}), createEffect(() => {
if (downloadDialogPageNum() === null)
return;
let closeOnEscape = (event) => {
event.key === "Escape" && (event.preventDefault(), event.stopPropagation(), setDownloadDialogPageNum(null));
};
window.addEventListener("keydown", closeOnEscape, !0), onCleanup(() => window.removeEventListener("keydown", closeOnEscape, !0));
}), (() => {
var _el$ = _tmpl$44(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.firstChild, _el$5 = _el$4.nextSibling, _el$6 = _el$5.nextSibling, _el$7 = _el$2.nextSibling, _el$8 = _el$7.firstChild, _el$9 = _el$8.firstChild, _el$0 = _el$9.firstChild, _el$1 = _el$0.nextSibling, _el$10 = _el$1.nextSibling, _el$11 = _el$10.nextSibling, _el$18 = _el$7.nextSibling, _el$21 = _el$18.nextSibling;
return addEventListener(_el$2, "wheel", stopEvent), addEventListener(_el$2, "pointerdown", stopEvent, !0), addEventListener(_el$2, "click", stopEvent, !0), _el$4.$$click = () => props.callbacks.onOpenScrollPreviewClick(), className(_el$4, READER_FLOATING_ICON_ACTION_CLASS), insert(_el$4, createComponent(Icon, {
name: "grid",
size: READER_ICON_SIZE
})), _el$5.$$click = () => props.callbacks.onFullscreenClick(), className(_el$5, READER_FLOATING_ICON_ACTION_CLASS), insert(_el$5, createComponent(Icon, {
get name() {
return props.fullscreenActive ? "fullscreen-exit" : "fullscreen";
},
size: READER_ICON_SIZE
})), _el$6.$$click = () => setDownloadDialogPageNum(props.progress.pageNum), className(_el$6, READER_FLOATING_ICON_ACTION_CLASS), insert(_el$6, createComponent(Icon, {
name: "download",
size: READER_ICON_SIZE
})), addEventListener(_el$7, "wheel", stopEvent), addEventListener(_el$7, "pointerdown", stopEvent, !0), addEventListener(_el$7, "click", stopEvent, !0), _el$0.$$click = () => props.callbacks.onOpenOriginalPageClick(), className(_el$0, READER_BUTTON_CLASS), insert(_el$0, createComponent(Icon, {
name: "external-link",
size: READER_ICON_SIZE
})), _el$1.$$click = () => setMoreOpen((open) => !open), className(_el$1, READER_BUTTON_CLASS), insert(_el$1, createComponent(Icon, {
name: "book-open",
size: READER_ICON_SIZE
})), _el$10.$$click = () => setHelpOpen(!0), className(_el$10, READER_BUTTON_CLASS), _el$11.$$click = () => props.callbacks.onCloseClick(), className(_el$11, READER_BUTTON_CLASS), insert(_el$11, createComponent(Icon, {
name: "close",
size: READER_ICON_SIZE
})), insert(_el$8, createComponent(Show, {
get when() {
return moreOpen();
},
get children() {
var _el$12 = _tmpl$10(), _el$13 = _el$12.firstChild, _el$14 = _el$13.nextSibling, _el$15 = _el$14.nextSibling, _el$16 = _el$15.nextSibling, _el$17 = _el$16.nextSibling;
return _el$13.$$click = () => {
let navigationMode = props.controls.navigationMode === "scroll" ? "paged" : "scroll";
props.callbacks.onControlsChange({
...props.controls,
navigationMode
}), showControlChange(navigationMode === "paged" ? texts_default.reader.pagedMode : texts_default.reader.scrollMode);
}, className(_el$13, READER_BUTTON_CLASS), insert(_el$13, createComponent(Icon, {
get name() {
return props.controls.navigationMode === "paged" ? "page" : "scroll-continuous";
},
size: READER_ICON_SIZE
})), _el$14.$$click = () => {
let direction = props.controls.direction === "rtl" ? "ltr" : props.controls.direction === "ltr" ? "ttb" : "rtl";
props.callbacks.onControlsChange({
...props.controls,
direction
}), showControlChange(direction === "rtl" ? texts_default.reader.directionRtl : direction === "ltr" ? texts_default.reader.directionLtr : texts_default.reader.directionTtb);
}, className(_el$14, READER_BUTTON_CLASS), insert(_el$14, createComponent(Icon, {
get name() {
return memo(() => props.controls.direction === "rtl")() ? "arrow-left" : props.controls.direction === "ltr" ? "arrow-right" : "arrow-down";
},
size: READER_ICON_SIZE
})), _el$15.$$click = () => {
let pageLayout = props.controls.pageLayout === "single" ? "double" : "single";
props.callbacks.onControlsChange({
...props.controls,
pageLayout
}), showControlChange(pageLayout === "double" ? texts_default.reader.doublePageMode : texts_default.reader.singlePageMode);
}, className(_el$15, READER_BUTTON_CLASS), insert(_el$15, () => props.controls.pageLayout === "double" ? "2P" : "1P"), _el$16.$$click = () => {
let rightTapAction = props.controls.rightTapAction === "previous" ? "next" : "previous";
props.callbacks.onControlsChange({
...props.controls,
rightTapAction
}), showControlChange(rightTapAction === "previous" ? texts_default.reader.rightTapPrevious : texts_default.reader.rightTapNext);
}, className(_el$16, READER_BUTTON_CLASS), insert(_el$16, () => props.controls.rightTapAction === "previous" ? "R-" : "R+"), _el$17.$$click = () => props.callbacks.onViewportAdjustClick(), className(_el$17, READER_BUTTON_CLASS), insert(_el$17, createComponent(Icon, {
name: "viewport",
size: READER_ICON_SIZE
})), createRenderEffect((_p$) => {
var _v$ = props.controls.navigationMode === "scroll" ? texts_default.reader.scrollMode : texts_default.reader.pagedMode, _v$2 = props.controls.navigationMode === "scroll" ? texts_default.reader.scrollMode : texts_default.reader.pagedMode, _v$3 = props.controls.direction === "rtl" ? texts_default.reader.directionRtl : props.controls.direction === "ltr" ? texts_default.reader.directionLtr : texts_default.reader.directionTtb, _v$4 = props.controls.pageLayout === "double" ? texts_default.reader.doublePageMode : texts_default.reader.singlePageMode, _v$5 = props.controls.navigationMode !== "paged", _v$6 = props.controls.rightTapAction === "previous" ? texts_default.reader.rightTapPrevious : texts_default.reader.rightTapNext, _v$7 = texts_default.reader.adjustScrollViewport, _v$8 = texts_default.reader.adjustScrollViewport, _v$9 = props.controls.navigationMode !== "scroll";
return _v$ !== _p$.e && setAttribute(_el$13, "aria-label", _p$.e = _v$), _v$2 !== _p$.t && setAttribute(_el$13, "title", _p$.t = _v$2), _v$3 !== _p$.a && setAttribute(_el$14, "aria-label", _p$.a = _v$3), _v$4 !== _p$.o && setAttribute(_el$15, "aria-label", _p$.o = _v$4), _v$5 !== _p$.i && (_el$15.disabled = _p$.i = _v$5), _v$6 !== _p$.n && setAttribute(_el$16, "aria-label", _p$.n = _v$6), _v$7 !== _p$.s && setAttribute(_el$17, "aria-label", _p$.s = _v$7), _v$8 !== _p$.h && setAttribute(_el$17, "title", _p$.h = _v$8), _v$9 !== _p$.r && (_el$17.disabled = _p$.r = _v$9), _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0,
h: void 0,
r: void 0
}), _el$12;
}
}), null), insert(_el$18, () => pageNumberText(props.progress.pageNum, props.progress.totalPages, props.controls.navigationMode, props.controls.pageLayout)), insert(_el$, createComponent(Show, {
get when() {
return props.fullscreenActive;
},
get children() {
var _el$19 = _tmpl$24(), _el$20 = _el$19.firstChild;
return insert(_el$20, fullscreenTime), _el$19;
}
}), _el$21), insert(_el$, createComponent(Show, {
get when() {
return controlChange();
},
keyed: !0,
children: (message) => (() => {
var _el$28 = _tmpl$53();
return insert(_el$28, message), _el$28;
})()
}), _el$21), addEventListener(_el$21, "wheel", stopEvent), addEventListener(_el$21, "pointerdown", stopEvent, !0), addEventListener(_el$21, "click", stopEvent, !0), insert(_el$21, createComponent(ProgressBar, {
class: "ehpeek-reader-progress textsize-lg",
get direction() {
return props.controls.direction === "rtl" ? "rtl" : "ltr";
},
get fillPercent() {
return progressFillPercent(props.progress);
},
get keepInputValue() {
return props.progress.keepInputValue;
},
get max() {
return Math.max(1, props.progress.maxProgressPageNum);
},
min: 1,
step: 1,
get value() {
return props.progress.pageNum;
},
get onPointerDown() {
return props.callbacks.onProgressPointerDown;
},
get onInput() {
return props.callbacks.onProgressInput;
},
get onCommit() {
return props.callbacks.onProgressCommit;
}
})), insert(_el$, createComponent(Show, {
get when() {
return memo(() => downloadDialogPageNum() !== null)() && props.downloadInfos.length > 0;
},
get children() {
var _el$22 = _tmpl$34(), _el$23 = _el$22.firstChild, _el$24 = _el$23.firstChild, _el$25 = _el$24.firstChild, _el$26 = _el$25.nextSibling, _el$27 = _el$24.nextSibling;
return addEventListener(_el$22, "wheel", stopEvent), addEventListener(_el$22, "pointerdown", stopEvent, !0), _el$22.$$click = (event) => {
event.stopPropagation(), event.target === event.currentTarget && setDownloadDialogPageNum(null);
}, insert(_el$25, () => `${texts_default.reader.download} · ${props.downloadInfos.map((info) => info.pageNum).join(", ")}`), _el$26.$$click = () => setDownloadDialogPageNum(null), className(_el$26, READER_BUTTON_CLASS), insert(_el$26, createComponent(Icon, {
name: "close",
size: READER_ICON_SIZE
})), insert(_el$27, createComponent(For, {
get each() {
return props.downloadInfos;
},
children: (downloadInfo) => (() => {
var _el$29 = _tmpl$63(), _el$30 = _el$29.firstChild, _el$31 = _el$30.firstChild, _el$32 = _el$31.nextSibling, _el$33 = _el$30.nextSibling, _el$34 = _el$33.firstChild, _el$35 = _el$34.nextSibling;
return _el$30.$$click = () => startImageDownload(downloadInfo.currentImageUrl, downloadInfo.currentFileName), className(_el$30, DOWNLOAD_OPTION_CLASS), insert(_el$31, () => `${texts_default.reader.downloadDisplayedImage} · ${downloadInfo.pageNum}`), insert(_el$32, () => downloadInfo.currentFileName), _el$33.$$click = () => {
downloadInfo.originalImageUrl && startImageDownload(downloadInfo.originalImageUrl);
}, className(_el$33, DOWNLOAD_OPTION_CLASS), insert(_el$34, () => `${texts_default.reader.downloadOriginalImage} · ${downloadInfo.pageNum}`), insert(_el$35, (() => {
var _c$ = memo(() => !!downloadInfo.originalImageUrl);
return () => _c$() ? texts_default.reader.originalImageSource : texts_default.reader.originalImageUnavailable;
})()), createRenderEffect(() => _el$33.disabled = !downloadInfo.originalImageUrl), _el$29;
})()
})), createRenderEffect((_p$) => {
var _v$0 = texts_default.reader.download, _v$1 = texts_default.button.close, _v$10 = texts_default.button.close;
return _v$0 !== _p$.e && setAttribute(_el$22, "aria-label", _p$.e = _v$0), _v$1 !== _p$.t && setAttribute(_el$26, "title", _p$.t = _v$1), _v$10 !== _p$.a && setAttribute(_el$26, "aria-label", _p$.a = _v$10), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$22;
}
}), null), insert(_el$, createComponent(Show, {
get when() {
return helpOpen();
},
get children() {
return createComponent(InteractionHelp, {
variant: "reader",
onClose: () => setHelpOpen(!1)
});
}
}), null), createRenderEffect((_p$) => {
var _v$11 = String(props.open), _v$12 = texts_default.gallery.scrollPreview, _v$13 = texts_default.gallery.scrollPreview, _v$14 = props.fullscreenActive ? texts_default.reader.exitFullscreen : texts_default.reader.fullscreen, _v$15 = props.fullscreenActive ? texts_default.reader.exitFullscreen : texts_default.reader.fullscreen, _v$16 = props.downloadInfos.length === 0, _v$17 = texts_default.reader.download, _v$18 = texts_default.reader.download, _v$19 = `ehpeek-reader-toolbar-buttons flex flex-col items-end gap-md coarse:gap-lg pointer-events-auto${props.open ? "" : " !hidden"}`, _v$20 = texts_default.reader.readingOptions, _v$21 = texts_default.reader.readingOptions, _v$22 = moreOpen(), _v$23 = texts_default.help.title, _v$24 = texts_default.help.title, _v$25 = texts_default.button.close, _v$26 = texts_default.button.close, _v$27 = props.controls.navigationMode === "scroll" && !props.open && !props.fullscreenActive, _v$28 = String(props.open);
return _v$11 !== _p$.e && setAttribute(_el$2, "data-open", _p$.e = _v$11), _v$12 !== _p$.t && setAttribute(_el$4, "aria-label", _p$.t = _v$12), _v$13 !== _p$.a && setAttribute(_el$4, "title", _p$.a = _v$13), _v$14 !== _p$.o && setAttribute(_el$5, "aria-label", _p$.o = _v$14), _v$15 !== _p$.i && setAttribute(_el$5, "title", _p$.i = _v$15), _v$16 !== _p$.n && (_el$6.disabled = _p$.n = _v$16), _v$17 !== _p$.s && setAttribute(_el$6, "aria-label", _p$.s = _v$17), _v$18 !== _p$.h && setAttribute(_el$6, "title", _p$.h = _v$18), _v$19 !== _p$.r && className(_el$8, _p$.r = _v$19), _v$20 !== _p$.d && setAttribute(_el$1, "aria-label", _p$.d = _v$20), _v$21 !== _p$.l && setAttribute(_el$1, "title", _p$.l = _v$21), _v$22 !== _p$.u && setAttribute(_el$1, "aria-expanded", _p$.u = _v$22), _v$23 !== _p$.c && setAttribute(_el$10, "aria-label", _p$.c = _v$23), _v$24 !== _p$.w && setAttribute(_el$10, "title", _p$.w = _v$24), _v$25 !== _p$.m && setAttribute(_el$11, "aria-label", _p$.m = _v$25), _v$26 !== _p$.f && setAttribute(_el$11, "title", _p$.f = _v$26), _v$27 !== _p$.y && (_el$18.hidden = _p$.y = _v$27), _v$28 !== _p$.g && setAttribute(_el$21, "data-open", _p$.g = _v$28), _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0,
h: void 0,
r: void 0,
d: void 0,
l: void 0,
u: void 0,
c: void 0,
w: void 0,
m: void 0,
f: void 0,
y: void 0,
g: void 0
}), _el$;
})();
}
function startImageDownload(url, name) {
try {
return GM_download({
url,
...name ? {
name
} : {},
onerror: (error) => {
console.error("[ehpeek]", error), window.alert(texts_default.errors.downloadFailed);
}
}), !0;
} catch (error) {
return console.error("[ehpeek]", error), window.alert(texts_default.errors.downloadFailed), !1;
}
}
function createFullscreenTime(enabled) {
let [time, setTime] = createSignal(TIME_FORMATTER.format(/* @__PURE__ */ new Date()));
return createEffect(() => {
if (!enabled())
return;
let updateTime = () => setTime(TIME_FORMATTER.format(/* @__PURE__ */ new Date()));
updateTime();
let interval = null, timeout = window.setTimeout(() => {
updateTime(), interval = window.setInterval(updateTime, 6e4);
}, 6e4 - Date.now() % 6e4);
onCleanup(() => {
window.clearTimeout(timeout), interval !== null && window.clearInterval(interval);
});
}), time;
}
function progressFillPercent(progress) {
let max = Math.max(1, progress.maxProgressPageNum), value = Math.min(max, Math.max(1, progress.pageNum));
return max > 1 ? (value - 1) / (max - 1) * 100 : 100;
}
function pageNumberText(pageNum, totalPages, navigationMode, pageLayout) {
if (totalPages && pageNum === totalPages + 1)
return texts_default.reader.endPage;
if (!totalPages)
return navigationMode === "paged" && pageLayout === "double" ? `${pageNum}–${pageNum + 1}` : String(pageNum);
let doublePageEnd = Math.min(totalPages, pageNum + 1);
return navigationMode === "paged" && pageLayout === "double" && doublePageEnd > pageNum ? `${pageNum}–${doublePageEnd} / ${totalPages}` : `${pageNum} / ${totalPages}`;
}
delegateEvents(["click", "pointerdown"]);
// src/components/Widgets/PriorityLoadQueue.ts
var PriorityLoadQueue = class {
constructor(concurrentLoads = 6) {
this.concurrentLoads = concurrentLoads;
this.pending = /* @__PURE__ */ new Map();
this.active = /* @__PURE__ */ new Set();
this.timer = null;
this.disposed = !1;
this.callbacks = {};
}
updateCallbacks(callbacks) {
this.callbacks = callbacks, this.schedule();
}
dispose() {
this.disposed = !0, this.pending.clear(), this.timer !== null && (window.clearTimeout(this.timer), this.timer = null);
}
sync(targets) {
this.pending = new Map(targets.filter(({ key }) => !this.active.has(key)).map((target) => [target.key, target])), this.schedule();
}
schedule() {
this.timer !== null || this.disposed || !this.callbacks.loadTarget || (this.timer = window.setTimeout(() => {
this.timer = null, this.process();
}, 0));
}
process() {
if (!this.disposed)
for (; this.active.size < this.concurrentLoads; ) {
let next = Array.from(this.pending.values()).sort((left, right) => left.priority - right.priority)[0];
if (!next)
return;
this.pending.delete(next.key), this.start(next);
}
}
start({ key, target }) {
let { loadTarget, markLoading, onLoaded, onError } = this.callbacks;
if (!loadTarget || !markLoading || !onLoaded || !onError)
return;
let token = markLoading(target);
token !== null && (this.active.add(key), loadTarget(target).then((loaded) => {
this.disposed || onLoaded(target, loaded, token);
}).catch((error) => {
this.disposed || onError(target, error, token);
}).finally(() => {
this.active.delete(key), this.process();
}));
}
};
// src/components/Widgets/VerticalPositionBar.tsx
var _tmpl$11 = /* @__PURE__ */ template("<div role=scrollbar><div></div><div><span>"), VARIANT_CLASS = {
reader: {
collapsedFillWidth: "w-20px coarse:w-24px",
collapsedInteractionWidth: "w-20px coarse:w-40px",
expandedFillWidth: "w-36px coarse:w-48px",
expandedInteractionWidth: "w-36px coarse:w-64px",
fill: "bg-[var(--color-reader-scrollbar,var(--color-muted))]",
track: "bg-[var(--color-reader-border,var(--color-border))]",
trackWidth: "right-4px w-6px"
},
site: {
collapsedFillWidth: "w-10px coarse:w-14px",
collapsedInteractionWidth: "w-14px coarse:w-24px",
expandedFillWidth: "w-[calc(var(--ui-control-size-sm)/2)]",
expandedInteractionWidth: "w-[calc(var(--ui-control-size-sm)/2)]",
fill: "bg-[var(--color-text)] opacity-70",
track: "bg-[var(--color-border)]",
trackWidth: "right-2px w-3px"
}
};
function VerticalPositionBar(props) {
let [dragging, setDragging] = createSignal(!1), track, thumb, dragOffset = 0, classes = () => VARIANT_CLASS[props.variant], minValue = () => props.minValue ?? 1, valueRange = () => Math.max(0, props.maxValue - minValue()), expanded = () => !!props.expanded || dragging(), visible = () => props.visible !== !1 || dragging(), interactionWidth = () => expanded() ? classes().expandedInteractionWidth : classes().collapsedInteractionWidth, fillWidth = () => expanded() ? classes().expandedFillWidth : classes().collapsedFillWidth, position = () => valueRange() === 0 ? 0 : (props.currentValue - minValue()) / valueRange() * 100, visibleRatio = () => clamp((props.visibleValueCount ?? 1) / Math.max(1, valueRange() + 1), 0, 1), valueAt = (clientY) => {
let trackRect = track.getBoundingClientRect(), travel = Math.max(1, trackRect.height - thumb.offsetHeight), ratio = clamp((clientY - trackRect.top - dragOffset) / travel, 0, 1);
return Math.round(minValue() + ratio * valueRange());
}, inputAt = (clientY) => {
let value = valueAt(clientY);
return props.onInput(value), value;
};
return (() => {
var _el$ = _tmpl$11(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.firstChild;
_el$.addEventListener("wheel", (event) => event.stopPropagation()), _el$.addEventListener("pointercancel", (event) => {
dragging() && (setDragging(!1), track.releasePointerCapture(event.pointerId), props.onCommit?.(props.currentValue));
}), _el$.$$pointerup = (event) => {
if (!dragging())
return;
setDragging(!1);
let value = inputAt(event.clientY);
track.releasePointerCapture(event.pointerId), props.onCommit?.(value);
}, _el$.$$pointermove = (event) => {
dragging() && inputAt(event.clientY);
}, _el$.$$pointerdown = (event) => {
event.preventDefault(), event.stopPropagation(), setDragging(!0), track.setPointerCapture(event.pointerId);
let thumbRect = thumb.getBoundingClientRect();
dragOffset = event.target instanceof Node && thumb.contains(event.target) ? event.clientY - thumbRect.top : thumbRect.height / 2, props.onPointerDown?.(event), inputAt(event.clientY);
}, _el$.$$contextmenu = (event) => {
event.preventDefault(), event.stopPropagation();
}, _el$.$$click = (event) => event.stopPropagation();
var _ref$ = track;
typeof _ref$ == "function" ? use(_ref$, _el$) : track = _el$;
var _ref$2 = thumb;
return typeof _ref$2 == "function" ? use(_ref$2, _el$3) : thumb = _el$3, createRenderEffect((_p$) => {
var _v$ = `ehpeek-position-bar ${props.class ?? ""} ${props.position === "fixed" ? "fixed" : "absolute"} inset-y-0 right-0 z-2 ${interactionWidth()} touch-none select-none transition-[width,opacity] duration-160 ease-in-out [--ehpeek-position-bar-thumb-min:calc(var(--ui-control-size-md)*1.5)] [--ehpeek-position-bar-thumb-max:calc(var(--ui-control-size-xl)*4)] ${visible() ? "opacity-100" : "opacity-0 pointer-events-none"}`, _v$2 = props.ariaLabel, _v$3 = minValue(), _v$4 = props.maxValue, _v$5 = props.currentValue, _v$6 = `absolute inset-y-0 ${classes().trackWidth} ${classes().track}`, _v$7 = `ehpeek-position-bar-thumb absolute right-0 flex ${interactionWidth()} items-center justify-end cursor-grab active:cursor-grabbing transition-[width,height] duration-160`, _v$8 = `clamp(var(--ehpeek-position-bar-thumb-min), ${visibleRatio() * 100}%, var(--ehpeek-position-bar-thumb-max))`, _v$9 = `${position()}%`, _v$0 = `translateY(-${position()}%)`, _v$1 = `block h-full rounded-l-md ${classes().fill} ${fillWidth()} shadow-[0_2px_10px_var(--color-shadow-control)] transition-[width,opacity] duration-160`;
return _v$ !== _p$.e && className(_el$, _p$.e = _v$), _v$2 !== _p$.t && setAttribute(_el$, "aria-label", _p$.t = _v$2), _v$3 !== _p$.a && setAttribute(_el$, "aria-valuemin", _p$.a = _v$3), _v$4 !== _p$.o && setAttribute(_el$, "aria-valuemax", _p$.o = _v$4), _v$5 !== _p$.i && setAttribute(_el$, "aria-valuenow", _p$.i = _v$5), _v$6 !== _p$.n && className(_el$2, _p$.n = _v$6), _v$7 !== _p$.s && className(_el$3, _p$.s = _v$7), _v$8 !== _p$.h && setStyleProperty(_el$3, "height", _p$.h = _v$8), _v$9 !== _p$.r && setStyleProperty(_el$3, "top", _p$.r = _v$9), _v$0 !== _p$.d && setStyleProperty(_el$3, "transform", _p$.d = _v$0), _v$1 !== _p$.l && className(_el$4, _p$.l = _v$1), _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0,
h: void 0,
r: void 0,
d: void 0,
l: void 0
}), _el$;
})();
}
delegateEvents(["click", "contextmenu", "pointerdown", "pointermove", "pointerup"]);
// src/components/Enhance/ScrollPreview.tsx
var _tmpl$12 = /* @__PURE__ */ template('<div class="flex w-full justify-center my-sm"><button type=button class="inline-flex min-h-[var(--ui-control-size-xs)] items-center justify-center gap-sm px-md rounded-xl border-0 bg-[var(--color-site-surface)] ehp-color-site-text font-sans textsize-sm font-700 cursor-pointer transition-[background-color,transform] duration-120 hover:bg-[var(--color-site-item-hover)] active:scale-98">'), _tmpl$25 = /* @__PURE__ */ template('<span class="block w-[var(--ui-icon-size-sm)] h-[var(--ui-icon-size-sm)] box-border animate-spin rounded-full border-2px border-solid ehp-color-spinner">'), _tmpl$35 = /* @__PURE__ */ template('<section class="ehpeek-scroll-preview fixed inset-0 z-[1300] box-border flex w-full h-[100dvh] flex-col overflow-hidden bg-[var(--color-background)] text-[var(--color-text)] font-sans textsize-md leading-[1.4]"><div class="flex min-h-[var(--ui-control-size-md)] flex-none items-center justify-between gap-md bg-[var(--color-elevated)] pt-[max(8px,env(safe-area-inset-top,0px))] pr-[max(8px,env(safe-area-inset-right,0px))] pb-sm pl-[max(8px,env(safe-area-inset-left,0px))] border-0 border-b border-[var(--color-border)] textsize-sm"><span class="flex items-center gap-sm opacity-75"></span><div class="flex flex-none gap-sm"><button type=button></button><button type=button><span aria-hidden=true>X</span></button></div></div><div class="relative min-h-0 w-full flex-1"><div class="absolute inset-0 box-border overflow-y-auto overflow-x-hidden overscroll-contain bg-[var(--color-surface)] cursor-grab [touch-action:none] [&[data-dragging=true]]:cursor-grabbing [&[data-dragging=true]]:select-none [scrollbar-gutter:stable] [-webkit-overflow-scrolling:touch]"><div class="relative w-full"><div class="absolute left-0 right-0 grid gap-8px p-0">'), _tmpl$45 = /* @__PURE__ */ template('<div class="relative flex min-w-0 items-center justify-center overflow-hidden rounded-sm bg-[var(--color-background)]">'), _tmpl$54 = /* @__PURE__ */ template('<button type=button class="flex w-full h-full flex-col items-center justify-center gap-sm border-0 !bg-transparent text-[var(--color-text)] font-inherit textsize-sm cursor-default"><span>'), _tmpl$64 = /* @__PURE__ */ template('<span class="pointer-events-none block flex-none max-w-full max-h-full"role=img>'), _tmpl$72 = /* @__PURE__ */ template('<a class="absolute inset-0 text-[var(--color-text)] no-underline hover:no-underline active:no-underline">'), _tmpl$82 = /* @__PURE__ */ template('<span class="pointer-events-none absolute inset-0 z-1 box-border rounded-sm border-6 coarse:border-8 border-solid border-[var(--color-danger)]"aria-hidden=true>'), _tmpl$92 = /* @__PURE__ */ template('<img class="pointer-events-none block max-w-full max-h-full object-contain select-none [-webkit-user-drag:none]"alt decoding=async>'), GRID_GAP = 8, MAX_TILE_WIDTH = 220, MIN_TILE_HEIGHT = 170, MAX_TILE_HEIGHT = 290, OVERSCAN_ROWS = 4, PREVIEW_CONCURRENT_LOADS = 2, PREVIEW_LOAD_RADIUS = 2, DECODE_CACHE_BYTES = 64 * 1024 * 1024, DECODE_CACHE_ITEMS = 160;
function ScrollPreview(props) {
let previewCache = untrack(() => props.previewCache), onExitPreview = untrack(() => props.onExitPreview), onOpenPage = untrack(() => props.onOpenPage), [open, setOpen] = createSignal(!1), [portalMount, setPortalMount] = createSignal(document.body), [targetPreviewIndex, setTargetPreviewIndex] = createSignal(untrack(() => previewCache.current().data.currentIndex)), [highlightedPageNum, setHighlightedPageNum] = createSignal(null), [targetPageNum, setTargetPageNum] = createSignal(null), historyEntry = !1, closeRequested = !1, pendingClose = null, finishClose = (afterClose) => {
historyEntry = !1, closeRequested = !1, pendingClose = null, setOpen(!1), props.onOpenChange(!1), afterClose?.();
}, requestClose = (afterClose) => {
if (!closeRequested) {
if (historyEntry) {
closeRequested = !0, pendingClose = afterClose, window.history.back();
return;
}
finishClose(afterClose);
}
}, openPreview = () => {
if (!open()) {
setPortalMount(document.fullscreenElement instanceof HTMLElement ? document.fullscreenElement : document.body);
let currentState = window.history.state;
window.history.pushState({
...currentState !== null && typeof currentState == "object" ? currentState : {},
ehpeekScrollPreview: !0
}, "", window.location.href), historyEntry = !0, setOpen(!0), props.onOpenChange(!0);
}
}, onPopState = () => {
!open() || !historyEntry || finishClose(pendingClose ?? void 0);
};
return createEffect(() => {
props.actionsRef({
gotoPreview: (previewIndex) => {
setHighlightedPageNum(null), setTargetPageNum(null), setTargetPreviewIndex(previewIndex), openPreview();
},
gotoPage: (pageNum) => {
setHighlightedPageNum(pageNum), setTargetPageNum(pageNum), setTargetPreviewIndex(previewCache.previewIndexForPage(pageNum)), openPreview();
}
});
}), onMount(() => {
window.addEventListener("popstate", onPopState), onCleanup(() => window.removeEventListener("popstate", onPopState));
}), onCleanup(() => {
open() && props.onOpenChange(!1);
}), [(() => {
var _el$ = _tmpl$12(), _el$2 = _el$.firstChild;
return _el$2.$$click = () => {
setHighlightedPageNum(props.continuePageNum), setTargetPageNum(null), setTargetPreviewIndex(previewCache.current().data.currentIndex), openPreview();
}, insert(_el$2, createComponent(Icon, {
name: "grid",
size: "var(--ui-icon-size-sm)"
}), null), insert(_el$2, () => texts_default.gallery.scrollPreview, null), _el$;
})(), createComponent(Show, {
get when() {
return open();
},
get children() {
return createComponent(Portal, {
get mount() {
return portalMount();
},
get children() {
return createComponent(ScrollPreviewOverlay, {
get highlightedPageNum() {
return highlightedPageNum();
},
onClose: (previewIndex) => {
requestClose(() => onExitPreview(previewIndex));
},
get onLoadError() {
return props.onLoadError;
},
onOpenPage: (pageUrl, pageNum) => {
requestClose(() => onOpenPage(pageUrl, pageNum));
},
previewCache,
get targetPageNum() {
return targetPageNum();
},
get targetPreviewIndex() {
return targetPreviewIndex();
}
});
}
});
}
})];
}
function ScrollPreviewOverlay(props) {
let previewCache = untrack(() => props.previewCache), onClose = untrack(() => props.onClose), onLoadError = untrack(() => props.onLoadError), initialPreview = untrack(() => previewCache.current()), totalImages = initialPreview.data.totalImages, maxPreviewIndex = initialPreview.data.maxIndex, decodeCache = new PreviewDecodeCache(DECODE_CACHE_BYTES, DECODE_CACHE_ITEMS), flingAnimator = new ScrollFlingAnimator(), previewLoadQueue = new PriorityLoadQueue(PREVIEW_CONCURRENT_LOADS), requestedPreviewIndexes = /* @__PURE__ */ new Set(), [failedPreviewIndexes, setFailedPreviewIndexes] = createSignal(/* @__PURE__ */ new Set()), [horizontalDragOffset, setHorizontalDragOffset] = createSignal(0), [loadingCount, setLoadingCount] = createSignal(0), [previewLoadReady, setPreviewLoadReady] = createSignal(!1), [scrollTop, setScrollTop] = createSignal(0), [viewportHeight, setViewportHeight] = createSignal(1), [layout, setLayout] = createSignal({
columns: 1,
rowHeight: MIN_TILE_HEIGHT + GRID_GAP,
tileHeight: MIN_TILE_HEIGHT,
width: 1
}), scroller, overlay, dragDirection = null, dragStartScrollTop = null, scrollFrame = null, loadToken = 0, initialized = !1, disposed = !1, totalRows = createMemo(() => Math.ceil(totalImages / layout().columns)), totalHeight = createMemo(() => Math.max(1, totalRows() * layout().rowHeight - GRID_GAP)), visibleStartRow = createMemo(() => clamp(Math.floor(scrollTop() / layout().rowHeight) - OVERSCAN_ROWS, 0, Math.max(0, totalRows() - 1))), visibleEndRow = createMemo(() => clamp(Math.ceil((scrollTop() + viewportHeight()) / layout().rowHeight) + OVERSCAN_ROWS, visibleStartRow(), Math.max(0, totalRows() - 1))), visibleStartPageNum = createMemo(() => visibleStartRow() * layout().columns + 1), visibleEndPageNum = createMemo(() => Math.min(totalImages, (visibleEndRow() + 1) * layout().columns)), screenStartPageNum = createMemo(() => Math.floor(scrollTop() / layout().rowHeight) * layout().columns + 1), screenEndPageNum = createMemo(() => {
let bottom = Math.max(scrollTop(), scrollTop() + viewportHeight() - 1), endRow = Math.floor(bottom / layout().rowHeight);
return Math.min(totalImages, (endRow + 1) * layout().columns);
}), visibleSlots = createMemo(() => {
previewCache.previewDataVersion();
let slots = [];
for (let pageNum = visibleStartPageNum(); pageNum <= visibleEndPageNum(); pageNum += 1)
slots.push({
item: previewCache.previewItem(pageNum),
pageNum
});
return slots;
}), centeredPageNum = () => {
let currentLayout = layout(), centerRow = Math.floor((scrollTop() + viewportHeight() / 2) / currentLayout.rowHeight);
return clamp(centerRow * currentLayout.columns + Math.floor(currentLayout.columns / 2) + 1, 1, totalImages);
}, centeredPreviewIndex = () => previewCache.previewIndexForPage(centeredPageNum()), scrollPositionPage = () => {
let maxScrollTop = Math.max(0, totalHeight() - viewportHeight());
return maxScrollTop === 0 || totalImages <= 1 ? 1 : Math.round(1 + clamp(scrollTop() / maxScrollTop, 0, 1) * (totalImages - 1));
}, scrollToPositionPage = (pageNum) => {
flingAnimator.cancel();
let maxScrollTop = Math.max(0, totalHeight() - scroller.clientHeight), ratio = totalImages <= 1 ? 0 : (clamp(pageNum, 1, totalImages) - 1) / (totalImages - 1);
scroller.scrollTop = ratio * maxScrollTop, setScrollTop(scroller.scrollTop);
};
createPointerGestureElement(() => scroller ?? null, () => ({
dragAxis: "any",
onStart: () => {
flingAnimator.cancel(), dragDirection = null, dragStartScrollTop = scroller.scrollTop;
},
onMove: (info) => {
if (dragDirection === null && (dragDirection = Math.abs(info.dx) > Math.abs(info.dy) ? "horizontal" : "vertical"), dragDirection === "horizontal") {
setHorizontalDragOffset(info.dx);
return;
}
dragStartScrollTop !== null && (scroller.scrollTop = dragStartScrollTop - info.dy);
},
onEnd: (info) => {
if (dragStartScrollTop = null, dragDirection === "horizontal") {
let offset = horizontalDragOffset(), exit = Math.abs(offset) >= overlay.clientWidth * 0.2 || Math.abs(info.velocityX) >= 0.6;
if (dragDirection = null, exit) {
let direction = offset === 0 ? Math.sign(info.velocityX) || 1 : Math.sign(offset), previewIndex = centeredPreviewIndex();
overlay.animate([{
opacity: overlay.style.opacity,
transform: overlay.style.transform
}, {
opacity: 0.7,
transform: `translate3d(${direction * 100}vw, 0, 0) scale(var(--ehpeek-reader-fullscreen-ui-scale, 1)) scale(0.97)`
}], {
duration: 180,
easing: "cubic-bezier(0.2, 0.8, 0.2, 1)",
fill: "forwards"
}).finished.then(() => onClose(previewIndex));
return;
}
overlay.animate([{
opacity: overlay.style.opacity,
transform: overlay.style.transform
}, {
opacity: 1,
transform: "translate3d(0, 0, 0) scale(var(--ehpeek-reader-fullscreen-ui-scale, 1))"
}], {
duration: 180,
easing: "cubic-bezier(0.2, 0.8, 0.2, 1)"
}).finished.then(() => setHorizontalDragOffset(0));
return;
}
dragDirection = null, flingAnimator.start({
axis: "y",
scroller,
initialVelocity: -info.velocityY,
setScrollPosition: (position) => {
scroller.scrollTop = position;
},
canRun: () => !disposed && scroller.isConnected,
onStop: () => setScrollTop(scroller.scrollTop)
});
}
})), previewLoadQueue.updateCallbacks({
loadTarget: (previewIndex) => previewCache.load(previewIndex),
markLoading: (previewIndex) => requestedPreviewIndexes.has(previewIndex) ? null : (requestedPreviewIndexes.add(previewIndex), setFailedPreviewIndexes((current) => {
if (!current.has(previewIndex))
return current;
let next = new Set(current);
return next.delete(previewIndex), next;
}), setLoadingCount((count) => count + 1), ++loadToken),
onLoaded: () => {
setLoadingCount((count) => Math.max(0, count - 1));
},
onError: (previewIndex, error) => {
requestedPreviewIndexes.delete(previewIndex), setFailedPreviewIndexes((current) => new Set(current).add(previewIndex)), setLoadingCount((count) => Math.max(0, count - 1)), onLoadError(error);
}
});
let syncPreviewLoadQueue = (centerIndex, retryIndex) => {
let firstIndex = Math.max(0, centerIndex - PREVIEW_LOAD_RADIUS), lastIndex = Math.min(maxPreviewIndex, centerIndex + PREVIEW_LOAD_RADIUS), targets = [];
for (let previewIndex = firstIndex; previewIndex <= lastIndex; previewIndex += 1)
targets.push({
key: previewIndex,
priority: previewIndex === retryIndex ? -1 : Math.abs(previewIndex - centerIndex),
target: previewIndex
});
previewLoadQueue.sync(targets);
};
createEffect(() => {
if (!previewLoadReady())
return;
let centerIndex = previewCache.previewIndexForPage(centeredPageNum());
syncPreviewLoadQueue(centerIndex);
});
let scrollToPage = (pageNum, currentLayout = untrack(layout)) => {
let centeredTop = Math.floor((clamp(pageNum, 1, totalImages) - 1) / currentLayout.columns) * currentLayout.rowHeight - (scroller.clientHeight - currentLayout.tileHeight) / 2;
scroller.scrollTop = clamp(centeredTop, 0, Math.max(0, totalHeight() - scroller.clientHeight)), setScrollTop(scroller.scrollTop);
}, scrollToPreview = (previewIndex, currentLayout) => {
scrollToPage(previewIndex * initialPreview.data.pageSize + 1, currentLayout);
};
createEffect(() => {
let previewIndex = props.targetPreviewIndex, pageNum = props.targetPageNum;
initialized && scroller.isConnected && (pageNum === null ? scrollToPreview(previewIndex, untrack(layout)) : scrollToPage(pageNum, untrack(layout)));
});
let updateLayout = () => {
setPreviewLoadReady(!1);
let width = Math.max(1, scroller.clientWidth), height = Math.max(1, scroller.clientHeight), previous = untrack(layout), anchorPageIndex = Math.floor(untrack(scrollTop) / previous.rowHeight) * previous.columns, columns = Math.max(1, Math.ceil((width + GRID_GAP) / (MAX_TILE_WIDTH + GRID_GAP))), tileWidth = Math.max(1, (width - GRID_GAP * (columns - 1)) / columns), tileHeight = Math.round(clamp(tileWidth * 1.42, MIN_TILE_HEIGHT, MAX_TILE_HEIGHT)), next = {
columns,
rowHeight: tileHeight + GRID_GAP,
tileHeight,
width
};
setLayout(next), setViewportHeight(height), queueMicrotask(() => untrack(() => {
scroller.isConnected && (initialized ? (scroller.scrollTop = Math.floor(anchorPageIndex / next.columns) * next.rowHeight, setScrollTop(scroller.scrollTop)) : (initialized = !0, props.targetPageNum === null ? scrollToPreview(props.targetPreviewIndex, next) : scrollToPage(props.targetPageNum, next)), setPreviewLoadReady(!0));
}));
};
return onMount(() => {
let previousBodyOverflow = document.body.style.overflow, previousHtmlOverflow = document.documentElement.style.overflow;
document.body.style.overflow = "hidden", document.documentElement.style.overflow = "hidden";
let resizeObserver = new ResizeObserver(updateLayout);
resizeObserver.observe(scroller), updateLayout(), onCleanup(() => {
disposed = !0, flingAnimator.cancel(), previewLoadQueue.dispose(), resizeObserver.disconnect(), document.body.style.overflow = previousBodyOverflow, document.documentElement.style.overflow = previousHtmlOverflow, decodeCache.dispose(), scrollFrame !== null && window.cancelAnimationFrame(scrollFrame);
});
}), (() => {
var _el$3 = _tmpl$35(), _el$4 = _el$3.firstChild, _el$5 = _el$4.firstChild, _el$7 = _el$5.nextSibling, _el$8 = _el$7.firstChild, _el$9 = _el$8.nextSibling, _el$0 = _el$4.nextSibling, _el$1 = _el$0.firstChild, _el$10 = _el$1.firstChild, _el$11 = _el$10.firstChild, _ref$ = overlay;
typeof _ref$ == "function" ? use(_ref$, _el$3) : overlay = _el$3, insert(_el$5, createComponent(Show, {
get when() {
return loadingCount() > 0;
},
get children() {
return _tmpl$25();
}
}), null), insert(_el$5, () => `${Math.min(totalImages, screenStartPageNum())}–${screenEndPageNum()} / ${totalImages}`, null), _el$8.$$click = () => {
props.highlightedPageNum !== null && (flingAnimator.cancel(), scrollToPage(props.highlightedPageNum));
}, className(_el$8, READER_FLOATING_ACTION_CLASS), insert(_el$8, () => texts_default.button.current), _el$9.$$click = () => onClose(centeredPreviewIndex()), className(_el$9, READER_FLOATING_ACTION_CLASS), _el$1.addEventListener("wheel", () => flingAnimator.cancel()), _el$1.addEventListener("scroll", () => {
scrollFrame === null && (scrollFrame = window.requestAnimationFrame(() => {
scrollFrame = null, setScrollTop(scroller.scrollTop);
}));
});
var _ref$2 = scroller;
return typeof _ref$2 == "function" ? use(_ref$2, _el$1) : scroller = _el$1, insert(_el$11, createComponent(For, {
get each() {
return visibleSlots();
},
children: (slot) => createComponent(PreviewTile, {
decodeCache,
get failed() {
return failedPreviewIndexes().has(previewCache.previewIndexForPage(slot.pageNum));
},
get height() {
return layout().tileHeight;
},
get highlighted() {
return slot.pageNum === props.highlightedPageNum;
},
get item() {
return slot.item;
},
get pageNum() {
return slot.pageNum;
},
get onOpenPage() {
return props.onOpenPage;
},
onRetry: () => {
let retryIndex = previewCache.previewIndexForPage(slot.pageNum);
syncPreviewLoadQueue(previewCache.previewIndexForPage(centeredPageNum()), retryIndex);
}
})
})), insert(_el$0, createComponent(VerticalPositionBar, {
get ariaLabel() {
return texts_default.gallery.scrollPreview;
},
get currentValue() {
return scrollPositionPage();
},
expanded: !0,
maxValue: totalImages,
onInput: scrollToPositionPage,
position: "absolute",
variant: "reader",
get visibleValueCount() {
return screenEndPageNum() - screenStartPageNum() + 1;
}
}), null), createRenderEffect((_p$) => {
var _v$ = `${1 - Math.min(0.15, Math.abs(horizontalDragOffset()) / Math.max(1, window.innerWidth) * 0.15)}`, _v$2 = `translate3d(${horizontalDragOffset()}px, 0, 0) scale(var(--ehpeek-reader-fullscreen-ui-scale, 1)) scale(${1 - Math.min(0.03, Math.abs(horizontalDragOffset()) / Math.max(1, window.innerWidth) * 0.03)})`, _v$3 = props.highlightedPageNum === null, _v$4 = texts_default.button.close, _v$5 = texts_default.button.close, _v$6 = `${totalHeight()}px`, _v$7 = `repeat(${layout().columns}, minmax(0, 1fr))`, _v$8 = `${visibleStartRow() * layout().rowHeight}px`;
return _v$ !== _p$.e && setStyleProperty(_el$3, "opacity", _p$.e = _v$), _v$2 !== _p$.t && setStyleProperty(_el$3, "transform", _p$.t = _v$2), _v$3 !== _p$.a && (_el$8.disabled = _p$.a = _v$3), _v$4 !== _p$.o && setAttribute(_el$9, "aria-label", _p$.o = _v$4), _v$5 !== _p$.i && setAttribute(_el$9, "title", _p$.i = _v$5), _v$6 !== _p$.n && setStyleProperty(_el$10, "height", _p$.n = _v$6), _v$7 !== _p$.s && setStyleProperty(_el$11, "grid-template-columns", _p$.s = _v$7), _v$8 !== _p$.h && setStyleProperty(_el$11, "top", _p$.h = _v$8), _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0,
h: void 0
}), _el$3;
})();
}
function PreviewTile(props) {
let releaseDecodedImage = null;
return createEffect(() => {
releaseDecodedImage?.(), releaseDecodedImage = props.item?.thumbnail.url ? props.decodeCache.retain(props.item.thumbnail.url) : null;
}), onCleanup(() => releaseDecodedImage?.()), (() => {
var _el$12 = _tmpl$45();
return insert(_el$12, createComponent(Show, {
get when() {
return props.item;
},
keyed: !0,
get fallback() {
return (() => {
var _el$13 = _tmpl$54(), _el$14 = _el$13.firstChild;
return _el$13.$$click = () => props.onRetry(), insert(_el$13, createComponent(Show, {
get when() {
return props.failed;
},
get children() {
return createComponent(Icon, {
name: "refresh",
size: "var(--ui-icon-size-lg)"
});
}
}), _el$14), insert(_el$14, () => props.pageNum), createRenderEffect((_p$) => {
var _v$9 = !!props.failed, _v$0 = !props.failed;
return _v$9 !== _p$.e && _el$13.classList.toggle("cursor-pointer", _p$.e = _v$9), _v$0 !== _p$.t && (_el$13.disabled = _p$.t = _v$0), _p$;
}, {
e: void 0,
t: void 0
}), _el$13;
})();
},
children: (item) => [createComponent(Show, {
get when() {
return item.thumbnail.kind === "background";
},
get fallback() {
return (() => {
var _el$18 = _tmpl$92();
return setAttribute(_el$18, "draggable", !1), createRenderEffect((_p$) => {
var _v$19 = item.thumbnail.url, _v$20 = item.thumbnail.width, _v$21 = item.thumbnail.height;
return _v$19 !== _p$.e && setAttribute(_el$18, "src", _p$.e = _v$19), _v$20 !== _p$.t && setAttribute(_el$18, "width", _p$.t = _v$20), _v$21 !== _p$.a && setAttribute(_el$18, "height", _p$.a = _v$21), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$18;
})();
},
get children() {
var _el$15 = _tmpl$64();
return createRenderEffect((_p$) => {
var _v$1 = `url(${JSON.stringify(item.thumbnail.url)})`, _v$10 = item.thumbnail.backgroundPosition, _v$11 = item.thumbnail.backgroundRepeat, _v$12 = item.thumbnail.backgroundSize, _v$13 = `${item.thumbnail.height}px`, _v$14 = `${item.thumbnail.width}px`, _v$15 = `Page ${item.pageNum}`;
return _v$1 !== _p$.e && setStyleProperty(_el$15, "background-image", _p$.e = _v$1), _v$10 !== _p$.t && setStyleProperty(_el$15, "background-position", _p$.t = _v$10), _v$11 !== _p$.a && setStyleProperty(_el$15, "background-repeat", _p$.a = _v$11), _v$12 !== _p$.o && setStyleProperty(_el$15, "background-size", _p$.o = _v$12), _v$13 !== _p$.i && setStyleProperty(_el$15, "height", _p$.i = _v$13), _v$14 !== _p$.n && setStyleProperty(_el$15, "width", _p$.n = _v$14), _v$15 !== _p$.s && setAttribute(_el$15, "aria-label", _p$.s = _v$15), _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0
}), _el$15;
}
}), (() => {
var _el$16 = _tmpl$72();
return _el$16.$$click = (event) => {
event.preventDefault(), event.stopPropagation(), props.onOpenPage(item.pageUrl, item.pageNum);
}, setAttribute(_el$16, "draggable", !1), createRenderEffect((_p$) => {
var _v$16 = item.pageUrl, _v$17 = `Page ${item.pageNum}`, _v$18 = props.highlighted ? "page" : void 0;
return _v$16 !== _p$.e && setAttribute(_el$16, "href", _p$.e = _v$16), _v$17 !== _p$.t && setAttribute(_el$16, "aria-label", _p$.t = _v$17), _v$18 !== _p$.a && setAttribute(_el$16, "aria-current", _p$.a = _v$18), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$16;
})(), createComponent(Show, {
get when() {
return props.highlighted;
},
get children() {
return _tmpl$82();
}
})]
})), createRenderEffect((_$p) => setStyleProperty(_el$12, "height", `${props.height}px`)), _el$12;
})();
}
var PreviewDecodeCache = class {
constructor(byteLimit, itemLimit) {
__publicField(this, "bytes", 0);
__publicField(this, "entries", /* @__PURE__ */ new Map());
this.byteLimit = byteLimit, this.itemLimit = itemLimit;
}
retain(url) {
let entry = this.ensure(url);
return entry.pins += 1, this.touch(url, entry), () => {
let current = this.entries.get(url);
current === entry && (current.pins = Math.max(0, current.pins - 1), this.prune());
};
}
dispose() {
for (let entry of this.entries.values())
entry.image.removeAttribute("src");
this.entries.clear(), this.bytes = 0;
}
ensure(url) {
let cached = this.entries.get(url);
if (cached)
return cached;
let image2 = new Image(), entry = {
bytes: 0,
image: image2,
pins: 0
};
return image2.decoding = "async", image2.onload = () => {
let bytes = Math.max(1, image2.naturalWidth) * Math.max(1, image2.naturalHeight) * 4;
this.bytes += bytes - entry.bytes, entry.bytes = bytes, image2.decode().catch(() => {
}).finally(() => this.prune());
}, image2.onerror = () => {
entry.pins === 0 && this.evict(url, entry);
}, image2.src = url, this.entries.set(url, entry), this.prune(), entry;
}
touch(url, entry) {
this.entries.delete(url), this.entries.set(url, entry);
}
prune() {
for (; this.entries.size > this.itemLimit || this.bytes > this.byteLimit; ) {
let removable = Array.from(this.entries).find(([, entry]) => entry.pins === 0);
if (!removable)
break;
this.evict(removable[0], removable[1]);
}
}
evict(url, entry) {
this.entries.get(url) === entry && (this.entries.delete(url), this.bytes = Math.max(0, this.bytes - entry.bytes), entry.image.onload = null, entry.image.onerror = null, entry.image.removeAttribute("src"));
}
};
delegateEvents(["click"]);
// src/state/readHistory.ts
var HISTORY_KEY_PREFIX = "ehpeek:history:", HISTORY_QUEUE_KEY_PREFIX = "ehpeek:hist_q:", READ_HISTORY_LIMIT = 3e3, HISTORY_COMPACT_THRESHOLD = 4e3, SAVE_DELAY_MS = 1e4, ReadHistorySession = class {
constructor(baseRecord) {
this.baseRecord = baseRecord;
this.pending = null;
this.lastSaved = null;
this.timer = null;
this.flush = () => {
this.timer !== null && (window.clearTimeout(this.timer), this.timer = null), this.pending && (this.sameProgress(this.pending, this.lastSaved) || (saveReadHistory(this.pending), this.lastSaved = this.pending), this.pending = null);
};
this.onVisibilityChange = () => {
document.visibilityState === "hidden" && this.flush();
};
window.addEventListener("pagehide", this.flush), document.addEventListener("visibilitychange", this.onVisibilityChange);
}
update(pageNum, totalPages) {
if (!pageNum || pageNum <= 0)
return;
let nextRecord = {
...this.baseRecord,
pageNum,
totalPages,
updatedAt: Date.now()
};
this.sameProgress(nextRecord, this.lastSaved) || (this.pending = nextRecord, this.schedule());
}
dispose() {
this.flush(), window.removeEventListener("pagehide", this.flush), document.removeEventListener("visibilitychange", this.onVisibilityChange);
}
schedule() {
this.timer === null && (this.timer = window.setTimeout(this.flush, SAVE_DELAY_MS));
}
sameProgress(left, right) {
return !!(left && right && left.galleryId === right.galleryId && left.token === right.token && left.pageNum === right.pageNum && left.totalPages === right.totalPages);
}
};
function loadReadHistory(galleryId, token) {
return GM_getValue(historyKey(galleryId, token), null);
}
function loadReadHistoryRecords() {
return GM_listValues().filter((key) => key.startsWith(HISTORY_QUEUE_KEY_PREFIX)).sort((left, right) => right.localeCompare(left)).map((key) => {
let reference = GM_getValue(key, null);
if (!reference)
return null;
let record = GM_getValue(
`${HISTORY_KEY_PREFIX}${reference}`,
null
);
return record?.queueOrder === queueOrderFromKey(key) ? record : null;
}).filter((record) => record !== null);
}
function clearReadHistory() {
for (let key of GM_listValues())
(key.startsWith(HISTORY_KEY_PREFIX) || key.startsWith(HISTORY_QUEUE_KEY_PREFIX)) && GM_deleteValue(key);
state.gallery.readHistoryCount.set(0);
}
function removeReadHistory(galleryId, token) {
let key = historyKey(galleryId, token), record = GM_getValue(key, null);
record && (GM_deleteValue(key), GM_deleteValue(queueKey(record.queueOrder)), state.gallery.readHistoryCount.set(
Math.max(0, state.gallery.readHistoryCount.reload() - 1)
));
}
function updateReadHistoryGalleryInfo(galleryId, token, gallery2) {
let record = loadReadHistory(galleryId, token);
if (!record)
return null;
let updated = {
...record,
gallery: mergeGalleryInfo(record.gallery, gallery2)
};
return GM_setValue(historyKey(galleryId, token), updated), updated;
}
function recordGalleryVisit(galleryId, token, totalPages, gallery2) {
let existing = loadReadHistory(galleryId, token), record = existing ? {
...existing,
gallery: mergeGalleryInfo(existing.gallery, gallery2),
totalPages,
updatedAt: Date.now()
} : {
gallery: gallery2,
galleryId,
pageNum: -1,
token,
totalPages,
updatedAt: Date.now()
};
return saveReadHistory(record), record;
}
function saveReadHistory(record) {
let key = historyKey(record.galleryId, record.token), previous = GM_getValue(key, null), exists = previous !== null, queueOrder = createQueueOrder();
if (GM_setValue(key, {
...record,
gallery: mergeGalleryInfo(previous?.gallery, record.gallery),
queueOrder
}), GM_setValue(queueKey(queueOrder), historyReference(record.galleryId, record.token)), !exists) {
let count = state.gallery.readHistoryCount.reload() + 1;
state.gallery.readHistoryCount.set(count), count >= HISTORY_COMPACT_THRESHOLD && pruneReadHistory();
}
}
function mergeGalleryInfo(previous, current) {
let merged = {
category: current?.category ?? previous?.category,
categoryClass: current?.categoryClass ?? previous?.categoryClass,
coverUrl: current?.coverUrl ?? previous?.coverUrl,
language: current?.language ?? previous?.language,
posted: current?.posted ?? previous?.posted,
rating: current?.rating ?? (typeof previous?.rating == "number" ? previous.rating : void 0),
title: current?.title ?? previous?.title,
titleSub: current?.titleSub ?? previous?.titleSub,
uploader: current?.uploader ?? previous?.uploader
}, entries = Object.entries(merged).filter((entry) => entry[1] !== void 0);
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
}
function historyKey(galleryId, token) {
return `${HISTORY_KEY_PREFIX}${historyReference(galleryId, token)}`;
}
function pruneReadHistory() {
let keys = GM_listValues(), queueKeys = keys.filter((key) => key.startsWith(HISTORY_QUEUE_KEY_PREFIX)), records = keys.filter((key) => key.startsWith(HISTORY_KEY_PREFIX)).map((key) => ({ key, record: GM_getValue(key, null) })).filter(
(entry) => entry.record !== null
).sort((left, right) => right.record.updatedAt - left.record.updatedAt), retained = records.slice(0, READ_HISTORY_LIMIT);
for (let entry of records.slice(retained.length))
GM_deleteValue(entry.key);
retained.reverse().forEach((entry, index) => {
let queueOrder = compactQueueOrder(index), record = { ...entry.record, queueOrder };
GM_setValue(entry.key, record), GM_setValue(
queueKey(queueOrder),
historyReference(record.galleryId, record.token)
);
});
for (let key of queueKeys) {
let reference = GM_getValue(key, null), record = reference ? GM_getValue(`${HISTORY_KEY_PREFIX}${reference}`, null) : null;
(!record || queueKey(record.queueOrder) !== key) && GM_deleteValue(key);
}
state.gallery.readHistoryCount.set(retained.length);
}
function historyReference(galleryId, token) {
return `${galleryId}:${token}`;
}
function queueKey(order) {
return `${HISTORY_QUEUE_KEY_PREFIX}${order}`;
}
function queueOrderFromKey(key) {
return key.slice(HISTORY_QUEUE_KEY_PREFIX.length);
}
function createQueueOrder() {
let [randomValue = 0] = crypto.getRandomValues(new Uint32Array(1)), random = randomValue.toString(36).padStart(7, "0");
return `${Date.now().toString().padStart(13, "0")}-${random}`;
}
function compactQueueOrder(index) {
return index.toString().padStart(20, "0");
}
// src/components/Enhance/ReadHistory.tsx
var _tmpl$13 = /* @__PURE__ */ template('<nav class="flex flex-col items-center gap-sm border-0 border-y border-solid ehp-color-site-border-subtle-b p-md">'), _tmpl$26 = /* @__PURE__ */ template('<div class="grid w-full grid-cols-[1fr_auto_1fr] items-center gap-sm"><span></span><span class="text-center textsize-md font-600 ehp-color-site-text"><span class=block>'), _tmpl$36 = /* @__PURE__ */ template('<button type=button class="min-h-xs justify-self-end px-sm coarse:min-h-sm coarse:px-md rounded-sm border-0 bg-transparent ehp-color-site-text textsize-md font-600 cursor-pointer [touch-action:manipulation] hover:bg-[var(--color-site-item-hover)]">'), _tmpl$46 = /* @__PURE__ */ template("<div>"), _tmpl$55 = /* @__PURE__ */ template('<div class="p-xl text-center textsize-md ehp-color-site-text opacity-72">'), _tmpl$65 = /* @__PURE__ */ template("<button type=button><span>");
function ReadHistoryPage(props) {
let [items, setItems] = createSignal(untrack(() => props.items)), pageCount = createMemo(() => Math.max(1, Math.ceil(items().length / props.pageSize))), [pageIndex, setPageIndex] = createSignal(Math.min(props.initialPageIndex, untrack(pageCount) - 1)), pageItems = createMemo(() => {
let start = pageIndex() * props.pageSize;
return items().slice(start, start + props.pageSize);
}), visibleRange = createMemo(() => {
if (items().length === 0)
return "0 / 0";
let start = pageIndex() * props.pageSize + 1, end = Math.min(start + props.pageSize - 1, items().length);
return texts_default.history.range.replace("{start}", String(start)).replace("{end}", String(end)).replace("{total}", String(items().length));
}), navigate = (nextPageIndex, scrollToPageBar = "top", updateUrl = !0) => {
let nextIndex = Math.max(0, Math.min(nextPageIndex, pageCount() - 1));
nextIndex !== pageIndex() && (setPageIndex(nextIndex), updateUrl && window.history.pushState(window.history.state, "", readHistoryUrl(nextIndex)), props.source.handle.scrollReadHistoryPage(scrollToPageBar));
}, clearHistory = () => {
window.confirm(texts_default.history.clearConfirm) && (clearReadHistory(), setItems([]), setPageIndex(0), window.history.replaceState(window.history.state, "", readHistoryUrl()));
}, removeHistoryItem = (item) => {
if (!window.confirm(texts_default.history.removeConfirm))
return;
removeReadHistory(item.galleryId, item.token);
let nextItems = items().filter((candidate) => candidate.galleryId !== item.galleryId || candidate.token !== item.token), nextPageCount = Math.max(1, Math.ceil(nextItems.length / props.pageSize)), nextPageIndex = Math.min(pageIndex(), nextPageCount - 1);
setItems(nextItems), setPageIndex(nextPageIndex), window.history.replaceState(window.history.state, "", readHistoryUrl(nextPageIndex));
};
createEffect(() => {
props.source.handle.updateReadHistoryItems(pageItems());
}), onMount(() => {
let syncFromHistory = () => {
let page2 = extractPageType();
page2.type === "readHistory" && navigate(page2.pageIndex, "top", !1);
};
window.addEventListener("popstate", syncFromHistory);
let stopRemoval = props.source.handle.listenForReadHistoryRemoval(removeHistoryItem);
onCleanup(() => {
stopRemoval(), window.removeEventListener("popstate", syncFromHistory);
});
});
let navigation = (showHeader) => (() => {
var _el$ = _tmpl$13();
return insert(_el$, showHeader && (() => {
var _el$2 = _tmpl$26(), _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling, _el$5 = _el$4.firstChild;
return insert(_el$4, visibleRange, _el$5), insert(_el$5, () => texts_default.history.limit.replace("{limit}", String(READ_HISTORY_LIMIT))), insert(_el$2, (() => {
var _c$2 = memo(() => items().length > 0);
return () => _c$2() && (() => {
var _el$6 = _tmpl$36();
return _el$6.$$click = clearHistory, insert(_el$6, () => texts_default.button.clearHistory), _el$6;
})();
})(), null), _el$2;
})(), null), insert(_el$, (() => {
var _c$ = memo(() => pageCount() > 1);
return () => _c$() && createComponent(ScrollPageBar, {
get currentIndex() {
return pageIndex();
},
get maxIndex() {
return pageCount() - 1;
},
onNavigate: navigate,
urlForIndex: readHistoryUrl
});
})(), null), _el$;
})();
return (() => {
var _el$7 = _tmpl$46();
return insert(_el$7, createComponent(PageSwipe, {
canNavigate: (direction) => direction === "next" ? pageIndex() + 1 < pageCount() : pageIndex() > 0,
onNavigate: (direction) => navigate(direction === "next" ? pageIndex() + 1 : pageIndex() - 1),
target: () => props.source.elems.resultList.Component()
}), null), insert(_el$7, (() => {
var _c$3 = memo(() => items().length === 0);
return () => _c$3() && (() => {
var _el$8 = _tmpl$55();
return insert(_el$8, () => texts_default.history.empty), _el$8;
})();
})(), null), insert(_el$7, (() => {
var _c$4 = memo(() => items().length > 0);
return () => _c$4() && navigation(!0);
})(), null), insert(_el$7, (() => {
var _c$5 = memo(() => pageCount() > 1);
return () => _c$5() && createComponent(Portal, {
get mount() {
return props.source.elems.navigationBottomMount.Component();
},
get children() {
return navigation(!1);
}
});
})(), null), _el$7;
})();
}
function ReadButton(props) {
let buttonClassName = () => props.variant === "touchGallery" ? "ehpeek-continue-reading ehpeek-touch-gallery-primary-button flex min-w-0 w-full h-full min-h-[var(--ui-control-size-xl)] flex-col items-center justify-center gap-md py-md px-lg border-0 bg-transparent ehp-color-site-accent text-center uppercase [touch-action:manipulation] [font-size:var(--ui-font-size-prominent)] font-700" : "ehpeek-continue-reading flex box-border w-full max-w-full min-h-sm items-center gap-sm py-sm px-xs border-0 bg-transparent text-[var(--color-site-accent)] hover:bg-[var(--color-site-accent-hover)] shadow-none cursor-pointer text-left font-sans textsize-sm font-700 leading-[1.2]", detailClassName = () => props.variant === "touchGallery" ? "ehpeek-continue-reading-page block mt-2px ehp-color-site-accent [font-size:var(--ui-font-size-prominent)] font-600 opacity-78 normal-case" : "ehpeek-continue-reading-page inline-block ml-auto opacity-72 textsize-xs font-600 whitespace-nowrap";
return (() => {
var _el$9 = _tmpl$65(), _el$0 = _el$9.firstChild;
return _el$9.$$click = (event) => {
event.preventDefault(), event.stopPropagation(), props.onClick();
}, insert(_el$9, (() => {
var _c$6 = memo(() => !!props.hasHistory);
return () => _c$6() ? texts_default.reader.continueReading : texts_default.reader.startReading;
})(), _el$0), insert(_el$0, (() => {
var _c$7 = memo(() => !!props.totalPages);
return () => _c$7() ? `${props.currentPage}/${props.totalPages}` : String(props.currentPage);
})()), createRenderEffect((_p$) => {
var _v$ = buttonClassName(), _v$2 = detailClassName();
return _v$ !== _p$.e && className(_el$9, _p$.e = _v$), _v$2 !== _p$.t && className(_el$0, _p$.t = _v$2), _p$;
}, {
e: void 0,
t: void 0
}), _el$9;
})();
}
delegateEvents(["click"]);
// src/components/Enhance/SearchHistory.tsx
var _tmpl$14 = /* @__PURE__ */ template('<section class="ehpeek-search-history fixed z-ui flex box-border max-h-[60dvh] flex-col overflow-hidden overflow-y-auto overscroll-contain rounded-md border ehp-color-site-border ehp-color-site-elevated ehp-color-site-text font-sans"role=list>'), _tmpl$27 = /* @__PURE__ */ template('<div class="flex min-w-0 flex-none items-stretch border-0 border-b ehp-color-site-border-subtle-b last:border-b-0"role=listitem><button type=button></button><button type=button class="appearance-none inline-flex w-60px min-h-lg flex-none items-center justify-center border-0 border-l ehp-color-site-border-subtle-b bg-transparent ehp-color-site-text textsize-xl font-inherit leading-1 cursor-pointer [touch-action:manipulation] active:bg-[var(--color-site-item-hover)]">×');
function SearchHistory(props) {
let dropdown, [searchValue, setSearchValue] = createSignal(untrack(() => props.source.data.value)), [history, setHistory] = createSignal(loadSearchHistory()), [open, setOpen] = createSignal(!1), [activeIndex, setActiveIndex] = createSignal(-1), [position, setPosition] = createSignal(null), itemButtons = [], visiblePosition = () => open() && !searchValue().trim() && history().length > 0 ? position() : null, selectHistory = (item) => {
props.source.handle.applySearchSelection(item), setOpen(!1);
};
return onMount(() => {
let updatePosition = () => {
setPosition(props.source.handle.readSearchOverlayPosition());
}, showHistory = () => {
updatePosition(), setActiveIndex(-1), setOpen(!0);
}, moveSelection = (offset) => {
let items = history();
if (items.length === 0)
return;
let current = activeIndex(), next = current < 0 ? offset > 0 ? 0 : items.length - 1 : (current + offset + items.length) % items.length;
setActiveIndex(next), window.requestAnimationFrame(() => itemButtons[next]?.scrollIntoView({
block: "nearest"
}));
}, onInputKeyDown = (event) => {
if (visiblePosition())
if (event.key === "ArrowDown" || event.key === "ArrowUp")
event.preventDefault(), moveSelection(event.key === "ArrowDown" ? 1 : -1);
else if (event.key === "Enter" && activeIndex() >= 0) {
event.preventDefault();
let item = history()[activeIndex()];
item !== void 0 && selectHistory(item);
} else event.key === "Escape" && (event.preventDefault(), setOpen(!1));
}, updateSearchValue = (value, focused) => {
setSearchValue(value), !value.trim() && focused && showHistory();
}, recordSearch = (sourceValue) => {
let value = sourceValue.trim();
value && setHistory(addSearchHistory(value));
}, disconnect = props.source.handle.listenSearchHistoryOverlay({
onFocus: showHistory,
onInput: updateSearchValue,
onKeyDown: onInputKeyDown,
onOutsidePointer: () => setOpen(!1),
onPositionChange: updatePosition,
onSubmit: recordSearch
}, () => dropdown ?? null);
updateSearchValue(props.source.data.value, !1), onCleanup(disconnect);
}), createComponent(Show, {
get when() {
return visiblePosition();
},
keyed: !0,
children: (currentPosition) => (() => {
var _el$ = _tmpl$14(), _ref$ = dropdown;
return typeof _ref$ == "function" ? use(_ref$, _el$) : dropdown = _el$, insert(_el$, createComponent(For, {
get each() {
return history();
},
children: (item, index) => (() => {
var _el$2 = _tmpl$27(), _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling;
return _el$3.$$click = () => selectHistory(item), _el$3.addEventListener("pointerenter", () => setActiveIndex(index())), use((button2) => {
itemButtons[index()] = button2;
}, _el$3), setAttribute(_el$3, "title", item), insert(_el$3, item), _el$4.$$click = () => {
setHistory(removeSearchHistory(item));
}, createRenderEffect(() => className(_el$3, `appearance-none block min-w-0 min-h-lg flex-1 overflow-hidden text-ellipsis whitespace-nowrap px-lg border-0 ehp-color-site-text text-left textsize-lg font-inherit cursor-pointer [touch-action:manipulation] active:bg-[var(--color-site-item-hover)] ${activeIndex() === index() ? "bg-[var(--color-site-item-hover)]" : "bg-transparent"}`)), _el$2;
})()
})), createRenderEffect((_p$) => {
var _v$ = `${currentPosition.left}px`, _v$2 = `${currentPosition.top}px`, _v$3 = `${currentPosition.width}px`;
return _v$ !== _p$.e && setStyleProperty(_el$, "left", _p$.e = _v$), _v$2 !== _p$.t && setStyleProperty(_el$, "top", _p$.t = _v$2), _v$3 !== _p$.a && setStyleProperty(_el$, "width", _p$.a = _v$3), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$;
})()
});
}
delegateEvents(["click"]);
// src/components/Enhance/MyTags.ts
async function loadMyTagsPage(tagSet) {
let url = new URL("/mytags", window.location.origin);
tagSet && url.searchParams.set("tagset", tagSet);
let response = await requestPage(url.href);
return extractMyTagsPageData(response.document, tagSet);
}
function loadMyTagAppearances() {
return state.gallery.myTagAppearances.stored() ? state.gallery.myTagAppearances.reload() : null;
}
async function refreshMyTags(initialPage) {
try {
let initialData = initialPage ?? await loadMyTagsPage(), options = initialData.options;
state.gallery.myTagSets.set(options);
let appearances = (options.length > 0 ? await Promise.all(options.map(async (option2) => option2.selected ? initialData : loadMyTagsPage(option2.value))) : [initialData]).flatMap((page2) => page2.enabled ? page2.appearances : []), unique = Array.from(new Map(appearances.map((appearance) => [appearance.name, appearance])).values());
return state.gallery.myTagAppearances.set(unique), unique;
} catch (error) {
return console.error("[ehpeek] Could not load My Tags", error), null;
}
}
// node_modules/.pnpm/[email protected]/node_modules/solid-js/store/dist/store.js
var $RAW = /* @__PURE__ */ Symbol("store-raw"), $NODE = /* @__PURE__ */ Symbol("store-node"), $HAS = /* @__PURE__ */ Symbol("store-has"), $SELF = /* @__PURE__ */ Symbol("store-self");
function wrap$1(value) {
let p = value[$PROXY];
if (!p && (Object.defineProperty(value, $PROXY, {
value: p = new Proxy(value, proxyTraps$1)
}), !Array.isArray(value))) {
let keys = Object.keys(value), desc = Object.getOwnPropertyDescriptors(value), proto = Object.getPrototypeOf(value), isClass = proto !== null && value !== null && typeof value == "object" && !Array.isArray(value) && proto !== Object.prototype;
if (isClass) {
let descriptors = Object.getOwnPropertyDescriptors(proto);
keys.push(...Object.keys(descriptors)), Object.assign(desc, descriptors);
}
for (let i = 0, l = keys.length; i < l; i++) {
let prop = keys[i];
isClass && prop === "constructor" || desc[prop].get && Object.defineProperty(value, prop, {
configurable: !0,
enumerable: desc[prop].enumerable,
get: desc[prop].get.bind(p)
});
}
}
return p;
}
function isWrappable(obj) {
let proto;
return obj != null && typeof obj == "object" && (obj[$PROXY] || !(proto = Object.getPrototypeOf(obj)) || proto === Object.prototype || Array.isArray(obj));
}
function unwrap(item, set = /* @__PURE__ */ new Set()) {
let result, unwrapped, v, prop;
if (result = item != null && item[$RAW]) return result;
if (!isWrappable(item) || set.has(item)) return item;
if (Array.isArray(item)) {
Object.isFrozen(item) ? item = item.slice(0) : set.add(item);
for (let i = 0, l = item.length; i < l; i++)
v = item[i], (unwrapped = unwrap(v, set)) !== v && (item[i] = unwrapped);
} else {
Object.isFrozen(item) ? item = Object.assign({}, item) : set.add(item);
let keys = Object.keys(item), desc = Object.getOwnPropertyDescriptors(item);
for (let i = 0, l = keys.length; i < l; i++)
prop = keys[i], !desc[prop].get && (v = item[prop], (unwrapped = unwrap(v, set)) !== v && (item[prop] = unwrapped));
}
return item;
}
function getNodes(target, symbol) {
let nodes = target[symbol];
return nodes || Object.defineProperty(target, symbol, {
value: nodes = /* @__PURE__ */ Object.create(null)
}), nodes;
}
function getNode(nodes, property, value) {
if (nodes[property]) return nodes[property];
let [s, set] = createSignal(value, {
equals: !1,
internal: !0
});
return s.$ = set, nodes[property] = s;
}
function proxyDescriptor$1(target, property) {
let desc = Reflect.getOwnPropertyDescriptor(target, property);
return !desc || desc.get || !desc.configurable || property === $PROXY || property === $NODE || (delete desc.value, delete desc.writable, desc.get = () => target[$PROXY][property]), desc;
}
function trackSelf(target) {
getListener() && getNode(getNodes(target, $NODE), $SELF)();
}
function ownKeys(target) {
return trackSelf(target), Reflect.ownKeys(target);
}
var proxyTraps$1 = {
get(target, property, receiver) {
if (property === $RAW) return target;
if (property === $PROXY) return receiver;
if (property === $TRACK)
return trackSelf(target), receiver;
let nodes = getNodes(target, $NODE), tracked = nodes[property], value = tracked ? tracked() : target[property];
if (property === $NODE || property === $HAS || property === "__proto__") return value;
if (!tracked) {
let desc = Object.getOwnPropertyDescriptor(target, property);
getListener() && (typeof value != "function" || Object.prototype.hasOwnProperty.call(target, property)) && !(desc && desc.get) && (value = getNode(nodes, property, value)());
}
return isWrappable(value) ? wrap$1(value) : value;
},
has(target, property) {
return property === $RAW || property === $PROXY || property === $TRACK || property === $NODE || property === $HAS || property === "__proto__" ? !0 : (getListener() && getNode(getNodes(target, $HAS), property)(), property in target);
},
set() {
return !0;
},
deleteProperty() {
return !0;
},
ownKeys,
getOwnPropertyDescriptor: proxyDescriptor$1
};
function setProperty(state2, property, value, deleting = !1) {
if (property === "__proto__" || !deleting && state2[property] === value) return;
let prev = state2[property], len = state2.length;
value === void 0 ? (delete state2[property], state2[$HAS] && state2[$HAS][property] && prev !== void 0 && state2[$HAS][property].$()) : (state2[property] = value, state2[$HAS] && state2[$HAS][property] && prev === void 0 && state2[$HAS][property].$());
let nodes = getNodes(state2, $NODE), node;
if ((node = getNode(nodes, property, prev)) && node.$(() => value), Array.isArray(state2) && state2.length !== len) {
for (let i = state2.length; i < len; i++) (node = nodes[i]) && node.$();
(node = getNode(nodes, "length", len)) && node.$(state2.length);
}
(node = nodes[$SELF]) && node.$();
}
function mergeStoreNode(state2, value) {
let keys = Object.keys(value);
for (let i = 0; i < keys.length; i += 1) {
let key = keys[i];
isUnsafeKey$1(key) || setProperty(state2, key, value[key]);
}
}
function isUnsafeKey$1(property) {
return property === "__proto__" || property === "constructor" || property === "prototype";
}
function updateArray(current, next) {
if (typeof next == "function" && (next = next(current)), next = unwrap(next), Array.isArray(next)) {
if (current === next) return;
let i = 0, len = next.length;
for (; i < len; i++) {
let value = next[i];
current[i] !== value && setProperty(current, i, value);
}
setProperty(current, "length", len);
} else mergeStoreNode(current, next);
}
function updatePath(current, path, traversed = []) {
let part, prev = current;
if (path.length > 1) {
part = path.shift();
let partType = typeof part, isArray = Array.isArray(current);
if (partType === "string" && (part === "__proto__" || path.length > 1 && isUnsafeKey$1(part)))
return;
if (Array.isArray(part)) {
for (let i = 0; i < part.length; i++)
updatePath(current, [part[i]].concat(path), traversed);
return;
} else if (isArray && partType === "function") {
for (let i = 0; i < current.length; i++)
part(current[i], i) && updatePath(current, [i].concat(path), traversed);
return;
} else if (isArray && partType === "object") {
let {
from = 0,
to = current.length - 1,
by = 1
} = part;
for (let i = from; i <= to; i += by)
updatePath(current, [i].concat(path), traversed);
return;
} else if (path.length > 1) {
updatePath(current[part], path, [part].concat(traversed));
return;
}
prev = current[part], traversed = [part].concat(traversed);
}
let value = path[0];
typeof value == "function" && (value = value(prev, traversed), value === prev) || part === void 0 && value == null || (value = unwrap(value), part === void 0 || isWrappable(prev) && isWrappable(value) && !Array.isArray(value) ? mergeStoreNode(prev, value) : setProperty(current, part, value));
}
function createStore(...[store, options]) {
let unwrappedStore = unwrap(store || {}), isArray = Array.isArray(unwrappedStore), wrappedStore = wrap$1(unwrappedStore);
function setStore(...args) {
batch(() => {
isArray && args.length === 1 ? updateArray(unwrappedStore, args[0]) : updatePath(unwrappedStore, args);
});
}
return [wrappedStore, setStore];
}
// src/components/SettingsMenu.tsx
var _tmpl$15 = /* @__PURE__ */ template('<p class="box-border w-full m-0 px-md pb-md text-left whitespace-normal [overflow-wrap:anywhere] [contain:inline-size] [font-size:var(--ui-font-size-sm)] leading-[1.35] opacity-75">'), _tmpl$28 = /* @__PURE__ */ template('<div class="border-0 border-b ehp-color-site-border-subtle-b"><div class="flex items-stretch"><button type=button class="flex min-w-0 flex-1 min-h-[var(--ui-control-size-lg)] items-center justify-between gap-md py-sm pl-md pr-sm rounded-xs border-0 !bg-transparent hover:!bg-transparent active:!bg-transparent ehp-color-site-text font-inherit text-left [font-size:var(--ui-font-size-md)] cursor-pointer [-webkit-tap-highlight-color:transparent]"><span></span><span class="flex flex-none items-center gap-sm"><span class="[font-size:var(--ui-font-size-sm)] opacity-70"></span><span></span></span></button><button type=button class="flex flex-none w-[var(--ui-control-size-sm)] min-h-[var(--ui-control-size-lg)] items-center justify-center p-0 rounded-xs border-0 !bg-transparent hover:!bg-[var(--color-site-item-hover)] active:!bg-[var(--color-site-item-hover)] ehp-color-site-text cursor-pointer font-inherit [font-size:var(--ui-font-size-md)] font-700 [-webkit-tap-highlight-color:transparent]"><span class="flex w-[var(--ui-icon-size-md)] h-[var(--ui-icon-size-md)] items-center justify-center rounded-full border border-[var(--color-site-border-subtle)] leading-none">?'), _tmpl$37 = /* @__PURE__ */ template('<div class="absolute top-full right-0 z-2 mt-xs w-[calc(var(--ui-control-size-xl)*3)] overflow-hidden rounded-md border ehp-color-site-border ehp-color-site-elevated shadow-xl"role=menu>'), _tmpl$47 = /* @__PURE__ */ template('<a class="flex w-full min-h-[var(--ui-control-size-lg)] items-center gap-md px-md border-0 border-b ehp-color-site-border-subtle-b !bg-transparent hover:!bg-[var(--color-site-item-hover)] ehp-color-site-text no-underline text-left [font-size:var(--ui-font-size-md)] cursor-pointer">'), _tmpl$56 = /* @__PURE__ */ template('<button type=button class="flex w-full min-h-[var(--ui-control-size-lg)] items-center gap-md px-md border-0 border-b ehp-color-site-border-subtle-b !bg-transparent hover:!bg-[var(--color-site-item-hover)] ehp-color-site-text font-inherit text-left [font-size:var(--ui-font-size-md)] cursor-pointer"><span>'), _tmpl$66 = /* @__PURE__ */ template('<div class="flex w-full min-h-[var(--ui-control-size-lg)] items-center px-md border-0 border-b ehp-color-site-border-subtle-b ehp-color-site-text [font-size:var(--ui-font-size-md)] font-700">Ehpeek'), _tmpl$73 = /* @__PURE__ */ template('<a class="flex w-full min-h-[var(--ui-control-size-lg)] items-center overflow-hidden text-ellipsis whitespace-nowrap px-md border-0 border-b ehp-color-site-border-subtle-b ehp-color-site-text no-underline [font-size:var(--ui-font-size-md)] font-700 hover:bg-[var(--color-site-item-hover)]"href=https://github.com/yamipot/ehpeek target=_blank rel="noopener noreferrer">v'), _tmpl$83 = /* @__PURE__ */ template('<div class="ehpeek-settings-menu pointer-events-auto fixed top-24px right-24px coarse:top-8px coarse:right-8px z-overlay box-border flex w-[calc(var(--ui-control-size-xl)*6)] max-w-[calc(100vw-48px)] coarse:max-w-[calc(100vw-16px)] max-h-[calc(100vh-48px)] coarse:max-h-[calc(100dvh-16px)] flex-col overflow-hidden p-md border ehp-color-site-border rounded-sm ehp-color-site-elevated ehp-color-site-text [font-size:var(--ui-font-size-md)] leading-[1.2]"><div class="relative flex flex-none justify-end mb-sm"><button type=button class="flex w-[calc(var(--ui-control-size-xl)*3)] min-h-[var(--ui-control-size-md)] items-center justify-between gap-md px-md rounded-md border ehp-color-site-border !bg-transparent hover:!bg-[var(--color-site-item-hover)] active:!bg-[var(--color-site-item-hover)] ehp-color-site-text font-inherit text-left [font-size:var(--ui-font-size-md)] font-700 cursor-pointer [-webkit-tap-highlight-color:transparent]"aria-haspopup=menu><span></span><span aria-hidden=true></span></button></div><div class="min-h-0 overflow-x-hidden overflow-y-auto overscroll-contain"></div><div class="ehpeek-settings-actions grid grid-cols-3 flex-none gap-sm mt-md pt-md border-0 border-t border-t-[var(--color-site-border-subtle)]"><button type=button class="ehpeek-settings-apply block w-full min-h-[var(--ui-control-size-md)] py-xs px-md rounded-md border cursor-pointer font-inherit text-center [font-size:var(--ui-font-size-md)] font-700 leading-[1.1] transition-[filter,transform,box-shadow] duration-120 active:scale-98 border-[var(--color-site-accent)] bg-[var(--color-site-accent)] text-[var(--color-site-surface)] shadow-[0_2px_8px_var(--color-shadow-panel)] hover:brightness-108"></button><button type=button class="ehpeek-settings-default block w-full min-h-[var(--ui-control-size-md)] py-xs px-md rounded-md border cursor-pointer font-inherit text-center [font-size:var(--ui-font-size-md)] font-700 leading-[1.1] transition-[filter,transform,box-shadow] duration-120 active:scale-98 border-[var(--color-site-border-subtle)] bg-[var(--color-site-surface)] text-[var(--color-site-text)] hover:bg-[var(--color-site-item-hover)]"></button><button type=button class="ehpeek-settings-close block w-full min-h-[var(--ui-control-size-md)] py-xs px-md rounded-md border cursor-pointer font-inherit text-center [font-size:var(--ui-font-size-md)] font-700 leading-[1.1] transition-[filter,transform,box-shadow] duration-120 active:scale-98 border-[var(--color-site-border-subtle)] bg-[var(--color-site-surface)] text-[var(--color-site-text)] hover:bg-[var(--color-site-item-hover)]">'), _tmpl$93 = /* @__PURE__ */ template("<button type=button role=menuitemradio>"), SETTINGS_SECTIONS = [["general", texts_default.settings.general], ["enhance", texts_default.settings.enhance], ["options", texts_default.settings.options], ["about", texts_default.settings.about]];
var SETTINGS_DOT_CLASS = "block flex-none w-[var(--ui-status-dot-size-md)] h-[var(--ui-status-dot-size-md)] rounded-full";
function SwitchButton(props) {
let [helpOpen, setHelpOpen] = createSignal(!1);
return (() => {
var _el$ = _tmpl$28(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.firstChild, _el$5 = _el$4.nextSibling, _el$6 = _el$5.firstChild, _el$7 = _el$6.nextSibling, _el$8 = _el$3.nextSibling;
return _el$3.$$click = (event) => {
event.stopPropagation(), props.onChange(!props.checked);
}, insert(_el$4, () => props.label), insert(_el$6, (() => {
var _c$ = memo(() => !!props.checked);
return () => _c$() ? texts_default.settings.on : texts_default.settings.off;
})()), _el$8.$$click = (event) => {
event.stopPropagation(), setHelpOpen((open) => !open);
}, insert(_el$, createComponent(Show, {
get when() {
return helpOpen();
},
get children() {
var _el$9 = _tmpl$15();
return insert(_el$9, () => props.description), _el$9;
}
}), null), createRenderEffect(() => className(_el$7, `${SETTINGS_DOT_CLASS} ${props.checked ? "bg-[var(--color-state-on)]" : "bg-[var(--color-state-off)]"}`)), _el$;
})();
}
function SettingsMenu(props) {
let [draft, setDraft] = createStore(untrack(() => ({
...props.initState
}))), [activeTab, setActiveTab] = createSignal("general"), [categoryOpen, setCategoryOpen] = createSignal(!1), [helpOpen, setHelpOpen] = createSignal(!1), [changed, setChanged] = createSignal(!1), menu, close = () => changed() && !window.confirm(texts_default.settings.discardChanges) ? !1 : (props.onOpenChange(!1), !0), updateDraft = (key, value) => {
setChanged(!0), setDraft(key, value);
};
return createEffect(() => {
props.open && (setDraft({
...props.initState
}), setActiveTab("general"), setCategoryOpen(!1), setHelpOpen(!1), setChanged(!1));
}), onMount(() => {
let onPointerDown = (event) => {
props.open && (event.target instanceof Element && menu.contains(event.target) || close() || (event.preventDefault(), event.stopImmediatePropagation()));
}, onKeyDown = (event) => {
if (props.open && event.key === "Escape") {
if (categoryOpen()) {
setCategoryOpen(!1), event.preventDefault(), event.stopImmediatePropagation();
return;
}
close() || (event.preventDefault(), event.stopImmediatePropagation());
}
};
document.addEventListener("pointerdown", onPointerDown), document.addEventListener("keydown", onKeyDown), onCleanup(() => {
document.removeEventListener("pointerdown", onPointerDown), document.removeEventListener("keydown", onKeyDown);
});
}), createComponent(Show, {
get when() {
return props.open;
},
get children() {
var _el$0 = _tmpl$83(), _el$1 = _el$0.firstChild, _el$10 = _el$1.firstChild, _el$11 = _el$10.firstChild, _el$12 = _el$11.nextSibling, _el$14 = _el$1.nextSibling, _el$21 = _el$14.nextSibling, _el$22 = _el$21.firstChild, _el$23 = _el$22.nextSibling, _el$24 = _el$23.nextSibling, _ref$ = menu;
return typeof _ref$ == "function" ? use(_ref$, _el$0) : menu = _el$0, _el$10.$$click = () => setCategoryOpen((open) => !open), insert(_el$11, () => SETTINGS_SECTIONS.find(([tab]) => tab === activeTab())?.[1]), insert(_el$12, () => categoryOpen() ? "▴" : "▾"), insert(_el$1, createComponent(Show, {
get when() {
return categoryOpen();
},
get children() {
var _el$13 = _tmpl$37();
return insert(_el$13, createComponent(For, {
each: SETTINGS_SECTIONS,
children: ([tab, label]) => (() => {
var _el$25 = _tmpl$93();
return _el$25.$$click = () => {
setActiveTab(tab), setCategoryOpen(!1);
}, insert(_el$25, label), createRenderEffect((_p$) => {
var _v$ = `flex w-full min-h-[var(--ui-control-size-md)] items-center px-md border-0 border-b last:border-b-0 ehp-color-site-border-subtle-b ehp-color-site-text font-inherit text-left [font-size:var(--ui-font-size-md)] cursor-pointer [-webkit-tap-highlight-color:transparent] ${activeTab() === tab ? "bg-[var(--color-site-item-hover)] font-700" : "!bg-transparent hover:!bg-[var(--color-site-item-hover)]"}`, _v$2 = activeTab() === tab;
return _v$ !== _p$.e && className(_el$25, _p$.e = _v$), _v$2 !== _p$.t && setAttribute(_el$25, "aria-checked", _p$.t = _v$2), _p$;
}, {
e: void 0,
t: void 0
}), _el$25;
})()
})), _el$13;
}
}), null), insert(_el$14, createComponent(Show, {
get when() {
return activeTab() === "general";
},
get children() {
return [createComponent(SwitchButton, {
get checked() {
return draft.readerEnabled;
},
get description() {
return texts_default.settings.readerHelp;
},
get label() {
return texts_default.settings.readerLabel;
},
onChange: (value) => updateDraft("readerEnabled", value)
}), createComponent(SwitchButton, {
get checked() {
return draft.touchUiEnabled;
},
get description() {
return texts_default.settings.touchUiHelp;
},
get label() {
return texts_default.settings.touchUiLabel;
},
onChange: (value) => updateDraft("touchUiEnabled", value)
}), createComponent(Show, {
get when() {
return draft.readHistoryEnabled;
},
get children() {
var _el$15 = _tmpl$47();
return insert(_el$15, () => texts_default.settings.historyLabel), createRenderEffect(() => setAttribute(_el$15, "href", props.historyHref)), _el$15;
}
}), (() => {
var _el$16 = _tmpl$56(), _el$17 = _el$16.firstChild;
return _el$16.$$click = () => setHelpOpen(!0), insert(_el$17, () => texts_default.help.title), _el$16;
})()];
}
}), null), insert(_el$14, createComponent(Show, {
get when() {
return activeTab() === "enhance";
},
get children() {
return [createComponent(SwitchButton, {
get checked() {
return draft.enhanceSearchGridsEnabled;
},
get description() {
return texts_default.settings.enhanceSearchHelp;
},
get label() {
return texts_default.settings.enhanceSearchLabel;
},
onChange: (value) => updateDraft("enhanceSearchGridsEnabled", value)
}), createComponent(SwitchButton, {
get checked() {
return draft.enhanceThumbsGridsEnabled;
},
get description() {
return texts_default.settings.enhanceThumbsHelp;
},
get label() {
return texts_default.settings.enhanceThumbsLabel;
},
onChange: (value) => updateDraft("enhanceThumbsGridsEnabled", value)
}), createComponent(SwitchButton, {
get checked() {
return draft.myTagsEnabled;
},
get description() {
return texts_default.settings.myTagsHelp;
},
get label() {
return texts_default.settings.myTagsLabel;
},
onChange: (value) => updateDraft("myTagsEnabled", value)
}), createComponent(SwitchButton, {
get checked() {
return draft.readHistoryEnabled;
},
get description() {
return texts_default.settings.readHistoryHelp;
},
get label() {
return texts_default.settings.readHistoryLabel;
},
onChange: (value) => updateDraft("readHistoryEnabled", value)
}), createComponent(SwitchButton, {
get checked() {
return draft.searchHistoryEnabled;
},
get description() {
return texts_default.settings.searchHistoryHelp;
},
get label() {
return texts_default.settings.searchHistoryLabel;
},
onChange: (value) => updateDraft("searchHistoryEnabled", value)
})];
}
}), null), insert(_el$14, createComponent(Show, {
get when() {
return activeTab() === "options";
},
get children() {
return [createComponent(SwitchButton, {
get checked() {
return draft.readerFullscreenEnabled;
},
get description() {
return texts_default.settings.readerFullscreenHelp;
},
get label() {
return texts_default.settings.readerFullscreenLabel;
},
onChange: (value) => updateDraft("readerFullscreenEnabled", value)
}), createComponent(SwitchButton, {
get checked() {
return draft.openGalleryInNewTab;
},
get description() {
return texts_default.settings.openGalleryInNewTabHelp;
},
get label() {
return texts_default.settings.openGalleryInNewTabLabel;
},
onChange: (value) => updateDraft("openGalleryInNewTab", value)
}), createComponent(SwitchButton, {
get checked() {
return draft.includeUnreadHistoryEnabled;
},
get description() {
return texts_default.settings.includeUnreadHistoryHelp;
},
get label() {
return texts_default.settings.includeUnreadHistoryLabel;
},
onChange: (value) => updateDraft("includeUnreadHistoryEnabled", value)
})];
}
}), null), insert(_el$14, createComponent(Show, {
get when() {
return activeTab() === "about";
},
get children() {
return [_tmpl$66(), (() => {
var _el$19 = _tmpl$73(), _el$20 = _el$19.firstChild;
return insert(_el$19, "260723.1506", null), _el$19;
})()];
}
}), null), _el$22.$$click = (event) => {
event.stopPropagation(), props.onApply({
...draft
});
}, insert(_el$22, () => texts_default.button.apply), _el$23.$$click = (event) => {
event.stopPropagation(), setChanged(!0), setDraft({
...props.defaultState
});
}, insert(_el$23, () => texts_default.button.default), _el$24.$$click = (event) => {
event.stopPropagation(), close();
}, insert(_el$24, () => texts_default.button.close), insert(_el$0, createComponent(Show, {
get when() {
return helpOpen();
},
get children() {
return createComponent(InteractionHelp, {
variant: "site",
onClose: () => setHelpOpen(!1)
});
}
}), null), createRenderEffect(() => setAttribute(_el$10, "aria-expanded", categoryOpen())), _el$0;
}
});
}
delegateEvents(["click"]);
// src/components/Widgets/BackToTop.tsx
var _tmpl$16 = /* @__PURE__ */ template('<button type=button class="ehpeek-back-to-top fixed right-[max(16px,env(safe-area-inset-right,0px))] bottom-[calc(max(16px,env(safe-area-inset-bottom,0px))_+_64px)] z-ui inline-flex w-lg h-lg items-center justify-center rounded-full border-0 bg-[var(--color-site-elevated)] ehp-color-site-accent shadow-[0_4px_14px_var(--color-shadow-floating)] cursor-pointer [touch-action:none] active:scale-96">'), BACK_TO_TOP_POSITION_KEY = "ehpeek:back-to-top:position";
function BackToTop() {
let button2, drag = null, dragged = !1, [visible, setVisible] = createSignal(!1), [position, setPosition] = createSignal(null), positionStyle = () => {
let current = position();
return current ? {
bottom: `${current.bottom}px`,
right: `${current.right}px`
} : void 0;
};
return onMount(() => {
let updateVisibility = () => {
setVisible(window.scrollY > Math.max(320, window.innerHeight * 0.5));
};
updateVisibility();
let savedPosition = GM_getValue(BACK_TO_TOP_POSITION_KEY, null);
savedPosition && setPosition(savedPosition), window.addEventListener("scroll", updateVisibility, {
passive: !0
}), onCleanup(() => window.removeEventListener("scroll", updateVisibility));
}), createComponent(Show, {
get when() {
return visible();
},
get children() {
var _el$ = _tmpl$16();
_el$.$$click = (event) => {
if (dragged) {
event.preventDefault(), dragged = !1;
return;
}
window.scrollTo({
top: 0,
behavior: "smooth"
});
}, _el$.$$pointerup = (event) => {
if (!drag || drag.pointerId !== event.pointerId)
return;
button2.releasePointerCapture(event.pointerId), drag = null;
let current = position();
dragged && current && GM_setValue(BACK_TO_TOP_POSITION_KEY, current);
}, _el$.$$pointermove = (event) => {
if (!drag || drag.pointerId !== event.pointerId)
return;
let dx = event.clientX - drag.x, dy = event.clientY - drag.y;
dragged || (dragged = Math.hypot(dx, dy) > 4), setPosition(clampPosition({
bottom: drag.bottom - dy,
right: drag.right - dx
}, button2));
}, _el$.$$pointerdown = (event) => {
let rect = button2.getBoundingClientRect();
dragged = !1, drag = {
bottom: window.innerHeight - rect.bottom,
pointerId: event.pointerId,
right: window.innerWidth - rect.right,
x: event.clientX,
y: event.clientY
}, button2.setPointerCapture(event.pointerId);
};
var _ref$ = button2;
return typeof _ref$ == "function" ? use(_ref$, _el$) : button2 = _el$, insert(_el$, createComponent(Icon, {
name: "arrow-up"
})), createRenderEffect((_$p) => style(_el$, positionStyle(), _$p)), _el$;
}
});
}
function clampPosition(position, button2) {
return {
bottom: Math.min(Math.max(0, position.bottom), Math.max(0, window.innerHeight - button2.offsetHeight)),
right: Math.min(Math.max(0, position.right), Math.max(0, window.innerWidth - button2.offsetWidth))
};
}
delegateEvents(["pointerdown", "pointermove", "pointerup", "click"]);
// src/components/Widgets/ExternalDom.tsx
function DomNode2(props) {
let Component = createMemo(() => props.node?.Component);
return createComponent(Show, {
get when() {
return Component();
},
children: (Current) => createComponent(Dynamic, {
get component() {
return Current();
}
})
});
}
function DomNodes(props) {
return createComponent(For, {
get each() {
return props.nodes;
},
children: (node) => {
let Component = node.Component;
return createComponent(Component, {});
}
});
}
// src/components/TouchUI/GalleryInfoPanel.tsx
var _tmpl$17 = /* @__PURE__ */ template("<div>"), _tmpl$29 = /* @__PURE__ */ template('<div class="ehpeek-touch-gallery-rating-dialog fixed inset-0 z-overlay flex items-center justify-center p-md bg-black/65"role=dialog aria-modal=true aria-label="Rate gallery"><div class="box-border flex w-[min(92vw,420px)] flex-col gap-lg rounded-lg border ehp-color-site-border p-lg ehp-color-site-elevated ehp-color-site-text shadow-xl"><div class="textsize-md font-700">Rate gallery</div><button type=button class="relative inline-flex self-center max-w-full overflow-hidden p-0 border-0 bg-transparent cursor-pointer select-none [touch-action:manipulation] [-webkit-tap-highlight-color:transparent] focus-visible:rounded-xs focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--color-site-accent)] focus-visible:outline-offset-3px"><span class="flex gap-1px pointer-events-none text-[var(--color-muted)] opacity-40"aria-hidden=true></span><span aria-hidden=true></span></button><div class="grid grid-cols-2 gap-sm pt-md border-0 border-t border-t-[var(--color-site-border-subtle)]"><button type=button class="flex w-full min-h-[var(--ui-control-size-md)] items-center justify-center py-xs px-md rounded-md border cursor-pointer font-inherit text-center textsize-md font-700 leading-[1.1] transition-[filter,transform,box-shadow] duration-120 active:scale-98 disabled:opacity-50 disabled:cursor-default border-[var(--color-site-accent)] bg-[var(--color-site-accent)] text-[var(--color-site-surface)] shadow-[0_2px_8px_var(--color-shadow-panel)] hover:brightness-108">Submit</button><button type=button class="flex w-full min-h-[var(--ui-control-size-md)] items-center justify-center py-xs px-md rounded-md border cursor-pointer font-inherit text-center textsize-md font-700 leading-[1.1] transition-[filter,transform,box-shadow] duration-120 active:scale-98 disabled:opacity-50 disabled:cursor-default border-[var(--color-site-border-subtle)] bg-[var(--color-site-surface)] text-[var(--color-site-text)] hover:bg-[var(--color-site-item-hover)]">'), _tmpl$38 = /* @__PURE__ */ template('<section class="ehpeek-touch-gallery flex box-border w-full flex-col mb-sm large:mb-md ehp-color-site-text font-sans"><div class="ehpeek-touch-gallery-hero relative grid min-h-[clamp(130px,21vh,170px)] large:min-h-[clamp(260px,42vh,340px)] pt-sm large:pt-lg pr-[max(8px,env(safe-area-inset-right,0px))] large:pr-[max(16px,env(safe-area-inset-right,0px))] pb-24px large:pb-48px pl-[max(8px,env(safe-area-inset-left,0px))] large:pl-[max(16px,env(safe-area-inset-left,0px))] ehp-color-site-surface ehp-color-site-text"><div><div class="ehpeek-touch-gallery-hero-side flex self-stretch min-w-0 flex-col items-start gap-xs large:gap-sm pt-1px large:pt-2px"><div class="ehpeek-touch-gallery-heading flex min-w-0 w-full flex-none flex-col gap-xs large:gap-sm items-start pb-2px large:pb-xs"><div class="ehpeek-touch-gallery-title-main line-clamp-4 flex-none overflow-hidden [font-size:var(--ui-font-size-title)] font-400 leading-[1.16] text-left break-anywhere"></div><div class="ehpeek-touch-gallery-title-sub line-clamp-3 flex-none overflow-hidden opacity-82 textsize-md leading-[1.2] text-left break-anywhere"></div></div><div class="ehpeek-touch-gallery-category box-border flex-none self-start whitespace-nowrap rounded-xs border border-solid py-3px large:py-6px px-5px large:px-10px text-center textsize-md font-700 leading-[1.1] uppercase"></div></div></div></div><div class="ehpeek-touch-gallery-primary relative z-1 grid grid-cols-[1fr_1fr] min-h-[var(--ui-control-size-xl)] mt--9px large:mt--18px mr-[max(7px,env(safe-area-inset-right,0px))] large:mr-[max(14px,env(safe-area-inset-right,0px))] ml-[max(7px,env(safe-area-inset-left,0px))] large:ml-[max(14px,env(safe-area-inset-left,0px))] overflow-visible rounded-xs bg-[var(--color-site-elevated)] shadow-[0_2px_10px_var(--color-shadow-panel)]"><div class="ehpeek-touch-gallery-primary-actions flex min-w-0 border-0 border-l-4 large:border-l-8 border-solid border-l-[var(--color-site-page)]"></div></div><div class="ehpeek-touch-gallery-content flex flex-col gap-sm large:gap-lg pt-md large:pt-xl pr-[max(8px,env(safe-area-inset-right,0px))] large:pr-[max(16px,env(safe-area-inset-right,0px))] pb-sm large:pb-lg pl-[max(8px,env(safe-area-inset-left,0px))] large:pl-[max(16px,env(safe-area-inset-left,0px))] ehp-color-site-page ehp-color-site-text"><div class="ehpeek-touch-gallery-meta grid grid-cols-[repeat(3,minmax(0,1fr))] gap-y-sm large:gap-y-md gap-x-sm large:gap-x-lg items-center [font-size:var(--ui-font-size-prominent)] leading-[1.2] text-center">'), _tmpl$48 = /* @__PURE__ */ template('<div class="ehpeek-touch-gallery-cover flex self-center justify-self-stretch w-full max-h-full aspect-[2/3] items-center justify-center overflow-hidden rounded-3px">'), _tmpl$57 = /* @__PURE__ */ template('<button type=button class="ehpeek-touch-gallery-rating flex w-[65%] max-w-full flex-none self-end flex-col items-end gap-2px large:gap-xs mt-auto p-0 border-0 bg-transparent ehp-color-site-text font-inherit text-right cursor-pointer select-none [touch-action:manipulation] [-webkit-tap-highlight-color:transparent] focus-visible:rounded-xs focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--color-site-accent)] focus-visible:outline-offset-3px"aria-label="Rate gallery"><div class="ehpeek-touch-gallery-rating-stars relative inline-flex [&_.ehpeek-icon]:w-[var(--ui-icon-size-lg)] [&_.ehpeek-icon]:h-[var(--ui-icon-size-lg)]"><span class="ehpeek-touch-gallery-rating-stars-empty flex gap-1px text-[var(--color-muted)] opacity-40"aria-hidden=true></span><span aria-hidden=true></span></div><div class="ehpeek-touch-gallery-rating-meta flex items-center justify-end gap-3px large:gap-6px text-[var(--color-muted)] [font-size:var(--ui-font-size-prominent)] leading-[1.15] whitespace-nowrap"><span class=ehpeek-touch-gallery-rating-label aria-live=polite>'), _tmpl$67 = /* @__PURE__ */ template('<span class="ehpeek-touch-gallery-rating-count flex-none pl-3px large:pl-6px border-0 border-l border-[var(--color-site-border-subtle)] opacity-75">'), _tmpl$74 = /* @__PURE__ */ template('<div class="ehpeek-touch-gallery-meta-value line-clamp-2 min-w-0 overflow-hidden whitespace-normal break-normal">'), _tmpl$84 = /* @__PURE__ */ template('<div class="ehpeek-touch-gallery-tag-groups flex flex-col pt-2px"><button type=button><span>Tagging</span><span aria-hidden=true></span></button><div class="grid min-w-0 w-full grid-cols-[max-content_minmax(0,1fr)] items-start gap-x-xs large:gap-x-sm gap-y-sm large:gap-y-md">'), _tmpl$94 = /* @__PURE__ */ template('<div class="ehpeek-touch-gallery-actions-menu-panel absolute top-[calc(var(--ui-control-size-md)+8px)] right-0 z-overlay flex min-w-285px max-w-[min(78vw,320px)] flex-col overflow-hidden border ehp-color-site-border rounded-sm ehp-color-site-elevated">'), _tmpl$0 = /* @__PURE__ */ template('<div class="ehpeek-touch-gallery-actions-menu relative flex min-w-0 items-center justify-center"><button type=button class="ehpeek-touch-gallery-actions-menu-button inline-flex w-[var(--ui-control-size-md)] h-[var(--ui-control-size-md)] items-center justify-center border-0 bg-transparent ehp-color-site-text"aria-haspopup=menu>'), _tmpl$1 = /* @__PURE__ */ template('<section class="ehpeek-touch-gallery-tag-group contents"><div class="ehpeek-touch-gallery-tag-group-name box-border min-h-[var(--ui-control-size-sm)] whitespace-nowrap rounded-xl bg-[var(--color-site-elevated)] py-xs px-md text-center lowercase ehp-color-site-accent textsize-md font-600"></div><div class="ehpeek-touch-gallery-tags flex flex-wrap gap-xs large:gap-sm">'), _tmpl$102 = /* @__PURE__ */ template('<a class="ehpeek-touch-gallery-tag inline-flex flex-none box-border max-w-full min-h-[var(--ui-control-size-sm)] items-center overflow-hidden text-ellipsis whitespace-nowrap appearance-none m-0 py-0 rounded-xl border border-[var(--color-site-border-subtle)] bg-[var(--color-site-surface)] px-lg ehp-color-site-text font-inherit font-700 textsize-md cursor-pointer select-text no-underline transition-[border-color,background-color,color] duration-120 hover:border-[var(--color-site-border)] hover:bg-[var(--color-site-accent-hover)] hover:ehp-color-site-accent">'), _tmpl$112 = /* @__PURE__ */ template('<div role=dialog aria-modal=true><div class="ehpeek-touch-gallery-tag-menu-panel box-border flex w-full max-w-420px max-h-[calc(100dvh-32px)] flex-col overflow-x-hidden overflow-y-auto overscroll-contain whitespace-nowrap border ehp-color-site-border rounded-md ehp-color-site-elevated shadow-xl"role=menu>'), _tmpl$122 = /* @__PURE__ */ template('<div class="absolute top-full left-0 right-0 z-2 mt-xs max-h-240px overflow-y-auto overscroll-contain rounded-md border ehp-color-site-border ehp-color-site-elevated shadow-xl"role=listbox>'), _tmpl$132 = /* @__PURE__ */ template('<div class="flex flex-col gap-sm ehp-color-site-text textsize-md font-600"><span></span><div class=relative><button type=button class="flex box-border w-full min-h-[var(--ui-control-size-md)] items-center justify-between gap-md rounded-md border ehp-color-site-border !bg-transparent hover:!bg-[var(--color-site-item-hover)] active:!bg-[var(--color-site-item-hover)] ehp-color-site-text px-md font-inherit text-left textsize-md cursor-pointer"aria-haspopup=listbox><span class="min-w-0 overflow-hidden text-ellipsis whitespace-nowrap"></span><span class=flex-none aria-hidden=true>'), _tmpl$142 = /* @__PURE__ */ template('<div class="flex flex-col gap-sm ehp-color-site-text textsize-md font-600"><span></span><div class="overflow-hidden rounded-md border ehp-color-site-border"role=radiogroup>'), _tmpl$152 = /* @__PURE__ */ template('<div class="grid grid-cols-2 gap-md"><button type=button class="flex w-full min-h-[var(--ui-control-size-md)] items-center justify-center py-xs px-md rounded-md border cursor-pointer font-inherit text-center textsize-md font-700 leading-[1.1] transition-[filter,transform,box-shadow] duration-120 active:scale-98 disabled:opacity-50 disabled:cursor-default border-[var(--color-site-border-subtle)] bg-[var(--color-site-surface)] text-[var(--color-site-text)] hover:bg-[var(--color-site-item-hover)]"></button><button type=button class="flex w-full min-h-[var(--ui-control-size-md)] items-center justify-center py-xs px-md rounded-md border cursor-pointer font-inherit text-center textsize-md font-700 leading-[1.1] transition-[filter,transform,box-shadow] duration-120 active:scale-98 disabled:opacity-50 disabled:cursor-default gap-md border-[var(--color-site-accent)] bg-[var(--color-site-accent)] text-[var(--color-site-surface)] shadow-[0_2px_8px_var(--color-shadow-panel)] hover:brightness-108"><span>'), _tmpl$162 = /* @__PURE__ */ template('<div class="fixed inset-0 z-overlay flex items-center justify-center p-lg bg-black/65"role=dialog aria-modal=true><div class="box-border flex w-full max-w-420px max-h-[calc(100dvh-32px)] flex-col gap-lg overflow-y-auto overscroll-contain rounded-md border ehp-color-site-border ehp-color-site-elevated p-lg shadow-xl"><div class="ehp-color-site-text textsize-lg font-700">'), _tmpl$172 = /* @__PURE__ */ template("<button type=button role=menuitem><span>"), _tmpl$18 = /* @__PURE__ */ template("<button type=button role=option><span>"), _tmpl$19 = /* @__PURE__ */ template("<button type=button role=radio><span>"), _tmpl$20 = /* @__PURE__ */ template('<span class="contents [&_*]:!bg-transparent [&_*]:!text-inherit"translate=no>'), _tmpl$21 = /* @__PURE__ */ template('<div class="fixed inset-0 z-overlay flex items-center justify-center p-lg bg-black/65 font-sans textsize-md"role=dialog aria-modal=true><div class="ehpeek-touch-gallery-favorite-panel box-border flex w-full max-w-420px max-h-[calc(100dvh-32px)] flex-col overflow-hidden border ehp-color-site-border rounded-md ehp-color-site-elevated shadow-xl"><div class="flex flex-none items-center justify-between gap-md py-sm pl-lg pr-sm border-0 border-b ehp-color-site-border-subtle-b"><span class="min-w-0 overflow-hidden text-ellipsis whitespace-nowrap ehp-color-site-text textsize-md font-700"></span><button type=button class="inline-flex w-[var(--ui-control-size-md)] h-[var(--ui-control-size-md)] flex-none items-center justify-center p-0 rounded-md border ehp-color-site-border bg-[var(--color-site-surface)] ehp-color-site-text cursor-pointer hover:bg-[var(--color-site-item-hover)] active:scale-96"></button></div><div class="min-h-0 overflow-y-auto overscroll-contain">'), _tmpl$222 = /* @__PURE__ */ template('<div class="ehpeek-touch-gallery-favorite-menu relative z-2 min-w-0"><button type=button aria-haspopup=menu><span class="block leading-[1.15]"></span><span class="ehpeek-touch-gallery-favorite-icon block mt-2px opacity-78 normal-case"aria-hidden=true>'), _tmpl$232 = /* @__PURE__ */ template('<div class="ehpeek-touch-gallery-favorite-loading flex min-h-[var(--ui-control-size-lg)] items-center gap-md py-md px-lg border-0 border-b ehp-color-site-border-subtle-b bg-transparent ehp-color-site-text font-inherit textsize-md leading-[1.2] text-left">'), _tmpl$242 = /* @__PURE__ */ template('<button type=button><span class="ehpeek-touch-gallery-favorite-option-icon flex-none ehp-color-site-text"aria-hidden=true></span><span></span><span aria-hidden=true>'), RATING_STAR_INDEXES = [0, 1, 2, 3, 4];
function GalleryInfoPanel(props) {
let source = untrack(() => props.source), rating = source.data.rating, hasCover = source.elems.cover !== null, [ratingValue, setRatingValue] = createSignal(rating?.value ?? 0), [ratingPreview, setRatingPreview] = createSignal(null), [ratingPickerOpen, setRatingPickerOpen] = createSignal(!1), [ratingSubmitted, setRatingSubmitted] = createSignal(rating?.rated ?? !1), [ratingCount] = createSignal(rating?.count ?? ""), [ratingValueLabel] = createSignal(rating?.label ?? ""), initialTagGroups = source.data.tagGroups.map((group) => ({
...group,
tags: group.tags.flatMap(({
contentSourceIndex,
...tag2
}) => {
let contentSource = source.elems.tagContents[contentSourceIndex];
return contentSource ? [{
...tag2,
contentSource
}] : [];
})
})), [tagGroups, setTagGroups] = createSignal(initialTagGroups), [selectedTag, setSelectedTag] = createSignal(null), [tagging, setTagging] = createSignal(!1), hasNewTag = () => source.elems.newTag !== null, displayedRating = createMemo(() => ratingPreview() ?? ratingValue()), ratingLabel = createMemo(() => {
let preview = ratingPreview();
return preview !== null ? `Rate as ${preview.toFixed(1)} stars` : ratingValueLabel();
});
onMount(() => {
let stopObservingTags = source.handle.observeGalleryTagGroups(setTagGroups);
onCleanup(stopObservingTags);
});
let submitRating = (value) => {
if (!rating)
return !1;
try {
return source.handle.submitGalleryRating(value), setRatingValue(value), setRatingPreview(null), setRatingSubmitted(!0), !0;
} catch (error) {
return setRatingPreview(null), console.error("[ehpeek]", error), window.alert(error instanceof Error ? error.message : texts_default.errors.loadFailed), !1;
}
}, openTagMenu = (tag2) => {
try {
source.handle.openGalleryTagMenu(tag2), setSelectedTag(tag2);
} catch (error) {
console.error("[ehpeek] Gallery tag actions failed", error), window.alert(error instanceof Error ? error.message : texts_default.errors.loadFailed);
}
}, closeTagMenu = () => {
selectedTag() && (source.handle.closeGalleryTagMenu(), setSelectedTag(null));
}, updateTag = (updatedTag) => {
setTagGroups((groups) => groups.map((group) => ({
...group,
tags: group.tags.map((tag2) => tag2.url === updatedTag.url ? updatedTag : tag2)
}))), setSelectedTag((tag2) => tag2?.url === updatedTag.url ? updatedTag : tag2);
};
return (() => {
var _el$ = _tmpl$38(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.firstChild, _el$5 = _el$4.firstChild, _el$6 = _el$5.firstChild, _el$7 = _el$6.nextSibling, _el$8 = _el$5.nextSibling, _el$9 = _el$2.nextSibling, _el$0 = _el$9.firstChild, _el$1 = _el$9.nextSibling, _el$10 = _el$1.firstChild;
return className(_el$3, `ehpeek-touch-gallery-summary grid gap-9px large:gap-18px items-stretch ${hasCover ? "grid-cols-[minmax(60px,38%)_minmax(0,1fr)] large:grid-cols-[minmax(120px,38%)_minmax(0,1fr)]" : "grid-cols-1"}`), insert(_el$3, hasCover && (() => {
var _el$21 = _tmpl$48();
return insert(_el$21, createComponent(DomNode2, {
get node() {
return source.elems.cover;
}
})), _el$21;
})(), _el$4), insert(_el$6, () => source.data.titleMain), insert(_el$7, () => source.data.titleSub), insert(_el$8, () => source.data.category), insert(_el$4, rating && (() => {
var _el$22 = _tmpl$57(), _el$23 = _el$22.firstChild, _el$24 = _el$23.firstChild, _el$25 = _el$24.nextSibling, _el$26 = _el$23.nextSibling, _el$27 = _el$26.firstChild;
return _el$22.addEventListener("blur", () => {
setRatingPreview(null);
}), _el$22.addEventListener("pointerleave", () => {
setRatingPreview(null);
}), _el$22.$$click = () => {
setRatingPreview(null), setRatingPickerOpen(!0);
}, _el$23.$$pointermove = (event) => {
event.pointerType === "mouse" && setRatingPreview(ratingFromPointer(event.clientX, event.currentTarget));
}, insert(_el$24, createComponent(For, {
each: RATING_STAR_INDEXES,
children: () => createComponent(Icon, {
name: "star"
})
})), insert(_el$25, createComponent(For, {
each: RATING_STAR_INDEXES,
children: () => createComponent(Icon, {
name: "star",
filled: !0
})
})), insert(_el$27, ratingLabel), insert(_el$26, (() => {
var _c$2 = memo(() => !!ratingCount());
return () => _c$2() && (() => {
var _el$28 = _tmpl$67();
return insert(_el$28, ratingCount), _el$28;
})();
})(), null), createRenderEffect((_p$) => {
var _v$4 = `ehpeek-touch-gallery-rating-stars-fill absolute top-0 left-0 flex gap-1px overflow-hidden ${ratingSubmitted() ? "text-[var(--color-rating-submitted)]" : "ehp-color-site-accent"}`, _v$5 = `${displayedRating() / 5 * 100}%`;
return _v$4 !== _p$.e && className(_el$25, _p$.e = _v$4), _v$5 !== _p$.t && setStyleProperty(_el$25, "width", _p$.t = _v$5), _p$;
}, {
e: void 0,
t: void 0
}), _el$22;
})(), null), _el$9.addEventListener("dragstart", (event) => event.preventDefault()), insert(_el$9, createComponent(TouchGalleryFavoriteButton, {
source
}), _el$0), insert(_el$0, () => props.primaryAction), insert(_el$10, createComponent(For, {
get each() {
return source.data.summary;
},
children: (item) => (() => {
var _el$29 = _tmpl$74();
return insert(_el$29, () => item.value), _el$29;
})()
}), null), insert(_el$10, createComponent(TouchGalleryActionsMenu, {
get items() {
return source.elems.actionItems;
}
}), null), insert(_el$1, (() => {
var _c$ = memo(() => tagGroups().length > 0);
return () => _c$() && (() => {
var _el$30 = _tmpl$84(), _el$31 = _el$30.firstChild, _el$32 = _el$31.firstChild, _el$33 = _el$32.nextSibling, _el$34 = _el$31.nextSibling;
return _el$30.addEventListener("dragstart", (event) => event.preventDefault()), _el$31.$$click = () => {
setTagging((enabled) => !enabled);
}, insert(_el$34, createComponent(For, {
get each() {
return tagGroups();
},
children: (group) => createComponent(TouchGalleryTagGroup, {
group,
get tagging() {
return tagging();
},
onTagOpen: openTagMenu
})
})), createRenderEffect((_p$) => {
var _v$6 = `inline-flex self-end min-h-[var(--ui-control-size-xs)] items-center justify-center gap-sm large:gap-md mb-xs large:mb-sm rounded-xl border-0 px-md large:px-lg font-inherit font-700 textsize-sm cursor-pointer transition-[background-color,color] duration-120 ${tagging() ? "bg-[var(--color-site-accent-hover)] ehp-color-site-accent" : "bg-[var(--color-site-surface)] ehp-color-site-text"}`, _v$7 = tagging(), _v$8 = `block flex-none w-[var(--ui-status-dot-size-lg)] h-[var(--ui-status-dot-size-lg)] rounded-full ${tagging() ? "bg-[var(--color-state-on)]" : "bg-[var(--color-state-off)]"}`;
return _v$6 !== _p$.e && className(_el$31, _p$.e = _v$6), _v$7 !== _p$.t && setAttribute(_el$31, "aria-pressed", _p$.t = _v$7), _v$8 !== _p$.a && className(_el$33, _p$.a = _v$8), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$30;
})();
})(), null), insert(_el$1, createComponent(Show, {
get when() {
return hasNewTag();
},
get children() {
var _el$11 = _tmpl$17();
return insert(_el$11, createComponent(TouchGalleryNewTag, {
source
})), createRenderEffect(() => className(_el$11, tagging() ? "block" : "hidden")), _el$11;
}
}), null), insert(_el$, createComponent(TouchGalleryTagMenu, {
source,
get tag() {
return selectedTag();
},
onClose: closeTagMenu,
onTagUpdated: updateTag
}), null), insert(_el$, createComponent(Show, {
get when() {
return ratingPickerOpen();
},
get children() {
var _el$12 = _tmpl$29(), _el$13 = _el$12.firstChild, _el$14 = _el$13.firstChild, _el$15 = _el$14.nextSibling, _el$16 = _el$15.firstChild, _el$17 = _el$16.nextSibling, _el$18 = _el$15.nextSibling, _el$19 = _el$18.firstChild, _el$20 = _el$19.nextSibling;
return _el$12.$$click = (event) => {
event.target === event.currentTarget && (setRatingPreview(null), setRatingPickerOpen(!1));
}, _el$15.$$click = (event) => {
setRatingPreview(ratingFromPointer(event.clientX, event.currentTarget));
}, insert(_el$16, createComponent(For, {
each: RATING_STAR_INDEXES,
children: () => createComponent(Icon, {
name: "star",
size: 48
})
})), insert(_el$17, createComponent(For, {
each: RATING_STAR_INDEXES,
children: () => createComponent(Icon, {
name: "star",
size: 48,
filled: !0
})
})), _el$19.$$click = () => {
submitRating(displayedRating()) && setRatingPickerOpen(!1);
}, _el$20.$$click = () => {
setRatingPreview(null), setRatingPickerOpen(!1);
}, insert(_el$20, () => texts_default.button.close), createRenderEffect((_p$) => {
var _v$ = `Rate gallery: ${displayedRating().toFixed(1)} stars`, _v$2 = `absolute top-0 left-0 flex gap-1px overflow-hidden pointer-events-none ${ratingSubmitted() ? "text-[var(--color-rating-submitted)]" : "ehp-color-site-accent"}`, _v$3 = `${displayedRating() / 5 * 100}%`;
return _v$ !== _p$.e && setAttribute(_el$15, "aria-label", _p$.e = _v$), _v$2 !== _p$.t && className(_el$17, _p$.t = _v$2), _v$3 !== _p$.a && setStyleProperty(_el$17, "width", _p$.a = _v$3), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$12;
}
}), null), createRenderEffect((_$p) => style(_el$8, source.data.categoryAppearance, _$p)), _el$;
})();
}
function TouchGalleryActionsMenu(props) {
let [open, setOpen] = createSignal(!1), root;
return onMount(() => {
let onClick = (event) => {
event.target instanceof Element && root.contains(event.target) || setOpen(!1);
};
document.addEventListener("click", onClick), onCleanup(() => {
document.removeEventListener("click", onClick);
});
}), (() => {
var _el$35 = _tmpl$0(), _el$36 = _el$35.firstChild, _ref$ = root;
return typeof _ref$ == "function" ? use(_ref$, _el$35) : root = _el$35, _el$36.$$click = (event) => {
event.stopPropagation(), setOpen((value) => !value);
}, insert(_el$36, createComponent(Icon, {
name: "menu"
})), insert(_el$35, createComponent(Show, {
get when() {
return open();
},
get children() {
var _el$37 = _tmpl$94();
return insert(_el$37, createComponent(DomNodes, {
get nodes() {
return props.items;
}
})), _el$37;
}
}), null), createRenderEffect(() => setAttribute(_el$36, "aria-expanded", open())), _el$35;
})();
}
function TouchGalleryTagGroup(props) {
return (() => {
var _el$38 = _tmpl$1(), _el$39 = _el$38.firstChild, _el$40 = _el$39.nextSibling;
return insert(_el$39, () => props.group.namespace), _el$40.$$click = (event) => {
if (!props.tagging || event.defaultPrevented || event.button !== 0 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
return;
let href = (event.target instanceof Element ? event.target.closest("a.ehpeek-touch-gallery-tag") : null)?.getAttribute("href"), tag2 = props.group.tags.find((candidate) => candidate.url === href);
tag2 && (event.preventDefault(), props.onTagOpen(tag2));
}, insert(_el$40, createComponent(For, {
get each() {
return props.group.tags;
},
children: (tag2) => createComponent(TouchGalleryTag, {
tag: tag2
})
})), _el$38;
})();
}
function TouchGalleryTag(props) {
return (() => {
var _el$41 = _tmpl$102();
return setAttribute(_el$41, "draggable", !1), insert(_el$41, createComponent(TouchGalleryTagContent, {
get tag() {
return props.tag;
}
})), createRenderEffect((_p$) => {
var _v$9 = props.tag.url, _v$0 = props.tag.appearance.backgroundColor, _v$1 = props.tag.appearance.borderColor, _v$10 = props.tag.appearance.color, _v$11 = props.tag.label;
return _v$9 !== _p$.e && setAttribute(_el$41, "href", _p$.e = _v$9), _v$0 !== _p$.t && setStyleProperty(_el$41, "background-color", _p$.t = _v$0), _v$1 !== _p$.a && setStyleProperty(_el$41, "border-color", _p$.a = _v$1), _v$10 !== _p$.o && setStyleProperty(_el$41, "color", _p$.o = _v$10), _v$11 !== _p$.i && setAttribute(_el$41, "aria-label", _p$.i = _v$11), _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0
}), _el$41;
})();
}
function TouchGalleryTagMenu(props) {
let onClose = untrack(() => props.onClose), onTagUpdated = untrack(() => props.onTagUpdated), [favoriteDialogOpen, setFavoriteDialogOpen] = createSignal(!1), tagSets = state.gallery.myTagSets.reload(), [selectedTagSet, setSelectedTagSet] = createSignal(tagSets.find((option2) => option2.selected)?.value ?? tagSets[0]?.value ?? "1"), [collectionOpen, setCollectionOpen] = createSignal(!1), [tagMode, setTagMode] = createSignal("marked"), [updating, setUpdating] = createSignal(!1), [favoriteTag, setFavoriteTag] = createSignal(null);
onMount(() => {
let onKeyDown = (event) => {
event.key === "Escape" && props.tag && props.onClose();
};
document.addEventListener("keydown", onKeyDown), onCleanup(() => {
document.removeEventListener("keydown", onKeyDown);
});
});
let updateFavoriteTag = async (tag2) => {
if (!updating()) {
setUpdating(!0);
try {
let myTagsPage = tag2.myTag ? await props.source.handle.removeFavoriteTag(tag2) : await props.source.handle.submitFavoriteTag(tag2, selectedTagSet(), tagMode()), updateAppearance = (appearance) => onTagUpdated({
...tag2,
appearance: appearance ? {
...tag2.appearance,
backgroundColor: appearance.backgroundColor,
color: appearance.color
} : {
backgroundColor: "",
borderColor: "",
color: ""
},
myTag: appearance ? {
id: appearance.id,
tagSet: appearance.tagSet
} : null
});
updateAppearance(myTagsPage.appearances.find((item) => item.name === tag2.name)), setFavoriteDialogOpen(!1), onClose(), refreshMyTags(myTagsPage).then((appearances) => {
appearances && updateAppearance(appearances.find((item) => item.name === tag2.name));
});
} catch (error) {
console.error("[ehpeek]", error), window.alert(error instanceof Error ? error.message : texts_default.errors.loadFailed);
} finally {
setUpdating(!1);
}
}
};
return [(() => {
var _el$42 = _tmpl$112(), _el$43 = _el$42.firstChild;
return _el$42.$$click = (event) => {
event.target === event.currentTarget && props.onClose();
}, _el$43.$$click = () => {
updating() || props.onClose();
}, insert(_el$43, createComponent(Show, {
get when() {
return !updating();
},
get fallback() {
return createComponent(WelcomeIcon, {
embedded: !0,
get label() {
return texts_default.reader.loading;
},
showIcon: !1
});
},
get children() {
return [createComponent(DomNode2, {
get node() {
return props.source.elems.tagMenuAction;
}
}), createComponent(Show, {
get when() {
return props.tag;
},
children: (tag2) => createComponent(Show, {
get when() {
return !tag2().myTag;
},
get fallback() {
return (() => {
var _el$63 = _tmpl$172(), _el$64 = _el$63.firstChild;
return _el$63.$$click = (event) => {
event.stopPropagation(), updateFavoriteTag(tag2());
}, insert(_el$63, createComponent(Icon, {
name: "heart",
filled: !0
}), _el$64), insert(_el$64, () => texts_default.gallery.removeFavoriteTag), createRenderEffect(() => className(_el$63, sharedApply.galleryTagMenuItem)), _el$63;
})();
},
get children() {
var _el$61 = _tmpl$172(), _el$62 = _el$61.firstChild;
return _el$61.$$click = () => {
setFavoriteTag(tag2()), setCollectionOpen(!1), setFavoriteDialogOpen(!0);
}, insert(_el$61, createComponent(Icon, {
name: "heart"
}), _el$62), insert(_el$62, () => texts_default.gallery.favoriteTag), createRenderEffect(() => className(_el$61, sharedApply.galleryTagMenuItem)), _el$61;
}
})
})];
}
})), createRenderEffect((_p$) => {
var _v$12 = `ehpeek-touch-gallery-tag-menu-dialog fixed inset-0 z-overlay flex items-center justify-center p-lg bg-black/65 transition-opacity duration-120 ${props.tag ? "visible opacity-100" : "invisible opacity-0 pointer-events-none"}`, _v$13 = !props.tag, _v$14 = props.tag?.label ?? "";
return _v$12 !== _p$.e && className(_el$42, _p$.e = _v$12), _v$13 !== _p$.t && setAttribute(_el$42, "aria-hidden", _p$.t = _v$13), _v$14 !== _p$.a && setAttribute(_el$42, "aria-label", _p$.a = _v$14), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$42;
})(), createComponent(Show, {
get when() {
return favoriteDialogOpen();
},
get children() {
var _el$44 = _tmpl$162(), _el$45 = _el$44.firstChild, _el$46 = _el$45.firstChild;
return _el$44.$$click = (event) => {
event.target === event.currentTarget && (setCollectionOpen(!1), setFavoriteDialogOpen(!1));
}, insert(_el$46, () => texts_default.gallery.favoriteTag), insert(_el$45, createComponent(Show, {
get when() {
return !updating();
},
get fallback() {
return createComponent(WelcomeIcon, {
embedded: !0,
get label() {
return texts_default.reader.loading;
},
showIcon: !1
});
},
get children() {
return [(() => {
var _el$47 = _tmpl$132(), _el$48 = _el$47.firstChild, _el$49 = _el$48.nextSibling, _el$50 = _el$49.firstChild, _el$51 = _el$50.firstChild, _el$52 = _el$51.nextSibling;
return insert(_el$48, () => texts_default.gallery.tagCollection), _el$50.$$click = () => setCollectionOpen((open) => !open), insert(_el$51, () => tagSets.find((option2) => option2.value === selectedTagSet())?.label ?? selectedTagSet()), insert(_el$52, () => collectionOpen() ? "▴" : "▾"), insert(_el$49, createComponent(Show, {
get when() {
return collectionOpen();
},
get children() {
var _el$53 = _tmpl$122();
return insert(_el$53, createComponent(For, {
each: tagSets,
children: (option2) => (() => {
var _el$65 = _tmpl$18(), _el$66 = _el$65.firstChild;
return _el$65.$$click = () => {
setSelectedTagSet(option2.value), setCollectionOpen(!1);
}, insert(_el$66, () => option2.label), insert(_el$65, createComponent(Show, {
get when() {
return selectedTagSet() === option2.value;
},
get children() {
return createComponent(Icon, {
name: "check"
});
}
}), null), createRenderEffect((_p$) => {
var _v$15 = `flex box-border w-full min-h-[var(--ui-control-size-md)] items-center justify-between gap-md px-md border-0 border-b last:border-b-0 ehp-color-site-border-subtle-b ehp-color-site-text font-inherit text-left textsize-md cursor-pointer ${selectedTagSet() === option2.value ? "bg-[var(--color-site-item-hover)] font-700" : "!bg-transparent hover:!bg-[var(--color-site-item-hover)]"}`, _v$16 = selectedTagSet() === option2.value;
return _v$15 !== _p$.e && className(_el$65, _p$.e = _v$15), _v$16 !== _p$.t && setAttribute(_el$65, "aria-selected", _p$.t = _v$16), _p$;
}, {
e: void 0,
t: void 0
}), _el$65;
})()
})), createRenderEffect(() => setAttribute(_el$53, "aria-label", texts_default.gallery.tagCollection)), _el$53;
}
}), null), createRenderEffect(() => setAttribute(_el$50, "aria-expanded", collectionOpen())), _el$47;
})(), (() => {
var _el$54 = _tmpl$142(), _el$55 = _el$54.firstChild, _el$56 = _el$55.nextSibling;
return insert(_el$55, () => texts_default.gallery.tagBehavior), insert(_el$56, createComponent(For, {
get each() {
return [["marked", texts_default.gallery.markTag], ["watched", texts_default.gallery.watchTag], ["hidden", texts_default.gallery.hideTag]];
},
children: ([value, label]) => (() => {
var _el$67 = _tmpl$19(), _el$68 = _el$67.firstChild;
return _el$67.$$click = () => setTagMode(value), insert(_el$68, label), insert(_el$67, createComponent(Show, {
get when() {
return tagMode() === value;
},
get children() {
return createComponent(Icon, {
name: "check"
});
}
}), null), createRenderEffect((_p$) => {
var _v$17 = `flex box-border w-full min-h-[var(--ui-control-size-md)] items-center justify-between gap-md px-md border-0 border-b last:border-b-0 ehp-color-site-border-subtle-b ehp-color-site-text font-inherit text-left textsize-md cursor-pointer ${tagMode() === value ? "bg-[var(--color-site-item-hover)] font-700" : "!bg-transparent hover:!bg-[var(--color-site-item-hover)]"}`, _v$18 = tagMode() === value;
return _v$17 !== _p$.e && className(_el$67, _p$.e = _v$17), _v$18 !== _p$.t && setAttribute(_el$67, "aria-checked", _p$.t = _v$18), _p$;
}, {
e: void 0,
t: void 0
}), _el$67;
})()
})), createRenderEffect(() => setAttribute(_el$56, "aria-label", texts_default.gallery.tagBehavior)), _el$54;
})(), (() => {
var _el$57 = _tmpl$152(), _el$58 = _el$57.firstChild, _el$59 = _el$58.nextSibling, _el$60 = _el$59.firstChild;
return _el$58.$$click = () => {
setCollectionOpen(!1), setFavoriteDialogOpen(!1);
}, insert(_el$58, () => texts_default.button.close), _el$59.$$click = () => {
let tag2 = favoriteTag();
tag2 && updateFavoriteTag(tag2);
}, insert(_el$59, createComponent(Icon, {
name: "heart"
}), _el$60), insert(_el$60, () => texts_default.button.confirm), _el$57;
})()];
}
}), null), createRenderEffect(() => setAttribute(_el$44, "aria-label", texts_default.gallery.favoriteTag)), _el$44;
}
})];
}
function TouchGalleryNewTag(props) {
return createComponent(DomNode2, {
get node() {
return props.source.elems.newTag;
}
});
}
function TouchGalleryTagContent(props) {
let host;
return onMount(() => {
onCleanup(props.tag.contentSource.mirrorContentTo(host));
}), (() => {
var _el$69 = _tmpl$20(), _ref$2 = host;
return typeof _ref$2 == "function" ? use(_ref$2, _el$69) : host = _el$69, _el$69;
})();
}
function TouchGalleryFavoriteButton(props) {
let [favorite, setFavorite] = createSignal(untrack(() => ({
...props.source.data.favorite
}))), [open, setOpen] = createSignal(!1), [loadingState, setLoadingState] = createSignal("idle"), [options, setOptions] = createSignal([]), favorited = () => favorite().favorited;
onMount(() => {
let onKeyDown = (event) => {
event.key === "Escape" && open() && setOpen(!1);
};
document.addEventListener("keydown", onKeyDown), onCleanup(() => {
document.removeEventListener("keydown", onKeyDown);
});
}), createEffect(() => {
if (!open())
return;
let documentOverflow = document.documentElement.style.overflow, bodyOverflow = document.body.style.overflow;
document.documentElement.style.overflow = "hidden", document.body.style.overflow = "hidden", onCleanup(() => {
document.documentElement.style.overflow = documentOverflow, document.body.style.overflow = bodyOverflow;
});
});
let openMenu = async () => {
let currentFavorite = favorite();
if (currentFavorite.actionUrl) {
setOpen(!0), setLoadingState("loading");
try {
setOptions(await props.source.handle.loadGalleryFavoriteOptions(currentFavorite.actionUrl, currentFavorite.favorited)), setLoadingState("idle");
} catch (error) {
console.error("[ehpeek]", error), setLoadingState("failed");
}
}
}, updateFavorite = async (option2) => {
let actionUrl = favorite().actionUrl;
if (!(!actionUrl || loadingState() === "loading")) {
setLoadingState("loading");
try {
await props.source.handle.updateGalleryFavorite(actionUrl, option2.value), setFavorite({
...favorite(),
color: option2.color,
favorited: option2.value !== "favdel",
label: option2.value === "favdel" ? "Not Favorited" : option2.label
}), setLoadingState("idle"), setOpen(!1);
} catch (error) {
console.error("[ehpeek]", error), setLoadingState("failed");
}
}
};
return (() => {
var _el$70 = _tmpl$222(), _el$71 = _el$70.firstChild, _el$72 = _el$71.firstChild, _el$73 = _el$72.nextSibling;
return _el$71.$$click = (event) => {
event.stopPropagation(), open() ? setOpen(!1) : openMenu();
}, insert(_el$72, () => favorite().label), insert(_el$73, createComponent(Icon, {
name: "heart",
get filled() {
return favorited();
}
})), insert(_el$70, createComponent(Show, {
get when() {
return open();
},
get children() {
return createComponent(Portal, {
get children() {
var _el$74 = _tmpl$21(), _el$75 = _el$74.firstChild, _el$76 = _el$75.firstChild, _el$77 = _el$76.firstChild, _el$78 = _el$77.nextSibling, _el$79 = _el$76.nextSibling;
return _el$74.$$click = (event) => {
event.target === event.currentTarget && setOpen(!1);
}, insert(_el$77, () => favorite().label), _el$78.$$click = () => setOpen(!1), insert(_el$78, createComponent(Icon, {
name: "close",
size: "var(--ui-icon-size-md)"
})), insert(_el$79, createComponent(Show, {
get when() {
return loadingState() === "loading";
},
get children() {
return createComponent(WelcomeIcon, {
embedded: !0,
get label() {
return texts_default.reader.loading;
},
showIcon: !1
});
}
}), null), insert(_el$79, createComponent(Show, {
get when() {
return loadingState() === "failed";
},
get children() {
return createComponent(TouchGalleryFavoriteStatus, {
text: "Failed"
});
}
}), null), insert(_el$79, createComponent(Show, {
get when() {
return loadingState() === "idle";
},
get children() {
return createComponent(For, {
get each() {
return options();
},
children: (option2) => createComponent(TouchGalleryFavoriteOption, {
option: option2,
onSelect: () => {
updateFavorite(option2);
}
})
});
}
}), null), createRenderEffect((_p$) => {
var _v$19 = favorite().label, _v$20 = texts_default.button.close, _v$21 = texts_default.button.close;
return _v$19 !== _p$.e && setAttribute(_el$74, "aria-label", _p$.e = _v$19), _v$20 !== _p$.t && setAttribute(_el$78, "aria-label", _p$.t = _v$20), _v$21 !== _p$.a && setAttribute(_el$78, "title", _p$.a = _v$21), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$74;
}
});
}
}), null), createRenderEffect((_p$) => {
var _v$22 = `ehpeek-touch-gallery-primary-button ehpeek-touch-gallery-favorite-button flex min-w-0 w-full h-full min-h-[var(--ui-control-size-xl)] flex-col items-center justify-center gap-md py-md px-lg border-0 bg-transparent ehp-color-site-text text-center uppercase [touch-action:manipulation] [font-size:var(--ui-font-size-prominent)] font-700 normal-case ${favorited() ? "ehpeek-touch-gallery-favorite-on" : "ehpeek-touch-gallery-favorite-off"}`, _v$23 = favorite().color ?? void 0, _v$24 = open();
return _v$22 !== _p$.e && className(_el$71, _p$.e = _v$22), _v$23 !== _p$.t && setStyleProperty(_el$71, "color", _p$.t = _v$23), _v$24 !== _p$.a && setAttribute(_el$71, "aria-expanded", _p$.a = _v$24), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$70;
})();
}
function TouchGalleryFavoriteStatus(props) {
return (() => {
var _el$80 = _tmpl$232();
return insert(_el$80, () => props.text), _el$80;
})();
}
function TouchGalleryFavoriteOption(props) {
return (() => {
var _el$81 = _tmpl$242(), _el$82 = _el$81.firstChild, _el$83 = _el$82.nextSibling, _el$84 = _el$83.nextSibling;
return _el$81.$$click = (event) => {
event.stopPropagation(), props.onSelect();
}, insert(_el$82, createComponent(Icon, {
name: "heart",
get filled() {
return props.option.value !== "favdel";
}
})), insert(_el$83, () => props.option.label), insert(_el$84, createComponent(Icon, {
name: "check"
})), createRenderEffect((_p$) => {
var _v$25 = `ehpeek-touch-gallery-favorite-option flex min-h-[var(--ui-control-size-lg)] items-center gap-md py-md px-lg border-0 border-b ehp-color-site-border-subtle-b bg-transparent ehp-color-site-text font-inherit textsize-md leading-[1.2] text-left ${props.option.value === "favdel" ? "ehpeek-touch-gallery-favorite-option-remove" : ""}`, _v$26 = props.option.selected, _v$27 = props.option.color ?? void 0, _v$28 = `ml-auto flex-none ehp-color-site-text ${props.option.selected ? "visible" : "invisible"}`, _v$29 = props.option.color ?? void 0;
return _v$25 !== _p$.e && className(_el$81, _p$.e = _v$25), _v$26 !== _p$.t && setAttribute(_el$81, "aria-pressed", _p$.t = _v$26), _v$27 !== _p$.a && setStyleProperty(_el$82, "color", _p$.a = _v$27), _v$28 !== _p$.o && className(_el$84, _p$.o = _v$28), _v$29 !== _p$.i && setStyleProperty(_el$84, "color", _p$.i = _v$29), _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0
}), _el$81;
})();
}
function ratingFromPointer(clientX, element) {
let rect = element.getBoundingClientRect(), progress = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
return Math.max(0.5, Math.ceil(progress * 10) / 2);
}
delegateEvents(["click", "pointermove"]);
// src/components/TouchUI/FavoritesPanel.tsx
var _tmpl$30 = /* @__PURE__ */ template('<div class="border-0 border-t border-t-[var(--color-site-border-subtle)]">'), _tmpl$210 = /* @__PURE__ */ template('<div class="box-border w-full min-w-0 overflow-hidden rounded-xs coarse:rounded-md border ehp-color-site-border bg-[var(--color-site-elevated)]"><button type=button class="flex box-border w-full min-h-xs coarse:min-h-md items-center justify-between gap-sm coarse:gap-md px-sm coarse:px-md py-xs coarse:py-sm rounded-xs border-0 !bg-transparent ehp-color-site-text text-left textsize-sm coarse:textsize-md font-700 font-inherit cursor-pointer hover:!bg-[var(--color-site-item-hover)] active:!bg-[var(--color-site-item-hover)]"><span class="flex min-w-0 items-center gap-sm overflow-hidden"><span class="min-w-0 overflow-hidden text-ellipsis whitespace-nowrap"> [<!>]</span></span><span class="flex h-20px w-20px flex-none items-center justify-center leading-none"aria-hidden=true>'), _tmpl$39 = /* @__PURE__ */ template('<button type=button><span class="flex min-w-0 items-center gap-sm"><span class="min-w-0 overflow-hidden text-ellipsis whitespace-nowrap"> [<!>]'), _tmpl$49 = /* @__PURE__ */ template('<span class="block h-15px w-15px flex-none bg-no-repeat"aria-hidden=true>');
function FavoritesCategorySelect(props) {
let container, [open, setOpen] = createSignal(!1), selected = () => props.source.data.favoritesCategory?.categories.find((category) => category.selected) ?? props.source.data.favoritesCategory?.categories[0] ?? null;
return onMount(() => {
let closeOnOutsidePointer = (event) => {
event.target instanceof Node && !container.contains(event.target) && setOpen(!1);
};
document.addEventListener("pointerdown", closeOnOutsidePointer, !0), onCleanup(() => document.removeEventListener("pointerdown", closeOnOutsidePointer, !0));
}), (() => {
var _el$ = _tmpl$210(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.firstChild, _el$5 = _el$4.firstChild, _el$7 = _el$5.nextSibling, _el$6 = _el$7.nextSibling, _el$8 = _el$3.nextSibling, _ref$ = container;
return typeof _ref$ == "function" ? use(_ref$, _el$) : container = _el$, _el$2.$$click = () => setOpen((value) => !value), insert(_el$3, () => categoryIndicator(selected()?.appearance), _el$4), insert(_el$4, () => selected()?.label, _el$5), insert(_el$4, () => selected()?.count, _el$7), insert(_el$8, () => open() ? "−" : "+"), insert(_el$, createComponent(Show, {
get when() {
return open();
},
get children() {
var _el$9 = _tmpl$30();
return insert(_el$9, createComponent(For, {
get each() {
return props.source.data.favoritesCategory?.categories;
},
children: (category, index) => (() => {
var _el$0 = _tmpl$39(), _el$1 = _el$0.firstChild, _el$10 = _el$1.firstChild, _el$11 = _el$10.firstChild, _el$13 = _el$11.nextSibling, _el$12 = _el$13.nextSibling;
return _el$0.$$click = () => props.source.handle.activateFavoriteCategory(index()), insert(_el$1, () => categoryIndicator(category.appearance), _el$10), insert(_el$10, () => category.label, _el$11), insert(_el$10, () => category.count, _el$13), createRenderEffect(() => className(_el$0, `flex box-border w-full min-h-xs coarse:min-h-md items-center px-sm coarse:px-md py-xs coarse:py-sm border-0 border-b ehp-color-site-border-subtle-b last:border-b-0 text-left textsize-sm coarse:textsize-md font-inherit no-underline cursor-pointer ${category.selected ? "bg-[var(--color-site-accent-hover)] ehp-color-site-accent font-700" : "!bg-transparent ehp-color-site-text hover:!bg-[var(--color-site-item-hover)]"}`)), _el$0;
})()
})), _el$9;
}
}), null), createRenderEffect(() => setAttribute(_el$2, "aria-expanded", open())), _el$;
})();
}
function categoryIndicator(appearance) {
return (() => {
var _el$14 = _tmpl$49();
return createRenderEffect((_$p) => style(_el$14, appearance ? {
"background-image": appearance.backgroundImage,
"background-position": appearance.backgroundPosition,
"background-size": appearance.backgroundSize
} : void 0, _$p)), _el$14;
})();
}
delegateEvents(["click"]);
// src/components/TouchUI/SearchPanel.tsx
var _tmpl$31 = /* @__PURE__ */ template('<section class="ehpeek-touch-search-panel box-border flex w-[calc(100%_-_16px)] large:w-[calc(100%_-_32px)] max-w-960px flex-col gap-sm large:gap-md mx-auto mb-sm large:mb-lg p-sm large:p-lg border ehp-color-site-border rounded-sm large:rounded-lg ehp-color-site-surface ehp-color-site-text shadow-[0_8px_24px_var(--color-shadow-panel)] font-sans">'), _tmpl$211 = /* @__PURE__ */ template('<button type=button class="appearance-none inline-flex min-h-[var(--ui-control-size-sm)] items-center px-md border-0 rounded-sm large:rounded-md bg-transparent ehp-color-site-accent text-left textsize-md font-700 font-inherit leading-[1.2] no-underline cursor-pointer [touch-action:manipulation] active:bg-[var(--color-site-accent-hover)]">'), _tmpl$310 = /* @__PURE__ */ template("<button>"), _tmpl$410 = /* @__PURE__ */ template('<span class="contents [&>*:not([hidden])]:col-span-full">');
var TOUCH_SEARCH_ACTION_CLASS = "appearance-none inline-flex box-border w-[var(--ui-control-size-md)] h-[var(--ui-control-size-md)] items-center justify-center p-0 rounded-sm large:rounded-md border-0 bg-transparent cursor-pointer transition-[background-color,transform] duration-120 [touch-action:manipulation] active:scale-96 active:bg-[var(--color-site-item-hover)] [--ehpeek-touch-search-icon-size:var(--ui-icon-size-md)]";
function TouchSearchPanel(props) {
return (() => {
var _el$ = _tmpl$31();
return insert(_el$, createComponent(DomNode2, {
get node() {
return props.source.elems.searchBox;
}
}), null), insert(_el$, createComponent(DomNode2, {
get node() {
return props.source.elems.fileSearch;
}
}), null), insert(_el$, () => props.after, null), _el$;
})();
}
function TouchSearchCategoryToggle(props) {
let [open, setOpen] = createSignal(!1);
return createEffect(() => props.source.handle.updateCategoryVisibility(open())), createComponent(ToggleButton, {
get expanded() {
return open();
},
get label() {
return texts_default.search.categories;
},
onClick: () => setOpen((value) => !value)
});
}
function TouchSearchOptionToggle(props) {
let [open, setOpen] = createSignal(!1);
return createComponent(ToggleButton, {
get expanded() {
return open();
},
get label() {
return texts_default.search[props.option];
},
onClick: () => {
props.option === "advancedOptions" ? props.source.handle.toggleAdvancedOptions() : props.source.handle.toggleFileSearch(), setOpen((value) => !value);
}
});
}
function ToggleButton(props) {
return (() => {
var _el$2 = _tmpl$211();
return _el$2.$$click = () => props.onClick(), insert(_el$2, () => props.label), createRenderEffect(() => setAttribute(_el$2, "aria-expanded", props.expanded)), _el$2;
})();
}
function TouchSearchAction(props) {
let source = untrack(() => props.source), search2 = untrack(() => props.action === "search"), label = search2 ? source.data.searchLabel : source.data.clearLabel ?? "", original = search2 ? source.elems.searchSubmit : source.elems.clearButton;
return [(() => {
var _el$3 = _tmpl$310();
return _el$3.$$click = (event) => {
event.preventDefault(), search2 ? source.handle.activateSearch() : source.handle.clearSearchText();
}, setAttribute(_el$3, "type", search2 ? "submit" : "button"), setAttribute(_el$3, "aria-label", label), setAttribute(_el$3, "title", label), insert(_el$3, createComponent(Icon, {
name: search2 ? "search" : "close",
size: "var(--ehpeek-touch-search-icon-size)"
})), createRenderEffect(() => className(_el$3, search2 ? `${TOUCH_SEARCH_ACTION_CLASS} z-1 ${source.data.hasClear ? "col-start-3" : "col-start-2"} row-start-1 ehp-color-site-accent` : `${TOUCH_SEARCH_ACTION_CLASS} z-1 col-start-2 row-start-1 ehp-color-site-text`)), _el$3;
})(), (() => {
var _el$4 = _tmpl$410();
return insert(_el$4, createComponent(DomNode2, {
node: original
})), _el$4;
})()];
}
delegateEvents(["click"]);
// src/components/TouchUI/TopBar.tsx
var _tmpl$40 = /* @__PURE__ */ template('<div class="absolute top-[calc(100%+4px)] left-0 z-overlay flex gap-xs p-xs overflow-hidden border ehp-color-site-border rounded-sm ehp-color-site-elevated"role=menu><button type=button class="inline-flex w-[var(--ui-control-size-xl)] h-[var(--ui-control-size-xl)] items-center justify-center rounded-md border-0 bg-transparent ehp-color-site-text no-underline [touch-action:manipulation] active:bg-[var(--color-site-item-hover)] [--ehpeek-touch-top-bar-icon-size:var(--ui-control-size-xs)]">'), _tmpl$212 = /* @__PURE__ */ template('<div class="ehpeek-touch-top-bar-ui-menu relative"><button type=button class="ehpeek-touch-top-bar-ui-menu-button inline-flex w-[var(--ui-control-size-xl)] h-[var(--ui-control-size-xl)] items-center justify-center rounded-md border-0 bg-transparent ehp-color-site-text no-underline [touch-action:manipulation] active:bg-[var(--color-site-item-hover)] [--ehpeek-touch-top-bar-icon-size:var(--ui-control-size-xs)]"aria-haspopup=menu>'), _tmpl$311 = /* @__PURE__ */ template('<button type=button class="inline-flex w-[var(--ui-control-size-xl)] h-[var(--ui-control-size-xl)] items-center justify-center rounded-md border-0 bg-transparent ehp-color-site-text no-underline [touch-action:manipulation] active:bg-[var(--color-site-item-hover)] [--ehpeek-touch-top-bar-icon-size:var(--ui-control-size-xs)]">'), _tmpl$411 = /* @__PURE__ */ template('<div class="ehpeek-touch-top-bar-menu-panel absolute top-[calc(100%+4px)] right-0 z-overlay flex w-180px coarse:w-[calc(100vw-32px)] max-w-[calc(100vw-12px)] coarse:max-w-360px flex-col overflow-hidden border ehp-color-site-border rounded-sm ehp-color-site-elevated">'), _tmpl$58 = /* @__PURE__ */ template('<div class="ehpeek-touch-top-bar-menu relative"><button type=button class="ehpeek-touch-top-bar-menu-button inline-flex w-[var(--ui-control-size-xl)] h-[var(--ui-control-size-xl)] items-center justify-center rounded-md border-0 bg-transparent ehp-color-site-text no-underline [touch-action:manipulation] active:bg-[var(--color-site-item-hover)] [--ehpeek-touch-top-bar-icon-size:var(--ui-control-size-xs)]"aria-haspopup=menu>'), _tmpl$68 = /* @__PURE__ */ template('<nav class="ehpeek-touch-top-bar relative z-ui flex box-border w-full h-[var(--ui-control-size-xl)] items-center justify-between pl-[max(12px,env(safe-area-inset-left,0px))] pr-[max(12px,env(safe-area-inset-right,0px))] ehp-color-site-surface ehp-color-site-text font-sans"><div class="flex items-center gap-xs"><a class="ehpeek-touch-top-bar-project inline-flex w-[var(--ui-control-size-xl)] h-[var(--ui-control-size-xl)] items-center justify-center rounded-md border-0 bg-transparent ehp-color-site-text no-underline [touch-action:manipulation] active:bg-[var(--color-site-item-hover)] [--ehpeek-touch-top-bar-icon-size:var(--ui-control-size-xs)] [--ehpeek-touch-top-bar-project-icon-size:var(--ui-control-size-sm)]"></a></div><div class="flex items-center gap-xs"><a class="ehpeek-touch-top-bar-home inline-flex w-[var(--ui-control-size-xl)] h-[var(--ui-control-size-xl)] items-center justify-center rounded-md border-0 bg-transparent ehp-color-site-text no-underline [touch-action:manipulation] active:bg-[var(--color-site-item-hover)] [--ehpeek-touch-top-bar-icon-size:var(--ui-control-size-xs)]"></a><a class="ehpeek-touch-top-bar-favorites inline-flex w-[var(--ui-control-size-xl)] h-[var(--ui-control-size-xl)] items-center justify-center rounded-md border-0 bg-transparent ehp-color-site-text no-underline [touch-action:manipulation] active:bg-[var(--color-site-item-hover)] [--ehpeek-touch-top-bar-icon-size:var(--ui-control-size-xs)]"></a><button type=button class="ehpeek-touch-top-bar-settings inline-flex w-[var(--ui-control-size-xl)] h-[var(--ui-control-size-xl)] items-center justify-center rounded-md border-0 bg-transparent ehp-color-site-text no-underline [touch-action:manipulation] active:bg-[var(--color-site-item-hover)] [--ehpeek-touch-top-bar-icon-size:var(--ui-control-size-xs)]">'), _tmpl$75 = /* @__PURE__ */ template('<a class="ehpeek-touch-top-bar-history inline-flex w-[var(--ui-control-size-xl)] h-[var(--ui-control-size-xl)] items-center justify-center rounded-md border-0 bg-transparent ehp-color-site-text no-underline [touch-action:manipulation] active:bg-[var(--color-site-item-hover)] [--ehpeek-touch-top-bar-icon-size:var(--ui-control-size-xs)]">'), TOUCH_TOP_BAR_ICON_SIZE = "var(--ehpeek-touch-top-bar-icon-size)", TOUCH_TOP_BAR_PROJECT_ICON_SIZE = "var(--ehpeek-touch-top-bar-project-icon-size)", TOUCH_TOP_BAR_SINGLE_COLUMN_ICON_SIZE = "calc(var(--ehpeek-touch-top-bar-icon-size) * 1.1)";
var NEXT_UI_SCALE = {
small: "medium",
medium: "large",
large: "small"
};
function TouchTopBarUiMenu(props) {
let [open, setOpen] = createSignal(!1), root;
return onMount(() => {
let onClick = (event) => {
event.target instanceof Element && root.contains(event.target) || setOpen(!1);
};
document.addEventListener("click", onClick), onCleanup(() => document.removeEventListener("click", onClick));
}), (() => {
var _el$ = _tmpl$212(), _el$2 = _el$.firstChild, _ref$ = root;
return typeof _ref$ == "function" ? use(_ref$, _el$) : root = _el$, _el$2.$$click = (event) => {
event.stopPropagation(), setOpen((value) => !value);
}, insert(_el$2, createComponent(Icon, {
name: "palette",
size: TOUCH_TOP_BAR_ICON_SIZE,
strokeWidth: 1.75
})), insert(_el$, createComponent(Show, {
get when() {
return open();
},
get children() {
var _el$3 = _tmpl$40(), _el$4 = _el$3.firstChild;
return _el$4.$$click = () => props.uiScale.onChange(NEXT_UI_SCALE[props.uiScale.value()]), insert(_el$4, createComponent(Icon, {
name: "viewport",
size: TOUCH_TOP_BAR_ICON_SIZE
})), insert(_el$3, createComponent(Show, {
get when() {
return props.columns;
},
children: (columns) => (() => {
var _el$5 = _tmpl$311();
return _el$5.$$click = () => columns().onChange(!columns().enabled()), insert(_el$5, createComponent(Icon, {
name: "pages",
get size() {
return columns().enabled() ? TOUCH_TOP_BAR_ICON_SIZE : TOUCH_TOP_BAR_SINGLE_COLUMN_ICON_SIZE;
}
})), createRenderEffect((_p$) => {
var _v$6 = texts_default.settings.columnsLabel, _v$7 = columns().enabled(), _v$8 = texts_default.settings.columnsLabel;
return _v$6 !== _p$.e && setAttribute(_el$5, "aria-label", _p$.e = _v$6), _v$7 !== _p$.t && setAttribute(_el$5, "aria-pressed", _p$.t = _v$7), _v$8 !== _p$.a && setAttribute(_el$5, "title", _p$.a = _v$8), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$5;
})()
}), null), createRenderEffect((_p$) => {
var _v$ = `${texts_default.settings.uiScaleLabel}: ${props.uiScale.value()}`, _v$2 = `${texts_default.settings.uiScaleLabel}: ${props.uiScale.value()}`;
return _v$ !== _p$.e && setAttribute(_el$4, "aria-label", _p$.e = _v$), _v$2 !== _p$.t && setAttribute(_el$4, "title", _p$.t = _v$2), _p$;
}, {
e: void 0,
t: void 0
}), _el$3;
}
}), null), createRenderEffect((_p$) => {
var _v$3 = texts_default.settings.uiControlsLabel, _v$4 = open(), _v$5 = texts_default.settings.uiControlsLabel;
return _v$3 !== _p$.e && setAttribute(_el$2, "aria-label", _p$.e = _v$3), _v$4 !== _p$.t && setAttribute(_el$2, "aria-expanded", _p$.t = _v$4), _v$5 !== _p$.a && setAttribute(_el$2, "title", _p$.a = _v$5), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$;
})();
}
function TouchTopBarMenu(props) {
let [open, setOpen] = createSignal(!1), root;
return onMount(() => {
let onClick = (event) => {
event.target instanceof Element && root.contains(event.target) || setOpen(!1);
};
document.addEventListener("click", onClick), onCleanup(() => {
document.removeEventListener("click", onClick);
});
}), (() => {
var _el$6 = _tmpl$58(), _el$7 = _el$6.firstChild, _ref$2 = root;
return typeof _ref$2 == "function" ? use(_ref$2, _el$6) : root = _el$6, _el$7.$$click = (event) => {
event.stopPropagation(), setOpen((value) => !value);
}, insert(_el$7, createComponent(Icon, {
name: "menu",
size: TOUCH_TOP_BAR_ICON_SIZE
})), insert(_el$6, createComponent(Show, {
get when() {
return open();
},
get children() {
var _el$8 = _tmpl$411();
return insert(_el$8, createComponent(For, {
get each() {
return props.navItems;
},
children: (item) => {
let Component = item.Component;
return createComponent(Component, {});
}
})), _el$8;
}
}), null), createRenderEffect(() => setAttribute(_el$7, "aria-expanded", open())), _el$6;
})();
}
function TouchTopBar(props) {
return (() => {
var _el$9 = _tmpl$68(), _el$0 = _el$9.firstChild, _el$1 = _el$0.firstChild, _el$10 = _el$0.nextSibling, _el$11 = _el$10.firstChild, _el$12 = _el$11.nextSibling, _el$13 = _el$12.nextSibling;
return insert(_el$1, createComponent(Icon, {
name: "panda-peek",
size: TOUCH_TOP_BAR_PROJECT_ICON_SIZE,
strokeWidth: 1.8
})), insert(_el$0, createComponent(TouchTopBarUiMenu, {
get uiScale() {
return props.uiScale;
},
get columns() {
return props.columns;
}
}), null), insert(_el$11, createComponent(Icon, {
name: "search",
size: TOUCH_TOP_BAR_ICON_SIZE
})), insert(_el$12, createComponent(Icon, {
name: "heart",
size: TOUCH_TOP_BAR_ICON_SIZE
})), insert(_el$10, createComponent(Show, {
get when() {
return props.historyHref;
},
children: (historyHref) => (() => {
var _el$14 = _tmpl$75();
return insert(_el$14, createComponent(Icon, {
name: "history",
size: TOUCH_TOP_BAR_ICON_SIZE
})), createRenderEffect(() => setAttribute(_el$14, "href", historyHref())), _el$14;
})()
}), _el$13), _el$13.$$click = (event) => {
event.stopPropagation(), props.onSettingsMenuOpen();
}, insert(_el$13, createComponent(Icon, {
name: "settings",
size: TOUCH_TOP_BAR_ICON_SIZE
})), insert(_el$10, createComponent(TouchTopBarMenu, {
get navItems() {
return props.source.elems.navItems;
}
}), null), createRenderEffect((_p$) => {
var _v$9 = props.source.data.homeHref, _v$0 = props.source.data.homeHref, _v$1 = props.source.data.favoritesHref;
return _v$9 !== _p$.e && setAttribute(_el$1, "href", _p$.e = _v$9), _v$0 !== _p$.t && setAttribute(_el$11, "href", _p$.t = _v$0), _v$1 !== _p$.a && setAttribute(_el$12, "href", _p$.a = _v$1), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$9;
})();
}
delegateEvents(["click"]);
// src/eh/dom/styles.css
var styles_default = `/* Style dynamic EhSyringe suggestions without observing its injected nodes. */
.ehs-injected .eh-syringe-lite-auto-complete-list {
max-height: 60dvh !important;
padding-block: 8px !important;
}
.ehs-injected .eh-syringe-lite-auto-complete-list .auto-complete-item {
box-sizing: border-box;
min-height: 52px;
padding: 8px 16px !important;
font-size: var(--ui-font-size-lg) !important;
line-height: 1.25 !important;
}
.ehs-injected .eh-syringe-lite-auto-complete-list .auto-complete-text {
font-size: inherit !important;
line-height: inherit !important;
}
/* Keep TouchUI result tables inside the viewport without adding layout wrappers. */
html.ehpeek-constrain-results-to-viewport,
body.ehpeek-constrain-results-to-viewport {
min-width: 0 !important;
max-width: 100% !important;
overflow-x: hidden !important;
}
.ehpeek-constrain-results-to-viewport .ido,
.ehpeek-constrain-results-to-viewport .itg {
box-sizing: border-box;
min-width: 0 !important;
width: 100% !important;
max-width: 100% !important;
}
.ehpeek-expand-favorites-search {
box-sizing: border-box;
min-width: 0 !important;
width: 100% !important;
max-width: 100% !important;
}
.ehpeek-constrain-favorites-navigation .searchnav {
box-sizing: border-box;
max-width: 100% !important;
overflow-x: auto;
}
.ehpeek-hide-original-favorites-categories {
display: none !important;
}
.ehpeek-contain-search-results,
.ehpeek-contain-favorites-results {
box-sizing: border-box;
min-width: 0 !important;
width: 100% !important;
max-width: 100% !important;
overflow-x: auto;
overscroll-behavior-x: contain;
}
.ehpeek-contain-search-results.gld {
overflow-x: clip;
overflow-y: visible;
}
.ehpeek-enable-search-swipe-input {
overscroll-behavior-x: contain;
touch-action: pan-y;
}
.ehpeek-enable-search-swipe-input,
.ehpeek-enable-search-swipe-input * {
user-select: none !important;
-webkit-user-select: none !important;
}
/* Compact wide Favorites columns while preserving the original table structure. */
.ehpeek-compact-all-favorites-results {
table-layout: auto !important;
width: 100% !important;
overflow-x: hidden !important;
}
.ehpeek-compact-all-favorites-results > tbody > tr > .gl2e {
width: auto !important;
overflow-wrap: anywhere !important;
}
.ehpeek-compact-all-favorites-results .glink {
white-space: normal !important;
overflow-wrap: anywhere !important;
}
.ehpeek-compact-all-favorites-results .gl4e table {
table-layout: fixed !important;
width: 100% !important;
max-width: 100% !important;
}
.ehpeek-compact-all-favorites-results .gl4e td {
min-width: 0 !important;
overflow-wrap: anywhere !important;
}
.ehpeek-compact-all-favorites-results .gl4e td.tc {
width: 4em !important;
white-space: nowrap !important;
}
.ehpeek-compact-all-favorites-results > tbody > tr > .glfe {
width: 1% !important;
white-space: nowrap !important;
}
@media (max-width: 849px) {
.ehpeek-contain-favorites-results {
table-layout: auto !important;
width: 100% !important;
}
.ehpeek-contain-favorites-results > tbody > tr > .gl2e {
width: auto !important;
overflow-wrap: anywhere !important;
}
.ehpeek-contain-favorites-results .glink {
white-space: normal !important;
overflow-wrap: anywhere !important;
}
.ehpeek-contain-favorites-results .gl4e table {
table-layout: fixed !important;
width: 100% !important;
max-width: 100% !important;
}
.ehpeek-contain-favorites-results .gl4e td {
min-width: 0 !important;
overflow-wrap: anywhere !important;
}
.ehpeek-contain-favorites-results .gl4e td.tc {
width: 4em !important;
white-space: nowrap !important;
}
.ehpeek-contain-favorites-results > tbody > tr > .glfe {
width: 1% !important;
white-space: nowrap !important;
}
}
/* Present original Search rows through one shared EhPeek layout state. */
.ehpeek-layout-search-grid {
display: block !important;
width: 100% !important;
table-layout: auto !important;
}
.ehpeek-layout-search-grid > tbody {
display: block !important;
}
.ehpeek-layout-search-grid.ehpeek-search-result-columns > tbody {
display: grid !important;
grid-template-columns: repeat(2, minmax(0, 1fr));
align-items: start;
gap: 8px;
}
.ehpeek-layout-search-grid > tbody > tr {
position: relative !important;
display: grid !important;
grid-template-columns: clamp(112px, 34%, 250px) minmax(0, 1fr) !important;
align-items: start !important;
column-gap: 0 !important;
width: 100% !important;
cursor: pointer !important;
}
.ehpeek-layout-search-grid > tbody > tr:has(> .glfe) {
grid-template-columns: clamp(112px, 34%, 250px) minmax(0, 1fr) auto !important;
}
.ehpeek-layout-search-grid > tbody > tr.ehpeek-expand-coverless-search-grid {
grid-template-columns: minmax(0, 1fr) !important;
}
.ehpeek-layout-search-grid > tbody > tr > .gl1e {
width: auto !important;
}
.ehpeek-layout-search-grid > tbody > tr > .gl2e {
box-sizing: border-box !important;
grid-column: auto;
align-self: stretch !important;
min-width: 0 !important;
width: auto !important;
height: 100% !important;
padding-left: 0 !important;
}
.ehpeek-layout-search-grid > tbody > tr.ehpeek-expand-coverless-search-grid > .gl2e {
grid-column: 1 !important;
}
.ehpeek-layout-search-grid > tbody > tr > .glfe {
width: auto !important;
margin-left: 6px !important;
}
.ehpeek-layout-search-grid > tbody > tr > .gl1e > div,
.ehpeek-layout-search-grid > tbody > tr > .gl1e img {
width: 100% !important;
height: auto !important;
}
.ehpeek-layout-search-grid .gl4e {
box-sizing: border-box !important;
display: flex !important;
flex-direction: column !important;
align-items: stretch !important;
justify-content: flex-start !important;
gap: 12px !important;
min-height: 0 !important;
width: 100% !important;
padding-left: 6px !important;
}
.ehpeek-layout-search-grid .gl4e:has(> .ehpeek-read-history-actions) {
display: grid !important;
grid-template-columns: minmax(0, 1fr);
}
.ehpeek-layout-search-grid .gl4e > .ehpeek-read-history-actions {
position: static !important;
justify-self: end;
width: auto !important;
}
.ehpeek-layout-search-grid .glink {
min-height: 0 !important;
height: auto !important;
overflow: visible !important;
overflow-wrap: anywhere !important;
white-space: normal !important;
word-break: normal !important;
text-align: left !important;
font-size: var(--ui-font-size-md) !important;
font-weight: 700 !important;
line-height: 1.35 !important;
}
.ehpeek-layout-search-grid .gl3e {
position: static !important;
display: flex !important;
flex-flow: row wrap !important;
align-items: center !important;
align-content: flex-start !important;
justify-content: flex-start !important;
gap: 8px 12px !important;
float: none !important;
min-height: 0 !important;
width: 100% !important;
height: auto !important;
margin: 0 !important;
padding: 0 !important;
font-weight: 600 !important;
}
.ehpeek-layout-search-grid .gl3e > * {
position: static !important;
flex: 0 0 auto !important;
min-width: 0 !important;
width: auto !important;
height: auto !important;
margin: 0 !important;
padding: 0 !important;
font-size: var(--ui-font-size-sm) !important;
font-weight: 600 !important;
line-height: 1.3 !important;
}
.ehpeek-layout-search-grid .gl3e > .ir {
width: 80px !important;
height: 16px !important;
background-repeat: no-repeat !important;
}
.ehpeek-layout-search-grid .gl3e > .gldown {
width: auto !important;
height: auto !important;
}
.ehpeek-layout-search-grid .gl3e > :is(.cn, .cs, [class*="ct"]) {
box-sizing: border-box !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
width: max(72px, 6em) !important;
height: max(32px, 2.2em) !important;
padding: 0 0.6em !important;
}
.ehpeek-layout-search-grid .ehpeek-stack-search-grid-tags {
position: static !important;
flex: 0 0 auto !important;
min-height: 0 !important;
width: 100% !important;
height: auto !important;
margin: 0 !important;
padding: 0 !important;
}
.ehpeek-layout-search-grid .ehpeek-stack-search-grid-tags :is(table, tbody, tr) {
min-height: 0 !important;
height: auto !important;
margin: 0 !important;
}
.ehpeek-layout-search-grid .ehpeek-stack-search-grid-tags td {
min-height: 0 !important;
height: auto !important;
vertical-align: top !important;
}
.ehpeek-layout-search-grid .ehpeek-stack-search-grid-tags :is(.gt, .gtl, .gtw, td.tc) {
font-size: var(--ui-font-size-sm) !important;
line-height: 1.2 !important;
}
.ehpeek-cover-search-grid-row {
grid-column: 1 / 3;
grid-row: 1;
}
/* Reading state remains visual and never changes the original title text. */
.ehpeek-prefix-read-history-label::before {
box-sizing: border-box;
content: attr(data-ehpeek-history-label);
display: inline-flex;
align-items: center;
margin-right: 4px;
padding: 0;
border: 0;
border-radius: 0;
outline: 0;
background: transparent;
box-shadow: none;
color: var(--color-site-accent);
vertical-align: middle;
font-size: var(--ui-font-size-sm);
font-weight: 700;
line-height: 1.3;
}
[data-ehpeek-read-history="visited"] {
--ehpeek-read-history-tint: color-mix(in srgb, var(--color-site-accent) 6%, transparent);
}
[data-ehpeek-read-history="reading"] {
--ehpeek-read-history-tint: color-mix(in srgb, var(--color-site-accent) 12%, transparent);
}
tr[data-ehpeek-read-history] > td,
[data-ehpeek-read-history]:not(tr) {
box-shadow: inset 0 0 0 9999px var(--ehpeek-read-history-tint) !important;
}
/* Style retained Search controls without replacing their original classes. */
.ehpeek-reset-search-box-layout {
box-sizing: border-box !important;
min-width: 0 !important;
width: 100% !important;
height: auto !important;
margin: 0 !important;
padding: 0 !important;
border: 0 !important;
text-align: left !important;
font-size: var(--ui-font-size-md) !important;
}
.ehpeek-stack-search-form {
display: flex !important;
flex-direction: column !important;
gap: 12px !important;
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
.ehpeek-overlay-search-actions {
box-sizing: border-box !important;
display: grid !important;
grid-template-columns: minmax(0, 1fr) 60px !important;
align-items: start !important;
gap: 0 !important;
min-width: 0 !important;
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
.ehpeek-overlay-search-actions[data-ehpeek-has-clear="true"] {
grid-template-columns: minmax(0, 1fr) 60px 60px !important;
}
.ehpeek-expand-search-input {
appearance: none !important;
box-sizing: border-box !important;
grid-column: 1 / -1 !important;
grid-row: 1 !important;
min-width: 0 !important;
width: 100% !important;
height: 60px !important;
margin: 0 !important;
padding: 0 72px 0 16px !important;
border: 1px solid var(--color-site-border) !important;
border-radius: 6px !important;
outline: none !important;
background: var(--color-site-elevated) !important;
color: var(--color-site-text) !important;
font-size: var(--ui-font-size-md) !important;
line-height: 1.2 !important;
}
.ehpeek-overlay-search-actions[data-ehpeek-has-clear="true"] .ehpeek-expand-search-input {
padding-right: 132px !important;
}
.ehpeek-expand-search-input:focus {
border-color: var(--color-site-accent) !important;
background: var(--color-site-elevated) !important;
box-shadow: 0 0 0 3px var(--color-site-accent-hover) !important;
}
.ehpeek-hide-original-search-action {
display: none !important;
}
.ehpeek-layout-search-categories {
width: 100% !important;
margin: 0 !important;
border-collapse: collapse !important;
}
.ehpeek-layout-search-categories[aria-hidden="true"] {
display: none !important;
}
.ehpeek-layout-search-categories > tbody {
display: grid !important;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)) !important;
gap: 4px !important;
}
.ehpeek-layout-search-categories tr {
display: contents !important;
}
.ehpeek-layout-search-categories td {
padding: 0 !important;
}
.ehpeek-layout-search-categories [id^="cat_"] {
box-sizing: border-box !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
min-width: 0 !important;
width: 100% !important;
height: 52px !important;
padding-inline: 12px !important;
border: 1px solid var(--color-site-border) !important;
border-radius: 6px !important;
color: #ffffff !important;
text-align: center !important;
white-space: nowrap !important;
font-size: var(--ui-font-size-md) !important;
font-weight: 700 !important;
line-height: 1.15 !important;
box-shadow: 0 2px 6px var(--color-shadow-control) !important;
cursor: pointer !important;
user-select: none !important;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
transition: opacity 120ms;
}
.ehpeek-layout-search-categories [id^="cat_"]:active {
opacity: 0.7;
}
.ehpeek-layout-search-categories [id^="cat_"][data-disabled] {
opacity: 0.4;
}
.ehpeek-wrap-search-options {
display: flex !important;
flex-wrap: wrap !important;
align-items: center !important;
justify-content: flex-start !important;
gap: 8px 12px !important;
width: 100% !important;
padding: 0 !important;
font-size: 0 !important;
}
.ehpeek-wrap-search-options > a {
appearance: none !important;
display: inline-flex !important;
align-items: center !important;
min-height: 40px !important;
padding-inline: 12px !important;
border: 0 !important;
border-radius: 6px !important;
background: transparent !important;
color: var(--color-site-accent) !important;
text-align: left !important;
text-decoration: none !important;
font: inherit !important;
font-size: var(--ui-font-size-md) !important;
font-weight: 700 !important;
line-height: 1.2 !important;
cursor: pointer !important;
touch-action: manipulation;
}
.ehpeek-wrap-search-options > a:active {
background: var(--color-site-accent-hover) !important;
}
.ehpeek-expand-search-advanced-options {
box-sizing: border-box !important;
width: 100% !important;
padding: 0 !important;
color: var(--color-site-text) !important;
}
.ehpeek-reset-search-box-layout .searchadv {
box-sizing: border-box !important;
width: 100% !important;
padding-top: 12px !important;
font-size: var(--ui-font-size-md) !important;
}
.ehpeek-reset-search-box-layout .searchadv > div,
.ehpeek-expand-file-search .searchadv > div {
flex-wrap: wrap !important;
justify-content: flex-start !important;
gap: 8px !important;
}
.ehpeek-reset-search-box-layout .searchadv > div > div,
.ehpeek-expand-file-search .searchadv > div > div {
padding: 8px !important;
}
.ehpeek-expand-file-search {
box-sizing: border-box !important;
width: 100% !important;
margin: 0 !important;
padding: 16px !important;
border: 1px solid var(--color-site-border) !important;
border-radius: 6px !important;
background: var(--color-site-elevated) !important;
color: var(--color-site-text) !important;
text-align: left !important;
font-size: var(--ui-font-size-md) !important;
}
.ehpeek-expand-file-search form {
display: flex !important;
flex-direction: column !important;
gap: 8px !important;
}
.ehpeek-expand-file-search form > div {
padding: 0 !important;
}
/* Apply dynamic My Tag colors through variables instead of direct presentation writes. */
.ehpeek-color-my-tag {
background-color: var(--ehpeek-my-tag-background) !important;
}
.ehpeek-color-my-tag > a {
color: var(--ehpeek-my-tag-color) !important;
}
.ehpeek-expand-gallery-actions {
height: auto !important;
max-height: none !important;
overflow: visible !important;
}
.ehpeek-fit-gallery-cover {
display: block;
width: 100%;
max-width: 100%;
height: 100%;
max-height: 100%;
margin-inline: auto;
object-fit: contain;
object-position: center;
}
/* Hide original GalleryInfo content only while the TouchUI panel owns its host. */
.ehpeek-hide-original-gallery-info > :not([data-ehpeek-anchor="gallery-info"]) {
display: none !important;
}
.ehpeek-layout-gallery-action,
.ehpeek-layout-top-bar-menu-item {
box-sizing: border-box !important;
display: block !important;
position: static !important;
float: none !important;
min-height: 52px !important;
width: 100% !important;
height: auto !important;
margin: 0 !important;
padding: 12px 16px !important;
border: 0 !important;
border-bottom: 1px solid var(--color-site-border-subtle) !important;
background: transparent !important;
color: var(--color-site-text) !important;
text-align: left !important;
text-decoration: none !important;
white-space: normal !important;
font-size: var(--ui-font-size-md) !important;
line-height: 1.2 !important;
}
.ehpeek-layout-top-bar-menu-item {
min-height: var(--ui-control-size-lg) !important;
padding: 12px !important;
font-size: var(--ui-font-size-md) !important;
}
.ehpeek-layout-new-tag-form {
box-sizing: border-box !important;
display: block !important;
width: 100% !important;
padding-top: 12px !important;
}
.ehpeek-layout-new-tag-form form {
display: flex !important;
align-items: center !important;
gap: 8px !important;
min-width: 0 !important;
width: 100% !important;
}
.ehpeek-layout-new-tag-form #newtagfield {
box-sizing: border-box !important;
flex: 1 1 auto !important;
min-width: 0 !important;
height: 40px !important;
padding-inline: 12px !important;
border: 1px solid var(--color-site-border) !important;
border-radius: 3px !important;
outline: none !important;
background: var(--color-site-surface) !important;
color: var(--color-site-text) !important;
font: inherit !important;
font-size: var(--ui-font-size-md) !important;
}
.ehpeek-layout-new-tag-form #newtagfield:focus {
border-color: var(--color-site-accent) !important;
}
.ehpeek-layout-new-tag-form #newtagbutton {
box-sizing: border-box !important;
flex: 0 0 auto !important;
height: 40px !important;
padding-inline: 16px !important;
border: 1px solid var(--color-site-accent) !important;
border-radius: 3px !important;
background: var(--color-site-accent) !important;
color: var(--color-background) !important;
font: inherit !important;
font-size: var(--ui-font-size-md) !important;
font-weight: 700 !important;
cursor: pointer !important;
}
.ehpeek-layout-gallery-tag-menu {
box-sizing: border-box !important;
display: flex !important;
flex-direction: column !important;
float: none !important;
width: 100% !important;
height: auto !important;
margin: 0 !important;
padding: 0 !important;
font-size: var(--ui-font-size-md) !important;
}
.ehpeek-layout-gallery-tag-menu img {
display: none !important;
}
.ehpeek-layout-gallery-tag-menu a,
.ehpeek-layout-gallery-tag-menu-item {
box-sizing: border-box !important;
display: flex !important;
align-items: center !important;
gap: 12px !important;
min-height: 52px !important;
width: 100% !important;
padding: 12px 16px !important;
border: 0 !important;
border-bottom: 1px solid var(--color-site-border-subtle) !important;
background: transparent !important;
color: var(--color-site-text) !important;
text-align: left !important;
text-decoration: none !important;
font: inherit !important;
font-size: var(--ui-font-size-md) !important;
cursor: pointer !important;
}
.ehpeek-enable-touch-comment-score .c5 {
white-space: nowrap !important;
}
.ehpeek-enable-touch-comment-score .c7[aria-hidden="true"] {
display: none !important;
}
.ehpeek-enable-touch-comment-score .c7[aria-hidden="false"] {
display: block !important;
}
/* Scope preview interaction visuals to one managed thumbnail root. */
.ehpeek-suppress-thumbnail-tap-highlight a {
outline: none !important;
-webkit-tap-highlight-color: transparent;
}
.ehpeek-enable-preview-swipe-input {
touch-action: pan-y;
user-select: none;
}
.ehpeek-enable-preview-swipe-input img {
-webkit-user-drag: none;
}
.ehpeek-hide-original-preview-page-bars :is(.ptt, .ptb, .gpc) {
display: none !important;
}
@media (pointer: fine) {
.ehpeek-stack-search-form {
gap: 8px !important;
}
.ehpeek-overlay-search-actions {
grid-template-columns: minmax(0, 1fr) 40px !important;
}
.ehpeek-overlay-search-actions[data-ehpeek-has-clear="true"] {
grid-template-columns: minmax(0, 1fr) 40px 40px !important;
}
.ehpeek-expand-search-input {
height: 40px !important;
padding: 0 48px 0 10px !important;
border-radius: 4px !important;
font-size: var(--ui-font-size-md) !important;
}
.ehpeek-overlay-search-actions[data-ehpeek-has-clear="true"] .ehpeek-expand-search-input {
padding-right: 88px !important;
}
.ehpeek-layout-search-categories > tbody {
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)) !important;
}
.ehpeek-layout-search-categories [id^="cat_"] {
height: 32px !important;
padding-inline: 8px !important;
border-radius: 4px !important;
font-size: var(--ui-font-size-md) !important;
}
.ehpeek-wrap-search-options {
gap: 4px 6px !important;
}
.ehpeek-wrap-search-options > a {
min-height: 32px !important;
padding-inline: 10px !important;
border-radius: 4px !important;
font-size: var(--ui-font-size-md) !important;
}
.ehpeek-reset-search-box-layout .searchadv {
padding-top: 8px !important;
font-size: var(--ui-font-size-md) !important;
}
.ehpeek-reset-search-box-layout .searchadv > div,
.ehpeek-expand-file-search .searchadv > div {
gap: 4px !important;
}
.ehpeek-reset-search-box-layout .searchadv > div > div,
.ehpeek-expand-file-search .searchadv > div > div {
padding: 4px !important;
}
.ehpeek-expand-file-search {
padding: 8px !important;
border-radius: 3px !important;
font-size: var(--ui-font-size-sm) !important;
}
.ehpeek-expand-file-search form {
gap: 4px !important;
}
}
@media (pointer: coarse) {
.ehpeek-suppress-thumbnail-tap-highlight > :is(:hover, :active) {
border-color: var(--color-site-border) !important;
}
.ehpeek-suppress-thumbnail-tap-highlight a:is(:focus, :active) {
outline: none !important;
}
}
/* The root state scopes original Gallery layout overrides to TouchUI pages. */
.ehpeek-touch-gallery-page {
--touch-gallery-gutter: clamp(16px, 2.5vw, 36px);
}
html.ehpeek-touch-gallery-page,
body.ehpeek-touch-gallery-page {
min-width: 0 !important;
overflow-x: hidden !important;
text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
html.ehpeek-touch-gallery-page.ehpeek-gallery-wide-layout-root,
body.ehpeek-touch-gallery-page.ehpeek-gallery-wide-layout-root {
height: 100dvh !important;
overflow-y: hidden !important;
}
body.ehpeek-touch-gallery-page.ehpeek-gallery-wide-layout-root {
padding-top: 0 !important;
padding-bottom: 0 !important;
}
body.ehpeek-touch-gallery-page {
box-sizing: border-box;
padding-left: 0 !important;
padding-right: 0 !important;
background: var(--color-site-page) !important;
font-size: var(--ui-font-size-sm) !important;
line-height: 1.35 !important;
}
body.ehpeek-touch-gallery-page :is(
.ehpeek-hide-original-gallery-info,
.gpc,
#gdt[class],
#cdiv,
.ptt,
.ptb
) {
box-sizing: border-box !important;
width: calc(100% - (var(--touch-gallery-gutter) * 2)) !important;
max-width: none !important;
margin-left: auto !important;
margin-right: auto !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
body.ehpeek-touch-gallery-page :is(#gdt[class], .ptt, .ptb) {
overflow-x: auto !important;
-webkit-overflow-scrolling: touch;
}
body.ehpeek-touch-gallery-page #gdt[class] {
display: grid !important;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
align-items: start;
}
body.ehpeek-touch-gallery-page #gdt :is(.gdtm, .gdtl),
body.ehpeek-touch-gallery-page #gdt > div {
display: flex !important;
box-sizing: border-box !important;
min-width: 0 !important;
align-items: center !important;
justify-content: center !important;
justify-self: center;
}
body.ehpeek-touch-gallery-page #gdt a {
display: flex !important;
align-items: center;
justify-content: center;
}
body.ehpeek-touch-gallery-page #cdiv {
font-size: var(--ui-font-size-md) !important;
line-height: 1.5 !important;
}
body.ehpeek-touch-gallery-page #cdiv .c6 {
font-size: var(--ui-font-size-prominent) !important;
line-height: 1.5 !important;
overflow-wrap: anywhere;
}
body.ehpeek-touch-gallery-page :is(
#cdiv .c3,
#cdiv .c4,
#cdiv .c5,
#cdiv .c7,
#formdiv
) {
font-size: var(--ui-font-size-sm) !important;
line-height: 1.4 !important;
}
body.ehpeek-touch-gallery-page #postnewcomment {
margin: 16px 0 !important;
font-size: 0 !important;
}
body.ehpeek-touch-gallery-page #postnewcomment a {
display: inline-flex !important;
min-height: var(--ui-control-size-sm);
align-items: center;
padding: 12px 16px;
border: 1px solid var(--color-site-border);
border-radius: 6px;
background: var(--color-site-elevated);
color: var(--color-site-accent);
font-size: var(--ui-font-size-md);
text-decoration: none;
}
body.ehpeek-touch-gallery-page #cdiv form {
display: flex !important;
flex-wrap: wrap;
gap: 12px;
align-items: center;
}
body.ehpeek-touch-gallery-page :is(#cdiv textarea, #commenttext) {
display: block !important;
box-sizing: border-box !important;
width: 100% !important;
min-height: 160px !important;
flex: 1 0 100%;
padding: 16px !important;
border-radius: 6px !important;
font: inherit !important;
font-size: var(--ui-font-size-md) !important;
line-height: 1.5 !important;
}
body.ehpeek-touch-gallery-page #cdiv :is(
button,
input[type="button"],
input[type="submit"],
input[type="text"],
select
) {
box-sizing: border-box !important;
min-height: var(--ui-control-size-sm) !important;
padding: 12px 16px !important;
border-radius: 6px !important;
font: inherit !important;
font-size: var(--ui-font-size-md) !important;
}
body.ehpeek-touch-gallery-page #cdiv :is(
button,
input[type="button"],
input[type="submit"]
) {
flex: 1 1 180px;
cursor: pointer;
}
body.ehpeek-touch-gallery-page #cdiv input[type="text"] {
min-width: min(100%, 240px);
}
@media (orientation: landscape) {
body.ehpeek-touch-gallery-page :is(
.ehpeek-hide-original-gallery-info,
.gpc,
#gdt[class],
#cdiv,
.ptt,
.ptb
) {
width: min(
calc(100% - (var(--touch-gallery-gutter) * 2)),
90dvh
) !important;
}
}
body.ehpeek-touch-gallery-page .ehpeek-touch-gallery-layout {
box-sizing: border-box;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-rows: minmax(0, 1fr) auto;
column-gap: var(--touch-gallery-gutter);
width: calc(100% - (var(--touch-gallery-gutter) * 2));
height: calc(100dvh - var(--ui-control-size-xl));
padding-bottom: env(safe-area-inset-bottom, 0px);
margin-inline: auto;
align-items: start;
overflow: hidden;
}
body.ehpeek-touch-gallery-page :is(
.ehpeek-touch-gallery-layout-left,
.ehpeek-touch-gallery-layout-right
) {
grid-row: 1;
display: block;
min-width: 0;
min-height: 0;
height: 100%;
overflow-y: auto;
overscroll-behavior-y: contain;
scrollbar-width: none;
-ms-overflow-style: none;
}
body.ehpeek-touch-gallery-page :is(
.ehpeek-touch-gallery-layout-left,
.ehpeek-touch-gallery-layout-right
)::-webkit-scrollbar {
display: none;
}
body.ehpeek-touch-gallery-page .ehpeek-touch-gallery-layout :is(
.ehpeek-hide-original-gallery-info,
.gtb,
.contents,
.gpc,
#cdiv,
.ptt,
.ptb
) {
min-width: 0 !important;
width: 100% !important;
max-width: 100% !important;
}
body.ehpeek-touch-gallery-page .ehpeek-touch-gallery-layout #gdt[class] {
min-width: 0 !important;
width: 100% !important;
max-width: 100% !important;
}
body.ehpeek-touch-gallery-page
.ehpeek-touch-gallery-layout-left
> .ehpeek-hide-original-gallery-info {
padding-bottom: 0 !important;
}
body.ehpeek-touch-gallery-page
.ehpeek-touch-gallery-layout-left
.ehpeek-touch-gallery {
margin-bottom: 0;
}
body.ehpeek-touch-gallery-page .ehpeek-touch-gallery-layout > .dp {
box-sizing: border-box;
grid-row: 2;
grid-column: 1 / -1;
min-width: 0;
width: 100%;
}
`;
// ehpeek-uno-css:ehpeek:uno.css
var ehpeek_uno_default = `/* layer: preflights */
*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}
/* layer: shortcuts */
.scrollbar-hidden{scrollbar-width:none;-ms-overflow-style:none;}
.container{width:100%;}
.container\\!{width:100% !important;}
.z-overlay{z-index:1100;}
.z-reader{z-index:1200;}
.z-ui{z-index:1000;}
.my-sm{margin-top:8px;margin-bottom:8px;}
:root[data-ehpeek-ui-scale="large"] .large\\:mb-lg,
.mb-lg{margin-bottom:16px;}
:root[data-ehpeek-ui-scale="large"] .large\\:mb-md{margin-bottom:12px;}
:root[data-ehpeek-ui-scale="large"] .large\\:mb-sm,
.mb-sm{margin-bottom:8px;}
.mb-xs{margin-bottom:4px;}
.mt-md{margin-top:12px;}
.mt-xs{margin-top:4px;}
.scrollbar-hidden::-webkit-scrollbar{display:none;}
.h-lg{height:52px;}
.h-md{height:40px;}
.\\!h-sm{height:32px !important;}
.h-sm{height:32px;}
.min-h-lg{min-height:52px;}
.min-h-sm{min-height:32px;}
.min-h-xs{min-height:24px;}
.w-lg{width:52px;}
.w-md{width:40px;}
.\\!w-sm{width:32px !important;}
.w-sm{width:32px;}
:root[data-ehpeek-ui-scale="large"] .large\\:gap-lg,
.gap-lg{gap:16px;}
:root[data-ehpeek-ui-scale="large"] .large\\:gap-md,
.gap-md{gap:12px;}
:root[data-ehpeek-ui-scale="large"] .large\\:gap-sm,
.gap-sm{gap:8px;}
.gap-xl{gap:24px;}
:root[data-ehpeek-ui-scale="large"] .large\\:gap-xs,
.gap-xs{gap:4px;}
:root[data-ehpeek-ui-scale="large"] .large\\:gap-x-lg{column-gap:16px;}
:root[data-ehpeek-ui-scale="large"] .large\\:gap-x-sm,
.gap-x-sm{column-gap:8px;}
.gap-x-xs{column-gap:4px;}
:root[data-ehpeek-ui-scale="large"] .large\\:gap-y-md{row-gap:12px;}
.gap-y-sm{row-gap:8px;}
.ehp-color-site-border{border-color:var(--color-site-border);}
.ehp-color-spinner{border-color:var(--color-border);border-top-color:var(--color-accent);}
.ehp-color-site-border-subtle-b{border-bottom-color:var(--color-site-border-subtle);}
:root[data-ehpeek-ui-scale="large"] .large\\:rounded-lg,
.rounded-lg{border-radius:8px;}
:root[data-ehpeek-ui-scale="large"] .large\\:rounded-md,
.rounded-md{border-radius:6px;}
.rounded-sm{border-radius:4px;}
.rounded-xl{border-radius:10px;}
.rounded-xs{border-radius:3px;}
.focus-visible\\:rounded-xs:focus-visible{border-radius:3px;}
.ehp-color-reader{background-color:var(--color-reader-background);color:var(--color-reader-text);}
.ehp-color-site-elevated{background-color:var(--color-site-elevated);--un-shadow:0 8px 24px var(--un-shadow-color, var(--color-shadow-elevated));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
.ehp-color-site-page{background-color:var(--color-site-page);}
.ehp-color-site-surface{background-color:var(--color-site-surface);}
:root[data-ehpeek-ui-scale="large"] .large\\:p-lg,
.p-lg{padding:16px;}
.p-md{padding:12px;}
.p-sm{padding:8px;}
.p-xl{padding:24px;}
.p-xs{padding:4px;}
:root[data-ehpeek-ui-scale="large"] .large\\:px-lg,
.px-lg{padding-left:16px;padding-right:16px;}
.px-md{padding-left:12px;padding-right:12px;}
.px-sm{padding-left:8px;padding-right:8px;}
.px-xl{padding-left:24px;padding-right:24px;}
.px-xs{padding-left:4px;padding-right:4px;}
.py-lg{padding-top:16px;padding-bottom:16px;}
.py-md{padding-top:12px;padding-bottom:12px;}
.py-sm{padding-top:8px;padding-bottom:8px;}
.py-xs{padding-top:4px;padding-bottom:4px;}
:root[data-ehpeek-ui-scale="large"] .large\\:pb-lg{padding-bottom:16px;}
.pb-md{padding-bottom:12px;}
.pb-sm{padding-bottom:8px;}
:root[data-ehpeek-ui-scale="large"] .large\\:pb-xs{padding-bottom:4px;}
.pl-lg{padding-left:16px;}
.pl-md{padding-left:12px;}
.pl-xl{padding-left:24px;}
.pr-sm{padding-right:8px;}
:root[data-ehpeek-ui-scale="large"] .large\\:pt-lg{padding-top:16px;}
.pt-md{padding-top:12px;}
.pt-sm{padding-top:8px;}
:root[data-ehpeek-ui-scale="large"] .large\\:pt-xl{padding-top:24px;}
.textsize-lg{font-size:var(--ui-font-size-lg);}
.textsize-md{font-size:var(--ui-font-size-md);}
.textsize-sm{font-size:var(--ui-font-size-sm);}
.textsize-xl{font-size:var(--ui-font-size-xl);}
.textsize-xs{font-size:var(--ui-font-size-xs);}
.ehp-color-site-accent{color:var(--color-site-accent);}
.ehp-color-site-text{color:var(--color-site-text);}
.ehp-color-text{color:var(--color-text);}
.hover\\:ehp-color-site-accent:hover{color:var(--color-site-accent);}
@media (pointer: coarse){
.coarse\\:\\!h-md{height:40px !important;}
.coarse\\:min-h-md{min-height:40px;}
.coarse\\:min-h-sm{min-height:32px;}
.coarse\\:\\!w-md{width:40px !important;}
.coarse\\:gap-lg{gap:16px;}
.coarse\\:gap-md{gap:12px;}
.coarse\\:rounded-md{border-radius:6px;}
.coarse\\:p-lg{padding:16px;}
.coarse\\:px-md{padding-left:12px;padding-right:12px;}
.coarse\\:py-sm{padding-top:8px;padding-bottom:8px;}
.coarse\\:textsize-md{font-size:var(--ui-font-size-md);}
}
@media (min-width: 640px){
.container{max-width:640px;}
.container\\!{max-width:640px !important;}
}
@media (min-width: 768px){
.container{max-width:768px;}
.container\\!{max-width:768px !important;}
}
@media (min-width: 1024px){
.container{max-width:1024px;}
.container\\!{max-width:1024px !important;}
}
@media (min-width: 1280px){
.container{max-width:1280px;}
.container\\!{max-width:1280px !important;}
}
@media (min-width: 1536px){
.container{max-width:1536px;}
.container\\!{max-width:1536px !important;}
}
/* layer: default */
.\\[--ehpeek-position-bar-thumb-max\\:calc\\(var\\(--ui-control-size-xl\\)\\*4\\)\\]{--ehpeek-position-bar-thumb-max:calc(var(--ui-control-size-xl) * 4);}
.\\[--ehpeek-position-bar-thumb-min\\:calc\\(var\\(--ui-control-size-md\\)\\*1\\.5\\)\\]{--ehpeek-position-bar-thumb-min:calc(var(--ui-control-size-md) * 1.5);}
.\\[--ehpeek-touch-search-icon-size\\:var\\(--ui-icon-size-md\\)\\]{--ehpeek-touch-search-icon-size:var(--ui-icon-size-md);}
.\\[--ehpeek-touch-top-bar-icon-size\\:var\\(--ui-control-size-xs\\)\\]{--ehpeek-touch-top-bar-icon-size:var(--ui-control-size-xs);}
.\\[--ehpeek-touch-top-bar-project-icon-size\\:var\\(--ui-control-size-sm\\)\\]{--ehpeek-touch-top-bar-project-icon-size:var(--ui-control-size-sm);}
.\\[--progress-bar-fill\\:0\\%\\]{--progress-bar-fill:0%;}
.\\[--progress-bar-track-direction\\:to_right\\]{--progress-bar-track-direction:to right;}
.\\[-webkit-appearance\\:none\\]{-webkit-appearance:none;}
.\\[-webkit-overflow-scrolling\\:touch\\]{-webkit-overflow-scrolling:touch;}
.\\[-webkit-tap-highlight-color\\:transparent\\]{-webkit-tap-highlight-color:transparent;}
.\\[-webkit-user-drag\\:none\\]{-webkit-user-drag:none;}
.\\[accent-color\\:var\\(--color-text\\)\\]{accent-color:var(--color-text);}
.\\[appearance\\:none\\]{appearance:none;}
.\\[contain\\:inline-size\\]{contain:inline-size;}
.\\[container-type\\:size\\]{container-type:size;}
.\\[direction\\:ltr\\]{direction:ltr;}
.\\[font-size\\:min\\(25vw\\,35cqi\\,35cqb\\,180px\\)\\]{font-size:min(25vw,35cqi,35cqb,180px);}
.\\[font-size\\:min\\(var\\(--ui-font-size-xl\\)\\,var\\(--reader-end-font-size\\)\\)\\]{font-size:min(var(--ui-font-size-xl),var(--reader-end-font-size));}
.\\[font-size\\:var\\(--ui-font-size-md\\)\\]{font-size:var(--ui-font-size-md);}
.\\[font-size\\:var\\(--ui-font-size-prominent\\)\\]{font-size:var(--ui-font-size-prominent);}
.\\[font-size\\:var\\(--ui-font-size-sm\\)\\]{font-size:var(--ui-font-size-sm);}
.\\[font-size\\:var\\(--ui-font-size-title\\)\\]{font-size:var(--ui-font-size-title);}
.\\[font-variant-numeric\\:tabular-nums\\]{font-variant-numeric:tabular-nums;}
.\\[overflow-wrap\\:anywhere\\],
.break-anywhere{overflow-wrap:anywhere;}
.\\[scrollbar-gutter\\:stable\\]{scrollbar-gutter:stable;}
.\\[touch-action\\:manipulation\\]{touch-action:manipulation;}
.\\[touch-action\\:none\\],
.touch-none,
#ehpeek-reader[data-navigation-mode=paged] .\\[\\#ehpeek-reader\\[data-navigation-mode\\=paged\\]_\\&\\]\\:touch-none{touch-action:none;}
.\\[touch-action\\:pan-x_pan-y\\]{touch-action:pan-x pan-y;}
.\\[unicode-bidi\\:plaintext\\]{unicode-bidi:plaintext;}
.pointer-events-auto{pointer-events:auto;}
.\\[\\&\\[data-open\\=false\\]\\]\\:pointer-events-none[data-open=false],
.pointer-events-none{pointer-events:none;}
.visible{visibility:visible;}
.invisible{visibility:hidden;}
.absolute{position:absolute;}
.fixed{position:fixed;}
.relative{position:relative;}
.static{position:static;}
.inset-0{inset:0;}
.inset-y-0{top:0;bottom:0;}
.bottom-\\[calc\\(12px\\+env\\(safe-area-inset-bottom\\,0px\\)\\)\\]{bottom:calc(12px + env(safe-area-inset-bottom,0px));}
.bottom-\\[calc\\(max\\(16px\\,env\\(safe-area-inset-bottom\\,0px\\)\\)_\\+_64px\\)\\]{bottom:calc(max(16px,env(safe-area-inset-bottom,0px)) + 64px);}
.bottom-\\[calc\\(var\\(--ui-control-size-lg\\)\\*2\\+44px\\+env\\(safe-area-inset-bottom\\,0px\\)\\)\\]{bottom:calc(var(--ui-control-size-lg) * 2 + 44px + env(safe-area-inset-bottom,0px));}
.left-\\[max\\(10px\\,env\\(safe-area-inset-left\\,0px\\)\\)\\]{left:max(10px,env(safe-area-inset-left,0px));}
.left-\\[max\\(12px\\,env\\(safe-area-inset-left\\,0px\\)\\)\\]{left:max(12px,env(safe-area-inset-left,0px));}
.left-0{left:0;}
.left-1\\/2{left:50%;}
.right-\\[max\\(12px\\,env\\(safe-area-inset-right\\,0px\\)\\)\\]{right:max(12px,env(safe-area-inset-right,0px));}
.right-\\[max\\(16px\\,env\\(safe-area-inset-right\\,0px\\)\\)\\]{right:max(16px,env(safe-area-inset-right,0px));}
.right-0{right:0;}
.right-10px{right:10px;}
.right-24px{right:24px;}
.right-2px{right:2px;}
.right-4px{right:4px;}
.right-auto{right:auto;}
.top-\\[calc\\(100\\%\\+4px\\)\\]{top:calc(100% + 4px);}
.top-\\[calc\\(10px\\+env\\(safe-area-inset-top\\,0px\\)\\)\\]{top:calc(10px + env(safe-area-inset-top,0px));}
.top-\\[calc\\(var\\(--ui-control-size-md\\)\\+8px\\)\\]{top:calc(var(--ui-control-size-md) + 8px);}
.top-0{top:0;}
.top-1\\/2{top:50%;}
.top-24px{top:24px;}
.top-full{top:100%;}
.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;}
.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;line-clamp:3;}
.line-clamp-4{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:4;line-clamp:4;}
.z-\\[1150\\]{z-index:1150;}
.z-\\[1200\\]{z-index:1200;}
.z-\\[1300\\]{z-index:1300;}
.z-1{z-index:1;}
.z-2{z-index:2;}
.z-3{z-index:3;}
.z-4{z-index:4;}
.grid{display:grid;}
.\\[\\&\\>\\*\\:not\\(\\[hidden\\]\\)\\]\\:col-span-full>*:not([hidden]){grid-column:1/-1;}
.col-start-2{grid-column-start:2;}
.col-start-3{grid-column-start:3;}
.row-start-1{grid-row-start:1;}
.auto-rows-\\[100\\%\\]{grid-auto-rows:100%;}
:root[data-ehpeek-ui-scale="large"] .large\\:grid-cols-\\[minmax\\(120px\\,38\\%\\)_minmax\\(0\\,1fr\\)\\]{grid-template-columns:minmax(120px,38%) minmax(0,1fr);}
.grid-cols-\\[1fr_1fr\\]{grid-template-columns:1fr 1fr;}
.grid-cols-\\[1fr_auto_1fr\\]{grid-template-columns:1fr auto 1fr;}
.grid-cols-\\[48px_minmax\\(64px\\,1fr\\)_64px_64px\\]{grid-template-columns:48px minmax(64px,1fr) 64px 64px;}
.grid-cols-\\[auto_96px_96px\\]{grid-template-columns:auto 96px 96px;}
.grid-cols-\\[max-content_minmax\\(0\\,1fr\\)\\]{grid-template-columns:max-content minmax(0,1fr);}
.grid-cols-\\[minmax\\(60px\\,38\\%\\)_minmax\\(0\\,1fr\\)\\]{grid-template-columns:minmax(60px,38%) minmax(0,1fr);}
.grid-cols-\\[repeat\\(3\\,minmax\\(0\\,1fr\\)\\)\\],
.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr));}
.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr));}
.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr));}
.m-0{margin:0;}
.m12{margin:3rem;}
.m15{margin:3.75rem;}
.m16\\.2{margin:4.05rem;}
.m17{margin:4.25rem;}
.m20{margin:5rem;}
.m3{margin:0.75rem;}
.m5{margin:1.25rem;}
.m7{margin:1.75rem;}
.m8{margin:2rem;}
.m9{margin:2.25rem;}
.mx-auto{margin-left:auto;margin-right:auto;}
.my-auto{margin-top:auto;margin-bottom:auto;}
:root[data-ehpeek-ui-scale="large"] .large\\:ml-\\[max\\(14px\\,env\\(safe-area-inset-left\\,0px\\)\\)\\]{margin-left:max(14px,env(safe-area-inset-left,0px));}
:root[data-ehpeek-ui-scale="large"] .large\\:mr-\\[max\\(14px\\,env\\(safe-area-inset-right\\,0px\\)\\)\\]{margin-right:max(14px,env(safe-area-inset-right,0px));}
:root[data-ehpeek-ui-scale="large"] .large\\:mt--18px{margin-top:-18px;}
.mb-0{margin-bottom:0;}
.mb-10px{margin-bottom:10px;}
.ml-\\[max\\(7px\\,env\\(safe-area-inset-left\\,0px\\)\\)\\]{margin-left:max(7px,env(safe-area-inset-left,0px));}
.ml-auto{margin-left:auto;}
.mr-\\[max\\(7px\\,env\\(safe-area-inset-right\\,0px\\)\\)\\]{margin-right:max(7px,env(safe-area-inset-right,0px));}
.mt--9px{margin-top:-9px;}
.mt-0{margin-top:0;}
.mt-2px{margin-top:2px;}
.mt-auto{margin-top:auto;}
.last\\:mb-0:last-child{margin-bottom:0;}
.box-border{box-sizing:border-box;}
.block{display:block;}
.inline-block{display:inline-block;}
.contents{display:contents;}
.\\!hidden{display:none !important;}
.hidden{display:none;}
.aspect-\\[2\\/3\\]{aspect-ratio:2/3;}
:root[data-ehpeek-ui-scale="large"] .large\\:min-h-\\[clamp\\(260px\\,42vh\\,340px\\)\\]{min-height:clamp(260px,42vh,340px);}
:root[data-ehpeek-ui-scale="large"] .large\\:w-\\[calc\\(100\\%_-_32px\\)\\]{width:calc(100% - 32px);}
.\\!h-\\[var\\(--ui-control-size-lg\\)\\]{height:var(--ui-control-size-lg) !important;}
.\\!min-w-\\[var\\(--ui-control-size-lg\\)\\]{min-width:var(--ui-control-size-lg) !important;}
.\\!w-\\[calc\\(var\\(--ui-control-size-lg\\)\\*2\\)\\]{width:calc(var(--ui-control-size-lg) * 2) !important;}
.\\[\\&_\\.ehpeek-icon\\]\\:h-\\[var\\(--ui-icon-size-lg\\)\\] .ehpeek-icon{height:var(--ui-icon-size-lg);}
.\\[\\&_\\.ehpeek-icon\\]\\:w-\\[var\\(--ui-icon-size-lg\\)\\] .ehpeek-icon{width:var(--ui-icon-size-lg);}
.h-\\[100dvh\\]{height:100dvh;}
.h-\\[2\\.4em\\]{height:2.4em;}
.h-\\[var\\(--reader-frame-height\\)\\]{height:var(--reader-frame-height);}
.h-\\[var\\(--reader-page-height\\)\\]{height:var(--reader-page-height);}
.h-\\[var\\(--ui-control-size-md\\)\\]{height:var(--ui-control-size-md);}
.h-\\[var\\(--ui-control-size-xl\\)\\]{height:var(--ui-control-size-xl);}
.h-\\[var\\(--ui-icon-size-md\\)\\]{height:var(--ui-icon-size-md);}
.h-\\[var\\(--ui-icon-size-sm\\)\\]{height:var(--ui-icon-size-sm);}
.h-\\[var\\(--ui-status-dot-size-lg\\)\\]{height:var(--ui-status-dot-size-lg);}
.h-\\[var\\(--ui-status-dot-size-md\\)\\]{height:var(--ui-status-dot-size-md);}
.h-108px{height:108px;}
.h-15px{height:15px;}
.h-20px{height:20px;}
.h-40px{height:40px;}
.h-64px{height:64px;}
.h-full{height:100%;}
.h1{height:0.25rem;}
.max-h-\\[60dvh\\]{max-height:60dvh;}
.max-h-\\[calc\\(100dvh-32px\\)\\]{max-height:calc(100dvh - 32px);}
.max-h-\\[calc\\(100vh-48px\\)\\]{max-height:calc(100vh - 48px);}
.max-h-\\[min\\(720px\\,calc\\(100dvh-32px\\)\\)\\]{max-height:min(720px,calc(100dvh - 32px));}
.max-h-240px{max-height:240px;}
.max-h-full{max-height:100%;}
.max-h-screen{max-height:100vh;}
.max-w-\\[calc\\(100vw-12px\\)\\]{max-width:calc(100vw - 12px);}
.max-w-\\[calc\\(100vw-20px\\)\\]{max-width:calc(100vw - 20px);}
.max-w-\\[calc\\(100vw-32px\\)\\]{max-width:calc(100vw - 32px);}
.max-w-\\[calc\\(100vw-48px\\)\\]{max-width:calc(100vw - 48px);}
.max-w-\\[min\\(78vw\\,320px\\)\\]{max-width:min(78vw,320px);}
.max-w-\\[min\\(86vw\\,760px\\)\\]{max-width:min(86vw,760px);}
.max-w-420px{max-width:420px;}
.max-w-480px{max-width:480px;}
.max-w-520px{max-width:520px;}
.max-w-960px{max-width:960px;}
.max-w-full{max-width:100%;}
.max-w-screen{max-width:100vw;}
.min-h-\\[clamp\\(130px\\,21vh\\,170px\\)\\]{min-height:clamp(130px,21vh,170px);}
.min-h-\\[var\\(--ui-control-size-lg\\)\\]{min-height:var(--ui-control-size-lg);}
.min-h-\\[var\\(--ui-control-size-md\\)\\]{min-height:var(--ui-control-size-md);}
.min-h-\\[var\\(--ui-control-size-sm\\)\\]{min-height:var(--ui-control-size-sm);}
.min-h-\\[var\\(--ui-control-size-xl\\)\\]{min-height:var(--ui-control-size-xl);}
.min-h-\\[var\\(--ui-control-size-xs\\)\\]{min-height:var(--ui-control-size-xs);}
.min-h-0{min-height:0;}
.min-h-full{min-height:100%;}
.min-w-\\[var\\(--ui-control-size-md\\)\\]{min-width:var(--ui-control-size-md);}
.min-w-0{min-width:0;}
.min-w-285px{min-width:285px;}
.min-w-full{min-width:100%;}
.w-\\[65\\%\\]{width:65%;}
.w-\\[calc\\(100\\%_-_16px\\)\\]{width:calc(100% - 16px);}
.w-\\[calc\\(var\\(--ui-control-size-sm\\)\\/2\\)\\]{width:calc(var(--ui-control-size-sm) / 2);}
.w-\\[calc\\(var\\(--ui-control-size-xl\\)\\*3\\)\\]{width:calc(var(--ui-control-size-xl) * 3);}
.w-\\[calc\\(var\\(--ui-control-size-xl\\)\\*6\\)\\]{width:calc(var(--ui-control-size-xl) * 6);}
.w-\\[min\\(680px\\,calc\\(100vw-24px\\)\\)\\]{width:min(680px,calc(100vw - 24px));}
.w-\\[min\\(92vw\\,420px\\)\\]{width:min(92vw,420px);}
.w-\\[var\\(--reader-frame-width\\)\\]{width:var(--reader-frame-width);}
.w-\\[var\\(--reader-page-width\\)\\]{width:var(--reader-page-width);}
.w-\\[var\\(--ui-control-size-md\\)\\]{width:var(--ui-control-size-md);}
.w-\\[var\\(--ui-control-size-sm\\)\\]{width:var(--ui-control-size-sm);}
.w-\\[var\\(--ui-control-size-xl\\)\\]{width:var(--ui-control-size-xl);}
.w-\\[var\\(--ui-icon-size-md\\)\\]{width:var(--ui-icon-size-md);}
.w-\\[var\\(--ui-icon-size-sm\\)\\]{width:var(--ui-icon-size-sm);}
.w-\\[var\\(--ui-status-dot-size-lg\\)\\]{width:var(--ui-status-dot-size-lg);}
.w-\\[var\\(--ui-status-dot-size-md\\)\\]{width:var(--ui-status-dot-size-md);}
.w-10px{width:10px;}
.w-14px{width:14px;}
.w-15px{width:15px;}
.w-180px{width:180px;}
.w-20px{width:20px;}
.w-36px{width:36px;}
.w-3px{width:3px;}
.w-40px{width:40px;}
.w-42px{width:42px;}
.w-60px{width:60px;}
.w-64px{width:64px;}
.w-6px{width:6px;}
.w-auto{width:auto;}
.w-fit{width:fit-content;}
.w-full{width:100%;}
.w-max{width:max-content;}
.flex{display:flex;}
.inline-flex{display:inline-flex;}
.flex-\\[0_0_100\\%\\]{flex:0 0 100%;}
.flex-\\[0_0_var\\(--reader-page-width\\)\\]{flex:0 0 var(--reader-page-width);}
.flex-1{flex:1 1 0%;}
.flex-none{flex:none;}
.flex-row{flex-direction:row;}
.flex-col{flex-direction:column;}
.flex-wrap{flex-wrap:wrap;}
.table{display:table;}
.border-collapse{border-collapse:collapse;}
.border-separate{border-collapse:separate;}
.border-spacing-4px{--un-border-spacing-x:4px;--un-border-spacing-y:4px;border-spacing:var(--un-border-spacing-x) var(--un-border-spacing-y);}
.origin-center{transform-origin:center;}
.-translate-x-1\\/2{--un-translate-x:-50%;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}
.-translate-y-1\\/2{--un-translate-y:-50%;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}
.\\[\\&\\[data-open\\=false\\]\\]\\:translate-y-\\[calc\\(100\\%\\+16px\\)\\][data-open=false]{--un-translate-y:calc(100% + 16px);transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}
.active\\:scale-96:active{--un-scale-x:0.96;--un-scale-y:0.96;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}
.active\\:scale-98:active{--un-scale-x:0.98;--un-scale-y:0.98;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}
.transform{transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}
@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
.animate-spin{animation:spin 1s linear infinite;}
.cursor-default{cursor:default;}
.disabled\\:cursor-default:disabled{cursor:default;}
.cursor-pointer{cursor:pointer;}
.cursor-grab{cursor:grab;}
.\\[\\&\\[data-dragging\\=true\\]\\]\\:cursor-grabbing[data-dragging=true]{cursor:grabbing;}
.active\\:cursor-grabbing:active{cursor:grabbing;}
.touch-pan-y{--un-pan-y:pan-y;touch-action:var(--un-pan-x) var(--un-pan-y) var(--un-pinch-zoom);}
.select-text{-webkit-user-select:text;user-select:text;}
.\\[\\&\\[data-dragging\\=true\\]\\]\\:select-none[data-dragging=true],
.select-none,
#ehpeek-reader[data-navigation-mode=paged] .\\[\\#ehpeek-reader\\[data-navigation-mode\\=paged\\]_\\&\\]\\:select-none{-webkit-user-select:none;user-select:none;}
.resize{resize:both;}
.appearance-none{-webkit-appearance:none;appearance:none;}
.items-start{align-items:flex-start;}
.items-end{align-items:flex-end;}
.items-center{align-items:center;}
.items-stretch{align-items:stretch;}
.self-start{align-self:flex-start;}
.self-end{align-self:flex-end;}
.self-center{align-self:center;}
.self-stretch{align-self:stretch;}
.justify-start{justify-content:flex-start;}
.justify-end{justify-content:flex-end;}
.justify-center{justify-content:center;}
.justify-between{justify-content:space-between;}
.justify-self-end{justify-self:end;}
.justify-self-stretch{justify-self:stretch;}
:root[data-ehpeek-ui-scale="large"] .large\\:gap-18px{gap:18px;}
:root[data-ehpeek-ui-scale="large"] .large\\:gap-6px{gap:6px;}
.gap-1px{gap:1px;}
.gap-2px{gap:2px;}
.gap-3px{gap:3px;}
.gap-8px{gap:8px;}
.gap-9px{gap:9px;}
.gap-x-3px{column-gap:3px;}
.overflow-auto{overflow:auto;}
.overflow-hidden,
#ehpeek-reader[data-navigation-mode=paged] .\\[\\#ehpeek-reader\\[data-navigation-mode\\=paged\\]_\\&\\]\\:overflow-hidden{overflow:hidden;}
.overflow-visible{overflow:visible;}
.overflow-x-auto{overflow-x:auto;}
.overflow-x-hidden{overflow-x:hidden;}
.overflow-y-auto{overflow-y:auto;}
.overscroll-contain{overscroll-behavior:contain;}
.overscroll-x-contain{overscroll-behavior-x:contain;}
.scroll-auto{scroll-behavior:auto;}
.text-ellipsis{text-overflow:ellipsis;}
.whitespace-normal{white-space:normal;}
.whitespace-nowrap{white-space:nowrap;}
.whitespace-pre-line{white-space:pre-line;}
.break-normal{overflow-wrap:normal;word-break:normal;}
.\\!border{border-width:1px !important;}
.border{border-width:1px;}
.border-0{border-width:0px;}
.border-2px{border-width:2px;}
.border-4,
.border-4px{border-width:4px;}
.border-6{border-width:6px;}
.border-y{border-top-width:1px;border-bottom-width:1px;}
:root[data-ehpeek-ui-scale="large"] .large\\:border-l-8{border-left-width:8px;}
.border-b{border-bottom-width:1px;}
.border-l{border-left-width:1px;}
.border-l-4{border-left-width:4px;}
.border-t{border-top-width:1px;}
.last\\:border-b-0:last-child{border-bottom-width:0px;}
.\\!border-transparent{border-color:transparent !important;}
.border-\\[var\\(--color-border\\)\\]{border-color:var(--color-border);}
.border-\\[var\\(--color-danger\\)\\]{border-color:var(--color-danger);}
.border-\\[var\\(--color-reader-border\\)\\]{border-color:var(--color-reader-border);}
.border-\\[var\\(--color-site-accent\\)\\]{border-color:var(--color-site-accent);}
.border-\\[var\\(--color-site-border-subtle\\)\\]{border-color:var(--color-site-border-subtle);}
.border-\\[var\\(--color-site-swipe-border\\)\\]{border-color:var(--color-site-swipe-border);}
.hover\\:border-\\[var\\(--color-site-border\\)\\]:hover{border-color:var(--color-site-border);}
.border-l-\\[var\\(--color-site-page\\)\\]{border-left-color:var(--color-site-page);}
.border-t-\\[var\\(--color-reader-accent\\)\\]{border-top-color:var(--color-reader-accent);}
.border-t-\\[var\\(--color-site-border-subtle\\)\\]{border-top-color:var(--color-site-border-subtle);}
.rounded{border-radius:0.25rem;}
.rounded-3px{border-radius:3px;}
.rounded-full{border-radius:9999px;}
.rounded-l-md{border-top-left-radius:0.375rem;border-bottom-left-radius:0.375rem;}
.border-solid{border-style:solid;}
.\\!bg-\\[color-mix\\(in_srgb\\,var\\(--color-site-page\\)_82\\%\\,black\\)\\]{background-color:color-mix(in srgb,var(--color-site-page) 82%,black) !important;}
.\\!bg-transparent,
.\\[\\&_\\*\\]\\:\\!bg-transparent *{background-color:transparent !important;}
.bg-\\[var\\(--color-background\\)\\]{background-color:var(--color-background);}
.bg-\\[var\\(--color-badge\\)\\]{background-color:var(--color-badge);}
.bg-\\[var\\(--color-border\\)\\]{background-color:var(--color-border);}
.bg-\\[var\\(--color-control\\)\\]{background-color:var(--color-control);}
.bg-\\[var\\(--color-elevated\\)\\]{background-color:var(--color-elevated);}
.bg-\\[var\\(--color-loading\\)\\]{background-color:var(--color-loading);}
.bg-\\[var\\(--color-reader-border\\,var\\(--color-border\\)\\)\\]{background-color:var(--color-reader-border,var(--color-border));}
.bg-\\[var\\(--color-reader-scrollbar\\,var\\(--color-muted\\)\\)\\]{background-color:var(--color-reader-scrollbar,var(--color-muted));}
.bg-\\[var\\(--color-reader-surface\\)\\]{background-color:var(--color-reader-surface);}
.bg-\\[var\\(--color-site-accent-hover\\)\\]{background-color:var(--color-site-accent-hover);}
.bg-\\[var\\(--color-site-accent\\)\\]{background-color:var(--color-site-accent);}
.bg-\\[var\\(--color-site-elevated\\)\\]{background-color:var(--color-site-elevated);}
.bg-\\[var\\(--color-site-item-hover\\)\\]{background-color:var(--color-site-item-hover);}
.bg-\\[var\\(--color-site-surface\\)\\]{background-color:var(--color-site-surface);}
.bg-\\[var\\(--color-site-swipe-background\\)\\]{background-color:var(--color-site-swipe-background);}
.bg-\\[var\\(--color-state-off\\)\\]{background-color:var(--color-state-off);}
.bg-\\[var\\(--color-state-on\\)\\]{background-color:var(--color-state-on);}
.bg-\\[var\\(--color-surface\\)\\]{background-color:var(--color-surface);}
.bg-\\[var\\(--color-text\\)\\]{background-color:var(--color-text);}
.bg-black\\/65{background-color:rgb(0 0 0 / 0.65);}
.bg-transparent{background-color:transparent;}
.hover\\:\\!bg-\\[var\\(--color-site-item-hover\\)\\]:hover{background-color:var(--color-site-item-hover) !important;}
.hover\\:\\!bg-transparent:hover{background-color:transparent !important;}
.hover\\:bg-\\[var\\(--color-badge\\)\\]:hover{background-color:var(--color-badge);}
.hover\\:bg-\\[var\\(--color-site-accent-hover\\)\\]:hover{background-color:var(--color-site-accent-hover);}
.hover\\:bg-\\[var\\(--color-site-item-hover\\)\\]:hover{background-color:var(--color-site-item-hover);}
.active\\:\\!bg-\\[var\\(--color-site-item-hover\\)\\]:active{background-color:var(--color-site-item-hover) !important;}
.active\\:\\!bg-transparent:active{background-color:transparent !important;}
.active\\:bg-\\[var\\(--color-site-accent-hover\\)\\]:active{background-color:var(--color-site-accent-hover);}
.active\\:bg-\\[var\\(--color-site-item-hover\\)\\]:active{background-color:var(--color-site-item-hover);}
.bg-no-repeat{background-repeat:no-repeat;}
.object-contain{object-fit:contain;}
.\\!p-0{padding:0 !important;}
.p-\\[var\\(--reader-end-padding\\)\\]{padding:var(--reader-end-padding);}
.p-0{padding:0;}
:root[data-ehpeek-ui-scale="large"] .large\\:px-10px{padding-left:10px;padding-right:10px;}
:root[data-ehpeek-ui-scale="large"] .large\\:py-6px{padding-top:6px;padding-bottom:6px;}
.px{padding-left:1rem;padding-right:1rem;}
.px-\\[0\\.6em\\]{padding-left:0.6em;padding-right:0.6em;}
.px-0{padding-left:0;padding-right:0;}
.px-5px{padding-left:5px;padding-right:5px;}
.py-0{padding-top:0;padding-bottom:0;}
.py-3px{padding-top:3px;padding-bottom:3px;}
.py-56px{padding-top:56px;padding-bottom:56px;}
:root[data-ehpeek-ui-scale="large"] .large\\:pb-48px{padding-bottom:48px;}
:root[data-ehpeek-ui-scale="large"] .large\\:pl-\\[max\\(16px\\,env\\(safe-area-inset-left\\,0px\\)\\)\\]{padding-left:max(16px,env(safe-area-inset-left,0px));}
:root[data-ehpeek-ui-scale="large"] .large\\:pl-6px{padding-left:6px;}
:root[data-ehpeek-ui-scale="large"] .large\\:pr-\\[max\\(16px\\,env\\(safe-area-inset-right\\,0px\\)\\)\\]{padding-right:max(16px,env(safe-area-inset-right,0px));}
:root[data-ehpeek-ui-scale="large"] .large\\:pt-2px,
.pt-2px{padding-top:2px;}
.pb-24px{padding-bottom:24px;}
.pb-2px{padding-bottom:2px;}
.pb-72px{padding-bottom:72px;}
.pl-\\[max\\(12px\\,env\\(safe-area-inset-left\\,0px\\)\\)\\]{padding-left:max(12px,env(safe-area-inset-left,0px));}
.pl-\\[max\\(8px\\,env\\(safe-area-inset-left\\,0px\\)\\)\\]{padding-left:max(8px,env(safe-area-inset-left,0px));}
.pl-3px{padding-left:3px;}
.pl-56px{padding-left:56px;}
.pl-72px{padding-left:72px;}
.pr-\\[max\\(12px\\,env\\(safe-area-inset-right\\,0px\\)\\)\\]{padding-right:max(12px,env(safe-area-inset-right,0px));}
.pr-\\[max\\(8px\\,env\\(safe-area-inset-right\\,0px\\)\\)\\]{padding-right:max(8px,env(safe-area-inset-right,0px));}
.pr-56px{padding-right:56px;}
.pr-72px{padding-right:72px;}
.pt-\\[max\\(8px\\,env\\(safe-area-inset-top\\,0px\\)\\)\\]{padding-top:max(8px,env(safe-area-inset-top,0px));}
.pt-1px{padding-top:1px;}
.text-center{text-align:center;}
.text-left{text-align:left;}
.text-right{text-align:right;}
.align-middle{vertical-align:middle;}
.\\!text-\\[var\\(--color-site-text\\)\\]{color:var(--color-site-text) !important;}
.text-\\[var\\(--color-danger\\)\\]{color:var(--color-danger);}
.text-\\[var\\(--color-muted\\)\\]{color:var(--color-muted);}
.text-\\[var\\(--color-rating-submitted\\)\\]{color:var(--color-rating-submitted);}
.text-\\[var\\(--color-reader-muted\\)\\]{color:var(--color-reader-muted);}
.text-\\[var\\(--color-site-accent\\)\\]{color:var(--color-site-accent);}
.text-\\[var\\(--color-site-surface\\)\\]{color:var(--color-site-surface);}
.text-\\[var\\(--color-site-text\\)\\]{color:var(--color-site-text);}
.text-\\[var\\(--color-text\\)\\]{color:var(--color-text);}
.visited\\:\\!text-\\[var\\(--color-site-text\\)\\]:visited{color:var(--color-site-text) !important;}
.hover\\:\\!text-\\[var\\(--color-site-text\\)\\]:hover{color:var(--color-site-text) !important;}
.active\\:\\!text-\\[var\\(--color-site-text\\)\\]:active{color:var(--color-site-text) !important;}
.\\[\\&_\\*\\]\\:\\!text-inherit *{color:inherit !important;}
.font-400{font-weight:400;}
.font-500{font-weight:500;}
.font-600{font-weight:600;}
.font-700{font-weight:700;}
.font-850{font-weight:850;}
.leading-\\[1\\.05\\]{line-height:1.05;}
.leading-\\[1\\.1\\]{line-height:1.1;}
.leading-\\[1\\.15\\]{line-height:1.15;}
.leading-\\[1\\.16\\]{line-height:1.16;}
.leading-\\[1\\.2\\]{line-height:1.2;}
.leading-\\[1\\.3\\]{line-height:1.3;}
.leading-\\[1\\.35\\]{line-height:1.35;}
.leading-\\[1\\.4\\]{line-height:1.4;}
.leading-\\[1\\.45\\]{line-height:1.45;}
.leading-\\[1\\],
.leading-none{line-height:1;}
.leading-1{line-height:0.25rem;}
.font-inherit{font-family:inherit;}
.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;}
.font-sans{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";}
.uppercase{text-transform:uppercase;}
.lowercase{text-transform:lowercase;}
.normal-case{text-transform:none;}
.no-underline{text-decoration:none;}
.hover\\:no-underline:hover{text-decoration:none;}
.active\\:no-underline:active{text-decoration:none;}
.tab{-moz-tab-size:4;-o-tab-size:4;tab-size:4;}
.accent-\\[var\\(--color-reader-accent\\)\\]{accent-color:var(--color-reader-accent);}
.\\[\\&\\[data-open\\=false\\]\\]\\:opacity-0[data-open=false],
.opacity-0{opacity:0;}
.opacity-100{opacity:1;}
.opacity-40{opacity:0.4;}
.opacity-70{opacity:0.7;}
.opacity-72{opacity:0.72;}
.opacity-75{opacity:0.75;}
.opacity-78{opacity:0.78;}
.opacity-80{opacity:0.8;}
.opacity-82{opacity:0.82;}
.opacity-85{opacity:0.85;}
.hover\\:opacity-100:hover{opacity:1;}
.focus-visible\\:opacity-100:focus-visible{opacity:1;}
.disabled\\:opacity-40:disabled{opacity:0.4;}
.disabled\\:opacity-50:disabled{opacity:0.5;}
.shadow-\\[0_2px_10px_var\\(--color-shadow-control\\)\\]{--un-shadow:0 2px 10px var(--un-shadow-color, var(--color-shadow-control));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
.shadow-\\[0_2px_10px_var\\(--color-shadow-panel\\)\\]{--un-shadow:0 2px 10px var(--un-shadow-color, var(--color-shadow-panel));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
.shadow-\\[0_2px_8px_var\\(--color-shadow-panel\\)\\]{--un-shadow:0 2px 8px var(--un-shadow-color, var(--color-shadow-panel));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
.shadow-\\[0_4px_14px_var\\(--color-shadow-floating\\)\\]{--un-shadow:0 4px 14px var(--un-shadow-color, var(--color-shadow-floating));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
.shadow-\\[0_6px_20px_var\\(--color-shadow-floating\\)\\]{--un-shadow:0 6px 20px var(--un-shadow-color, var(--color-shadow-floating));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
.shadow-\\[0_8px_24px_var\\(--color-shadow-panel\\)\\]{--un-shadow:0 8px 24px var(--un-shadow-color, var(--color-shadow-panel));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
.shadow-none{--un-shadow:0 0 var(--un-shadow-color, rgb(0 0 0 / 0));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
.shadow-xl{--un-shadow:var(--un-shadow-inset) 0 20px 25px -5px var(--un-shadow-color, rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 8px 10px -6px var(--un-shadow-color, rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
.focus-visible\\:outline-2:focus-visible{outline-width:2px;}
.focus-visible\\:outline-\\[var\\(--color-site-accent\\)\\]:focus-visible{outline-color:var(--color-site-accent);}
.focus-visible\\:outline-offset-3px:focus-visible{outline-offset:3px;}
.outline{outline-style:solid;}
.focus-visible\\:outline:focus-visible{outline-style:solid;}
.hover\\:brightness-108:hover{--un-brightness:brightness(1.08);filter:var(--un-blur) var(--un-brightness) var(--un-contrast) var(--un-drop-shadow) var(--un-grayscale) var(--un-hue-rotate) var(--un-invert) var(--un-saturate) var(--un-sepia);}
.filter{filter:var(--un-blur) var(--un-brightness) var(--un-contrast) var(--un-drop-shadow) var(--un-grayscale) var(--un-hue-rotate) var(--un-invert) var(--un-saturate) var(--un-sepia);}
.backdrop-filter{-webkit-backdrop-filter:var(--un-backdrop-blur) var(--un-backdrop-brightness) var(--un-backdrop-contrast) var(--un-backdrop-grayscale) var(--un-backdrop-hue-rotate) var(--un-backdrop-invert) var(--un-backdrop-opacity) var(--un-backdrop-saturate) var(--un-backdrop-sepia);backdrop-filter:var(--un-backdrop-blur) var(--un-backdrop-brightness) var(--un-backdrop-contrast) var(--un-backdrop-grayscale) var(--un-backdrop-hue-rotate) var(--un-backdrop-invert) var(--un-backdrop-opacity) var(--un-backdrop-saturate) var(--un-backdrop-sepia);}
.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
.transition-\\[background-color\\,color\\]{transition-property:background-color,color;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
.transition-\\[background-color\\,transform\\]{transition-property:background-color,transform;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
.transition-\\[border-color\\,background-color\\,color\\]{transition-property:border-color,background-color,color;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
.transition-\\[filter\\,transform\\,box-shadow\\]{transition-property:filter,transform,box-shadow;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
.transition-\\[opacity\\,transform\\]{transition-property:opacity,transform;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
.transition-\\[width\\,height\\]{transition-property:width,height;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
.transition-\\[width\\,opacity\\]{transition-property:width,opacity;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
.duration-120{transition-duration:120ms;}
.duration-160{transition-duration:160ms;}
.ease-in-out{transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);}
.will-change-transform{will-change:transform;}
@media (orientation: landscape){
.landscape\\:w-\\[min\\(600px\\,calc\\(100vw-24px\\)\\)\\]{width:min(600px,calc(100vw - 24px));}
}
@media (pointer: coarse){
.coarse\\:right-8px{right:8px;}
.coarse\\:top-\\[calc\\(8px\\+env\\(safe-area-inset-top\\,0px\\)\\)\\]{top:calc(8px + env(safe-area-inset-top,0px));}
.coarse\\:top-8px{top:8px;}
.coarse\\:grid-cols-\\[48px_minmax\\(40px\\,1fr\\)_80px_80px\\]{grid-template-columns:48px minmax(40px,1fr) 80px 80px;}
.coarse\\:grid-cols-\\[minmax\\(0\\,1\\.6fr\\)_minmax\\(0\\,1fr\\)_minmax\\(0\\,1fr\\)\\]{grid-template-columns:minmax(0,1.6fr) minmax(0,1fr) minmax(0,1fr);}
.coarse\\:block{display:block;}
.coarse\\:max-h-\\[calc\\(100dvh-16px\\)\\]{max-height:calc(100dvh - 16px);}
.coarse\\:max-w-\\[calc\\(100vw-16px\\)\\]{max-width:calc(100vw - 16px);}
.coarse\\:max-w-360px{max-width:360px;}
.coarse\\:w-\\[calc\\(100vw-32px\\)\\]{width:calc(100vw - 32px);}
.coarse\\:w-14px{width:14px;}
.coarse\\:w-24px{width:24px;}
.coarse\\:w-40px{width:40px;}
.coarse\\:w-48px{width:48px;}
.coarse\\:w-64px{width:64px;}
.coarse\\:w-full{width:100%;}
.coarse\\:border-spacing-6px{--un-border-spacing-x:6px;--un-border-spacing-y:6px;border-spacing:var(--un-border-spacing-x) var(--un-border-spacing-y);}
.coarse\\:border-8{border-width:8px;}
}`;
// src/theme.css
var theme_default = `:root {
/* --color-background: #070707;
--color-surface: #151515;
--color-elevated: #232323;
--color-text: #f3f3f3;
--color-accent: #4da3ff; */
--color-background: var(--color-site-page);
--color-surface: var(--color-site-surface);
--color-elevated: var(--color-site-elevated);
--color-text: var(--color-site-text);
--color-accent: var(--color-site-accent);
--color-danger: #ffb2a7;
--color-state-on: #4ec46a;
--color-state-off: #8c8f96;
--color-rating-submitted: #8595ff;
--color-shadow: #000000;
--color-site-favorite-0: #646464;
--color-site-favorite-1: #ff6868;
--color-site-favorite-2: #ffa561;
--color-site-favorite-3: #fff56b;
--color-site-favorite-4: #68ff8b;
--color-site-favorite-5: #cdff84;
--color-site-favorite-6: #8afeff;
--color-site-favorite-7: #7268ff;
--color-site-favorite-8: #ac57fe;
--color-site-favorite-9: #fe50c8;
--color-muted: color-mix(in srgb, var(--color-text) 72%, transparent);
/* --color-border: color-mix(in srgb, var(--color-text) 18%, transparent); */
--color-border: var(--color-site-border);
--color-track: color-mix(in srgb, var(--color-text) 34%, var(--color-background));
--color-danger-soft: color-mix(in srgb, var(--color-danger) 12%, transparent);
--color-danger-border: color-mix(in srgb, var(--color-danger) 64%, transparent);
--color-control: color-mix(in srgb, var(--color-elevated) 88%, transparent);
--color-badge: color-mix(in srgb, var(--color-background) 34%, transparent);
--color-shadow-panel: color-mix(in srgb, var(--color-shadow) 32%, transparent);
--color-shadow-elevated: color-mix(in srgb, var(--color-shadow) 38%, transparent);
--color-shadow-control: color-mix(in srgb, var(--color-shadow) 40%, transparent);
--color-shadow-floating: color-mix(in srgb, var(--color-shadow) 42%, transparent);
--color-site-accent-hover: color-mix(in srgb, var(--color-site-accent) 12%, transparent);
--color-site-border-subtle: color-mix(in srgb, var(--color-site-border) 16%, transparent);
--color-site-item-hover: color-mix(in srgb, var(--color-site-text) 8%, transparent);
--color-loading: color-mix(
in srgb,
color-mix(in srgb, var(--color-site-elevated) 80%, var(--color-site-surface)) 92%,
transparent
);
--color-site-swipe-background: color-mix(in srgb, var(--color-site-elevated) 94%, transparent);
--color-site-swipe-border: color-mix(in srgb, var(--color-site-border) 38%, transparent);
}
:root[data-ehpeek-site="e-hentai"] {
--color-site-page: #e3e0d1;
--color-site-surface: #edebdf;
--color-site-elevated: #f3f0e0;
--color-site-text: #5c0d11;
--color-site-accent: #8f4701;
--color-site-border: #5c0d12;
}
:root[data-ehpeek-site="exhentai"] {
--color-site-page: #34353b;
--color-site-surface: #4f535b;
--color-site-elevated: #3f4249;
--color-site-text: #f1f1f1;
--color-site-accent: #f0b35a;
--color-site-border: #8d7454;
}
`;
// src/components/Reader/Viewport.tsx
var _tmpl$41 = /* @__PURE__ */ template("<div tabindex=-1><main>"), _tmpl$213 = /* @__PURE__ */ template('<section><div class="flex w-[var(--reader-frame-width)] h-[var(--reader-frame-height)] items-center justify-center overflow-hidden [container-type:size]">'), _tmpl$312 = /* @__PURE__ */ template('<button type=button class="ehpeek-reader-page-reload appearance-none inline-flex w-64px h-64px items-center justify-center border border-[var(--color-border)] rounded-md bg-[var(--color-control)] text-[var(--color-text)] cursor-pointer font-sans textsize-lg font-700 leading-1 hover:bg-[var(--color-badge)] active:scale-96 [touch-action:manipulation]">'), _tmpl$412 = /* @__PURE__ */ template('<div class="max-w-[min(86vw,760px)] break-anywhere [direction:ltr] [unicode-bidi:plaintext]">'), _tmpl$59 = /* @__PURE__ */ template('<div class="max-w-[min(86vw,760px)] opacity-80 break-anywhere textsize-sm font-500 leading-[1.4] [direction:ltr] [unicode-bidi:plaintext]">'), _tmpl$69 = /* @__PURE__ */ template("<div>"), _tmpl$76 = /* @__PURE__ */ template('<span class="flex w-full h-full flex-col items-center justify-center gap-xl overflow-hidden"aria-hidden=true><span class="block max-w-full flex-none m-0 p-0 text-center leading-[1] whitespace-nowrap [direction:ltr] [unicode-bidi:plaintext]"></span><span class="block w-md h-md flex-none box-border animate-spin rounded-full border-4px border-solid border-[var(--color-reader-border)] border-t-[var(--color-reader-accent)]">'), FALLBACK_ASPECT_RATIO = 1.42, PAGE_SLOT_SPACING = 8, DEFAULT_DECODED_IMAGE_CACHE_LIMIT = 24, DECODED_IMAGE_CACHE_BYTES = 96 * 1024 * 1024, HORIZONTAL_FLING_VELOCITY_MULTIPLIER = 1.4, HORIZONTAL_FLING_MAX_VELOCITY = 1.8;
function pageWindowNumbers(currentPageNum, windowSize) {
let numbers = [];
for (let offset = -windowSize; offset <= windowSize; offset += 1)
numbers.push(currentPageNum + offset);
return numbers;
}
function PagesViewport(props) {
let [slots, setSlots] = createSignal([]), [revision, setRevision] = createSignal(0), [renderedScrollSizeScale, setRenderedScrollSizeScale] = createSignal(untrack(() => props.scrollSizeScale)), horizontalAnimator = new ScrollAnimator("x"), verticalAnimator = new ScrollAnimator("y"), flingAnimator = new ScrollFlingAnimator(), pageSlots2 = [], scroller, scrollerApi, dragStartPosition = null, resizeFrame = null, scrollScaleRevision = 0, moveRequestToken = 0, disposed = !1, syncedDirection = untrack(() => props.direction), syncedNavigationMode = untrack(() => props.navigationMode), decodedImageCacheLimit = Math.max(0, Math.floor(untrack(() => props.decodedImageCacheLimit) ?? DEFAULT_DECODED_IMAGE_CACHE_LIMIT)), cachedImages = /* @__PURE__ */ new Map(), cachedImageBytes = 0, refresh = () => setRevision((value) => value + 1), pagedMode = () => props.navigationMode === "paged", horizontalAxis = () => props.direction !== "ttb", slotFor = (pageNum) => pageSlots2.find((slot) => slot.pageNum === pageNum), viewportWidth = () => scrollerApi.viewportWidth(), viewportHeight = () => scrollerApi.viewportHeight(), scrollTop = () => scrollerApi.scrollTop(), visualSlotIndex = (index, slotCount) => props.direction === "rtl" ? slotCount - 1 - index : index, horizontalAnchorOffset = (pageSlots3, anchor2) => {
let orderedSlots = props.direction === "rtl" ? pageSlots3.slice().reverse() : pageSlots3, offset = 0;
for (let slot of orderedSlots) {
let extent = slot.frameWidth + PAGE_SLOT_SPACING;
if (slot.pageNum === anchor2.pageNum)
return offset + extent * anchor2.xRatio;
offset += extent;
}
return null;
}, applySlotSize = (slot) => {
let aspectRatio = pageSlotAspectRatio(slot);
if (pagedMode()) {
let availableWidth = props.pageLayout === "double" ? Math.max(1, (viewportWidth() - 3) / 2) : viewportWidth();
if (slot.kind !== "page") {
slot.frameWidth = availableWidth, slot.frameHeight = viewportHeight();
return;
}
let frameSize = containFitFrame(aspectRatio, availableWidth, viewportHeight());
slot.frameWidth = frameSize.width, slot.frameHeight = frameSize.height;
return;
}
let sizeScale = renderedScrollSizeScale(), reference = props.scrollFitImageSize, referenceAspectRatio = reference ? reference.height / reference.width : props.window.pages.get(props.scrollFitPageNum)?.aspectRatio ?? FALLBACK_ASPECT_RATIO, scaleMultiplier = sizeScale === "one-to-one" && reference ? 1 / containFitScale(reference.width, reference.height, viewportWidth(), viewportHeight()) : typeof sizeScale == "number" ? sizeScale : 1, referenceFrame = containFitFrame(referenceAspectRatio, viewportWidth(), viewportHeight(), scaleMultiplier);
horizontalAxis() ? (slot.frameHeight = referenceFrame.height, slot.frameWidth = referenceFrame.height / aspectRatio) : (slot.frameWidth = referenceFrame.width, slot.frameHeight = referenceFrame.width * aspectRatio);
}, renderSlots = () => {
for (let slot of pageSlots2)
applySlotSize(slot);
setSlots(pageSlots2.slice()), refresh();
}, refreshSlot = (slot) => {
applySlotSize(slot), refresh();
}, pageOffset = (pageNum) => {
let elements = slotFor(pageNum)?.elements;
return elements ? scrollerApi.slotOffset(elements, props.navigationMode, props.direction, props.pageLayout) : null;
}, verticalScrollBoundsForElements = (firstElements, lastElements) => {
let bounds = {};
if (firstElements && (bounds.min = scrollerApi.slotTop(firstElements)), lastElements) {
let lastElementsRect = lastElements.node.getBoundingClientRect(), lastElementsTop = scrollerApi.slotTop(lastElements);
bounds.max = lastElementsTop + lastElementsRect.height - viewportHeight();
}
return bounds.min === void 0 && bounds.max === void 0 ? null : (bounds.min !== void 0 && bounds.max !== void 0 && (bounds.max = Math.max(bounds.min, bounds.max)), bounds);
}, verticalScrollBounds = () => props.navigationMode !== "scroll" || horizontalAxis() ? null : verticalScrollBoundsForElements(slotFor(1)?.elements, props.window.totalPages ? slotFor(props.window.totalPages + 1)?.elements : null), moveToTop = (nextScrollTop) => {
scrollerApi.moveToTop(nextScrollTop, verticalScrollBounds());
}, horizontalScrollBounds = () => {
if (props.navigationMode !== "scroll" || !horizontalAxis())
return null;
let firstElements = slotFor(1)?.elements, endElements = props.window.totalPages ? slotFor(props.window.totalPages + 1)?.elements : null, bounds = {};
return props.direction === "rtl" ? (firstElements && (bounds.max = scrollerApi.slotLeft(firstElements) + firstElements.node.getBoundingClientRect().width - viewportWidth()), endElements && (bounds.min = scrollerApi.slotLeft(endElements))) : (firstElements && (bounds.min = scrollerApi.slotLeft(firstElements)), endElements && (bounds.max = scrollerApi.slotLeft(endElements) + endElements.node.getBoundingClientRect().width - viewportWidth())), bounds.min !== void 0 && bounds.max !== void 0 && (bounds.max = Math.max(bounds.min, bounds.max)), bounds.min === void 0 && bounds.max === void 0 ? null : bounds;
}, moveToLeft = (nextScrollLeft) => {
let bounds = horizontalScrollBounds();
scrollerApi.moveToLeft(bounds ? clamp(nextScrollLeft, bounds.min ?? Number.NEGATIVE_INFINITY, bounds.max ?? Number.POSITIVE_INFINITY) : nextScrollLeft);
}, pageNumAtPoint = (point) => {
let element = document.elementFromPoint(point.clientX, point.clientY), pageNode = element instanceof Element ? element.closest(".ehpeek-page") : null;
if (!pageNode || !scroller.contains(pageNode))
return null;
let pageNum = Number(pageNode.dataset.ehpeekPageNum || "");
return Number.isFinite(pageNum) ? pageNum : null;
}, stopMotion = () => {
moveRequestToken += 1, dragStartPosition = null, flingAnimator.cancel(), horizontalAnimator.cancel(), verticalAnimator.cancel();
}, performPageMove = (pageNum, motion, onComplete) => {
let delta = pageOffset(pageNum);
return delta === null ? !1 : (horizontalAxis() ? horizontalAnimator.scrollTo(scroller, scrollerApi.scrollLeft() + delta, motion, onComplete) : pagedMode() ? verticalAnimator.scrollTo(scroller, scrollTop() + delta, motion, onComplete) : (moveToTop(scrollTop() + delta), onComplete?.()), !0);
}, moveToPage = (pageNum, motion = "instant", onComplete) => {
let requestToken = ++moveRequestToken;
performPageMove(pageNum, motion, onComplete) || queueMicrotask(() => {
untrack(() => {
!disposed && requestToken === moveRequestToken && performPageMove(pageNum, motion, onComplete);
});
});
}, resizePages = () => {
for (let slot of pageSlots2)
applySlotSize(slot);
refresh();
}, gestureDragging = createPointerGestureElement(() => scroller ?? null, () => props.callbacks.pointer), syncWindow = (options) => {
let anchor2 = props.navigationMode === "scroll" && horizontalAxis() && syncedNavigationMode === props.navigationMode && syncedDirection === props.direction ? scrollerApi.centerAnchor() : null, oldAnchorOffset = anchor2 ? horizontalAnchorOffset(pageSlots2, anchor2) : null, oldScrollLeft = scrollerApi.scrollLeft(), oldSlots = new Map(pageSlots2.map((slot) => [slot.pageNum, slot])), nextSlots = [];
for (let pageNum of pageWindowNumbers(options.currentPageNum, options.windowSize)) {
let kind = pageSlotKind(pageNum, options.totalPages), oldSlot = oldSlots.get(pageNum), slot = oldSlot && oldSlot.kind === kind ? oldSlot : pageSlot(pageNum, kind);
if (!oldSlot && kind === "page") {
let cached = cachedImages.get(pageNum);
cached && (cachedImages.delete(pageNum), cachedImageBytes -= cached.bytes, slot.state = "ready", slot.image = cached.image, slot.width = cached.width, slot.height = cached.height);
}
if (kind === "page") {
let page2 = options.pages.get(pageNum);
page2 && applyPageMetaToSlot(slot, page2);
} else
clearNonPageSlotMeta(slot);
nextSlots.push(slot);
}
let nextSet = new Set(nextSlots);
for (let slot of pageSlots2)
if (!nextSet.has(slot)) {
if (slot.kind === "page" && slot.state === "ready" && slot.image) {
let width = positiveNumber(slot.image.naturalWidth) ?? slot.width, height = positiveNumber(slot.image.naturalHeight) ?? slot.height, cached = {
bytes: width && height ? width * height * 4 : 0,
height,
image: slot.image,
width
}, previous = cachedImages.get(slot.pageNum);
previous && (cachedImageBytes -= previous.bytes), cachedImages.delete(slot.pageNum), cachedImages.set(slot.pageNum, cached), cachedImageBytes += cached.bytes;
}
slot.token += 1;
}
for (; cachedImages.size > decodedImageCacheLimit || cachedImageBytes > DECODED_IMAGE_CACHE_BYTES; ) {
let oldest = cachedImages.entries().next().value;
if (!oldest)
break;
cachedImages.delete(oldest[0]), cachedImageBytes -= oldest[1].bytes, oldest[1].image.removeAttribute("src");
}
pageSlots2 = nextSlots, pageSlots2.forEach((slot, index) => {
slot.index = index;
}), renderSlots();
let newAnchorOffset = anchor2 ? horizontalAnchorOffset(pageSlots2, anchor2) : null;
oldAnchorOffset !== null && newAnchorOffset !== null && moveToLeft(oldScrollLeft + newAnchorOffset - oldAnchorOffset), syncedNavigationMode = props.navigationMode, syncedDirection = props.direction;
}, actions = {
focus: () => scroller.focus({
preventScroll: !0
}),
isDragging: gestureDragging,
beginDrag() {
stopMotion(), dragStartPosition = {
left: scrollerApi.scrollLeft(),
top: scrollTop()
};
},
cancelDrag: () => {
dragStartPosition = null;
},
moveDrag(delta) {
return dragStartPosition === null ? !1 : (pagedMode() ? horizontalAxis() ? scrollerApi.moveToLeft(dragStartPosition.left - delta.dx) : moveToTop(dragStartPosition.top - delta.dy) : (moveToLeft(dragStartPosition.left - delta.dx), moveToTop(dragStartPosition.top - delta.dy)), !0);
},
moveToLeft,
resetPosition: () => scrollerApi.resetPosition(),
stopMotion,
markPageLoading(pageNum) {
let slot = slotFor(pageNum);
return !slot || slot.kind !== "page" || slot.state !== "idle" ? null : (slot.state = "loading", slot.errorMessage = null, slot.token += 1, refreshSlot(slot), slot.token);
},
async loadPageImage(pageNum, token, slotImage) {
let image2 = pageImageDom(pageNum, slotImage), pendingSlot = slotFor(pageNum);
pendingSlot && pendingSlot.token === token && (pendingSlot.width = slotImage.width, pendingSlot.height = slotImage.height, refreshSlot(pendingSlot)), await loadImage(image2);
let slot = slotFor(pageNum);
return !slot || slot.token !== token || !slot.elements ? !1 : (slot.state = "ready", slot.image = image2, slot.errorMessage = null, slot.width = positiveNumber(image2.naturalWidth) ?? slotImage.width, slot.height = positiveNumber(image2.naturalHeight) ?? slotImage.height, refreshSlot(slot), !0);
},
setPageError(pageNum, token, errorMessage) {
let slot = slotFor(pageNum);
return !slot || slot.token !== token ? !1 : (slot.state = "error", slot.image = null, slot.errorMessage = errorMessage, refresh(), !0);
},
resetPageError(pageNum) {
let slot = slotFor(pageNum);
return !slot || slot.kind !== "page" || slot.state !== "error" ? !1 : (slot.state = "idle", slot.errorMessage = null, refreshSlot(slot), !0);
},
resetPageLoading(pageNum, token) {
let slot = slotFor(pageNum);
return !slot || slot.kind !== "page" || slot.state !== "loading" || slot.token !== token ? !1 : (slot.state = "idle", refreshSlot(slot), !0);
},
moveToPage,
moveToTop,
scrollLeft: () => scrollerApi.scrollLeft(),
scrollTop,
viewportWidth,
pageOffset,
centerPageNum() {
for (let slot of pageSlots2)
if (slot.elements && slot.kind !== "blank" && scrollerApi.slotContainsViewportTarget(slot.elements, props.direction))
return slot.pageNum;
return null;
},
isHitEndPage(point) {
let pageNum = pageNumAtPoint(point);
return pageNum !== null && slotFor(pageNum)?.kind === "end";
},
pageImageHeight(pageNum) {
let slot = slotFor(pageNum);
return slot?.image?.naturalHeight || slot?.height || null;
},
pageImageReady(pageNum) {
let slot = slotFor(pageNum);
return slot?.state === "ready" && slot.image !== null;
},
pageImageWidth(pageNum) {
let slot = slotFor(pageNum);
return slot?.image?.naturalWidth || slot?.width || null;
},
pageZoomScale(pageNum) {
let slot = slotFor(pageNum), frameRect = slot?.elements?.frame.getBoundingClientRect(), imageWidth = slot?.image?.naturalWidth || slot?.width, imageHeight = slot?.image?.naturalHeight || slot?.height;
if (!frameRect || !imageWidth || !imageHeight)
return 1;
let readerScale = Math.min(frameRect.width / imageWidth, frameRect.height / imageHeight), overlayScale = Math.min(1, viewportWidth() / imageWidth, viewportHeight() / imageHeight);
return readerScale > 0 && overlayScale > 0 ? readerScale / overlayScale : 1;
},
pageNumAtPoint,
startVerticalFlingFromDragVelocity(dragVelocityY, onStop) {
flingAnimator.start({
axis: "y",
scroller,
initialVelocity: -dragVelocityY,
setScrollPosition: moveToTop,
canRun: () => !disposed && props.navigationMode === "scroll" && !horizontalAxis(),
onStop
});
},
startHorizontalFlingFromDragVelocity(dragVelocityX, onStop) {
flingAnimator.start({
axis: "x",
scroller,
initialVelocity: -dragVelocityX * HORIZONTAL_FLING_VELOCITY_MULTIPLIER,
maxVelocity: HORIZONTAL_FLING_MAX_VELOCITY,
setScrollPosition: moveToLeft,
canRun: () => !disposed && props.navigationMode === "scroll" && horizontalAxis(),
onStop
});
}
};
untrack(() => props.actionsRef(actions)), createEffect(() => syncWindow(props.window)), createEffect(() => {
let navigationMode = props.navigationMode, direction = props.direction, scrollFitImageSize = props.scrollFitImageSize, scrollSizeScale = props.scrollSizeScale;
if (navigationMode !== "scroll") {
setRenderedScrollSizeScale(1);
return;
}
let anchor2 = scrollerApi.centerAnchor(), scaleRevision = ++scrollScaleRevision;
setRenderedScrollSizeScale(scrollSizeScale), untrack(resizePages), queueMicrotask(() => {
disposed || scaleRevision !== scrollScaleRevision || (anchor2 ? scrollerApi.restoreCenterAnchor(anchor2) : direction === "ttb" ? scrollerApi.centerHorizontal() : scrollerApi.centerVertical());
});
});
let scrollStripStyle = () => (revision(), props.navigationMode !== "scroll" ? {} : props.direction === "ttb" ? {
width: `${Math.max(viewportWidth(), ...pageSlots2.map((slot) => slot.frameWidth))}px`
} : {
height: `${Math.max(viewportHeight(), ...pageSlots2.map((slot) => slot.frameHeight))}px`,
width: "max-content"
}), stripClass = () => props.navigationMode === "paged" ? props.direction === "ttb" && props.pageLayout === "double" ? "grid grid-cols-2 auto-rows-[100%] w-full h-full gap-x-3px" : props.direction === "ttb" ? "flex flex-col w-full h-full" : `flex flex-row w-auto h-full${props.pageLayout === "double" ? " gap-3px" : ""}` : props.direction === "ttb" ? "flex flex-col min-h-full mx-auto py-56px px-0 pb-72px" : `flex flex-row min-w-full my-auto py-0 ${props.direction === "rtl" ? "pl-72px pr-56px" : "pl-56px pr-72px"}`;
return onMount(() => {
let observer = new ResizeObserver(() => {
resizeFrame === null && (resizeFrame = window.requestAnimationFrame(() => {
resizeFrame = null, untrack(resizePages);
}));
});
observer.observe(scroller), onCleanup(() => observer.disconnect());
}), onCleanup(() => {
disposed = !0, stopMotion();
for (let cached of cachedImages.values())
cached.image.removeAttribute("src");
cachedImages.clear(), cachedImageBytes = 0, resizeFrame !== null && (window.cancelAnimationFrame(resizeFrame), resizeFrame = null);
}), (() => {
var _el$ = _tmpl$41(), _el$2 = _el$.firstChild;
return _el$.addEventListener("wheel", (event) => {
let delta = Math.abs(event.deltaX) > Math.abs(event.deltaY) ? event.deltaX : event.deltaY;
props.callbacks.onWheel(delta, event);
}), _el$.addEventListener("scroll", () => props.callbacks.onNativeScroll()), use((element) => {
scroller = element, scrollerApi = createPagesScroller(element);
}, _el$), insert(_el$2, createComponent(For, {
get each() {
return slots();
},
children: (slot) => createComponent(PageSlotView, {
get doublePageSide() {
return doublePageSide(slot.pageNum, props.window.currentPageNum, props.navigationMode, props.pageLayout, props.direction);
},
get direction() {
return props.direction;
},
get navigationMode() {
return props.navigationMode;
},
get pageLayout() {
return props.pageLayout;
},
slot,
get revision() {
return revision();
},
get visualIndex() {
return visualSlotIndex(slot.index, slots().length);
},
onReloadPage: (pageNum) => props.callbacks.onReloadPage(pageNum)
})
})), createRenderEffect((_p$) => {
var _v$ = "w-full h-full overflow-auto overscroll-contain scroll-auto cursor-grab scrollbar-hidden " + (!props.zoomActive && props.navigationMode === "scroll" && props.direction === "ttb" ? "[touch-action:pan-x_pan-y] " : "touch-none ") + "[&[data-dragging=true]]:cursor-grabbing [&[data-dragging=true]]:select-none [#ehpeek-reader[data-navigation-mode=paged]_&]:overflow-hidden [#ehpeek-reader[data-navigation-mode=paged]_&]:touch-none [#ehpeek-reader[data-navigation-mode=paged]_&]:select-none", _v$2 = `ehpeek-reader-page-strip ${stripClass()}`, _v$3 = scrollStripStyle();
return _v$ !== _p$.e && className(_el$, _p$.e = _v$), _v$2 !== _p$.t && className(_el$2, _p$.t = _v$2), _p$.a = style(_el$2, _v$3, _p$.a), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$;
})();
}
function PageSlotView(props) {
let node, frame, content = createMemo(() => (props.revision, {
pageNum: props.slot.pageNum,
kind: props.slot.kind,
state: props.slot.state,
errorMessage: props.slot.errorMessage ?? void 0
})), image2 = createMemo(() => (props.revision, props.slot.state === "ready" ? props.slot.image : null)), slotStyle = createMemo(() => {
props.revision;
let frameShortSide = Math.min(props.slot.frameWidth, props.slot.frameHeight);
return {
"--reader-page-height": `${props.slot.frameHeight + PAGE_SLOT_SPACING}px`,
"--reader-page-width": `${props.slot.frameWidth + PAGE_SLOT_SPACING}px`,
"--reader-frame-width": `${props.slot.frameWidth}px`,
"--reader-frame-height": `${props.slot.frameHeight}px`,
"--reader-end-font-size": `${Math.max(10, frameShortSide * 0.11)}px`,
"--reader-end-padding": `${Math.min(24, Math.max(4, frameShortSide * 0.06))}px`,
order: String(props.visualIndex)
};
});
return onCleanup(() => {
props.slot.elements?.node === node && (props.slot.elements = null);
}), (() => {
var _el$3 = _tmpl$213(), _el$4 = _el$3.firstChild, _ref$ = node;
return typeof _ref$ == "function" ? use(_ref$, _el$3) : node = _el$3, use((element) => {
frame = element, props.slot.elements = {
node,
frame
};
}, _el$4), insert(_el$4, createComponent(Show, {
get when() {
return image2();
},
keyed: !0,
get fallback() {
return createComponent(PageSlotPlaceholder, {
get content() {
return content();
},
get text() {
return slotPlaceholderText(content());
},
get onReloadPage() {
return props.onReloadPage;
}
});
},
children: (currentImage) => currentImage
})), createRenderEffect((_p$) => {
var _v$4 = `ehpeek-page flex items-center ${props.doublePageSide === "left" ? "justify-end" : props.doublePageSide === "right" ? "justify-start" : "justify-center"} ${pageSlotClass(props.navigationMode, props.direction, props.pageLayout)}`, _v$5 = String(props.slot.pageNum), _v$6 = slotStyle();
return _v$4 !== _p$.e && className(_el$3, _p$.e = _v$4), _v$5 !== _p$.t && setAttribute(_el$3, "data-ehpeek-page-num", _p$.t = _v$5), _p$.a = style(_el$3, _v$6, _p$.a), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$3;
})();
}
function doublePageSide(pageNum, currentPageNum, navigationMode, pageLayout, direction) {
if (navigationMode !== "paged" || pageLayout !== "double")
return null;
let firstInPair = Math.abs(pageNum - currentPageNum) % 2 === 0;
return direction === "rtl" ? firstInPair ? "right" : "left" : firstInPair ? "left" : "right";
}
function pageSlotClass(navigationMode, direction, pageLayout) {
return navigationMode === "scroll" ? direction === "ttb" ? "w-full h-[var(--reader-page-height)] items-start pb-sm" : "flex-[0_0_var(--reader-page-width)] w-[var(--reader-page-width)] h-full pr-sm" : direction === "ttb" ? pageLayout === "double" ? "w-full h-full" : "flex-[0_0_100%] w-full h-full" : pageLayout === "double" ? "h-full" : "flex-[0_0_100%] w-full h-full";
}
function PageSlotPlaceholder(props) {
let stop = (event) => {
event.preventDefault(), event.stopPropagation();
};
return (() => {
var _el$5 = _tmpl$69();
return insert(_el$5, createComponent(Show, {
get when() {
return props.content.state === "error";
},
get fallback() {
return createComponent(Show, {
get when() {
return props.content.state === "loading";
},
get fallback() {
return props.text;
},
get children() {
var _el$9 = _tmpl$76(), _el$0 = _el$9.firstChild;
return insert(_el$0, () => props.text), _el$9;
}
});
},
get children() {
return [(() => {
var _el$6 = _tmpl$312();
return _el$6.$$click = (event) => {
stop(event), props.onReloadPage(props.content.pageNum);
}, _el$6.$$pointerdown = stop, insert(_el$6, createComponent(Icon, {
name: "refresh",
size: 32
})), createRenderEffect((_p$) => {
var _v$7 = `${texts_default.reader.reloadPage} ${props.content.pageNum}`, _v$8 = texts_default.reader.reloadPage;
return _v$7 !== _p$.e && setAttribute(_el$6, "aria-label", _p$.e = _v$7), _v$8 !== _p$.t && setAttribute(_el$6, "title", _p$.t = _v$8), _p$;
}, {
e: void 0,
t: void 0
}), _el$6;
})(), (() => {
var _el$7 = _tmpl$412();
return insert(_el$7, () => texts_default.reader.failedPrefix), _el$7;
})(), createComponent(Show, {
get when() {
return props.content.errorMessage;
},
get children() {
var _el$8 = _tmpl$59();
return insert(_el$8, () => props.content.errorMessage), _el$8;
}
})];
}
})), createRenderEffect((_p$) => {
var _v$9 = props.content.state === "error" ? "flex w-full h-full flex-col items-center justify-center gap-lg bg-[var(--color-reader-surface)] p-xl text-[var(--color-danger)] text-center textsize-md font-700 leading-1" : "relative flex w-full h-full items-center justify-center bg-[var(--color-reader-surface)] text-[var(--color-reader-muted)] text-center " + (props.content.kind === "end" ? "p-[var(--reader-end-padding)] [direction:ltr] [font-size:min(var(--ui-font-size-xl),var(--reader-end-font-size))] font-700 leading-[1.3] [unicode-bidi:plaintext]" : "[font-size:min(25vw,35cqi,35cqb,180px)] font-mono font-850 leading-[1] [font-variant-numeric:tabular-nums]"), _v$0 = props.content.state === "loading" ? "status" : void 0, _v$1 = props.content.state === "loading" ? `${texts_default.reader.loading} ${props.text}` : void 0;
return _v$9 !== _p$.e && className(_el$5, _p$.e = _v$9), _v$0 !== _p$.t && setAttribute(_el$5, "role", _p$.t = _v$0), _v$1 !== _p$.a && setAttribute(_el$5, "aria-label", _p$.a = _v$1), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$5;
})();
}
function pageImageDom(pageNum, slotImage) {
let image2 = document.createElement("img");
return image2.className = "block w-full h-full object-contain select-none [-webkit-user-drag:none]", image2.alt = `Page ${pageNum}`, image2.decoding = "async", image2.loading = "eager", image2.draggable = !1, image2.setAttribute("fetchpriority", slotImage.highPriority ? "high" : "low"), image2.src = slotImage.imageUrl, slotImage.width && slotImage.height && (image2.width = slotImage.width, image2.height = slotImage.height), image2;
}
async function loadImage(image2) {
if (!(image2.complete && image2.naturalWidth > 0)) {
await new Promise((resolve, reject) => {
image2.addEventListener("load", () => resolve(), {
once: !0
}), image2.addEventListener("error", () => reject(new Error(texts_default.errors.imageLoadFailed)), {
once: !0
});
});
try {
await image2.decode();
} catch {
}
}
}
function createPagesScroller(element) {
let clampedTop = (scrollTop, bounds) => bounds ? clamp(scrollTop, bounds.min ?? Number.NEGATIVE_INFINITY, bounds.max ?? Number.POSITIVE_INFINITY) : scrollTop;
return {
element,
resetPosition() {
element.scrollLeft = 0, element.scrollTop = 0;
},
scrollLeft() {
return element.scrollLeft;
},
scrollTop() {
return element.scrollTop;
},
viewportWidth() {
return element.clientWidth || window.innerWidth || 1;
},
viewportHeight() {
return element.clientHeight;
},
moveToLeft(scrollLeft) {
element.scrollLeft = scrollLeft;
},
centerHorizontal() {
element.scrollLeft = Math.max(0, (element.scrollWidth - element.clientWidth) / 2);
},
centerVertical() {
element.scrollTop = Math.max(0, (element.scrollHeight - element.clientHeight) / 2);
},
centerAnchor() {
let viewportRect = element.getBoundingClientRect(), centerX = viewportRect.left + viewportRect.width / 2, centerY = viewportRect.top + viewportRect.height / 2, pages = Array.from(element.querySelectorAll(".ehpeek-page")), closest = null;
for (let node of pages) {
let rect2 = node.getBoundingClientRect(), dx = centerX < rect2.left ? rect2.left - centerX : centerX > rect2.right ? centerX - rect2.right : 0, dy = centerY < rect2.top ? rect2.top - centerY : centerY > rect2.bottom ? centerY - rect2.bottom : 0, distance = Math.hypot(dx, dy);
(!closest || distance < closest.distance) && (closest = {
distance,
node
});
}
if (!closest)
return null;
let rect = closest.node.getBoundingClientRect(), pageNum = Number(closest.node.dataset.ehpeekPageNum || "");
return Number.isFinite(pageNum) && rect.width > 0 && rect.height > 0 ? {
pageNum,
xRatio: (centerX - rect.left) / rect.width,
yRatio: (centerY - rect.top) / rect.height
} : null;
},
restoreCenterAnchor(anchor2) {
let node = element.querySelector(`.ehpeek-page[data-ehpeek-page-num="${anchor2.pageNum}"]`);
if (!node)
return;
let viewportRect = element.getBoundingClientRect(), pageRect = node.getBoundingClientRect(), centerX = viewportRect.left + viewportRect.width / 2, centerY = viewportRect.top + viewportRect.height / 2;
element.scrollLeft += pageRect.left + pageRect.width * anchor2.xRatio - centerX, element.scrollTop += pageRect.top + pageRect.height * anchor2.yRatio - centerY;
},
moveToTop(scrollTop, bounds) {
element.scrollTop = clampedTop(scrollTop, bounds);
},
slotTop(elements) {
let elementsRect = elements.node.getBoundingClientRect(), scrollerRect = element.getBoundingClientRect();
return element.scrollTop + elementsRect.top - scrollerRect.top;
},
slotLeft(elements) {
let elementsRect = elements.node.getBoundingClientRect(), scrollerRect = element.getBoundingClientRect();
return element.scrollLeft + elementsRect.left - scrollerRect.left;
},
slotOffset(elements, navigationMode, direction, pageLayout) {
let pageRect = elements.node.getBoundingClientRect(), scrollerRect = element.getBoundingClientRect();
return direction === "ttb" ? pageRect.top - scrollerRect.top : direction === "rtl" && (navigationMode === "scroll" || pageLayout === "double") ? pageRect.right - scrollerRect.right : pageRect.left - scrollerRect.left;
},
slotContainsViewportTarget(elements, direction) {
let scrollerRect = element.getBoundingClientRect(), rect = elements.node.getBoundingClientRect();
if (direction === "ttb") {
let target2 = scrollerRect.top + Math.min(80, scrollerRect.height * 0.14);
return rect.top <= target2 && rect.bottom > target2;
}
let offset = Math.min(80, scrollerRect.width * 0.14), target = direction === "rtl" ? scrollerRect.right - offset : scrollerRect.left + offset;
return rect.left <= target && rect.right > target;
}
};
}
function slotPlaceholderText(content) {
return content.state === "error" ? texts_default.reader.failedPrefix : content.kind === "end" ? texts_default.reader.end : content.kind === "blank" ? "" : String(content.pageNum);
}
function pageSlotKind(pageNum, totalPages) {
return pageNum < 1 ? "blank" : totalPages && pageNum === totalPages + 1 ? "end" : totalPages && pageNum > totalPages + 1 ? "blank" : "page";
}
function pageSlot(pageNum, kind) {
return {
pageNum,
index: 0,
kind,
state: kind === "page" ? "idle" : "ready",
aspectRatio: FALLBACK_ASPECT_RATIO,
width: null,
height: null,
image: null,
errorMessage: null,
frameWidth: 1,
frameHeight: Math.ceil(FALLBACK_ASPECT_RATIO),
elements: null,
token: 0
};
}
function applyPageMetaToSlot(slot, page2) {
let aspectRatio = normalizedAspectRatio(page2.aspectRatio, FALLBACK_ASPECT_RATIO);
slot.aspectRatio === aspectRatio && slot.state !== "error" || (slot.aspectRatio = aspectRatio, slot.kind = "page", slot.state = "idle", slot.image = null, slot.errorMessage = null, slot.width = null, slot.height = null, slot.token += 1);
}
function clearNonPageSlotMeta(slot) {
slot.kind !== "blank" && slot.kind !== "end" || (slot.state = "ready", slot.image = null, slot.errorMessage = null, slot.width = null, slot.height = null, slot.token += 1);
}
function pageSlotAspectRatio(slot) {
return slot.width && slot.height && slot.width > 0 && slot.height > 0 ? slot.height / slot.width : normalizedAspectRatio(slot.aspectRatio, FALLBACK_ASPECT_RATIO);
}
function containFitScale(imageWidth, imageHeight, viewportWidth, viewportHeight) {
return Math.min(Math.max(1, viewportWidth) / Math.max(1, imageWidth), Math.max(1, viewportHeight) / Math.max(1, imageHeight));
}
function containFitFrame(aspectRatio, viewportWidth, viewportHeight, scale = 1) {
let width = Math.max(1, Math.min(Math.max(1, viewportWidth), Math.max(1, viewportHeight) / aspectRatio) * scale);
return {
height: width * aspectRatio,
width
};
}
delegateEvents(["pointerdown", "click"]);
// src/components/Reader/ZoomOverlay.tsx
var _tmpl$50 = /* @__PURE__ */ template('<div class="fixed inset-0 z-4 flex items-center justify-center overflow-hidden ehp-color-reader pointer-events-none"><img class="block max-w-screen max-h-screen object-contain origin-center select-none will-change-transform [-webkit-user-drag:none]">'), MIN_SCALE = 1, MAX_SCALE = 5, CLOSE_SCALE = 1.02;
function ZoomOverlay(props) {
let [transform, setTransform] = createSignal("translate3d(0px, 0px, 0) scale(1)"), element, scale = 1, requestedScale = 1, closeScale = CLOSE_SCALE, minScale = MIN_SCALE, maxScale = MAX_SCALE, offsetX = 0, offsetY = 0, pinchStartScale = 1, pinchStartOffsetX = 0, pinchStartOffsetY = 0, pinchStartCenterX = 0, pinchStartCenterY = 0, dragStartOffsetX = 0, dragStartOffsetY = 0, renderTransform = () => {
setTransform(`translate3d(${offsetX}px, ${offsetY}px, 0) scale(${scale})`);
}, startPinch = (pinch) => {
pinchStartScale = scale, pinchStartOffsetX = offsetX, pinchStartOffsetY = offsetY, pinchStartCenterX = pinch.centerX, pinchStartCenterY = pinch.centerY;
}, actions = {
reset(reset2) {
scale = Math.max(0.01, reset2.scale), requestedScale = scale, closeScale = scale * CLOSE_SCALE, minScale = Math.min(MIN_SCALE, scale), maxScale = Math.max(MAX_SCALE, scale * MAX_SCALE), offsetX = 0, offsetY = 0, startPinch(reset2), renderTransform();
},
startPinch,
movePinch(pinch) {
if (!props.image)
return;
requestedScale = pinchStartScale * pinch.scale, scale = clamp(requestedScale, minScale, maxScale);
let rect = element.getBoundingClientRect(), viewportCenterX = rect.left + rect.width / 2, viewportCenterY = rect.top + rect.height / 2, ratio = scale / pinchStartScale;
offsetX = pinch.centerX - viewportCenterX - (pinchStartCenterX - viewportCenterX - pinchStartOffsetX) * ratio, offsetY = pinch.centerY - viewportCenterY - (pinchStartCenterY - viewportCenterY - pinchStartOffsetY) * ratio, renderTransform();
},
moveWheel(wheel) {
if (!props.image)
return;
let nextScale = clamp(scale * Math.exp(-clamp(wheel.delta, -100, 100) * 25e-4), minScale, maxScale);
if (nextScale === scale)
return;
let rect = element.getBoundingClientRect(), viewportCenterX = rect.left + rect.width / 2, viewportCenterY = rect.top + rect.height / 2, ratio = nextScale / scale;
offsetX = wheel.centerX - viewportCenterX - (wheel.centerX - viewportCenterX - offsetX) * ratio, offsetY = wheel.centerY - viewportCenterY - (wheel.centerY - viewportCenterY - offsetY) * ratio, scale = nextScale, requestedScale = nextScale, renderTransform();
},
endPinch() {
if (requestedScale <= closeScale) {
props.onClose();
return;
}
renderTransform();
},
startDrag() {
dragStartOffsetX = offsetX, dragStartOffsetY = offsetY;
},
moveDrag(move) {
props.image && (offsetX = dragStartOffsetX + move.dx, offsetY = dragStartOffsetY + move.dy, renderTransform());
}
};
return untrack(() => props.actionsRef(actions)), (() => {
var _el$ = _tmpl$50(), _el$2 = _el$.firstChild, _ref$ = element;
return typeof _ref$ == "function" ? use(_ref$, _el$) : element = _el$, createRenderEffect((_p$) => {
var _v$ = !props.image, _v$2 = props.image ? "" : "none", _v$3 = props.image?.imageUrl, _v$4 = props.image ? `Page ${props.image.pageNum}` : "", _v$5 = props.image?.width ?? void 0, _v$6 = props.image?.height ?? void 0, _v$7 = transform();
return _v$ !== _p$.e && (_el$.hidden = _p$.e = _v$), _v$2 !== _p$.t && setStyleProperty(_el$, "display", _p$.t = _v$2), _v$3 !== _p$.a && setAttribute(_el$2, "src", _p$.a = _v$3), _v$4 !== _p$.o && setAttribute(_el$2, "alt", _p$.o = _v$4), _v$5 !== _p$.i && setAttribute(_el$2, "width", _p$.i = _v$5), _v$6 !== _p$.n && setAttribute(_el$2, "height", _p$.n = _v$6), _v$7 !== _p$.s && setStyleProperty(_el$2, "transform", _p$.s = _v$7), _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0
}), _el$;
})();
}
// src/components/Reader/session.ts
var DEFAULT_WINDOW_SIZE = 10, ReaderSession = class {
constructor(options) {
this.animationFrames = /* @__PURE__ */ new Set();
this.timers = /* @__PURE__ */ new Set();
this.disposed = !1;
this.imageQueue = new PriorityLoadQueue(
options.concurrentLoads
);
let navigationMode = state.reader.navigationMode.value, [controls, setControls] = createSignal({
navigationMode,
direction: navigationMode === "scroll" ? state.reader.scrollDirection.value : state.reader.pagedDirection.value,
pageLayout: state.reader.pageLayout.value,
rightTapAction: state.reader.rightTapAction.value
}), [toolbarOpen, setToolbarOpen] = createSignal(!1), [viewportWindow, setViewportWindow] = createSignal(initialViewportWindow(options)), [zoomImage, setZoomImage] = createSignal(null), [currentPageNum, setCurrentPageNum] = createSignal(initialPageNumber(options)), [direction, setDirection] = createSignal(1), [downloadInfos, setDownloadInfos] = createSignal([]), [maxProgressPageNum, setMaxProgressPageNum] = createSignal(initialMaxProgressPageNumber(options)), [progressInputActive, setProgressInputActive] = createSignal(!1), [scrollBarVisible, setScrollBarVisible] = createSignal(!1), [scrollBarExpanded, setScrollBarExpanded] = createSignal(!1), [scrollViewportAdjusting, setScrollViewportAdjusting] = createSignal(!1), [scrollViewportTtbScale, setScrollViewportTtbScale] = createSignal(
state.reader.scrollTtbScale.value
), [scrollViewportHorizontalScale, setScrollViewportHorizontalScale] = createSignal(
state.reader.scrollHorizontalScale.value
), [scrollFitImageSize, setScrollFitImageSize] = createSignal(null), [readerViewportWidth, setReaderViewportWidth] = createSignal(Math.max(1, window.innerWidth)), [readerViewportHeight, setReaderViewportHeight] = createSignal(Math.max(1, window.innerHeight)), scrollViewportSizeScale = () => controls().direction === "ttb" ? scrollViewportTtbScale() : scrollViewportHorizontalScale(), setScrollViewportSizeScale = (scale) => {
controls().direction === "ttb" ? setScrollViewportTtbScale(scale) : setScrollViewportHorizontalScale(scale);
}, scrollFitScale = () => {
let imageSize = scrollFitImageSize();
return imageSize ? containFitScale(
imageSize.width,
imageSize.height,
readerViewportWidth(),
readerViewportHeight()
) : null;
};
this.state = {
navi: {
currentPageNum,
direction,
setCurrentPageNum,
setDirection,
setViewportWindow,
viewportWindow,
leftDragDelta: () => controls().direction === "rtl" ? -1 : 1,
leftTapDelta: () => controls().rightTapAction === "previous" ? 1 : -1,
rightDragDelta: () => controls().direction === "rtl" ? 1 : -1,
rightTapDelta: () => controls().rightTapAction === "previous" ? -1 : 1,
downloadInfos,
maxProgressPageNum,
progressInputActive,
setDownloadInfos,
setMaxProgressPageNum,
setProgressInputActive
},
ctrls: { update: setControls, value: controls },
toolbar: {
open: toolbarOpen,
toggle: () => setToolbarOpen((open) => !open)
},
scrollBar: {
expanded: scrollBarExpanded,
updateExpanded: setScrollBarExpanded,
updateVisible: setScrollBarVisible,
visible: scrollBarVisible
},
scrollViewport: {
adjusting: scrollViewportAdjusting,
scaleMode: () => scrollViewportSizeScale() === null ? "fit" : scrollViewportSizeScale() === "one-to-one" ? "one-to-one" : "custom",
scalePercent: () => {
let sizeScale = scrollViewportSizeScale();
if (sizeScale === "one-to-one")
return 100;
let fitScale = scrollFitScale();
return fitScale ? (sizeScale ?? 1) * fitScale * 100 : null;
},
fitImageSize: scrollFitImageSize,
fitScale: scrollFitScale,
setAdjusting: setScrollViewportAdjusting,
setFitImageSize: setScrollFitImageSize,
setViewportWidth: setReaderViewportWidth,
setSizeScale: setScrollViewportSizeScale,
setViewportHeight: setReaderViewportHeight,
viewportWidth: readerViewportWidth,
viewportHeight: readerViewportHeight,
sizeScale: scrollViewportSizeScale
},
overlay: { image: zoomImage, update: setZoomImage }
};
}
setTimeout(callback, delay) {
let timer = window.setTimeout(() => {
this.timers.delete(timer), callback();
}, delay);
return this.timers.add(timer), timer;
}
clearTimeout(timer) {
timer !== null && (window.clearTimeout(timer), this.timers.delete(timer));
}
requestAnimationFrame(callback) {
let frame = window.requestAnimationFrame((time) => {
this.animationFrames.delete(frame), callback(time);
});
return this.animationFrames.add(frame), frame;
}
cancelAnimationFrame(frame) {
frame !== null && (window.cancelAnimationFrame(frame), this.animationFrames.delete(frame));
}
dispose() {
if (!this.disposed) {
this.disposed = !0;
for (let timer of this.timers)
window.clearTimeout(timer);
for (let frame of this.animationFrames)
window.cancelAnimationFrame(frame);
this.imageQueue.dispose(), this.timers.clear(), this.animationFrames.clear();
}
}
};
function initialViewportWindow(options) {
return {
currentPageNum: initialPageNumber(options),
windowSize: options.renderWindowSize ?? DEFAULT_WINDOW_SIZE,
totalPages: options.totalPages && options.totalPages > 0 ? options.totalPages : void 0,
pages: /* @__PURE__ */ new Map()
};
}
function initialPageNumber(options) {
let totalPages = options.totalPages && options.totalPages > 0 ? options.totalPages : Number.MAX_SAFE_INTEGER;
return clamp(Math.round(options.initialPageNum), 1, totalPages);
}
function initialMaxProgressPageNumber(options) {
return options.totalPages && options.totalPages > 0 ? options.totalPages + 1 : 1;
}
// src/components/Reader/ScrollBar.tsx
function ReaderScrollBar(props) {
return createComponent(VerticalPositionBar, {
ariaLabel: "Reader position",
class: "ehpeek-reader-scrollbar",
get currentValue() {
return props.currentPage;
},
get expanded() {
return props.expanded;
},
get maxValue() {
return props.totalPages;
},
get onCommit() {
return props.callbacks.onProgressCommit;
},
get onInput() {
return props.callbacks.onProgressInput;
},
get onPointerDown() {
return props.callbacks.onProgressPointerDown;
},
position: "fixed",
variant: "reader",
get visible() {
return props.visible;
},
visibleValueCount: 1
});
}
// src/components/Reader/ViewportCanvas.tsx
var _tmpl$51 = /* @__PURE__ */ template('<div class="absolute inset-0 z-2 touch-none select-none">'), _tmpl$214 = /* @__PURE__ */ template("<span>"), _tmpl$313 = /* @__PURE__ */ template('<div class="ehpeek-reader-viewport-toolbar fixed bottom-[calc(12px+env(safe-area-inset-bottom,0px))] left-1/2 z-3 flex w-[min(680px,calc(100vw-24px))] -translate-x-1/2 flex-col items-center gap-sm rounded-lg border border-[var(--color-reader-border)] bg-[var(--color-control)] p-sm shadow-xl landscape:w-[min(600px,calc(100vw-24px))]"role=toolbar><div class="grid w-full grid-cols-[48px_minmax(64px,1fr)_64px_64px] items-center justify-center gap-sm coarse:grid-cols-[48px_minmax(40px,1fr)_80px_80px]"><span class="flex w-full flex-col items-center justify-center text-center font-mono textsize-sm font-600 leading-[1.05]"><span></span></span><input type=range class="w-full min-w-0 accent-[var(--color-reader-accent)]"min=10 max=500 step=1><button type=button></button><button type=button>1:1</button></div><div class="grid w-fit max-w-full grid-cols-[auto_96px_96px] items-stretch justify-center gap-sm coarse:w-full coarse:grid-cols-[minmax(0,1.6fr)_minmax(0,1fr)_minmax(0,1fr)]"><button type=button></button><button type=button></button><button type=button>'), _tmpl$413 = /* @__PURE__ */ template('<div class="fixed inset-0 z-1">'), MIN_SCALE_PERCENT = 10, MAX_SCALE_PERCENT = 500;
function ViewportCanvas(props) {
let pointers = /* @__PURE__ */ new Map(), pinchStart = null, interactionLayer, sliderPercent = () => Math.min(MAX_SCALE_PERCENT, Math.max(MIN_SCALE_PERCENT, props.scalePercent ?? 100)), clampScale = (scale) => Math.min(MAX_SCALE_PERCENT / 100, Math.max(MIN_SCALE_PERCENT / 100, scale)), stopInteraction = (event) => {
event.preventDefault(), event.stopPropagation();
}, endPointer = (event) => {
pointers.delete(event.pointerId), pointers.size < 2 && (pinchStart = null);
};
return createEffect(() => {
props.adjusting || (pointers.clear(), pinchStart = null);
}), (() => {
var _el$ = _tmpl$413();
return insert(_el$, () => props.children, null), insert(_el$, createComponent(Show, {
get when() {
return props.adjusting;
},
get children() {
return [(() => {
var _el$2 = _tmpl$51();
_el$2.addEventListener("pointercancel", endPointer), _el$2.$$pointerup = endPointer, _el$2.$$pointermove = (event) => {
if (!pointers.has(event.pointerId) || (pointers.set(event.pointerId, {
x: event.clientX,
y: event.clientY
}), !pinchStart || pointers.size < 2))
return;
let [first, second] = Array.from(pointers.values());
if (!first || !second)
return;
let distance = Math.hypot(second.x - first.x, second.y - first.y);
props.callbacks.onScaleChange(clampScale(pinchStart.scale * distance / pinchStart.distance));
}, _el$2.$$pointerdown = (event) => {
if (stopInteraction(event), pointers.set(event.pointerId, {
x: event.clientX,
y: event.clientY
}), interactionLayer.setPointerCapture(event.pointerId), pointers.size !== 2)
return;
let [first, second] = Array.from(pointers.values());
!first || !second || (pinchStart = {
distance: Math.max(1, Math.hypot(second.x - first.x, second.y - first.y)),
scale: (props.scalePercent ?? 100) / 100
});
}, _el$2.addEventListener("wheel", (event) => {
stopInteraction(event);
let deltaPixels = event.deltaY * (event.deltaMode === WheelEvent.DOM_DELTA_LINE ? 16 : event.deltaMode === WheelEvent.DOM_DELTA_PAGE ? interactionLayer.clientHeight : 1);
props.callbacks.onScaleChange(clampScale((props.scalePercent ?? 100) / 100 * Math.exp(-deltaPixels * 15e-4)));
}), _el$2.$$click = stopInteraction;
var _ref$ = interactionLayer;
return typeof _ref$ == "function" ? use(_ref$, _el$2) : interactionLayer = _el$2, _el$2;
})(), (() => {
var _el$3 = _tmpl$313(), _el$4 = _el$3.firstChild, _el$5 = _el$4.firstChild, _el$7 = _el$5.firstChild, _el$8 = _el$5.nextSibling, _el$9 = _el$8.nextSibling, _el$0 = _el$9.nextSibling, _el$1 = _el$4.nextSibling, _el$10 = _el$1.firstChild, _el$11 = _el$10.nextSibling, _el$12 = _el$11.nextSibling;
return insert(_el$5, createComponent(Show, {
get when() {
return props.scaleMode !== "custom";
},
get children() {
var _el$6 = _tmpl$214();
return insert(_el$6, (() => {
var _c$ = memo(() => props.scaleMode === "fit");
return () => _c$() ? texts_default.reader.fit : "1:1";
})()), _el$6;
}
}), _el$7), insert(_el$7, (() => {
var _c$2 = memo(() => props.scalePercent === null);
return () => _c$2() ? "—" : `${Math.round(props.scalePercent)}%`;
})()), _el$8.$$input = (event) => props.callbacks.onScaleChange(event.currentTarget.valueAsNumber / 100), _el$9.$$click = () => props.callbacks.onFit(), className(_el$9, `${READER_BUTTON_CLASS} w-full`), insert(_el$9, () => texts_default.reader.fit), _el$0.$$click = () => props.callbacks.onOneToOne(), className(_el$0, `${READER_BUTTON_CLASS} w-full`), _el$10.$$click = () => props.callbacks.onApplyAll(), className(_el$10, `${READER_BUTTON_CLASS} w-full whitespace-normal leading-[1.1]`), insert(_el$10, () => texts_default.reader.applyGlobally), _el$11.$$click = () => props.callbacks.onApply(), className(_el$11, `${READER_BUTTON_CLASS} w-full`), insert(_el$11, () => texts_default.button.apply), _el$12.$$click = () => props.callbacks.onClose(), className(_el$12, `${READER_BUTTON_CLASS} w-full`), insert(_el$12, () => texts_default.button.close), createRenderEffect((_p$) => {
var _v$ = texts_default.reader.adjustScrollViewport, _v$2 = texts_default.reader.resizeScrollViewport;
return _v$ !== _p$.e && setAttribute(_el$3, "aria-label", _p$.e = _v$), _v$2 !== _p$.t && setAttribute(_el$8, "aria-label", _p$.t = _v$2), _p$;
}, {
e: void 0,
t: void 0
}), createRenderEffect(() => _el$8.value = sliderPercent()), _el$3;
})()];
}
}), null), _el$;
})();
}
delegateEvents(["click", "pointerdown", "pointermove", "pointerup", "input"]);
// src/components/Reader/index.css
var Reader_default = `#ehpeek-reader,
[data-ehpeek-reader-container="true"]:fullscreen {
--color-reader-background: #070707;
--color-reader-surface: #151515;
--color-reader-elevated: #232323;
--color-reader-text: #f3f3f3;
--color-reader-accent: #4da3ff;
--color-reader-border: color-mix(in srgb, var(--color-reader-text) 18%, transparent);
--color-reader-muted: color-mix(in srgb, var(--color-reader-text) 72%, transparent);
--color-reader-scrollbar: color-mix(in srgb, var(--color-reader-text) 56%, transparent);
--color-background: var(--color-reader-background);
--color-surface: var(--color-reader-surface);
--color-elevated: var(--color-reader-elevated);
--color-text: var(--color-reader-text);
--color-accent: var(--color-reader-accent);
--color-border: color-mix(in srgb, var(--color-text) 18%, transparent);
--color-muted: color-mix(in srgb, var(--color-text) 72%, transparent);
--color-track: color-mix(in srgb, var(--color-text) 34%, var(--color-background));
--color-control: color-mix(in srgb, var(--color-elevated) 88%, transparent);
--color-badge: color-mix(in srgb, var(--color-background) 34%, transparent);
}
#ehpeek-reader,
#ehpeek-reader * {
box-sizing: border-box;
}
[data-ehpeek-reader-container="true"]:fullscreen {
width: 100%;
height: 100%;
overflow: hidden;
background: var(--color-reader-background);
}
[data-ehpeek-reader-container="true"]:fullscreen .ehpeek-reader-toolbar-buttons {
transform: scale(var(--ehpeek-reader-fullscreen-ui-scale, 1));
transform-origin: top right;
}
[data-ehpeek-reader-container="true"]:fullscreen .ehpeek-reader-floating-actions {
transform: scale(var(--ehpeek-reader-fullscreen-ui-scale, 1));
transform-origin: bottom right;
}
[data-ehpeek-reader-container="true"]:fullscreen .ehpeek-scroll-preview {
width: calc(100% * var(--ehpeek-reader-fullscreen-ui-scale-inverse, 1));
height: calc(100dvh * var(--ehpeek-reader-fullscreen-ui-scale-inverse, 1));
transform: scale(var(--ehpeek-reader-fullscreen-ui-scale, 1));
transform-origin: top left;
}
[data-ehpeek-reader-container="true"]:fullscreen .ehpeek-reader-download-dialog-panel,
[data-ehpeek-reader-container="true"]:fullscreen .ehpeek-reader-help-panel,
[data-ehpeek-reader-container="true"]:fullscreen .ehpeek-reader-page-reload {
transform: scale(var(--ehpeek-reader-fullscreen-ui-scale, 1));
transform-origin: center;
}
[data-ehpeek-reader-container="true"]:fullscreen .ehpeek-reader-control-change {
transform: translate(-50%, -50%) scale(var(--ehpeek-reader-fullscreen-ui-scale, 1));
transform-origin: center;
}
[data-ehpeek-reader-container="true"]:fullscreen .ehpeek-reader-viewport-toolbar {
transform: translateX(-50%) scale(var(--ehpeek-reader-fullscreen-ui-scale, 1));
transform-origin: bottom center;
}
[data-ehpeek-reader-container="true"]:fullscreen .ehpeek-reader-scrollbar {
transform: scaleX(var(--ehpeek-reader-fullscreen-ui-scale, 1));
transform-origin: right center;
}
[data-ehpeek-reader-container="true"]:fullscreen .ehpeek-reader-scrollbar {
--ehpeek-position-bar-thumb-min:
calc(var(--ui-control-size-md) * 1.5 * var(--ehpeek-reader-fullscreen-ui-scale, 1));
--ehpeek-position-bar-thumb-max:
calc(var(--ui-control-size-xl) * 4 * var(--ehpeek-reader-fullscreen-ui-scale, 1));
}
[data-ehpeek-reader-container="true"]:fullscreen .ehpeek-reader-progress {
font-size: var(--ehpeek-reader-fullscreen-progress-size, var(--ui-font-size-lg));
}
[data-ehpeek-reader-container="true"]:fullscreen .ehpeek-reader-fullscreen-status {
top: 0;
right: max(10px, env(safe-area-inset-right, 0px));
left: auto;
transform: scale(var(--ehpeek-reader-fullscreen-ui-scale, 1)) translateY(50%);
transform-origin: top right;
}
[data-ehpeek-reader-container="true"]:fullscreen .ehpeek-reader-page-number {
top: 0;
right: auto;
left: max(10px, env(safe-area-inset-left, 0px));
min-width: 0;
transform: scale(var(--ehpeek-reader-fullscreen-ui-scale, 1)) translateY(50%);
transform-origin: top left;
text-align: left;
}
[data-ehpeek-reader-container="true"]:fullscreen .ehpeek-reader-toolbar {
top: calc(max(var(--ui-control-size-xs), env(safe-area-inset-top, 0px)) + 8px);
}
[data-ehpeek-reader-container="true"]:fullscreen
#ehpeek-reader[data-navigation-mode="scroll"][data-read-direction="ttb"]
.ehpeek-reader-page-strip {
padding-top: 0;
}
#ehpeek-reader[data-navigation-mode="paged"][data-page-layout="double"]:not([data-read-direction="ttb"])
.ehpeek-page {
width: calc(50% - 1.5px);
flex: 0 0 calc(50% - 1.5px);
}
`;
// src/components/Reader/index.tsx
var _tmpl$60 = /* @__PURE__ */ template("<header class=contents>"), _tmpl$215 = /* @__PURE__ */ template('<div id=ehpeek-reader class="fixed inset-0 z-reader overflow-hidden ehp-color-reader font-sans textsize-sm leading-[1.4]">');
registerGlobalStyle("ehpeek-reader-style", Reader_default);
var DEFAULT_WINDOW_SIZE2 = 10, PAGED_SWIPE_THRESHOLD = 24, PAGED_WHEEL_THRESHOLD = 8, HORIZONTAL_SCROLL_WHEEL_FACTOR = 0.5, PROGRESS_IDLE_COMMIT_MS = 180, LOADED_IMAGE_INFO_CACHE_LIMIT = 160, SCROLL_GESTURE_IDLE_MS = 160, SCROLL_BAR_IDLE_MS = 900, SCROLL_BAR_SHOW_DISTANCE = 48, SCROLL_BAR_EXPAND_VIEWPORTS = 2, DOUBLE_TAP_MS = 340, DOUBLE_TAP_DISTANCE = 36, TAP_CANCEL_DISTANCE = 8, FALLBACK_ASPECT_RATIO2 = 1.42;
function Reader(props) {
let options = untrack(() => props.options), totalPages = options.totalPages ?? 0, previewCache = untrack(() => props.previewCache), callbacks = untrack(() => props.callbacks), session = new ReaderSession(options), readerState = session.state, scrollFitPageNum = readerState.navi.currentPageNum(), readerCallbacks2 = wireReaderCallbacks(session, options, previewCache, callbacks);
untrack(() => props.actionsRef({
gotoPage: readerCallbacks2.gotoPage
}));
let previousFullscreenActive = untrack(() => props.fullscreenActive);
return createEffect(() => {
let fullscreenActive = props.fullscreenActive;
fullscreenActive !== previousFullscreenActive && (previousFullscreenActive = fullscreenActive, session.requestAnimationFrame(() => {
session.requestAnimationFrame(readerCallbacks2.realignCurrentPage);
}));
}), onMount(() => {
readerCallbacks2.init(), onCleanup(() => {
readerCallbacks2.cleanup(), session.dispose();
});
}), (() => {
var _el$ = _tmpl$215();
return insert(_el$, createComponent(Show, {
get when() {
return !readerState.scrollViewport.adjusting();
},
get children() {
var _el$2 = _tmpl$60();
return insert(_el$2, createComponent(Toolbar, {
get callbacks() {
return readerCallbacks2.toolbar;
},
get controls() {
return readerState.ctrls.value();
},
get downloadInfos() {
return readerState.navi.downloadInfos();
},
get fullscreenActive() {
return props.fullscreenActive;
},
get open() {
return readerState.toolbar.open();
},
get progress() {
return {
pageNum: readerState.navi.currentPageNum(),
totalPages: options.totalPages,
maxProgressPageNum: readerState.navi.maxProgressPageNum(),
keepInputValue: readerState.navi.progressInputActive()
};
}
})), _el$2;
}
}), null), insert(_el$, createComponent(ViewportCanvas, {
get adjusting() {
return readerState.scrollViewport.adjusting();
},
get callbacks() {
return readerCallbacks2.viewportCanvas;
},
get scaleMode() {
return readerState.scrollViewport.scaleMode();
},
get scalePercent() {
return readerState.scrollViewport.scalePercent();
},
get children() {
return createComponent(PagesViewport, {
get actionsRef() {
return readerCallbacks2.viewportActionsRef;
},
get callbacks() {
return readerCallbacks2.viewport;
},
get decodedImageCacheLimit() {
return options.decodedImageCacheLimit;
},
get direction() {
return readerState.ctrls.value().direction;
},
get navigationMode() {
return readerState.ctrls.value().navigationMode;
},
get pageLayout() {
return readerState.ctrls.value().pageLayout;
},
get scrollFitImageSize() {
return readerState.scrollViewport.fitImageSize();
},
scrollFitPageNum,
get scrollSizeScale() {
return readerState.scrollViewport.sizeScale();
},
get window() {
return readerState.navi.viewportWindow();
},
get zoomActive() {
return readerState.overlay.image() !== null;
}
});
}
}), null), insert(_el$, createComponent(Show, {
get when() {
return readerState.ctrls.value().navigationMode === "scroll" && readerState.ctrls.value().direction === "ttb" && totalPages > 1;
},
get children() {
return createComponent(ReaderScrollBar, {
get callbacks() {
return readerCallbacks2.toolbar;
},
get currentPage() {
return readerState.navi.currentPageNum();
},
get expanded() {
return readerState.scrollBar.expanded();
},
totalPages,
get visible() {
return readerState.scrollBar.visible();
}
});
}
}), null), insert(_el$, createComponent(ZoomOverlay, {
get actionsRef() {
return readerCallbacks2.zoomOverlayActionsRef;
},
get image() {
return readerState.overlay.image();
},
onClose: () => readerState.overlay.update(null)
}), null), createRenderEffect((_p$) => {
var _v$ = readerState.ctrls.value().navigationMode, _v$2 = readerState.ctrls.value().pageLayout, _v$3 = readerState.ctrls.value().direction;
return _v$ !== _p$.e && setAttribute(_el$, "data-navigation-mode", _p$.e = _v$), _v$2 !== _p$.t && setAttribute(_el$, "data-page-layout", _p$.t = _v$2), _v$3 !== _p$.a && setAttribute(_el$, "data-read-direction", _p$.a = _v$3), _p$;
}, {
e: void 0,
t: void 0,
a: void 0
}), _el$;
})();
}
function wireReaderCallbacks(session, options, previewCache, callbacks) {
let state2 = session.state, viewportActions, zoomOverlay, totalPages = options.totalPages && options.totalPages > 0 ? options.totalPages : void 0, renderWindowSize = options.renderWindowSize ?? DEFAULT_WINDOW_SIZE2, preloadWindowSize = options.preloadWindowSize ?? DEFAULT_WINDOW_SIZE2, pages = /* @__PURE__ */ new Map(), loadedImages = /* @__PURE__ */ new Map(), pagedTargetPageNumber = null, syncToken = 0, closed = !1, scrollFitPageNum = state2.navi.currentPageNum(), pagedMode = () => state2.ctrls.value().navigationMode === "paged", pageTurnStep = () => state2.ctrls.value().pageLayout === "double" ? 2 : 1, updateReaderViewportSize = () => {
state2.scrollViewport.setViewportWidth(Math.max(1, window.innerWidth)), state2.scrollViewport.setViewportHeight(Math.max(1, window.innerHeight));
};
function requestReaderClose() {
closed || (closed = !0, callbacks.onClosed());
}
function setCurrentPageNumber(pageNumber, scrollIntoView, scrollMotion = "instant") {
pagedTargetPageNumber = null;
let target = clamp(Math.round(pageNumber), 1, maxProgressPageNum());
target !== state2.navi.currentPageNum() && (state2.navi.setDirection(target > state2.navi.currentPageNum() ? 1 : -1), state2.navi.setCurrentPageNum(target)), syncAfterPageChange({
scrollIntoView,
scrollMotion
});
}
function syncAfterPageChange(options2) {
let token = ++syncToken, missing = pageWindowNumbers(state2.navi.currentPageNum(), renderWindowSize).filter((number) => isRealPageNum(number) && !pages.has(number));
syncViewportWindow(), maintainLoadQueue(), notifyActivePageChange(), options2.scrollIntoView && scrollToCurrentPage(options2.scrollMotion), missing.length > 0 && loadMissingPages(missing, token);
}
async function loadMissingPages(pageNums, token) {
let pageGroups = /* @__PURE__ */ new Map();
for (let pageNum of pageNums) {
let previewIndex = previewCache.previewIndexForPage(pageNum);
pageGroups.set(previewIndex, [...pageGroups.get(previewIndex) ?? [], pageNum]);
}
await Promise.all(Array.from(pageGroups.values(), async (groupPageNums) => {
let loadingTokens = new Map(groupPageNums.flatMap((pageNum) => {
let loadingToken = viewportActions.markPageLoading(pageNum);
return loadingToken === null ? [] : [[pageNum, loadingToken]];
})), incoming;
try {
incoming = await previewCache.getPages(groupPageNums);
} catch (error) {
console.error("[ehpeek]", error);
let message = error instanceof Error ? error.message : texts_default.errors.loadFailed;
for (let [pageNum, loadingToken] of loadingTokens)
viewportActions.setPageError(pageNum, loadingToken, message);
return;
}
if (closed)
return;
addPages(incoming);
let loadedPageNums = new Set(incoming.flatMap((page2) => page2.pageNum && page2.pageNum > 0 ? [page2.pageNum] : []));
for (let [pageNum, loadingToken] of loadingTokens)
loadedPageNums.has(pageNum) ? viewportActions.resetPageLoading(pageNum, loadingToken) : viewportActions.setPageError(pageNum, loadingToken, texts_default.errors.imageNotFound);
})), !(closed || token !== syncToken) && (syncViewportWindow(), maintainLoadQueue(), notifyActivePageChange(), state2.ctrls.value().navigationMode === "scroll" && state2.navi.currentPageNum() === scrollFitPageNum && scrollToCurrentPage());
}
function addPages(incomingPages) {
for (let [index, page2] of incomingPages.entries()) {
let pageNum = pageNumForPage(page2, index);
pageNum > 0 && pages.set(pageNum, {
...page2,
aspectRatio: normalizedAspectRatio(page2.aspectRatio, FALLBACK_ASPECT_RATIO2),
pageNum
});
}
}
function syncViewportWindow() {
state2.navi.setViewportWindow({
currentPageNum: state2.navi.currentPageNum(),
windowSize: renderWindowSize,
totalPages,
pages: pageMetaForViewport()
}), updatePageNumber();
}
function maintainLoadQueue() {
let currentPageNum = state2.navi.currentPageNum(), pageNums = [currentPageNum];
state2.ctrls.value().navigationMode === "scroll" && !state2.scrollViewport.fitImageSize() && pageNums.push(scrollFitPageNum), state2.ctrls.value().navigationMode === "paged" && state2.ctrls.value().pageLayout === "double" && pageNums.push(currentPageNum + 1);
for (let offset = 1; offset <= preloadWindowSize; offset += 1)
pageNums.push(currentPageNum + offset * state2.navi.direction());
session.imageQueue.sync(Array.from(new Set(pageNums)).flatMap((pageNum, priority) => {
let target = loadTargetFor(pageNum);
return target ? [{
key: pageNum,
priority,
target
}] : [];
}));
}
function pageMetaForViewport() {
return new Map(Array.from(pages, ([pageNum, page2]) => [pageNum, {
aspectRatio: page2.aspectRatio
}]));
}
function loadTargetFor(pageNum) {
let page2 = pages.get(pageNum);
return page2 ? {
pageNum,
page: page2
} : null;
}
function maxProgressPageNum() {
return totalPages ? totalPages + 1 : Number.MAX_SAFE_INTEGER;
}
function isRealPageNum(pageNum) {
return pageNum >= 1 && (!totalPages || pageNum <= totalPages);
}
function turnPageBy(delta) {
if (pagedMode()) {
animatePagedStep(delta * pageTurnStep());
return;
}
setCurrentPageNumber(state2.navi.currentPageNum() + delta, !0);
}
function animatePagedStep(delta) {
let base = pagedTargetPageNumber ?? state2.navi.currentPageNum(), target = clamp(Math.round(base + delta), 1, maxProgressPageNum());
if (target === base) {
scrollToCurrentPage("animated");
return;
}
if (viewportActions.pageOffset(target) === null) {
pagedTargetPageNumber = null, setCurrentPageNumber(target, !0, "animated");
return;
}
state2.navi.setDirection(target > base ? 1 : -1), pagedTargetPageNumber = target, viewportActions.moveToPage(target, "animated", () => {
pagedTargetPageNumber === target && (pagedTargetPageNumber = null, setCurrentPageNumber(target, !0));
});
}
function scrollToCurrentPage(motion = "instant") {
viewportActions.moveToPage(state2.navi.currentPageNum(), motion);
}
function updatePageNumber() {
let pageNum = state2.navi.currentPageNum(), downloadPageNums = pagedMode() && state2.ctrls.value().pageLayout === "double" ? [pageNum, pageNum + 1] : [pageNum];
state2.navi.setDownloadInfos(downloadPageNums.flatMap((downloadPageNum) => {
let image2 = loadedImages.get(downloadPageNum);
return !image2 || !isRealPageNum(downloadPageNum) ? [] : (loadedImages.delete(downloadPageNum), loadedImages.set(downloadPageNum, image2), [{
currentFileName: displayedImageFileName(options.galleryId, downloadPageNum, image2.imageUrl),
currentImageUrl: image2.imageUrl,
imageHeight: viewportActions.pageImageHeight(downloadPageNum) ?? image2.height,
imageWidth: viewportActions.pageImageWidth(downloadPageNum) ?? image2.width,
originalImageUrl: image2.originalImageUrl,
pageNum: downloadPageNum
}]);
})), state2.navi.setMaxProgressPageNum(Math.max(1, maxProgressPageNum()));
}
function notifyActivePageChange() {
let page2 = pages.get(state2.navi.currentPageNum());
page2 && callbacks.onActivePageChange(page2);
}
function updateCurrentFromScroll() {
let next = viewportActions.centerPageNum();
next !== null && next !== state2.navi.currentPageNum() && (state2.navi.setDirection(next > state2.navi.currentPageNum() ? 1 : -1), state2.navi.setCurrentPageNum(next), syncAfterPageChange({
scrollIntoView: !1
}));
}
let onKeydown = (event) => {
shouldIgnoreKeyboardEvent(event) || (event.key === "Escape" ? (state2.overlay.image() !== null ? state2.overlay.update(null) : requestReaderClose(), event.preventDefault()) : (event.key === "ArrowLeft" || event.key === "ArrowRight" || state2.ctrls.value().direction === "ttb" && (event.key === "ArrowUp" || event.key === "ArrowDown")) && (event.preventDefault(), state2.overlay.image() === null && (event.key === "ArrowUp" || event.key === "ArrowDown" ? turnPageBy(event.key === "ArrowUp" ? -1 : 1) : turnPageBy(event.key === "ArrowLeft" ? state2.navi.leftTapDelta() : state2.navi.rightTapDelta()))));
}, gesture = wireGesture(), viewport = wireViewport(), scrollViewport = wireScrollViewport();
wireImageQueue();
let toolbar = wireToolbar();
return {
viewportActionsRef: (actions) => {
viewportActions = actions;
},
zoomOverlayActionsRef: (actions) => {
zoomOverlay = actions;
},
init: () => {
document.addEventListener("keydown", onKeydown, !0), window.addEventListener("resize", updateReaderViewportSize), viewportActions.focus(), updatePageNumber(), syncAfterPageChange({
scrollIntoView: !0
});
},
cleanup: () => {
document.removeEventListener("keydown", onKeydown, !0), window.removeEventListener("resize", updateReaderViewportSize);
},
gotoPage: (pageNum) => setCurrentPageNumber(pageNum, !0),
realignCurrentPage: () => {
scrollToCurrentPage();
},
toolbar,
viewport,
viewportCanvas: scrollViewport.callbacks
};
function wireScrollViewport() {
let adjustmentStartSizeScale = state2.scrollViewport.sizeScale(), pinchStartScale = null, updateImageScale = (scale) => {
if (scale === null) {
state2.scrollViewport.setSizeScale(null);
return;
}
let fitScale = state2.scrollViewport.fitScale();
fitScale && state2.scrollViewport.setSizeScale(normalizeReaderScrollSizeScale(scale / fitScale));
};
return {
startPinch: () => {
let scalePercent = state2.scrollViewport.scalePercent();
return scalePercent === null ? !1 : (pinchStartScale = scalePercent / 100, !0);
},
movePinch: (scale) => {
pinchStartScale !== null && updateImageScale(clamp(pinchStartScale * scale, 0.1, 5));
},
endPinch: () => {
pinchStartScale = null;
},
pinching: () => pinchStartScale !== null,
open: () => {
adjustmentStartSizeScale = state2.scrollViewport.sizeScale(), state2.scrollViewport.setAdjusting(!0);
},
callbacks: {
onApply: () => state2.scrollViewport.setAdjusting(!1),
onApplyAll: () => {
(state2.ctrls.value().direction === "ttb" ? state.reader.scrollTtbScale : state.reader.scrollHorizontalScale).set(state2.scrollViewport.sizeScale()), state2.scrollViewport.setAdjusting(!1);
},
onClose: () => {
state2.scrollViewport.setSizeScale(adjustmentStartSizeScale), state2.scrollViewport.setAdjusting(!1);
},
onFit: () => updateImageScale(null),
onOneToOne: () => state2.scrollViewport.setSizeScale("one-to-one"),
onScaleChange: updateImageScale
}
};
}
function wireViewport() {
let scrollFrame = null, scrollBarTimer = null, scrollGestureTimer = null, previousScrollPosition = null, scrollDistance = 0, scrollPosition = () => state2.ctrls.value().direction === "ttb" ? viewportActions.scrollTop() : viewportActions.scrollLeft(), updateScrollBarActivity = () => {
let currentScrollPosition = scrollPosition();
previousScrollPosition !== null && (scrollDistance += Math.abs(currentScrollPosition - previousScrollPosition)), previousScrollPosition = currentScrollPosition, scrollDistance >= SCROLL_BAR_SHOW_DISTANCE && state2.scrollBar.updateVisible(!0);
let viewportSize = state2.ctrls.value().direction === "ttb" ? window.innerHeight : window.innerWidth;
scrollDistance >= viewportSize * SCROLL_BAR_EXPAND_VIEWPORTS && state2.scrollBar.updateExpanded(!0), session.clearTimeout(scrollGestureTimer), scrollGestureTimer = session.setTimeout(() => {
scrollGestureTimer = null, scrollDistance = 0, previousScrollPosition = scrollPosition();
}, SCROLL_GESTURE_IDLE_MS), session.clearTimeout(scrollBarTimer), scrollBarTimer = session.setTimeout(() => {
scrollBarTimer = null, scrollDistance = 0, previousScrollPosition = scrollPosition(), state2.scrollBar.updateExpanded(!1), state2.scrollBar.updateVisible(!1);
}, SCROLL_BAR_IDLE_MS);
};
return onCleanup(() => {
session.clearTimeout(scrollBarTimer), session.clearTimeout(scrollGestureTimer);
}), {
onNativeScroll: () => {
if (state2.overlay.image() !== null || pagedMode() || state2.scrollViewport.adjusting() || (updateScrollBarActivity(), viewportActions.isDragging()))
return;
let previousPosition = scrollPosition();
state2.ctrls.value().direction === "ttb" ? viewportActions.moveToTop(previousPosition) : viewportActions.moveToLeft(previousPosition), !(scrollPosition() !== previousPosition || scrollFrame !== null) && (scrollFrame = session.requestAnimationFrame(() => {
scrollFrame = null, updateCurrentFromScroll();
}));
},
onReloadPage: (pageNum) => {
viewportActions.resetPageError(pageNum) && (pages.has(pageNum) ? maintainLoadQueue() : loadMissingPages([pageNum], ++syncToken));
},
onWheel: (delta, event) => {
if (state2.overlay.image() !== null) {
event.preventDefault(), zoomOverlay.moveWheel({
centerX: event.clientX,
centerY: event.clientY,
delta: wheelDeltaPixels(delta, event.deltaMode)
});
return;
}
if (!(!pagedMode() && state2.ctrls.value().direction === "ttb")) {
if (event.preventDefault(), !pagedMode()) {
let direction = state2.ctrls.value().direction === "rtl" ? -1 : 1;
viewportActions.moveToLeft(viewportActions.scrollLeft() + wheelDeltaPixels(delta, event.deltaMode) * direction * HORIZONTAL_SCROLL_WHEEL_FACTOR);
return;
}
!viewportActions.isDragging() && Math.abs(delta) >= PAGED_WHEEL_THRESHOLD && turnPageBy(delta > 0 ? 1 : -1);
}
},
pointer: gesture
};
}
function wireImageQueue() {
let rememberLoadedImage = (pageNum, loaded) => {
let image2 = {
pageNum,
imageUrl: loaded.imageUrl,
originalImageUrl: loaded.originalImageUrl ?? null,
width: positiveNumber(loaded.width),
height: positiveNumber(loaded.height)
};
for (pageNum === scrollFitPageNum && image2.width && image2.height && !state2.scrollViewport.fitImageSize() && state2.scrollViewport.setFitImageSize({
height: image2.height,
width: image2.width
}), loadedImages.delete(pageNum), loadedImages.set(pageNum, image2); loadedImages.size > LOADED_IMAGE_INFO_CACHE_LIMIT; ) {
let oldestPageNum = loadedImages.keys().next().value;
if (oldestPageNum === void 0)
break;
loadedImages.delete(oldestPageNum);
}
return image2;
}, installImage = async (target, loaded, token) => {
let imageUrl = loaded.imageUrl, width = positiveNumber(loaded.width), height = positiveNumber(loaded.height), installed = !1;
try {
installed = await viewportActions.loadPageImage(target.pageNum, token, {
imageUrl,
highPriority: target.pageNum === state2.navi.currentPageNum() || state2.ctrls.value().navigationMode === "paged" && state2.ctrls.value().pageLayout === "double" && target.pageNum === state2.navi.currentPageNum() + 1,
width,
height
});
} catch (error) {
let message = error instanceof Error ? error.message : texts_default.errors.imageLoadFailed;
viewportActions.setPageError(target.pageNum, token, message);
return;
}
if (installed && target.pageNum === scrollFitPageNum && !state2.scrollViewport.fitImageSize()) {
let fitWidth = viewportActions.pageImageWidth(target.pageNum), fitHeight = viewportActions.pageImageHeight(target.pageNum);
fitWidth && fitHeight && state2.scrollViewport.setFitImageSize({
height: fitHeight,
width: fitWidth
});
}
if (!closed) {
let currentPageNum = state2.navi.currentPageNum();
(target.pageNum === currentPageNum || pagedMode() && state2.ctrls.value().pageLayout === "double" && target.pageNum === currentPageNum + 1) && updatePageNumber();
}
};
session.imageQueue.updateCallbacks({
loadTarget: (target) => Promise.resolve(loadedImages.get(target.pageNum) ?? previewCache.loadImage(target.page)),
markLoading: (target) => viewportActions.markPageLoading(target.pageNum),
onLoaded: async (target, loaded, token) => {
let image2 = rememberLoadedImage(target.pageNum, loaded);
pageWindowNumbers(state2.navi.currentPageNum(), renderWindowSize).includes(target.pageNum) && await installImage(target, image2, token);
},
onError: (target, error, token) => {
let message = error instanceof Error ? error.message : texts_default.errors.loadFailed;
viewportActions.setPageError(target.pageNum, token, message);
}
});
}
function wireToolbar() {
let toolbar2 = {}, progressNavigationTimer = null, pendingProgressPageNum = null, updateControls = (requestedControls) => {
let previous = state2.ctrls.value(), controls = requestedControls.navigationMode === previous.navigationMode ? requestedControls : {
...requestedControls,
direction: requestedControls.navigationMode === "scroll" ? state.reader.scrollDirection.value : state.reader.pagedDirection.value
};
state.reader.navigationMode.set(controls.navigationMode), controls.navigationMode === "scroll" ? state.reader.scrollDirection.set(controls.direction) : state.reader.pagedDirection.set(controls.direction), state.reader.pageLayout.set(controls.pageLayout), state.reader.rightTapAction.set(controls.rightTapAction), state2.ctrls.update(controls), controls.navigationMode !== "scroll" && state2.scrollViewport.setAdjusting(!1), controls.navigationMode !== previous.navigationMode || controls.pageLayout !== previous.pageLayout ? (viewportActions.stopMotion(), viewportActions.resetPosition(), syncAfterPageChange({
scrollIntoView: !0
})) : controls.direction !== previous.direction && (syncViewportWindow(), scrollToCurrentPage());
}, cancelProgressNavigation = () => {
progressNavigationTimer !== null && (session.clearTimeout(progressNavigationTimer), progressNavigationTimer = null);
}, previewProgress = (pageNum) => {
let target = clamp(Math.round(pageNum), 1, maxProgressPageNum());
target !== state2.navi.currentPageNum() && (state2.navi.setDirection(target > state2.navi.currentPageNum() ? 1 : -1), state2.navi.setCurrentPageNum(target)), ++syncToken, syncViewportWindow(), scrollToCurrentPage(), updatePageNumber();
};
return onCleanup(cancelProgressNavigation), toolbar2.onCloseClick = requestReaderClose, toolbar2.onControlsChange = updateControls, toolbar2.onFullscreenClick = callbacks.onFullscreenToggle, toolbar2.onOpenOriginalPageClick = () => {
let page2 = pages.get(state2.navi.currentPageNum());
page2 && isRealPageNum(state2.navi.currentPageNum()) && callbacks.onOpenOriginalPage(page2);
}, toolbar2.onOpenScrollPreviewClick = () => {
callbacks.onOpenScrollPreview(state2.navi.currentPageNum());
}, toolbar2.onViewportAdjustClick = scrollViewport.open, toolbar2.onProgressPointerDown = (event) => {
state2.navi.setProgressInputActive(!0), cancelProgressNavigation(), event.stopPropagation();
}, toolbar2.onProgressInput = (pageNum) => {
!Number.isFinite(pageNum) || pageNum <= 0 || (state2.navi.setProgressInputActive(!0), pendingProgressPageNum = clamp(Math.round(pageNum), 1, maxProgressPageNum()), previewProgress(pendingProgressPageNum), cancelProgressNavigation(), progressNavigationTimer = session.setTimeout(() => toolbar2.onProgressCommit(pendingProgressPageNum ?? state2.navi.currentPageNum()), PROGRESS_IDLE_COMMIT_MS));
}, toolbar2.onProgressCommit = (value) => {
if (!state2.navi.progressInputActive() && pendingProgressPageNum === null)
return;
let pageNum = pendingProgressPageNum ?? value;
state2.navi.setProgressInputActive(!1), pendingProgressPageNum = null, cancelProgressNavigation(), Number.isFinite(pageNum) && pageNum > 0 && setCurrentPageNumber(pageNum, !0);
}, toolbar2;
}
function wireGesture() {
let gesture2 = {
get dragAxis() {
return state2.overlay.image() !== null || !pagedMode() ? "any" : state2.ctrls.value().direction === "ttb" ? "y" : "x";
}
}, tapTimer = null, pendingTap = null, isPageReloadButtonTarget = (event) => event.target instanceof Element && event.target.closest(".ehpeek-reader-page-reload") !== null, shouldStartDrag = (event) => state2.overlay.image() !== null || pagedMode() || state2.ctrls.value().direction !== "ttb" || event.pointerType === "mouse", imageAtPoint = (point) => {
let pageNum = viewportActions.pageNumAtPoint(point);
return pageNum === null || !viewportActions.pageImageReady(pageNum) ? null : loadedImages.get(pageNum) ?? null;
}, cancelPendingTap = () => {
tapTimer !== null && (session.clearTimeout(tapTimer), tapTimer = null), pendingTap = null;
}, toggleZoomAtPoint = (point) => {
if (state2.overlay.image() !== null)
return state2.overlay.update(null), !0;
let image2 = imageAtPoint(point);
if (!image2)
return !1;
viewportActions.stopMotion(), viewportActions.cancelDrag();
let zoomScale = viewportActions.pageZoomScale(image2.pageNum);
return state2.overlay.update(image2), zoomOverlay.reset({
centerX: point.clientX,
centerY: point.clientY,
scale: zoomScale
}), zoomOverlay.movePinch({
centerX: point.clientX,
centerY: point.clientY,
scale: 2
}), zoomOverlay.endPinch(), !0;
}, consumeDoubleTap = (info, event) => {
let now = event.timeStamp || performance.now(), nativeDoubleClick = event instanceof MouseEvent && event.detail >= 2, nearPendingTap = pendingTap ? now - pendingTap.time <= DOUBLE_TAP_MS && Math.hypot(info.clientX - pendingTap.info.clientX, info.clientY - pendingTap.info.clientY) <= DOUBLE_TAP_DISTANCE : !1;
return !nativeDoubleClick && !nearPendingTap || (cancelPendingTap(), !toggleZoomAtPoint(info)) ? !1 : (event.preventDefault(), !0);
}, runSingleTap = (info, event) => {
if (state2.overlay.image() !== null)
event.preventDefault();
else if (viewportActions.isHitEndPage(info))
requestReaderClose();
else {
let zone = info.clientX / viewportActions.viewportWidth();
zone >= 1 / 3 && zone <= 2 / 3 ? state2.toolbar.toggle() : turnPageBy(zone < 1 / 3 ? state2.navi.leftTapDelta() : state2.navi.rightTapDelta());
}
}, queueSingleTap = (info, event) => {
cancelPendingTap(), pendingTap = {
info,
event,
time: event.timeStamp || performance.now()
}, tapTimer = session.setTimeout(() => {
let pending = pendingTap;
pendingTap = null, tapTimer = null, pending && runSingleTap(pending.info, pending.event);
}, DOUBLE_TAP_MS);
};
return gesture2.onTap = (info, event) => {
viewportActions.cancelDrag(), !consumeDoubleTap(info, event) && queueSingleTap(info, event);
}, gesture2.onStart = () => {
if (state2.overlay.image() !== null) {
zoomOverlay.startDrag();
return;
}
viewportActions.beginDrag();
}, gesture2.onMove = (info) => {
if (state2.overlay.image() !== null) {
zoomOverlay.moveDrag(info);
return;
}
viewportActions.moveDrag({
dx: info.dx,
dy: info.dy
}) && (Math.abs(info.dx) >= TAP_CANCEL_DISTANCE || Math.abs(info.dy) >= TAP_CANCEL_DISTANCE) && cancelPendingTap();
}, gesture2.onEnd = (info) => {
if (state2.overlay.image() === null) {
if (viewportActions.cancelDrag(), !pagedMode()) {
state2.ctrls.value().direction === "ttb" ? (viewportActions.moveToTop(viewportActions.scrollTop()), viewportActions.startVerticalFlingFromDragVelocity(info.velocityY, () => updateCurrentFromScroll())) : (viewportActions.moveToLeft(viewportActions.scrollLeft()), viewportActions.startHorizontalFlingFromDragVelocity(info.velocityX, () => updateCurrentFromScroll())), updateCurrentFromScroll();
return;
}
if (state2.ctrls.value().direction === "ttb") {
info.dy >= PAGED_SWIPE_THRESHOLD ? turnPageBy(-1) : info.dy <= -PAGED_SWIPE_THRESHOLD ? turnPageBy(1) : scrollToCurrentPage("animated");
return;
}
info.dx >= PAGED_SWIPE_THRESHOLD ? turnPageBy(state2.navi.rightDragDelta()) : info.dx <= -PAGED_SWIPE_THRESHOLD ? turnPageBy(state2.navi.leftDragDelta()) : scrollToCurrentPage("animated");
}
}, gesture2.onPinchStart = (info) => {
if (cancelPendingTap(), viewportActions.stopMotion(), viewportActions.cancelDrag(), !pagedMode() && state2.overlay.image() === null)
return scrollViewport.startPinch();
if (state2.overlay.image() !== null)
return zoomOverlay.startPinch({
centerX: info.clientX,
centerY: info.clientY
}), !0;
let image2 = imageAtPoint(info);
if (!image2)
return !1;
let zoomScale = viewportActions.pageZoomScale(image2.pageNum);
return state2.overlay.update(image2), zoomOverlay.reset({
centerX: info.clientX,
centerY: info.clientY,
scale: zoomScale
}), !0;
}, gesture2.onPinchMove = (info) => {
if (scrollViewport.pinching()) {
scrollViewport.movePinch(info.scale);
return;
}
zoomOverlay.movePinch({
centerX: info.clientX,
centerY: info.clientY,
scale: info.scale
});
}, gesture2.onPinchEnd = () => {
if (scrollViewport.pinching()) {
scrollViewport.endPinch();
return;
}
zoomOverlay.endPinch();
}, gesture2.shouldCaptureDrag = (event) => isPageReloadButtonTarget(event) || !(event instanceof PointerEvent) || event.pointerType === "mouse" && event.button !== 0 ? !1 : shouldStartDrag(event), gesture2.shouldObserveTap = (event) => event instanceof PointerEvent && !isPageReloadButtonTarget(event) && event.pointerType !== "mouse" && !shouldStartDrag(event), gesture2.dragStartThreshold = TAP_CANCEL_DISTANCE, gesture2.tapMoveThreshold = TAP_CANCEL_DISTANCE, gesture2;
}
}
function wheelDeltaPixels(delta, mode) {
return mode === WheelEvent.DOM_DELTA_LINE ? delta * 16 : mode === WheelEvent.DOM_DELTA_PAGE ? delta * window.innerHeight : delta;
}
function displayedImageFileName(galleryId, pageNum, imageUrl) {
return `${galleryId}-p${String(pageNum).padStart(2, "0")}.${imageFileExtension(imageUrl)}`;
}
function imageFileExtension(imageUrl) {
try {
let extension = decodeURIComponent(new URL(imageUrl).pathname.split("/").pop() ?? "").match(/\.([a-z0-9]{2,5})$/i)?.[1]?.toLowerCase();
if (extension && ["avif", "bmp", "gif", "jpeg", "jpg", "png", "webp"].includes(extension))
return extension;
} catch {
return "";
}
return "";
}
function pageNumForPage(page2, index) {
let pageNum = page2?.pageNum;
return typeof pageNum == "number" && Number.isFinite(pageNum) && pageNum > 0 ? pageNum : index + 1;
}
function shouldIgnoreKeyboardEvent(event) {
if (event.isComposing)
return !0;
let eventTarget = event.target;
return eventTarget instanceof Element ? !!eventTarget.closest("input, textarea, select, [contenteditable='true'], [contenteditable='']") : !1;
}
// src/App/Reader.tsx
var activeReaderClose, activeReaderActions;
function gotoActiveReaderPage(pageNum) {
return activeReaderActions ? (activeReaderActions.gotoPage(pageNum), !0) : !1;
}
function openReaderFromUserAction(startPageUrl, callbacks, previewCache, viewport, preferredPageNum) {
let fullscreenLaunch = requestConfiguredFullscreen(viewport);
openReader(startPageUrl, callbacks, previewCache, viewport, preferredPageNum, fullscreenLaunch).catch(async (error) => {
if (fullscreenLaunch) {
let fullscreenEntered = await fullscreenLaunch.result;
document.fullscreenElement === fullscreenLaunch.host && await document.exitFullscreen().catch((fullscreenError) => {
console.warn("[ehpeek] Failed to exit fullscreen", fullscreenError);
}), fullscreenEntered && await fullscreenLaunch.fullscreen.exit(), fullscreenLaunch.host.remove(), await fullscreenLaunch.fullscreen.restore();
}
reportReaderOpenError(error);
});
}
async function openReaderFromHash(callbacks, previewCache, viewport) {
let peekPage = peekPageFromHash();
if (peekPage === null)
return;
let pages = previewCache.current().data.pages, page2 = pages.find((item) => item.pageNum === peekPage) ?? pages[0];
page2 && await openReader(page2.url, callbacks, previewCache, viewport).catch(reportReaderOpenError);
}
async function openOriginalReader(pageNum, previewCache) {
let page2 = (await previewCache.getPages([pageNum]))[0];
if (!page2 || page2.pageNum !== pageNum)
throw new Error(texts_default.errors.imageNotFound);
window.location.assign(page2.url);
}
async function openReader(startPageUrl, callbacks, previewCache, viewport, preferredPageNum, fullscreenLaunch) {
if (!state.reader.enabled.value)
return;
let preview = previewCache.current().data, gallery2 = galleryIdentityFromUrl(preview.currentUrl);
if (!gallery2)
return;
let currentPreviewIndex = preview.currentIndex, pageSize = preview.pageSize, maxPreviewIndex = preview.maxIndex, totalPages = preview.totalImages, startPageNum = preferredPageNum ?? peekPageFromHash() ?? galleryPageNumber(startPageUrl);
if (!startPageNum)
throw new Error(texts_default.errors.imageNotFound);
let historySession = callbacks.readHistoryEnabled ? new ReadHistorySession({
gallery: extractGalleryHistoryInfo(),
galleryId: gallery2.galleryId,
token: gallery2.token,
totalPages
}) : null;
if ((fullscreenLaunch ? await fullscreenLaunch.result : void 0) && document.fullscreenElement !== fullscreenLaunch?.host) {
historySession?.dispose(), await fullscreenLaunch?.fullscreen.exit(), fullscreenLaunch?.host.remove(), await fullscreenLaunch?.fullscreen.restore();
return;
}
let lastPageNum = startPageNum, onExit = () => {
if (historySession?.dispose(), callbacks.onReaderClosed(lastPageNum, totalPages ?? null), clearPeekLocation(), lastPageNum === startPageNum)
return;
let exitIndex = previewCache.previewIndexForPage(lastPageNum), galleryUrl = previewUrlForIndex(exitIndex);
if (callbacks.enhanceThumbsGridsEnabled) {
callbacks.onGotoPreviewIndex(exitIndex), previewCache.select(exitIndex).catch(() => {
window.location.replace(galleryUrl);
});
return;
}
exitIndex === currentPreviewIndex ? window.history.replaceState(window.history.state, "", galleryUrl) : window.location.replace(galleryUrl);
}, host = fullscreenLaunch?.host ?? createReaderHost(), fullscreen = fullscreenLaunch?.fullscreen ?? viewport.createFullscreen(host);
mountReader({
galleryId: gallery2.galleryId,
initialPageNum: startPageNum,
totalPages
}, previewCache, {
onActivePageChange: (page2) => {
page2.pageNum && (lastPageNum = page2.pageNum, callbacks.enhanceThumbsGridsEnabled && callbacks.onGotoPreviewIndex(previewCache.previewIndexForPage(page2.pageNum))), historySession?.update(page2.pageNum, totalPages), updatePeekLocation(page2.pageNum, pageSize, maxPreviewIndex);
},
onOpenOriginalPage: (page2) => {
historySession?.dispose(), window.location.assign(page2.url);
},
onOpenScrollPreview: (pageNum) => {
callbacks.onOpenScrollPreview(pageNum);
}
}, viewport.lockScroll, fullscreen, onExit, host);
}
function mountReader(options, previewCache, callbacks, lockPageScroll2, fullscreen, onExit, host) {
activeReaderClose?.();
let disposeRoot = () => {
}, unlockPageScroll = lockPageScroll2(), setFullscreenActive = (_active) => {
}, keepReaderOpen = !1, closing = !1, mountedReaderActions, close = () => requestClose();
function requestClose() {
closing || (clearPeekLocation(), onClosed());
}
let onPopState = (event) => {
event.state !== null && typeof event.state == "object" && event.state.ehpeekReader === !0 || onClosed();
};
async function onClosed() {
closing || (closing = !0, await fullscreen.exit().catch((error) => {
console.warn("[ehpeek] Failed to exit fullscreen", error);
}), disposeRoot(), disposeRoot = () => {
}, unlockPageScroll(), unlockPageScroll = () => {
}, host.remove(), await fullscreen.restore().catch((error) => {
console.warn("[ehpeek] Failed to restore page viewport", error);
}), activeReaderClose === close && (activeReaderClose = void 0), activeReaderActions === mountedReaderActions && (activeReaderActions = void 0), onExit());
}
host.isConnected || document.body.append(host), window.addEventListener("popstate", onPopState), window.history.pushState({
ehpeekReader: !0
}, "", window.location.href), activeReaderClose = close;
let unsubscribeFullscreen = fullscreen.subscribe((active) => {
setFullscreenActive(active), !active && !keepReaderOpen && requestClose(), keepReaderOpen = !1;
});
disposeRoot = render(() => {
let [fullscreenActive, updateFullscreenActive] = createSignal(fullscreen.active());
return setFullscreenActive = updateFullscreenActive, createComponent(Reader, {
actionsRef: (actions) => {
mountedReaderActions = actions, activeReaderActions = actions;
},
get callbacks() {
return {
...callbacks,
onClosed: requestClose,
onFullscreenToggle: () => {
fullscreen.active() ? (keepReaderOpen = !0, fullscreen.exit().catch((error) => {
keepReaderOpen = !1, console.warn("[ehpeek] Failed to exit fullscreen", error);
})) : fullscreen.enter().catch((error) => {
console.warn("[ehpeek] Fullscreen request failed", error);
});
}
};
},
options,
previewCache,
get fullscreenActive() {
return fullscreenActive();
}
});
}, host);
let previousDispose = disposeRoot;
disposeRoot = () => {
window.removeEventListener("popstate", onPopState), unsubscribeFullscreen(), previousDispose();
};
}
function requestConfiguredFullscreen(viewportSource) {
if (!state.reader.enabled.value || !state.reader.fullscreen.value || document.fullscreenElement)
return;
let host = createReaderHost();
document.body.append(host);
let fullscreen = viewportSource.createFullscreen(host);
return !document.fullscreenEnabled || typeof host.requestFullscreen != "function" ? {
fullscreen,
host,
result: Promise.resolve(!1)
} : {
fullscreen,
host,
result: fullscreen.enter().then(() => !0, (error) => (console.warn("[ehpeek] Fullscreen request failed", error), !1))
};
}
function createReaderHost() {
let host = document.createElement("div");
return host.dataset.ehpeekReaderContainer = "true", host;
}
function reportReaderOpenError(error) {
let message = error instanceof Error ? error.message : texts_default.errors.loadFailed;
console.error("[ehpeek]", error), window.alert(message);
}
// src/App/GalleryPreviewCache.ts
var PREVIEW_CACHE_LIMIT = 10;
function createGalleryPreviewCache(initialPreview) {
let [current, setCurrent] = createSignal(initialPreview), [loading, setLoading] = createSignal(!1), [previewDataVersion, setPreviewDataVersion] = createSignal(0), previews = /* @__PURE__ */ new Map(), pages = /* @__PURE__ */ new Map(), previewItems = /* @__PURE__ */ new Map(), pending = /* @__PURE__ */ new Map(), pageSize = initialPreview.data.pageSize, maxPreviewIndex = initialPreview.data.maxIndex, currentPreviewIndex = initialPreview.data.currentIndex, selectionId = 0, remember = (preview) => {
let index = preview.data.currentIndex;
previews.delete(index), previews.set(index, preview);
for (let page2 of preview.data.pages)
page2.pageNum && page2.pageNum > 0 && pages.set(page2.pageNum, page2);
for (let item of preview.data.previewItems)
previewItems.set(item.pageNum, item);
for (setPreviewDataVersion((version) => version + 1); previews.size > PREVIEW_CACHE_LIMIT; ) {
let removable;
for (let candidate of previews.keys())
if (candidate !== currentPreviewIndex && candidate !== index) {
removable = candidate;
break;
}
if (removable === void 0)
break;
previews.delete(removable);
}
}, previewIndexForPage = (pageNum) => previewPageIndexForGalleryPage(
pageNum,
pageSize,
maxPreviewIndex
), load = (previewIndex) => {
if (previewIndex < 0 || previewIndex > maxPreviewIndex)
return Promise.reject(new RangeError(`Invalid Preview index: ${previewIndex}`));
let cached = previews.get(previewIndex);
if (cached)
return previews.delete(previewIndex), previews.set(previewIndex, cached), Promise.resolve(cached);
let existing = pending.get(previewIndex);
if (existing)
return existing;
setLoading(!0);
let request = loadGalleryPreviewPage(
previewIndex,
initialPreview.data.currentUrl
).then(
(preview) => (pending.delete(previewIndex), setLoading(pending.size > 0), remember(preview), preview),
(error) => {
throw pending.delete(previewIndex), setLoading(pending.size > 0), error;
}
);
return pending.set(previewIndex, request), request;
}, getPages = async (pageNums) => {
let requested = Array.from(new Set(pageNums.filter((pageNum) => pageNum > 0))), previewIndexes = Array.from(new Set(requested.filter((pageNum) => !pages.has(pageNum)).map(previewIndexForPage)));
return await Promise.all(previewIndexes.map(load)), requested.flatMap((pageNum) => pages.get(pageNum) ?? []);
}, getPreviewItems = async (pageNums) => {
let requested = Array.from(new Set(pageNums.filter((pageNum) => pageNum > 0))), previewIndexes = Array.from(new Set(requested.filter((pageNum) => !previewItems.has(pageNum)).map(previewIndexForPage)));
return await Promise.all(previewIndexes.map(load)), requested.flatMap((pageNum) => previewItems.get(pageNum) ?? []);
}, select2 = async (previewIndex) => {
if (previewIndex === current().data.currentIndex)
return current();
let activeSelection = ++selectionId, preview = await load(previewIndex);
return activeSelection === selectionId && (currentPreviewIndex = preview.data.currentIndex, setCurrent(preview), window.history.replaceState(
window.history.state,
"",
preview.data.currentUrl
)), preview;
};
return remember(initialPreview), {
current,
getPages,
getPreviewItems,
load,
loadImage: loadEhImagePage,
loading,
previewDataVersion,
previewIndexForPage,
previewItem: (pageNum) => (previewDataVersion(), previewItems.get(pageNum) ?? null),
select: select2
};
}
// src/App/host.ts
function createAppMount(className2 = "", host = document.body ?? document.documentElement) {
let mount = createManagedElement("div");
return className2 && mount.replaceClasses(className2), host.append(mount.Component()), mount;
}
// ehpeek-spectrum-ui-scales:ehpeek:spectrum-ui-scales
var ehpeek_spectrum_ui_scales_default = { small: { control: { xs: "24px", sm: "32px", md: "40px", lg: "48px", xl: "56px" }, font: { xs: "10px", sm: "14px", md: "16px", prominent: "18px", title: "20px", lg: "22px", xl: "28px" }, icon: { sm: "16px", md: "20px", lg: "22px", xl: "26px" }, statusDot: { md: "10px", lg: "12px" } }, medium: { control: { xs: "32px", sm: "40px", md: "48px", lg: "56px", xl: "64px" }, font: { xs: "11px", sm: "16px", md: "20px", prominent: "22px", title: "25px", lg: "28px", xl: "36px" }, icon: { sm: "20px", md: "22px", lg: "26px", xl: "26px" }, statusDot: { md: "12px", lg: "14px" } }, large: { control: { xs: "40px", sm: "50px", md: "60px", lg: "70px", xl: "80px" }, font: { xs: "13px", sm: "19px", md: "24px", prominent: "27px", title: "31px", lg: "34px", xl: "44px" }, icon: { sm: "24px", md: "28px", lg: "30px", xl: "30px" }, statusDot: { md: "14px", lg: "16px" } } };
// src/App/uiScale.ts
function applyUiScale(scale) {
let values = ehpeek_spectrum_ui_scales_default[scale], root = document.documentElement;
root.dataset.ehpeekUiScale = scale, applySizeScale(root, "--ui-control-size", values.control), applySizeScale(root, "--ui-font-size", values.font), applySizeScale(root, "--ui-icon-size", values.icon), applySizeScale(root, "--ui-status-dot-size", values.statusDot);
}
function applySizeScale(root, prefix, values) {
for (let [name, value] of Object.entries(values))
root.style.setProperty(`${prefix}-${name}`, value);
}
// src/App/viewport.ts
var FULLSCREEN_UI_SCALE_PROPERTY = "--ehpeek-reader-fullscreen-ui-scale", FULLSCREEN_UI_SCALE_INVERSE_PROPERTY = "--ehpeek-reader-fullscreen-ui-scale-inverse", FULLSCREEN_PROGRESS_SIZE_PROPERTY = "--ehpeek-reader-fullscreen-progress-size";
function lockPageScroll() {
let documentElement = document.documentElement, body = document.body, documentOverflow = documentElement.style.overflow, bodyOverflow = body.style.overflow;
return documentElement.style.overflow = "hidden", body.style.overflow = "hidden", () => {
documentElement.style.overflow = documentOverflow, body.style.overflow = bodyOverflow;
};
}
function pageViewportForFullscreen() {
let existing = document.querySelector(
'meta[name="viewport"]'
), meta = existing ?? document.createElement("meta"), scale = Math.max(0.1, window.visualViewport?.scale ?? 1), snapshot = {
content: existing?.getAttribute("content") ?? null,
created: !existing,
meta,
scale,
scrollX: window.scrollX,
scrollY: window.scrollY
};
return existing || (meta.name = "viewport", document.head.append(meta)), meta.content = lockedViewportContent(snapshot.content, scale), snapshot;
}
async function restorePageViewport(snapshot) {
await nextAnimationFrame(), snapshot.created ? snapshot.meta.remove() : snapshot.content === null ? snapshot.meta.removeAttribute("content") : snapshot.meta.content = snapshot.content, await nextAnimationFrame(), await nextAnimationFrame(), window.scrollTo(snapshot.scrollX, snapshot.scrollY);
}
var readerViewport = {
createFullscreen: createReaderFullscreen,
lockScroll: lockPageScroll,
prepareFullscreen: pageViewportForFullscreen,
restore: restorePageViewport
};
function createReaderFullscreen(target, initialSnapshot = null) {
let snapshot = initialSnapshot, restorePromise = null, restore = async () => {
target.style.removeProperty(FULLSCREEN_UI_SCALE_PROPERTY), target.style.removeProperty(FULLSCREEN_UI_SCALE_INVERSE_PROPERTY), target.style.removeProperty(FULLSCREEN_PROGRESS_SIZE_PROPERTY), snapshot && (restorePromise ?? (restorePromise = restorePageViewport(snapshot).finally(() => {
restorePromise = null;
})), await restorePromise);
};
return {
active: () => document.fullscreenElement === target,
enter: async () => {
if (document.fullscreenElement || !document.fullscreenEnabled)
return;
snapshot = pageViewportForFullscreen();
let scaleBefore = window.visualViewport?.scale ?? 1;
try {
await target.requestFullscreen(), await nextAnimationFrame();
let scaleAfter = window.visualViewport?.scale ?? 1, uiScale = Math.min(1, Math.max(0.25, scaleBefore / Math.max(scaleAfter, 0.01))), progressSize = Number.parseFloat(getComputedStyle(target).getPropertyValue("--ui-font-size-lg")) || 28;
target.style.setProperty(FULLSCREEN_UI_SCALE_PROPERTY, String(uiScale)), target.style.setProperty(FULLSCREEN_UI_SCALE_INVERSE_PROPERTY, String(1 / uiScale)), target.style.setProperty(FULLSCREEN_PROGRESS_SIZE_PROPERTY, `${progressSize * uiScale}px`);
} catch (error) {
throw await restore(), error;
}
},
exit: async () => {
document.fullscreenElement === target && await document.exitFullscreen(), snapshot && await waitForViewportSettled(), target.style.removeProperty(FULLSCREEN_UI_SCALE_PROPERTY), target.style.removeProperty(FULLSCREEN_UI_SCALE_INVERSE_PROPERTY), target.style.removeProperty(FULLSCREEN_PROGRESS_SIZE_PROPERTY);
},
restore,
subscribe: (callback) => {
let onChange = () => {
let active = document.fullscreenElement === target;
active || (target.style.removeProperty(FULLSCREEN_UI_SCALE_PROPERTY), target.style.removeProperty(FULLSCREEN_UI_SCALE_INVERSE_PROPERTY), target.style.removeProperty(FULLSCREEN_PROGRESS_SIZE_PROPERTY)), callback(active);
};
return document.addEventListener("fullscreenchange", onChange), () => document.removeEventListener("fullscreenchange", onChange);
}
};
}
function lockedViewportContent(content, scale) {
let preserved = (content ?? "").split(",").map((item) => item.trim()).filter(
(item) => item && !/^(?:initial-scale|minimum-scale|maximum-scale|user-scalable|viewport-fit)\s*=/i.test(item)
), value = String(Math.round(scale * 1e3) / 1e3);
return [
...preserved,
`initial-scale=${value}`,
`minimum-scale=${value}`,
`maximum-scale=${value}`,
"user-scalable=no",
"viewport-fit=cover"
].join(", ");
}
function nextAnimationFrame() {
return new Promise((resolve) => {
window.requestAnimationFrame(() => resolve());
});
}
async function waitForViewportSettled() {
await nextAnimationFrame(), await new Promise((resolve) => {
let viewport = window.visualViewport, quietTimer = window.setTimeout(finish, 80), timeoutTimer = window.setTimeout(finish, 500), onResize = () => {
window.clearTimeout(quietTimer), quietTimer = window.setTimeout(finish, 80);
};
function finish() {
viewport?.removeEventListener("resize", onResize), window.clearTimeout(quietTimer), window.clearTimeout(timeoutTimer), resolve();
}
viewport?.addEventListener("resize", onResize);
}), await nextAnimationFrame();
}
// src/App/index.tsx
var _tmpl$61 = /* @__PURE__ */ template("<a href=#>");
function settingsMenuState(defaults = !1) {
let read = (setting) => defaults ? setting.defaultValue : setting.value;
return {
openGalleryInNewTab: read(state.app.openGalleryInNewTab),
readerEnabled: read(state.reader.enabled),
readerFullscreenEnabled: read(state.reader.fullscreen),
enhanceThumbsGridsEnabled: read(state.gallery.enhanceThumbs),
enhanceSearchGridsEnabled: read(state.search.enhance),
myTagsEnabled: read(state.gallery.myTags),
readHistoryEnabled: read(state.gallery.readHistory),
includeUnreadHistoryEnabled: read(state.gallery.includeUnreadHistory),
searchHistoryEnabled: read(state.search.history),
touchUiEnabled: read(state.touch.enabled)
};
}
function applySettingsMenuState(next) {
state.app.openGalleryInNewTab.set(next.openGalleryInNewTab), state.reader.enabled.set(next.readerEnabled), state.reader.fullscreen.set(next.readerFullscreenEnabled), state.gallery.enhanceThumbs.set(next.enhanceThumbsGridsEnabled), state.search.enhance.set(next.enhanceSearchGridsEnabled), state.gallery.myTags.set(next.myTagsEnabled), state.gallery.readHistory.set(next.readHistoryEnabled), state.gallery.includeUnreadHistory.set(next.includeUnreadHistoryEnabled), state.search.history.set(next.searchHistoryEnabled), state.touch.enabled.set(next.touchUiEnabled), window.location.reload();
}
var gState = (() => {
let settings2 = settingsMenuState(), [columnsEnabled, setColumnsEnabled] = createSignal(currentColumnsEnabled()), [settingsMenuOpen, setSettingsMenuOpen] = createSignal(!1), [uiScale, setUiScale] = createSignal(currentUiScale()), [readProgress, setReadProgress] = createSignal({
currentPage: 1,
hasHistory: !1,
totalPages: null
});
return {
galleryWideLayout: null,
columnsEnabled,
readProgress,
setReadProgress,
settings: settings2,
settingsMenuOpen,
setUiScale,
setColumnsEnabled,
setSettingsMenuOpen,
readHistoryPage: null,
searchResults: null,
scrollPreviewActions: void 0,
scrollPreviewOpen: !1,
thumbsGridsActions: void 0,
uiScale
};
})();
function currentUiScale() {
return window.matchMedia("(orientation: landscape)").matches ? state.app.landscapeUiScale.value : state.app.portraitUiScale.value;
}
function currentColumnsEnabled() {
return window.matchMedia("(orientation: landscape)").matches ? state.touch.landscapeColumns.value : state.touch.portraitColumns.value;
}
function updateUiScale() {
let scale = currentUiScale();
gState.setUiScale(scale), applyUiScale(scale);
}
function updateColumnsLayout() {
if (!gState.settings.touchUiEnabled)
return;
let enabled = currentColumnsEnabled();
gState.setColumnsEnabled(enabled), gState.galleryWideLayout?.updateEnabled(enabled), gState.readHistoryPage?.handle.updateResultColumns(enabled), gState.searchResults?.handle.updateResultColumns(enabled);
}
function setCurrentColumnsEnabled(enabled) {
(window.matchMedia("(orientation: landscape)").matches ? state.touch.landscapeColumns : state.touch.portraitColumns).set(enabled), gState.setColumnsEnabled(enabled), gState.galleryWideLayout?.updateEnabled(enabled), gState.readHistoryPage?.handle.updateResultColumns(enabled), gState.searchResults?.handle.updateResultColumns(enabled);
}
function setCurrentUiScale(scale) {
(window.matchMedia("(orientation: landscape)").matches ? state.app.landscapeUiScale : state.app.portraitUiScale).set(scale), gState.setUiScale(scale), applyUiScale(scale);
}
document.documentElement.setAttribute("data-ehpeek-site", ehSiteTheme());
updateUiScale();
registerGlobalStyle("ehpeek-uno-style", ehpeek_uno_default);
registerGlobalStyle("ehpeek-theme-style", theme_default);
registerGlobalStyle("ehpeek-dom-style", styles_default);
var readerCallbacks = {
get enhanceThumbsGridsEnabled() {
return gState.settings.enhanceThumbsGridsEnabled || gState.scrollPreviewOpen;
},
readHistoryEnabled: gState.settings.readHistoryEnabled,
onGotoPreviewIndex: (previewIndex) => {
gState.scrollPreviewOpen ? gState.scrollPreviewActions?.gotoPreview(previewIndex) : gState.thumbsGridsActions?.gotoPreview(previewIndex);
},
onOpenScrollPreview: (pageNum) => {
gState.scrollPreviewActions?.gotoPage(pageNum);
},
onReaderClosed: (currentPage, totalPages) => {
gState.settings.readHistoryEnabled && gState.setReadProgress({
currentPage,
hasHistory: !0,
totalPages
});
}
};
function allowFeatureFailure(name, run) {
try {
return run();
} catch (error) {
return console.error(`[ehpeek] ${name} failed`, error), null;
}
}
async function allowAsyncFeatureFailure(name, run) {
try {
return await run();
} catch (error) {
return console.error(`[ehpeek] ${name} failed`, error), null;
}
}
function openGalleryPage(previewCache, startPageUrl, preferredPageNum) {
preferredPageNum !== void 0 && gotoActiveReaderPage(preferredPageNum) || (state.reader.enabled.value ? openReaderFromUserAction(startPageUrl, readerCallbacks, previewCache, readerViewport, preferredPageNum) : preferredPageNum !== void 0 && openOriginalReader(preferredPageNum, previewCache).catch(reportReaderOpenError));
}
function openFromReadButton(previewCache) {
let pageNum = gState.settings.readHistoryEnabled ? gState.readProgress().currentPage : 1, firstPage = previewCache.current().data.pages[0];
firstPage && openGalleryPage(previewCache, firstPage.url, pageNum);
}
function GalleryReadButton(props) {
return createComponent(ReadButton, {
get currentPage() {
return memo(() => !!gState.settings.readHistoryEnabled)() ? gState.readProgress().currentPage : 1;
},
get hasHistory() {
return memo(() => !!gState.settings.readHistoryEnabled)() && gState.readProgress().hasHistory;
},
get totalPages() {
return gState.readProgress().totalPages;
},
onClick: () => openFromReadButton(props.previewCache),
get variant() {
return props.variant;
}
});
}
function installSettingsMenu() {
typeof GM_registerMenuCommand == "function" && GM_registerMenuCommand(texts_default.settings.openSettings, () => {
gState.setSettingsMenuOpen(!0);
}), createAppMount("fixed inset-0 z-[1150] pointer-events-none").mount(() => createComponent(SettingsMenu, {
get historyHref() {
return readHistoryUrl();
},
get open() {
return gState.settingsMenuOpen();
},
get defaultState() {
return settingsMenuState(!0);
},
get initState() {
return gState.settings;
},
onApply: (next) => {
applySettingsMenuState(next);
},
get onOpenChange() {
return gState.setSettingsMenuOpen;
}
}));
}
function injectEnhanceUI(page2, previewCache, searchTextInput, searchResultsDom, touchResultsDom) {
let galleryPage = page2.type === "gallery", searchPage = page2.type === "search" || page2.type === "favorites", preview = previewCache?.current() ?? null, previewMount = preview?.elems.mount ?? null, updateSearchGridModeSelector = () => {
mutateSearchGridModeSelect(state.search.grid.value, () => {
state.search.grid.set(!0), window.location.assign(new URL("/?inline_set=dm_e", window.location.href).href);
}, () => {
state.search.grid.set(!1);
});
};
galleryPage && preview && previewCache && gState.settings.readerEnabled && allowFeatureFailure("Reader thumbnail links", () => {
preview.handle.interceptPreviewImageOpen((pageUrl) => {
openGalleryPage(previewCache, pageUrl);
});
}), searchPage && allowFeatureFailure("Search grid mode selector", () => {
updateSearchGridModeSelector();
});
let searchGridEnabled = !!(searchPage && state.search.grid.value);
searchGridEnabled && allowFeatureFailure("Search grid", () => manageSearchGrids());
let updateSearchReadHistoryAppearance = () => {
!searchPage || !gState.settings.readHistoryEnabled || mutateSearchReadHistoryAppearance((galleryId, token) => loadReadHistory(galleryId, token)?.pageNum ?? null);
};
allowFeatureFailure("Search Read History appearance", updateSearchReadHistoryAppearance), gState.settings.openGalleryInNewTab && searchResultsDom && allowFeatureFailure("Gallery links in new tabs", () => {
searchResultsDom.handle.ensureGalleryLinksOpenInNewTab();
}), gState.settings.touchUiEnabled || allowFeatureFailure("Desktop settings entry", () => {
let settingsMount = manageSettingsMenuMount();
settingsMount && settingsMount.mount(() => (() => {
var _el$ = _tmpl$61();
return _el$.$$click = (event) => {
event.preventDefault(), event.stopPropagation(), gState.setSettingsMenuOpen(!0);
}, insert(_el$, () => texts_default.settings.menuLabel), _el$;
})());
}), !gState.settings.touchUiEnabled && galleryPage && preview && previewCache && allowFeatureFailure("Desktop Read button", () => {
manageGalleryContinueReadingButtonMount().mount(() => createComponent(GalleryReadButton, {
previewCache,
variant: "gallery"
}));
}), galleryPage && previewCache && previewMount ? allowFeatureFailure("Gallery Preview enhancements", () => {
previewMount.mount(() => [createComponent(ScrollPreview, {
actionsRef: (actions) => {
gState.scrollPreviewActions = actions;
},
get continuePageNum() {
return memo(() => !!(gState.settings.readHistoryEnabled && gState.readProgress().hasHistory))() ? gState.readProgress().currentPage : null;
},
onExitPreview: (previewIndex) => {
previewIndex !== previewCache.current().data.currentIndex && (gState.settings.enhanceThumbsGridsEnabled ? previewCache.select(previewIndex).catch(reportReaderOpenError) : window.location.assign(previewUrlForIndex(previewIndex, previewCache.current().data.currentUrl)));
},
onLoadError: reportReaderOpenError,
onOpenChange: (open) => {
gState.scrollPreviewOpen = open;
},
onOpenPage: (pageUrl, pageNum) => openGalleryPage(previewCache, pageUrl, pageNum),
previewCache
}), memo(() => memo(() => !!gState.settings.enhanceThumbsGridsEnabled)() ? createComponent(ThumbsGrids, {
actionsRef: (actions) => {
gState.thumbsGridsActions = actions;
},
onLoadError: reportReaderOpenError,
previewCache
}) : null)]);
}) : galleryPage && preview && previewCache && allowFeatureFailure("Original thumbnail grid", () => {
preview.elems.mount?.remove();
}), gState.settings.enhanceSearchGridsEnabled && searchResultsDom && (searchResultsDom.data.previousUrl || searchResultsDom.data.nextUrl) && allowFeatureFailure("Enhanced Search pagination", () => {
createAppMount().mount(() => createComponent(EnhanceSearchGrids, {
source: searchResultsDom,
onPageChange: (source) => {
allowFeatureFailure("Changed Search page", () => {
gState.searchResults = source, gState.settings.touchUiEnabled && source.handle.updateResultColumns(gState.columnsEnabled()), updateSearchGridModeSelector(), gState.settings.openGalleryInNewTab && source.handle.ensureGalleryLinksOpenInNewTab(), touchResultsDom?.handle.updateTouchResultsLayout(), searchGridEnabled && manageSearchGrids(), updateSearchReadHistoryAppearance();
});
}
}));
}), gState.settings.searchHistoryEnabled && searchTextInput && allowFeatureFailure("Search history", () => {
createAppMount().mount(() => createComponent(SearchHistory, {
source: searchTextInput
}));
});
}
function injectTouchUI(page2, previewCache) {
let galleryPage = page2.type === "gallery", searchPage = page2.type === "search" || page2.type === "favorites", resultsPage = searchPage || page2.type === "readHistory", preview = previewCache?.current() ?? null, columnsAvailable = galleryPage || page2.type === "readHistory" || searchPage && state.search.grid.value, resultsDom = resultsPage ? allowFeatureFailure("Touch results layout", () => manageTouchResultsPage(page2)) : null;
return allowFeatureFailure("Touch top bar", () => {
let topBarDom = manageTopBar();
topBarDom && topBarDom.elems.mount.mount(() => createComponent(TouchTopBar, {
get historyHref() {
return memo(() => !!gState.settings.readHistoryEnabled)() ? readHistoryUrl() : void 0;
},
get uiScale() {
return {
value: gState.uiScale,
onChange: setCurrentUiScale
};
},
get columns() {
return columnsAvailable ? {
enabled: gState.columnsEnabled,
onChange: setCurrentColumnsEnabled
} : void 0;
},
source: topBarDom,
onSettingsMenuOpen: () => {
gState.setSettingsMenuOpen(!0);
}
}));
}), (galleryPage || resultsPage) && allowFeatureFailure("Back to top", () => {
createAppMount("ehpeek-back-to-top-host").mount(() => createComponent(BackToTop, {}));
}), galleryPage && (allowFeatureFailure("Touch GalleryInfo", () => {
mutateGalleryTouchLayout();
let galleryInfoDom = manageGalleryInfo(preview?.data ?? null);
galleryInfoDom && (galleryInfoDom.handle.installGalleryInfoPanel(), galleryInfoDom.elems.mount.mount(() => createComponent(GalleryInfoPanel, {
source: galleryInfoDom,
get primaryAction() {
return preview && previewCache ? createComponent(GalleryReadButton, {
previewCache,
variant: "touchGallery"
}) : void 0;
}
})), preview && (gState.galleryWideLayout = mutateGalleryWideLayout(galleryInfoDom, preview, gState.columnsEnabled())));
}), allowFeatureFailure("Touch Gallery comments", () => {
mutateGalleryCommentsTouch();
})), searchPage && allowFeatureFailure("Touch Search panel", () => {
let searchPanelDom = manageSearchPanel();
searchPanelDom && (searchPanelDom.elems.mount.mount(() => createComponent(TouchSearchPanel, {
source: searchPanelDom,
get after() {
return memo(() => !!resultsDom?.data.favoritesCategory)() ? createComponent(FavoritesCategorySelect, {
source: resultsDom
}) : void 0;
}
})), searchPanelDom.elems.categoryToggleMount && searchPanelDom.elems.categoryToggleMount.mount(() => createComponent(TouchSearchCategoryToggle, {
source: searchPanelDom
})), searchPanelDom.elems.advancedToggleMount && searchPanelDom.elems.advancedToggleMount.mount(() => createComponent(TouchSearchOptionToggle, {
option: "advancedOptions",
source: searchPanelDom
})), searchPanelDom.elems.fileSearchToggleMount && searchPanelDom.elems.fileSearchToggleMount.mount(() => createComponent(TouchSearchOptionToggle, {
option: "fileSearch",
source: searchPanelDom
})), searchPanelDom.elems.searchActionMount.mount(() => createComponent(TouchSearchAction, {
action: "search",
source: searchPanelDom
})), searchPanelDom.elems.clearActionMount && searchPanelDom.elems.clearActionMount.mount(() => createComponent(TouchSearchAction, {
action: "clear",
source: searchPanelDom
})));
}), resultsDom;
}
async function injectPage(page2) {
updateUiScale();
let galleryPage = page2.type === "gallery", searchPage = page2.type === "search" || page2.type === "favorites";
if (page2.type === "settings") {
let titlePreference = extractGalleryTitlePreference();
titlePreference && state.gallery.titlePreference.set(titlePreference);
}
page2.type === "readHistory" && allowFeatureFailure("Read History page", () => {
let records = loadReadHistoryRecords(), pageCount = Math.max(1, Math.ceil(records.length / 25)), pageIndex = Math.min(page2.pageIndex, pageCount - 1), items = records.map((record) => ({
currentPage: record.pageNum,
galleryId: record.galleryId,
info: record.gallery,
token: record.token,
totalPages: record.totalPages,
updatedAt: record.updatedAt
})), titlePreference = state.gallery.titlePreference.reload(), historyDom = manageReadHistoryPage(items.slice(pageIndex * 25, (pageIndex + 1) * 25), titlePreference);
gState.readHistoryPage = historyDom, gState.settings.touchUiEnabled && historyDom?.handle.updateResultColumns(gState.columnsEnabled()), historyDom?.elems.navigationTopMount.mount(() => createComponent(ReadHistoryPage, {
initialPageIndex: pageIndex,
items,
pageSize: 25,
source: historyDom
}));
});
let galleryPreview = galleryPage ? allowFeatureFailure("Gallery Preview", () => manageGalleryPreview()) : null, galleryPreviewCache = galleryPreview ? allowFeatureFailure("Gallery Preview cache", () => createGalleryPreviewCache(galleryPreview)) : null;
page2.type === "gallery" && galleryPreview && allowFeatureFailure("Gallery Read History", () => {
if (!gState.settings.readHistoryEnabled) {
gState.setReadProgress({
currentPage: 1,
hasHistory: !1,
totalPages: galleryPreview.data.totalImages
});
return;
}
let existing = loadReadHistory(page2.galleryId, page2.token), galleryInfo = extractGalleryHistoryInfo(), record = existing;
gState.settings.includeUnreadHistoryEnabled ? record = recordGalleryVisit(page2.galleryId, page2.token, galleryPreview.data.totalImages, galleryInfo) : existing && (record = updateReadHistoryGalleryInfo(page2.galleryId, page2.token, galleryInfo)), gState.setReadProgress({
currentPage: record?.pageNum && record.pageNum > 0 ? record.pageNum : 1,
hasHistory: !!(record && record.pageNum > 0),
totalPages: record?.totalPages ?? galleryPreview.data.totalImages
});
});
let searchTextInput = searchPage ? allowFeatureFailure("Search text input", () => manageSearchTextInput()) : null, searchResultsSource = searchPage ? allowFeatureFailure("Search results", () => manageSearchResults()) : null;
if (gState.searchResults = searchResultsSource, gState.settings.touchUiEnabled && searchResultsSource?.handle.updateResultColumns(gState.columnsEnabled()), gState.settings.myTagsEnabled) {
if (page2.type === "myTags")
allowAsyncFeatureFailure("My Tags refresh", async () => {
let currentMyTags = extractMyTagsPageData();
await refreshMyTags(currentMyTags);
});
else if (galleryPage) {
let myTagAppearances = loadMyTagAppearances();
myTagAppearances ? allowFeatureFailure("Gallery My Tags appearance", () => {
mutateGalleryMyTags(myTagAppearances);
}) : allowAsyncFeatureFailure("My Tags appearance", async () => {
let appearances = await refreshMyTags();
appearances && mutateGalleryMyTags(appearances);
});
}
}
gState.settings.readHistoryEnabled && page2.type === "image" && allowFeatureFailure("Image Read History", () => {
let gallery2 = extractImageGalleryPage();
if (gallery2?.galleryId === page2.galleryId) {
let previous = loadReadHistory(gallery2.galleryId, gallery2.token);
new ReadHistorySession({
gallery: previous?.gallery,
galleryId: gallery2.galleryId,
token: gallery2.token,
totalPages: previous?.totalPages
}).update(page2.pageNum, previous?.totalPages);
}
});
let touchResultsDom = gState.settings.touchUiEnabled ? injectTouchUI(page2, galleryPreviewCache) : null;
injectEnhanceUI(page2, galleryPreviewCache, searchTextInput, searchResultsSource, touchResultsDom), page2.type === "gallery" && state.reader.enabled.value && page2.peekPage !== null && galleryPreviewCache && allowAsyncFeatureFailure("Reader deep link", () => openReaderFromHash(readerCallbacks, galleryPreviewCache, readerViewport));
}
ehSyringe_exports.initialize();
var historyRouteActive = extractPageType().type === "readHistory";
window.addEventListener("hashchange", () => {
let nextHistoryRouteActive = extractPageType().type === "readHistory";
historyRouteActive !== nextHistoryRouteActive && window.location.reload(), historyRouteActive = nextHistoryRouteActive;
});
async function startApp() {
document.readyState === "loading" && await new Promise((resolve) => {
document.addEventListener("DOMContentLoaded", () => resolve(), {
once: !0
});
});
let page2 = extractPageType(), onViewportResize = () => {
updateUiScale(), updateColumnsLayout();
};
window.addEventListener("resize", onViewportResize, {
passive: !0
}), installSettingsMenu(), await injectPage(page2);
}
startApp().catch((error) => {
console.error("[ehpeek] App startup failed", error);
});
delegateEvents(["click"]);
})();