Greasy Fork is available in English.

vim comic viewer

Universal comic reader

Ce script ne devrait pas être installé directement. C'est une librairie créée pour d'autres scripts. Elle doit être inclus avec la commande // @require https://update.greasyfork.org/scripts/417893/1476400/vim%20comic%20viewer.js

// ==UserScript==
// @name           vim comic viewer
// @name:ko        vim comic viewer
// @description    Universal comic reader
// @description:ko 만화 뷰어 라이브러리
// @version        19.0.0
// @namespace      https://greasyfork.org/en/users/713014-nanikit
// @exclude        *
// @match          http://unused-field.space/
// @author         nanikit
// @license        MIT
// @grant          GM.addValueChangeListener
// @grant          GM.getValue
// @grant          GM.removeValueChangeListener
// @grant          GM.setValue
// @grant          GM.xmlHttpRequest
// @grant          unsafeWindow
// @resource       link:@headlessui/react       https://cdn.jsdelivr.net/npm/@headlessui/react@2.1.8/dist/headlessui.prod.cjs
// @resource       link:@stitches/react         https://cdn.jsdelivr.net/npm/@stitches/react@1.3.1-1/dist/index.cjs
// @resource       link:clsx                    https://cdn.jsdelivr.net/npm/clsx@2.1.1/dist/clsx.js
// @resource       link:fflate                  https://cdn.jsdelivr.net/npm/fflate@0.8.2/lib/browser.cjs
// @resource       link:jotai                   https://cdn.jsdelivr.net/npm/jotai@2.10.0/index.js
// @resource       link:jotai/react             https://cdn.jsdelivr.net/npm/jotai@2.10.0/react.js
// @resource       link:jotai/react/utils       https://cdn.jsdelivr.net/npm/jotai@2.10.0/react/utils.js
// @resource       link:jotai/utils             https://cdn.jsdelivr.net/npm/jotai@2.10.0/utils.js
// @resource       link:jotai/vanilla           https://cdn.jsdelivr.net/npm/jotai@2.10.0/vanilla.js
// @resource       link:jotai/vanilla/utils     https://cdn.jsdelivr.net/npm/jotai@2.10.0/vanilla/utils.js
// @resource       link:jotai-cache             https://cdn.jsdelivr.net/npm/jotai-cache@0.5.0/dist/cjs/atomWithCache.js
// @resource       link:overlayscrollbars       https://cdn.jsdelivr.net/npm/overlayscrollbars@2.10.0/overlayscrollbars.cjs
// @resource       link:overlayscrollbars-react https://cdn.jsdelivr.net/npm/overlayscrollbars-react@0.5.6/overlayscrollbars-react.cjs.js
// @resource       link:react                   https://cdn.jsdelivr.net/npm/react@18.3.1/cjs/react.production.min.js
// @resource       link:react-dom               https://cdn.jsdelivr.net/npm/react-dom@18.3.1/cjs/react-dom.production.min.js
// @resource       link:react-toastify          https://cdn.jsdelivr.net/npm/react-toastify@10.0.5/dist/react-toastify.js
// @resource       link:scheduler               https://cdn.jsdelivr.net/npm/scheduler@0.23.2/cjs/scheduler.production.min.js
// @resource       link:vcv-inject-node-env     data:,unsafeWindow.process=%7Benv:%7BNODE_ENV:%22production%22%7D%7D
// @resource       overlayscrollbars-css        https://cdn.jsdelivr.net/npm/overlayscrollbars@2.10.0/styles/overlayscrollbars.min.css
// @resource       react-toastify-css           https://cdn.jsdelivr.net/npm/react-toastify@10.0.5/dist/ReactToastify.css
// ==/UserScript==
"use strict";

var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var mod_exports = {};
__export(mod_exports, {
  Viewer: () => Viewer,
  download: () => download,
  initialize: () => initialize,
  utils: () => utils_exports
});
module.exports = __toCommonJS(mod_exports);
var import_vcv_inject_node_env = require("vcv-inject-node-env");
var React = __toESM(require("react"));
var deps_exports = {};
__export(deps_exports, {
  Dialog: () => import_react2.Dialog,
  Fragment: () => import_react3.Fragment,
  Provider: () => import_jotai.Provider,
  RESET: () => import_utils.RESET,
  Tab: () => import_react2.Tab,
  atom: () => import_jotai.atom,
  atomWithCache: () => import_jotai_cache.atomWithCache,
  atomWithStorage: () => import_utils.atomWithStorage,
  createContext: () => import_react3.createContext,
  createJSONStorage: () => import_utils.createJSONStorage,
  createRef: () => import_react3.createRef,
  createRoot: () => import_react_dom.createRoot,
  createStitches: () => import_react.createStitches,
  createStore: () => import_jotai.createStore,
  deferred: () => deferred,
  forwardRef: () => import_react3.forwardRef,
  loadable: () => import_utils.loadable,
  selectAtom: () => import_utils.selectAtom,
  splitAtom: () => import_utils.splitAtom,
  throttle: () => throttle,
  useAtom: () => import_jotai.useAtom,
  useAtomValue: () => import_jotai.useAtomValue,
  useCallback: () => import_react3.useCallback,
  useEffect: () => import_react3.useEffect,
  useId: () => import_react3.useId,
  useImperativeHandle: () => import_react3.useImperativeHandle,
  useLayoutEffect: () => import_react3.useLayoutEffect,
  useMemo: () => import_react3.useMemo,
  useReducer: () => import_react3.useReducer,
  useRef: () => import_react3.useRef,
  useSetAtom: () => import_jotai.useSetAtom,
  useState: () => import_react3.useState,
  useStore: () => import_jotai.useStore
});
var import_react = require("@stitches/react");
__reExport(deps_exports, require("fflate"));
function deferred() {
  let methods;
  let state = "pending";
  const promise = new Promise((resolve, reject) => {
    methods = {
      async resolve(value) {
        await value;
        state = "fulfilled";
        resolve(value);
      },
      reject(reason) {
        state = "rejected";
        reject(reason);
      }
    };
  });
  Object.defineProperty(promise, "state", { get: () => state });
  return Object.assign(promise, methods);
}
function throttle(fn, timeframe) {
  let lastExecution = NaN;
  let flush = null;
  const throttled = (...args) => {
    flush = () => {
      try {
        fn.call(throttled, ...args);
      } finally {
        lastExecution = Date.now();
        flush = null;
      }
    };
    if (throttled.throttling) {
      return;
    }
    flush?.();
  };
  throttled.clear = () => {
    lastExecution = NaN;
  };
  throttled.flush = () => {
    lastExecution = NaN;
    flush?.();
    throttled.clear();
  };
  Object.defineProperties(throttled, {
    throttling: { get: () => Date.now() - lastExecution <= timeframe },
    lastExecution: { get: () => lastExecution }
  });
  return throttled;
}
var import_jotai = require("jotai");
var import_jotai_cache = require("jotai-cache");
var import_utils = require("jotai/utils");
var import_react2 = require("@headlessui/react");
var import_react3 = require("react");
var import_react_dom = require("react-dom");
var rootAtom = (0, import_jotai.atom)(null);
var viewerOptionsAtom = (0, import_jotai.atom)({});
var viewerStatusAtom = (0, import_jotai.atom)("idle");
var en_default = {
  "@@locale": "en",
  settings: "Settings",
  help: "Help",
  maxZoomOut: "Maximum zoom out",
  maxZoomIn: "Maximum zoom in",
  singlePageCount: "single page count",
  backgroundColor: "Background color",
  leftToRight: "Left to right",
  reset: "Reset",
  doYouReallyWantToReset: "Do you really want to reset?",
  errorIsOccurred: "Error is occurred.",
  failedToLoadImage: "Failed to load image.",
  loading: "Loading...",
  fullScreenRestorationGuide: "Enter full screen yourself if you want to keep the viewer open in full screen.",
  useFullScreen: "Use full screen",
  downloading: "Downloading...",
  cancel: "CANCEL",
  downloadComplete: "Download complete.",
  errorOccurredWhileDownloading: "Error occurred while downloading.",
  keyBindings: "Key bindings",
  toggleViewer: "Toggle viewer",
  toggleFullscreenSetting: "Toggle fullscreen setting",
  nextPage: "Next page",
  previousPage: "Previous page",
  download: "Download",
  refresh: "Refresh",
  increaseSinglePageCount: "Increase single page count",
  decreaseSinglePageCount: "Decrease single page count",
  anchorSinglePageCount: "Set single page view until before current page"
};
var ko_default = {
  "@@locale": "ko",
  settings: "설정",
  help: "도움말",
  maxZoomOut: "최대 축소",
  maxZoomIn: "최대 확대",
  singlePageCount: "한쪽 페이지 수",
  backgroundColor: "배경색",
  leftToRight: "왼쪽부터 보기",
  reset: "초기화",
  doYouReallyWantToReset: "정말 초기화하시겠어요?",
  errorIsOccurred: "에러가 발생했습니다.",
  failedToLoadImage: "이미지를 불러오지 못했습니다.",
  loading: "로딩 중...",
  fullScreenRestorationGuide: "뷰어 전체 화면을 유지하려면 직접 전체 화면을 켜 주세요 (F11).",
  useFullScreen: "전체 화면",
  downloading: "다운로드 중...",
  cancel: "취소",
  downloadComplete: "다운로드 완료",
  errorOccurredWhileDownloading: "다운로드 도중 오류가 발생했습니다",
  keyBindings: "단축키",
  toggleViewer: "뷰어 전환",
  toggleFullscreenSetting: "전체화면 설정 전환",
  nextPage: "다음 페이지",
  previousPage: "이전 페이지",
  download: "다운로드",
  refresh: "새로고침",
  increaseSinglePageCount: "한쪽 페이지 수 늘리기",
  decreaseSinglePageCount: "한쪽 페이지 수 줄이기",
  anchorSinglePageCount: "현재 페이지 전까지 한쪽 페이지로 설정"
};
var translations = { en: en_default, ko: ko_default };
var i18nStringsAtom = (0, import_jotai.atom)(getLanguage());
var i18nAtom = (0, import_jotai.atom)((get) => get(i18nStringsAtom), (_get, set) => {
  set(i18nStringsAtom, getLanguage());
});
i18nAtom.onMount = (set) => {
  addEventListener("languagechange", set);
  return () => {
    removeEventListener("languagechange", set);
  };
};
function getLanguage() {
  for (const language of navigator.languages) {
    const locale = language.split("-")[0];
    if (!locale) {
      continue;
    }
    const translation = translations[locale];
    if (translation) {
      return translation;
    }
  }
  return en_default;
}
var { styled, css, keyframes } = (0, import_react.createStitches)({});
function DownloadCancel({ onClick }) {
  const strings = (0, import_jotai.useAtomValue)(i18nAtom);
  return  React.createElement(SpaceBetween, null,  React.createElement("p", null, strings.downloading),  React.createElement("button", { onClick }, strings.cancel));
}
var SpaceBetween = styled("div", {
  display: "flex",
  flexFlow: "row nowrap",
  justifyContent: "space-between"
});
var MAX_RETRY_COUNT = 6;
var MAX_SAME_URL_RETRY_COUNT = 2;
function isDelay(sourceOrDelay) {
  return sourceOrDelay === void 0 || typeof sourceOrDelay !== "string" && !sourceOrDelay.src;
}
function toAdvancedObject(sourceOrDelay) {
  return isDelay(sourceOrDelay) ? { src: void 0 } : toAdvancedSource(sourceOrDelay);
}
function toAdvancedSource(source) {
  return typeof source === "string" ? { type: "image", src: source } : source;
}
async function* getMediaIterable({ media, index, comic, maxSize }) {
  if (!isDelay(media)) {
    yield getUrl(media);
  }
  if (!comic) {
    return;
  }
  let previous;
  let retryCount = 0;
  let sameUrlRetryCount = 0;
  while (sameUrlRetryCount <= MAX_SAME_URL_RETRY_COUNT && retryCount <= MAX_RETRY_COUNT) {
    const hadError = media !== void 0 || retryCount > 0;
    const medias = await comic({ cause: hadError ? "error" : "load", page: index, maxSize });
    const next = medias[index];
    if (isDelay(next)) {
      continue;
    }
    const url = getUrl(next);
    yield url;
    retryCount++;
    if (previous === url) {
      sameUrlRetryCount++;
      continue;
    }
    previous = url;
  }
}
function getUrl(source) {
  return typeof source === "string" ? source : source.src;
}
var isGmFetchAvailable = typeof GM.xmlHttpRequest === "function";
async function gmFetch(url, init) {
  const method = init?.body ? "POST" : "GET";
  const response = await GM.xmlHttpRequest({
    method,
    url,
    headers: {
      referer: `${location.origin}/`,
      ...init?.headers
    },
    responseType: init?.type === "text" ? void 0 : init?.type,
    data: init?.body
  });
  return response;
}
async function download(comic, options) {
  const { onError, onProgress, signal } = options || {};
  let startedCount = 0;
  let resolvedCount = 0;
  let rejectedCount = 0;
  let status = "ongoing";
  const pages = await comic({ cause: "download", maxSize: { width: Infinity, height: Infinity } });
  const digit = Math.floor(Math.log10(pages.length)) + 1;
  return archiveWithReport();
  async function archiveWithReport() {
    const result = await Promise.all(pages.map(downloadWithReport));
    if (signal?.aborted) {
      reportProgress({ transition: "cancelled" });
      signal.throwIfAborted();
    }
    const pairs = await Promise.all(result.map(toPair));
    const data = Object.assign({}, ...pairs);
    const value = deferred();
    const abort = (0, deps_exports.zip)(data, { level: 0 }, (error, array) => {
      if (error) {
        reportProgress({ transition: "error" });
        value.reject(error);
      } else {
        reportProgress({ transition: "complete" });
        value.resolve(array);
      }
    });
    signal?.addEventListener("abort", abort, { once: true });
    return value;
  }
  async function downloadWithReport(source, pageIndex) {
    const errors = [];
    startedCount++;
    reportProgress();
    for await (const event of downloadImage({ media: source, pageIndex })) {
      if ("error" in event) {
        errors.push(event.error);
        onError?.(event.error);
        continue;
      }
      if (event.url) {
        resolvedCount++;
      } else {
        rejectedCount++;
      }
      reportProgress();
      return event;
    }
    return {
      url: "",
      blob: new Blob([errors.map((x) => `${x}`).join("\n\n")])
    };
  }
  async function* downloadImage({ media, pageIndex }) {
    const maxSize = { width: Infinity, height: Infinity };
    const mediaParams = { media, index: pageIndex, comic, maxSize };
    for await (const url of getMediaIterable(mediaParams)) {
      if (signal?.aborted) {
        break;
      }
      try {
        const blob = await fetchBlobWithCacheIfPossible(url, signal);
        yield { url, blob };
      } catch (error) {
        yield await fetchBlobIgnoringCors(url, { signal, fetchError: error });
      }
    }
  }
  async function toPair({ url, blob }, index) {
    const array = new Uint8Array(await blob.arrayBuffer());
    const pad = `${index}`.padStart(digit, "0");
    const name = `${pad}${guessExtension(array) ?? getExtension(url)}`;
    return { [name]: array };
  }
  function reportProgress({ transition } = {}) {
    if (status !== "ongoing") {
      return;
    }
    if (transition) {
      status = transition;
    }
    onProgress?.({
      total: pages.length,
      started: startedCount,
      settled: resolvedCount + rejectedCount,
      rejected: rejectedCount,
      status
    });
  }
}
function getExtension(url) {
  if (!url) {
    return ".txt";
  }
  const extension = url.match(/\.[^/?#]{3,4}?(?=[?#]|$)/);
  return extension?.[0] || ".jpg";
}
function guessExtension(array) {
  const { 0: a, 1: b, 2: c, 3: d } = array;
  if (a === 255 && b === 216 && c === 255) {
    return ".jpg";
  }
  if (a === 137 && b === 80 && c === 78 && d === 71) {
    return ".png";
  }
  if (a === 82 && b === 73 && c === 70 && d === 70) {
    return ".webp";
  }
  if (a === 71 && b === 73 && c === 70 && d === 56) {
    return ".gif";
  }
}
async function fetchBlobWithCacheIfPossible(url, signal) {
  const response = await fetch(url, { signal });
  return await response.blob();
}
async function fetchBlobIgnoringCors(url, { signal, fetchError }) {
  if (isCrossOrigin(url) && !isGmFetchAvailable) {
    return {
      error: new Error(
        "It could be a CORS issue but cannot use GM.xmlhttpRequest",
        { cause: fetchError }
      )
    };
  }
  try {
    const response = await gmFetch(url, { signal, type: "blob" });
    if (response.status >= 400) {
      const body = await response.response.text();
      const message = `failed to load ${url} with HTTP ${response.status} ${response.statusText}
${body}`;
      return { error: new Error(message) };
    }
    return { url, blob: response.response };
  } catch (error) {
    if (isGmCancelled(error)) {
      return { error: new Error("download aborted") };
    } else {
      return { error: fetchError };
    }
  }
}
function isCrossOrigin(url) {
  return new URL(url).origin !== location.origin;
}
function isGmCancelled(error) {
  return error instanceof Function;
}
var utils_exports = {};
__export(utils_exports, {
  getSafeFileName: () => getSafeFileName,
  insertCss: () => insertCss,
  isTyping: () => isTyping,
  save: () => save,
  saveAs: () => saveAs,
  timeout: () => timeout,
  waitDomContent: () => waitDomContent
});
var timeout = (millisecond) => new Promise((resolve) => setTimeout(resolve, millisecond));
var waitDomContent = (document2) => document2.readyState === "loading" ? new Promise((r) => document2.addEventListener("readystatechange", r, { once: true })) : true;
var insertCss = (css2) => {
  const style = document.createElement("style");
  style.innerHTML = css2;
  document.head.append(style);
};
var isTyping = (event) => event.target?.tagName?.match?.(/INPUT|TEXTAREA/) || event.target?.isContentEditable;
var saveAs = async (blob, name) => {
  const a = document.createElement("a");
  a.download = name;
  a.rel = "noopener";
  a.href = URL.createObjectURL(blob);
  a.click();
  await timeout(4e4);
  URL.revokeObjectURL(a.href);
};
var getSafeFileName = (str) => {
  return str.replace(/[<>:"/\\|?*\x00-\x1f]+/gi, "").trim() || "download";
};
var save = (blob) => {
  return saveAs(blob, `${getSafeFileName(document.title)}.zip`);
};
var import_react_toastify = require("react-toastify");
GM.getResourceText("react-toastify-css").then(insertCss);
var aborterAtom = (0, import_jotai.atom)(null);
var cancelDownloadAtom = (0, import_jotai.atom)(null, (get) => {
  get(aborterAtom)?.abort();
});
var startDownloadAtom = (0, import_jotai.atom)(null, async (get, set, options) => {
  const aborter = new AbortController();
  set(aborterAtom, (previous) => {
    previous?.abort();
    return aborter;
  });
  const viewerOptions = get(viewerOptionsAtom);
  const source = options?.source ?? viewerOptions.source;
  if (!source) {
    return;
  }
  let toastId = null;
  addEventListener("beforeunload", confirmDownloadAbort);
  try {
    toastId = (0, import_react_toastify.toast)( React.createElement(DownloadCancel, { onClick: aborter.abort }), { autoClose: false, progress: 0 });
    return await download(source, {
      onProgress: reportProgress,
      onError: logIfNotAborted,
      signal: aborter.signal
    });
  } finally {
    removeEventListener("beforeunload", confirmDownloadAbort);
  }
  async function reportProgress(event) {
    if (!toastId) {
      return;
    }
    const { total, started, settled, rejected, status } = event;
    const value = started / total * 0.1 + settled / total * 0.89;
    switch (status) {
      case "ongoing":
        import_react_toastify.toast.update(toastId, { type: rejected > 0 ? "warning" : "default", progress: value });
        break;
      case "complete":
        import_react_toastify.toast.update(toastId, {
          type: "success",
          render: get(i18nAtom).downloadComplete,
          progress: 0.9999
        });
        await timeout(1e3);
        import_react_toastify.toast.done(toastId);
        break;
      case "error":
        import_react_toastify.toast.update(toastId, {
          type: "error",
          render: get(i18nAtom).errorOccurredWhileDownloading,
          progress: 0
        });
        break;
      case "cancelled":
        import_react_toastify.toast.done(toastId);
        break;
    }
  }
});
var downloadAndSaveAtom = (0, import_jotai.atom)(null, async (_get, set, options) => {
  const zip2 = await set(startDownloadAtom, options);
  if (zip2) {
    await save(new Blob([zip2]));
  }
});
function logIfNotAborted(error) {
  if (isNotAbort(error)) {
    console.error(error);
  }
}
function isNotAbort(error) {
  return !/aborted/i.test(`${error}`);
}
function confirmDownloadAbort(event) {
  event.preventDefault();
  event.returnValue = "";
}
var import_jotai2 = require("jotai");
var gmStorage = {
  getItem: GM.getValue,
  setItem: GM.setValue,
  removeItem: (key) => GM.deleteValue(key),
  subscribe: (key, callback) => {
    const idPromise = GM.addValueChangeListener(
      key,
      (_key, _oldValue, newValue) => callback(newValue)
    );
    return async () => {
      const id = await idPromise;
      await GM.removeValueChangeListener(id);
    };
  }
};
function atomWithGmValue(key, defaultValue) {
  return (0, import_utils.atomWithStorage)(key, defaultValue, gmStorage, { getOnInit: true });
}
var jsonSessionStorage = (0, import_utils.createJSONStorage)(() => sessionStorage);
function atomWithSession(key, defaultValue) {
  return (0, import_utils.atomWithStorage)(
    key,
    defaultValue,
    jsonSessionStorage,
    { getOnInit: true }
  );
}
var defaultPreferences = {
  backgroundColor: "#eeeeee",
  singlePageCount: 1,
  maxZoomOutExponent: 3,
  maxZoomInExponent: 3,
  pageDirection: "rightToLeft",
  isFullscreenPreferred: false,
  fullscreenNoticeCount: 0
};
var scriptPreferencesAtom = (0, import_jotai2.atom)({});
var preferencesPresetAtom = (0, import_jotai2.atom)("default");
var [backgroundColorAtom] = atomWithPreferences("backgroundColor");
var [singlePageCountStorageAtom] = atomWithPreferences("singlePageCount");
var [maxZoomOutExponentAtom] = atomWithPreferences("maxZoomOutExponent");
var [maxZoomInExponentAtom] = atomWithPreferences("maxZoomInExponent");
var [pageDirectionAtom] = atomWithPreferences("pageDirection");
var [isFullscreenPreferredAtom, isFullscreenPreferredPromiseAtom] = atomWithPreferences(
  "isFullscreenPreferred"
);
var [fullscreenNoticeCountAtom, fullscreenNoticeCountPromiseAtom] = atomWithPreferences(
  "fullscreenNoticeCount"
);
var wasImmersiveAtom = atomWithSession("vim_comic_viewer.was_immersive", false);
function atomWithPreferences(key) {
  const asyncAtomAtom = (0, import_jotai2.atom)((get) => {
    const preset = get(preferencesPresetAtom);
    const qualifiedKey = `vim_comic_viewer.preferences.${preset}.${key}`;
    return atomWithGmValue(qualifiedKey, void 0);
  });
  const cacheAtom = (0, import_jotai_cache.atomWithCache)((get) => get(get(asyncAtomAtom)));
  const manualAtom = (0, import_jotai2.atom)((get) => get(cacheAtom), updater);
  const loadableAtom = (0, import_utils.loadable)(manualAtom);
  const effectiveAtom = (0, import_jotai2.atom)((get) => {
    const value = get(loadableAtom);
    if (value.state === "hasData" && value.data !== void 0) {
      return value.data;
    }
    return get(scriptPreferencesAtom)[key] ?? defaultPreferences[key];
  }, updater);
  return [effectiveAtom, manualAtom];
  function updater(get, set, update) {
    return set(
      get(asyncAtomAtom),
      (value) => typeof update === "function" ? Promise.resolve(value).then(update) : update
    );
  }
}
var globalCss = document.createElement("style");
globalCss.innerHTML = `html, body {
  overflow: hidden;
}`;
function hideBodyScrollBar(doHide) {
  if (doHide) {
    document.head.append(globalCss);
  } else {
    globalCss.remove();
  }
}
async function setFullscreenElement(element) {
  if (element) {
    await element.requestFullscreen?.();
  } else {
    await document.exitFullscreen?.();
  }
}
function focusWithoutScroll(element) {
  element?.focus({ preventScroll: true });
}
function isUserGesturePermissionError(error) {
  return error?.message === "Permissions check failed";
}
var fullscreenElementAtom = (0, import_jotai.atom)(null);
var viewerElementAtom = (0, import_jotai.atom)(null);
var isViewerFullscreenAtom = (0, import_jotai.atom)((get) => {
  const viewerElement = get(viewerElementAtom);
  return !!viewerElement && viewerElement === get(fullscreenElementAtom);
});
var isImmersiveAtom = (0, import_jotai.atom)(false);
var isViewerImmersiveAtom = (0, import_jotai.atom)((get) => get(isImmersiveAtom));
var scrollBarStyleFactorAtom = (0, import_jotai.atom)(
  (get) => ({
    fullscreenElement: get(fullscreenElementAtom),
    viewerElement: get(viewerElementAtom)
  }),
  (get, set, factors) => {
    const { fullscreenElement, viewerElement, isImmersive } = factors;
    if (fullscreenElement !== void 0) {
      set(fullscreenElementAtom, fullscreenElement);
    }
    if (viewerElement !== void 0) {
      set(viewerElementAtom, viewerElement);
    }
    if (isImmersive !== void 0) {
      set(wasImmersiveAtom, isImmersive);
      set(isImmersiveAtom, isImmersive);
    }
    const canScrollBarDuplicate = !get(isViewerFullscreenAtom) && get(isImmersiveAtom);
    hideBodyScrollBar(canScrollBarDuplicate);
  }
);
var viewerFullscreenAtom = (0, import_jotai.atom)((get) => {
  get(isFullscreenPreferredAtom);
  return get(isViewerFullscreenAtom);
}, async (get, _set, value) => {
  const element = value ? get(viewerElementAtom) : null;
  const { fullscreenElement } = get(scrollBarStyleFactorAtom);
  if (element === fullscreenElement) {
    return true;
  }
  const fullscreenChange = new Promise((resolve) => {
    addEventListener("fullscreenchange", resolve, { once: true });
  });
  try {
    await setFullscreenElement(element);
    await fullscreenChange;
    return true;
  } catch (error) {
    if (isUserGesturePermissionError(error)) {
      return false;
    }
    throw error;
  }
});
var transitionDeferredAtom = (0, import_jotai.atom)({});
var transitionLockAtom = (0, import_jotai.atom)(null, async (get, set) => {
  const { deferred: previousLock } = get(transitionDeferredAtom);
  const lock = deferred();
  set(transitionDeferredAtom, { deferred: lock });
  await previousLock;
  return { deferred: lock };
});
var isFullscreenPreferredSettingsAtom = (0, import_jotai.atom)(
  (get) => get(isFullscreenPreferredAtom),
  async (get, set, value) => {
    const promise = set(isFullscreenPreferredAtom, value);
    const appliedValue = value === import_utils.RESET ? (await promise, get(isFullscreenPreferredAtom)) : value;
    const lock = await set(transitionLockAtom);
    try {
      const wasImmersive = get(wasImmersiveAtom);
      const shouldEnterFullscreen = appliedValue && wasImmersive;
      await set(viewerFullscreenAtom, shouldEnterFullscreen);
    } finally {
      lock.deferred.resolve();
    }
  }
);
var beforeRepaintAtom = (0, import_jotai.atom)({});
var useBeforeRepaint = () => {
  const { task } = (0, import_jotai.useAtomValue)(beforeRepaintAtom);
  (0, import_react3.useLayoutEffect)(() => {
    task?.();
  }, [task]);
};
function getCurrentRow({ elements, viewportHeight }) {
  if (!elements.length) {
    return;
  }
  const scrollCenter = viewportHeight / 2;
  const pages = elements.map((page) => ({
    page,
    rect: page.getBoundingClientRect()
  }));
  return pages.filter(isCenterCrossing);
  function isCenterCrossing({ rect: { y, height } }) {
    return y <= scrollCenter && y + height >= scrollCenter;
  }
}
function isVisible(element) {
  if ("checkVisibility" in element) {
    return element.checkVisibility();
  }
  const { x, y, width, height } = element.getBoundingClientRect();
  const elements = document.elementsFromPoint(x + width / 2, y + height / 2);
  return elements.includes(element);
}
function hasNoticeableDifference(middle, lastMiddle) {
  return Math.abs(middle - lastMiddle) > 0.01;
}
function getInPageRatio({ page, viewportHeight }) {
  const scrollCenter = viewportHeight / 2;
  const { y, height } = page.rect;
  return 1 - (y + height - scrollCenter) / height;
}
function getScrollPage(middle, container) {
  const element = getPagesFromScrollElement(container)?.item(Math.floor(middle));
  return element instanceof HTMLElement ? element : null;
}
function getCurrentMiddleFromScrollElement({
  scrollElement,
  previousMiddle
}) {
  const elements = getPagesFromScrollElement(scrollElement);
  if (!elements || !scrollElement) {
    return null;
  }
  return getPageScroll({
    elements: [...elements],
    viewportHeight: scrollElement.getBoundingClientRect().height,
    previousMiddle
  });
}
function getNewSizeIfResized({ scrollElement, previousSize }) {
  if (!scrollElement) {
    return;
  }
  const { width, height } = scrollElement.getBoundingClientRect();
  const scrollHeight = scrollElement.scrollHeight;
  const { width: previousWidth, height: previousHeight, scrollHeight: previousScrollHeight } = previousSize;
  const needsScrollRestoration = previousWidth === 0 || previousHeight === 0 || previousWidth !== width || previousHeight !== height || previousScrollHeight !== scrollHeight;
  return needsScrollRestoration ? { width, height, scrollHeight } : void 0;
}
function navigateByPointer(scrollElement, event) {
  const height = scrollElement?.clientHeight;
  if (!height || event.button !== 0) {
    return;
  }
  event.preventDefault();
  const isTop = event.clientY < height / 2;
  if (isTop) {
    goToPreviousArea(scrollElement);
  } else {
    goToNextArea(scrollElement);
  }
}
function goToPreviousArea(scrollElement) {
  const page = getCurrentPageFromScrollElement({ scrollElement, previousMiddle: Infinity });
  if (!page || !scrollElement) {
    return;
  }
  const viewerHeight = scrollElement.clientHeight;
  const ignorableHeight = viewerHeight * 0.05;
  const remainingHeight = scrollElement.scrollTop - Math.ceil(page.offsetTop) - 1;
  scrollElement.scrollBy({ top: getPreviousYDiff(page) });
  function getPreviousYDiff(page2) {
    if (remainingHeight > ignorableHeight) {
      const divisor = Math.ceil(remainingHeight / viewerHeight);
      return -Math.ceil(remainingHeight / divisor);
    } else {
      return getPreviousPageBottomOrStart(page2);
    }
  }
}
function goToNextArea(scrollElement) {
  const page = getCurrentPageFromScrollElement({ scrollElement, previousMiddle: 0 });
  if (!page || !scrollElement) {
    return;
  }
  const viewerHeight = scrollElement.clientHeight;
  const ignorableHeight = viewerHeight * 0.05;
  const scrollBottom = scrollElement.scrollTop + viewerHeight;
  const remainingHeight = page.offsetTop + page.clientHeight - Math.ceil(scrollBottom) - 1;
  scrollElement.scrollBy({ top: getNextYDiff(page) });
  function getNextYDiff(page2) {
    if (remainingHeight > ignorableHeight) {
      const divisor = Math.ceil(remainingHeight / viewerHeight);
      return Math.ceil(remainingHeight / divisor);
    } else {
      return getNextPageTopOrEnd(page2);
    }
  }
}
function toWindowScroll({ middle, lastMiddle, noSyncScroll, forFullscreen, scrollElement }) {
  if (noSyncScroll || !forFullscreen && !hasNoticeableDifference(middle, lastMiddle)) {
    return;
  }
  const page = getScrollPage(middle, scrollElement);
  const src = page?.querySelector("img[src], video[src]")?.src;
  if (!src) {
    return;
  }
  const original = findOriginElement(src, page);
  if (!original) {
    return;
  }
  const rect = original.getBoundingClientRect();
  const ratio = middle - Math.floor(middle);
  const top = scrollY + rect.y + rect.height * ratio - innerHeight / 2;
  return top;
}
function restoreScroll({ scrollable, middle }) {
  const page = getScrollPage(middle, scrollable);
  if (!page || !scrollable || scrollable.clientHeight < 1) {
    return false;
  }
  const { height: scrollableHeight } = scrollable.getBoundingClientRect();
  const { y: pageY, height: pageHeight } = page.getBoundingClientRect();
  const ratio = middle - Math.floor(middle);
  const restoredYDiff = pageY + pageHeight * ratio - scrollableHeight / 2;
  scrollable.scrollBy({ top: restoredYDiff });
  return true;
}
function getAbovePageIndex(scrollElement) {
  const children = getPagesFromScrollElement(scrollElement);
  if (!children || !scrollElement) {
    return;
  }
  const elements = [...children];
  const currentRow = getCurrentRow({ elements, viewportHeight: scrollElement.clientHeight });
  const firstPage = currentRow?.[0]?.page;
  return firstPage ? elements.indexOf(firstPage) : void 0;
}
function findOriginElement(src, page) {
  const fileName = src.split("/").pop()?.split("?")[0];
  const candidates = document.querySelectorAll(
    `img[src*="${fileName}"], video[src*="${fileName}"]`
  );
  const originals = [...candidates].filter(
    (media) => media.src === src && media.parentElement !== page && isVisible(media)
  );
  if (originals.length === 1) {
    return originals[0];
  }
  const links = document.querySelectorAll(`a[href*="${fileName}"`);
  const visibleLinks = [...links].filter(isVisible);
  if (visibleLinks.length === 1) {
    return visibleLinks[0];
  }
}
function getNextPageTopOrEnd(page) {
  const scrollable = page.offsetParent;
  if (!scrollable) {
    return;
  }
  const pageBottom = page.offsetTop + page.clientHeight;
  let cursor = page;
  while (cursor.nextElementSibling) {
    const next = cursor.nextElementSibling;
    if (pageBottom <= next.offsetTop) {
      return next.getBoundingClientRect().top;
    }
    cursor = next;
  }
  const { y, height } = cursor.getBoundingClientRect();
  return y + height;
}
function getPreviousPageBottomOrStart(page) {
  const scrollable = page.offsetParent;
  if (!scrollable) {
    return;
  }
  const pageTop = page.offsetTop;
  let cursor = page;
  while (cursor.previousElementSibling) {
    const previous = cursor.previousElementSibling;
    const previousBottom = previous.offsetTop + previous.clientHeight;
    if (previousBottom <= pageTop) {
      const { bottom } = previous.getBoundingClientRect();
      const { height: height2 } = scrollable.getBoundingClientRect();
      return bottom - height2;
    }
    cursor = previous;
  }
  const { y, height } = cursor.getBoundingClientRect();
  return y - height;
}
function getCurrentPageFromScrollElement({ scrollElement, previousMiddle }) {
  const middle = getCurrentMiddleFromScrollElement({ scrollElement, previousMiddle });
  if (!middle || !scrollElement) {
    return null;
  }
  return getScrollPage(middle, scrollElement);
}
function getPageScroll(params) {
  const currentPage = getCurrentPageFromElements(params);
  return currentPage ? getMiddle(currentPage) : void 0;
  function getMiddle(page) {
    const { viewportHeight, elements } = params;
    const ratio = getInPageRatio({ page, viewportHeight });
    return elements.indexOf(page.page) + ratio;
  }
}
function getCurrentPageFromElements({ elements, viewportHeight, previousMiddle }) {
  const currentRow = getCurrentRow({ elements, viewportHeight });
  if (!currentRow) {
    return;
  }
  return selectColumn(currentRow);
  function selectColumn(row) {
    const firstPage = row.find(({ page: page2 }) => page2 === elements[0]);
    if (firstPage) {
      return firstPage;
    }
    const lastPage = row.find(({ page: page2 }) => page2 === elements.at(-1));
    if (lastPage) {
      return lastPage;
    }
    const half = Math.floor(row.length / 2);
    if (row.length % 2 === 1) {
      return row[half];
    }
    const page = row[half]?.page;
    if (!page) {
      return;
    }
    const centerNextTop = elements.indexOf(page);
    const previousMiddlePage = previousMiddle < centerNextTop ? row[half - 1] : row[half];
    return previousMiddlePage;
  }
}
function getPagesFromScrollElement(scrollElement) {
  return scrollElement?.firstElementChild?.children;
}
function toViewerScroll({ scrollable, lastWindowToViewerMiddle, noSyncScroll }) {
  if (!scrollable || noSyncScroll) {
    return;
  }
  const viewerMedia = [
    ...scrollable.querySelectorAll("img[src], video[src]")
  ];
  const urlToViewerPages =  new Map();
  for (const media2 of viewerMedia) {
    urlToViewerPages.set(media2.src, media2);
  }
  const urls = [...urlToViewerPages.keys()];
  const media = getUrlMedia(urls);
  const siteMedia = media.filter((medium) => !viewerMedia.includes(medium));
  const visibleMedia = siteMedia.filter(isVisible);
  const viewportHeight = visualViewport?.height ?? innerHeight;
  const currentRow = getCurrentRow({ elements: visibleMedia, viewportHeight });
  if (!currentRow) {
    return;
  }
  const indexed = currentRow.map((sized) => [sized, getUrlIndex(sized.page, urls)]);
  const last = lastWindowToViewerMiddle - 0.5;
  const sorted = indexed.sort((a, b) => Math.abs(a[1] - last) - Math.abs(b[1] - last));
  const [page, index] = sorted[0] ?? [];
  if (!page || index === void 0) {
    return;
  }
  const pageRatio = getInPageRatio({ page, viewportHeight });
  const snappedRatio = Math.abs(pageRatio - 0.5) < 0.1 ? 0.5 : pageRatio;
  if (!hasNoticeableDifference(index + snappedRatio, lastWindowToViewerMiddle)) {
    return;
  }
  return index + snappedRatio;
}
function getUrlMedia(urls) {
  const media = document.querySelectorAll("img, video");
  return [...media].filter((medium) => getUrlIndex(medium, urls) !== -1);
}
function getUrlIndex(medium, urls) {
  if (medium instanceof HTMLImageElement) {
    const img = medium;
    const parent = img.parentElement;
    const imgUrlIndex = urls.findIndex((x) => x === img.src);
    const pictureUrlIndex = parent instanceof HTMLPictureElement ? getUrlIndexFromSrcset(parent, urls) : -1;
    return imgUrlIndex === -1 ? pictureUrlIndex : imgUrlIndex;
  } else if (medium instanceof HTMLVideoElement) {
    const video = medium;
    const videoUrlIndex = urls.findIndex((x) => x === video.src);
    const srcsetUrlIndex = getUrlIndexFromSrcset(video, urls);
    return videoUrlIndex === -1 ? srcsetUrlIndex : videoUrlIndex;
  }
  return -1;
}
function getUrlIndexFromSrcset(media, urls) {
  for (const url of getUrlsFromSources(media)) {
    const index = urls.findIndex((x) => x === url);
    if (index !== -1) {
      return index;
    }
  }
  return -1;
}
function getUrlsFromSources(picture) {
  const sources = [...picture.querySelectorAll("source")];
  return sources.flatMap((x) => getSrcFromSrcset(x.srcset));
}
function getSrcFromSrcset(srcset) {
  return srcset.split(",").map((x) => x.split(/\s+/)[0]).filter((x) => x !== void 0);
}
var scrollElementStateAtom = (0, import_jotai.atom)(null);
var scrollElementAtom = (0, import_jotai.atom)((get) => get(scrollElementStateAtom)?.div ?? null);
var scrollElementSizeAtom = (0, import_jotai.atom)({ width: 0, height: 0, scrollHeight: 0 });
var pageScrollMiddleAtom = (0, import_jotai.atom)(0.5);
var lastViewerToWindowMiddleAtom = (0, import_jotai.atom)(-1);
var lastWindowToViewerMiddleAtom = (0, import_jotai.atom)(-1);
var transferWindowScrollToViewerAtom = (0, import_jotai.atom)(null, (get, set) => {
  const scrollable = get(scrollElementAtom);
  const lastWindowToViewerMiddle = get(lastWindowToViewerMiddleAtom);
  const noSyncScroll = get(viewerOptionsAtom).noSyncScroll ?? false;
  const middle = toViewerScroll({ scrollable, lastWindowToViewerMiddle, noSyncScroll });
  if (!middle) {
    return;
  }
  set(pageScrollMiddleAtom, middle);
  set(lastWindowToViewerMiddleAtom, middle);
});
var transferViewerScrollToWindowAtom = (0, import_jotai.atom)(
  null,
  (get, set, { forFullscreen } = {}) => {
    const middle = get(pageScrollMiddleAtom);
    const scrollElement = get(scrollElementAtom);
    const lastMiddle = get(lastViewerToWindowMiddleAtom);
    const noSyncScroll = get(viewerOptionsAtom).noSyncScroll ?? false;
    const top = toWindowScroll({ middle, lastMiddle, scrollElement, noSyncScroll, forFullscreen });
    if (top !== void 0) {
      set(lastViewerToWindowMiddleAtom, middle);
      scroll({ behavior: "instant", top });
    }
  }
);
var synchronizeScrollAtom = (0, import_jotai.atom)(null, (get, set) => {
  const scrollElement = get(scrollElementAtom);
  if (!scrollElement) {
    return;
  }
  if (set(correctScrollAtom)) {
    return;
  }
  const middle = getCurrentMiddleFromScrollElement({
    scrollElement,
    previousMiddle: get(pageScrollMiddleAtom)
  });
  if (middle) {
    set(pageScrollMiddleAtom, middle);
    set(transferViewerScrollToWindowAtom);
  }
});
var correctScrollAtom = (0, import_jotai.atom)(null, (get, set) => {
  const scrollElement = get(scrollElementAtom);
  const previousSize = get(scrollElementSizeAtom);
  const newSize = getNewSizeIfResized({ scrollElement, previousSize });
  if (!newSize) {
    return false;
  }
  set(scrollElementSizeAtom, newSize);
  set(restoreScrollAtom);
  return true;
});
var restoreScrollAtom = (0, import_jotai.atom)(null, (get, set) => {
  const middle = get(pageScrollMiddleAtom);
  const scrollable = get(scrollElementAtom);
  const restored = restoreScroll({ scrollable, middle });
  if (restored) {
    set(beforeRepaintAtom, { task: () => set(correctScrollAtom) });
  }
});
var goNextAtom = (0, import_jotai.atom)(null, (get) => {
  goToNextArea(get(scrollElementAtom));
});
var goPreviousAtom = (0, import_jotai.atom)(null, (get) => {
  goToPreviousArea(get(scrollElementAtom));
});
var navigateAtom = (0, import_jotai.atom)(null, (get, _set, event) => {
  navigateByPointer(get(scrollElementAtom), event);
});
var singlePageCountAtom = (0, import_jotai.atom)(
  (get) => get(singlePageCountStorageAtom),
  async (get, set, value) => {
    const clampedValue = typeof value === "number" ? Math.max(0, value) : value;
    const middle = get(pageScrollMiddleAtom);
    const scrollElement = get(scrollElementAtom);
    await set(singlePageCountStorageAtom, clampedValue);
    set(beforeRepaintAtom, {
      task: () => {
        restoreScroll({ scrollable: scrollElement, middle });
        set(pageScrollMiddleAtom, middle);
      }
    });
  }
);
var anchorSinglePageCountAtom = (0, import_jotai.atom)(null, (get, set) => {
  const scrollElement = get(scrollElementAtom);
  const abovePageIndex = getAbovePageIndex(scrollElement);
  if (abovePageIndex !== void 0) {
    set(singlePageCountAtom, abovePageIndex);
  }
});
var maxSizeStateAtom = (0, import_jotai.atom)({ width: screen.width, height: screen.height });
var maxSizeAtom = (0, import_jotai.atom)(
  (get) => get(maxSizeStateAtom),
  (get, set, size) => {
    const current = get(maxSizeStateAtom);
    if (size.width <= current.width && size.height <= current.height) {
      return;
    }
    set(maxSizeStateAtom, {
      width: Math.max(size.width, current.width),
      height: Math.max(size.height, current.height)
    });
  }
);
var mediaSourcesAtom = (0, import_jotai.atom)([]);
var pageAtomsAtom = (0, import_jotai.atom)([]);
var refreshMediaSourceAtom = (0, import_jotai.atom)(null, async (get, set, params) => {
  const { source } = get(viewerOptionsAtom);
  if (!source) {
    return;
  }
  const medias = await source({ ...params, maxSize: get(maxSizeAtom) });
  if (source !== get(viewerOptionsAtom).source) {
    return;
  }
  if (!Array.isArray(medias)) {
    throw new Error(`Invalid comic source type: ${typeof medias}`);
  }
  set(mediaSourcesAtom, medias);
  if (params.cause === "load" && params.page === void 0) {
    set(
      pageAtomsAtom,
      medias.map((media, index) => createPageAtom({ initialSource: media, index, set }))
    );
  }
  if (params.page !== void 0) {
    return medias[params.page];
  }
});
function createPageAtom(params) {
  const { initialSource, index, set } = params;
  const triedUrls = [];
  let div = null;
  const stateAtom = (0, import_jotai.atom)({
    status: "loading",
    source: initialSource ? toAdvancedObject(initialSource) : { src: void 0 }
  });
  const loadAtom = (0, import_jotai.atom)(null, async (get, set2, cause) => {
    switch (cause) {
      case "load":
        triedUrls.length = 0;
        break;
      case "error":
        break;
    }
    if (isComplete()) {
      return;
    }
    let newSource;
    try {
      newSource = await set2(refreshMediaSourceAtom, { cause, page: index });
    } catch (error) {
      console.error(error);
      set2(stateAtom, (previous) => ({
        ...previous,
        status: "error",
        urls: Array.from(triedUrls)
      }));
      return;
    }
    if (isComplete()) {
      return;
    }
    if (isDelay(newSource)) {
      set2(stateAtom, { status: "error", urls: [], source: { src: void 0 } });
      return;
    }
    const source = toAdvancedSource(newSource);
    triedUrls.push(source.src);
    set2(stateAtom, { status: "loading", source });
    function isComplete() {
      return get(stateAtom).status === "complete";
    }
  });
  const aggregateAtom = (0, import_jotai.atom)((get) => {
    get(loadAtom);
    const state = get(stateAtom);
    const scrollElementSize = get(scrollElementSizeAtom);
    const compactWidthIndex = get(singlePageCountAtom);
    const maxZoomInExponent = get(maxZoomInExponentAtom);
    const maxZoomOutExponent = get(maxZoomOutExponentAtom);
    const { src, width, height } = state.source ?? {};
    const ratio = getImageToViewerSizeRatio({
      viewerSize: scrollElementSize,
      imgSize: { width, height }
    });
    const shouldBeOriginalSize = shouldMediaBeOriginalSize({
      maxZoomInExponent,
      maxZoomOutExponent,
      mediaRatio: ratio
    });
    const isLarge = ratio > 1;
    const canMessUpRow = shouldBeOriginalSize && isLarge;
    const mediaProps = { src, onError: reload };
    const divCss = {
      ...shouldBeOriginalSize ? { minHeight: scrollElementSize.height, height: "auto" } : { height: scrollElementSize.height },
      ...state.status !== "complete" ? { aspectRatio: width && height ? `${width} / ${height}` : "3 / 4" } : {}
    };
    const page = {
      index,
      state,
      div,
      setDiv: (newDiv) => {
        div = newDiv;
      },
      reloadAtom: loadAtom,
      fullWidth: index < compactWidthIndex || canMessUpRow,
      shouldBeOriginalSize,
      divCss,
      imageProps: state.source && state.source.type !== "video" ? { ...mediaProps, onLoad: setCompleteState } : void 0,
      videoProps: state.source?.type === "video" ? {
        ...mediaProps,
        controls: true,
        autoPlay: true,
        loop: true,
        muted: true,
        onLoadedMetadata: setCompleteState
      } : void 0
    };
    return page;
  });
  async function reload() {
    const isOverMaxRetry = triedUrls.length > MAX_RETRY_COUNT;
    const urlCountMap = triedUrls.reduce((acc, url) => {
      acc[url] = (acc[url] ?? 0) + 1;
      return acc;
    }, {});
    const isOverSameUrlRetry = Object.values(urlCountMap).some(
      (count) => count > MAX_SAME_URL_RETRY_COUNT
    );
    if (isOverMaxRetry || isOverSameUrlRetry) {
      set(stateAtom, (previous) => ({
        ...previous,
        status: "error",
        urls: [...new Set(triedUrls)]
      }));
      return;
    }
    set(stateAtom, (previous) => ({
      status: "loading",
      source: { ...previous.source, src: void 0 }
    }));
    await set(loadAtom, "error");
  }
  function setCompleteState(event) {
    const element = event.currentTarget;
    set(stateAtom, {
      status: "complete",
      source: {
        src: element.src,
        ...element instanceof HTMLImageElement ? {
          type: "image",
          width: element.naturalWidth,
          height: element.naturalHeight
        } : {
          type: "video",
          width: element.videoWidth,
          height: element.videoHeight
        }
      }
    });
  }
  if (isDelay(initialSource)) {
    set(loadAtom, "load");
  }
  return aggregateAtom;
}
function getImageToViewerSizeRatio({ viewerSize, imgSize }) {
  if (!imgSize.height && !imgSize.width) {
    return 1;
  }
  return Math.max(
    (imgSize.height ?? 0) / viewerSize.height,
    (imgSize.width ?? 0) / viewerSize.width
  );
}
function shouldMediaBeOriginalSize({ maxZoomOutExponent, maxZoomInExponent, mediaRatio }) {
  const minZoomRatio = Math.sqrt(2) ** maxZoomOutExponent;
  const maxZoomRatio = Math.sqrt(2) ** maxZoomInExponent;
  const isOver = minZoomRatio < mediaRatio || mediaRatio < 1 / maxZoomRatio;
  return isOver;
}
var externalFocusElementAtom = (0, import_jotai.atom)(null);
var setViewerImmersiveAtom = (0, import_jotai.atom)(null, async (get, set, value) => {
  const lock = await set(transitionLockAtom);
  try {
    await transactImmersive(get, set, value);
  } finally {
    lock.deferred.resolve();
  }
});
async function transactImmersive(get, set, value) {
  if (get(isViewerImmersiveAtom) === value) {
    return;
  }
  if (value) {
    set(externalFocusElementAtom, (previous) => previous ? previous : document.activeElement);
    set(transferWindowScrollToViewerAtom);
  }
  const scrollable = get(scrollElementAtom);
  if (!scrollable) {
    return;
  }
  const { fullscreenElement } = get(scrollBarStyleFactorAtom);
  try {
    if (get(isFullscreenPreferredAtom)) {
      const isAccepted = await set(viewerFullscreenAtom, value);
      if (!isAccepted) {
        const noticeCount = await get(fullscreenNoticeCountPromiseAtom) ?? 0;
        if (shouldShowF11Guide({ noticeCount })) {
          showF11Guide();
          return;
        }
      }
    }
  } finally {
    set(scrollBarStyleFactorAtom, { isImmersive: value });
    if (value) {
      focusWithoutScroll(scrollable);
    } else {
      if (fullscreenElement) {
        set(transferViewerScrollToWindowAtom, { forFullscreen: true });
      }
      const externalFocusElement = get(externalFocusElementAtom);
      focusWithoutScroll(externalFocusElement);
    }
  }
  function showF11Guide() {
    (0, import_react_toastify.toast)(get(i18nAtom).fullScreenRestorationGuide, {
      type: "info",
      onClose: () => {
        set(fullscreenNoticeCountPromiseAtom, (count) => (count ?? 0) + 1);
      }
    });
  }
}
var isBeforeUnloadAtom = (0, import_jotai.atom)(false);
var beforeUnloadAtom = (0, import_jotai.atom)(null, async (_get, set) => {
  set(isBeforeUnloadAtom, true);
  await waitUnloadFinishRoughly();
  set(isBeforeUnloadAtom, false);
});
beforeUnloadAtom.onMount = (set) => {
  addEventListener("beforeunload", set);
  return () => removeEventListener("beforeunload", set);
};
var fullscreenSynchronizationAtom = (0, import_jotai.atom)(
  (get) => {
    get(isBeforeUnloadAtom);
    return get(scrollBarStyleFactorAtom).fullscreenElement;
  },
  (get, set, element) => {
    const isFullscreenPreferred = get(isFullscreenPreferredAtom);
    const isFullscreen = element === get(scrollBarStyleFactorAtom).viewerElement;
    const wasImmersive = get(isViewerImmersiveAtom);
    const isViewerFullscreenExit = wasImmersive && !isFullscreen;
    const isNavigationExit = get(isBeforeUnloadAtom);
    const shouldExitImmersive = isFullscreenPreferred && isViewerFullscreenExit && !isNavigationExit;
    set(scrollBarStyleFactorAtom, {
      fullscreenElement: element,
      isImmersive: shouldExitImmersive ? false : void 0
    });
  }
);
fullscreenSynchronizationAtom.onMount = (set) => {
  const notify = () => set(document.fullscreenElement ?? null);
  document.addEventListener("fullscreenchange", notify);
  return () => document.removeEventListener("fullscreenchange", notify);
};
var setViewerElementAtom = (0, import_jotai.atom)(null, (_get, set, element) => {
  set(scrollBarStyleFactorAtom, { viewerElement: element });
});
var viewerModeAtom = (0, import_jotai.atom)((get) => {
  const isFullscreen = get(viewerFullscreenAtom);
  const isImmersive = get(isViewerImmersiveAtom);
  return isFullscreen ? "fullscreen" : isImmersive ? "window" : "normal";
});
var setViewerOptionsAtom = (0, import_jotai.atom)(null, async (get, set, options) => {
  try {
    const { source } = options;
    const previousOptions = get(viewerOptionsAtom);
    const shouldLoadSource = source && source !== previousOptions.source;
    if (!shouldLoadSource) {
      return;
    }
    set(viewerStatusAtom, (previous) => previous === "complete" ? "complete" : "loading");
    set(viewerOptionsAtom, options);
    await set(refreshMediaSourceAtom, { cause: "load" });
    set(viewerStatusAtom, "complete");
  } catch (error) {
    set(viewerStatusAtom, "error");
    throw error;
  }
});
var reloadErroredAtom = (0, import_jotai.atom)(null, (get, set) => {
  stop();
  for (const page of get(pageAtomsAtom).map(get)) {
    if (page.state.status !== "complete") {
      set(page.reloadAtom, "load");
    }
  }
});
var toggleImmersiveAtom = (0, import_jotai.atom)(null, async (get, set) => {
  const hasPermissionIssue = get(viewerModeAtom) === "window" && get(isFullscreenPreferredAtom);
  if (hasPermissionIssue) {
    await set(viewerFullscreenAtom, true);
    return;
  }
  await set(setViewerImmersiveAtom, !get(isViewerImmersiveAtom));
});
var toggleFullscreenAtom = (0, import_jotai.atom)(null, async (get, set) => {
  set(isFullscreenPreferredSettingsAtom, !get(isFullscreenPreferredSettingsAtom));
  if (get(viewerModeAtom) === "normal") {
    await set(setViewerImmersiveAtom, true);
  }
});
var blockSelectionAtom = (0, import_jotai.atom)(null, (_get, set, event) => {
  if (event.detail >= 2) {
    event.preventDefault();
  }
  if (event.buttons === 3) {
    set(toggleImmersiveAtom);
    event.preventDefault();
  }
});
async function waitUnloadFinishRoughly() {
  for (let i = 0; i < 5; i++) {
    await timeout(100);
  }
}
function shouldShowF11Guide({ noticeCount }) {
  const isUserFullscreen = innerHeight === screen.height || innerWidth === screen.width;
  return noticeCount < 3 && !isUserFullscreen;
}
var controllerPrimitiveAtom = (0, import_jotai.atom)(null);
var controllerAtom = (0, import_jotai.atom)((get) => get(controllerPrimitiveAtom), (get, set) => {
  const existing = get(controllerPrimitiveAtom);
  if (existing) {
    return existing;
  }
  const controller = new Controller(get, set);
  set(controllerPrimitiveAtom, controller);
  return controller;
});
controllerAtom.onMount = (set) => void set();
var effectivePreferencesAtom = (0, import_jotai.atom)(
  (get) => ({
    backgroundColor: get(backgroundColorAtom),
    singlePageCount: get(singlePageCountStorageAtom),
    maxZoomOutExponent: get(maxZoomOutExponentAtom),
    maxZoomInExponent: get(maxZoomInExponentAtom),
    pageDirection: get(pageDirectionAtom),
    isFullscreenPreferred: get(isFullscreenPreferredAtom),
    fullscreenNoticeCount: get(fullscreenNoticeCountAtom)
  }),
  (get, set, update) => {
    if (typeof update === "function") {
      const preferences = get(effectivePreferencesAtom);
      const newPreferences = update(preferences);
      return updatePreferences(newPreferences);
    }
    return updatePreferences(update);
    function updatePreferences(preferences) {
      return Promise.all([
        updateIfDefined(backgroundColorAtom, preferences.backgroundColor),
        updateIfDefined(singlePageCountAtom, preferences.singlePageCount),
        updateIfDefined(maxZoomOutExponentAtom, preferences.maxZoomOutExponent),
        updateIfDefined(maxZoomInExponentAtom, preferences.maxZoomInExponent),
        updateIfDefined(pageDirectionAtom, preferences.pageDirection),
        updateIfDefined(isFullscreenPreferredAtom, preferences.isFullscreenPreferred),
        updateIfDefined(fullscreenNoticeCountAtom, preferences.fullscreenNoticeCount)
      ]);
    }
    function updateIfDefined(atom3, value) {
      return value !== void 0 ? set(atom3, value) : Promise.resolve();
    }
  }
);
var Controller = class {
  constructor(get, set) {
    this.get = get;
    this.set = set;
    addEventListener("keydown", this.defaultGlobalKeyHandler);
    this.elementKeyHandler = this.defaultElementKeyHandler;
  }
  currentElementKeyHandler = null;
  get options() {
    return this.get(viewerOptionsAtom);
  }
  get status() {
    return this.get(viewerStatusAtom);
  }
  get container() {
    return this.get(scrollBarStyleFactorAtom).viewerElement;
  }
  downloader = {
    download: (options) => this.set(startDownloadAtom, options),
    downloadAndSave: (options) => this.set(downloadAndSaveAtom, options),
    cancel: () => this.set(cancelDownloadAtom)
  };
  get pages() {
    return this.get(pageAtomsAtom).map(this.get);
  }
  get viewerMode() {
    return this.get(viewerModeAtom);
  }
  get effectivePreferences() {
    return this.get(effectivePreferencesAtom);
  }
  set elementKeyHandler(handler) {
    const { currentElementKeyHandler, container } = this;
    const scrollable = this.container?.querySelector("div[data-overlayscrollbars-viewport]");
    if (currentElementKeyHandler) {
      container?.removeEventListener("keydown", currentElementKeyHandler);
      scrollable?.removeEventListener("keydown", currentElementKeyHandler);
    }
    if (handler) {
      container?.addEventListener("keydown", handler);
      scrollable?.addEventListener("keydown", handler);
    }
  }
  setOptions = (value) => {
    return this.set(setViewerOptionsAtom, value);
  };
  goPrevious = () => {
    this.set(goPreviousAtom);
  };
  goNext = () => {
    this.set(goNextAtom);
  };
  setManualPreferences = (value) => {
    return this.set(effectivePreferencesAtom, value);
  };
  setScriptPreferences = ({
    manualPreset,
    preferences
  }) => {
    if (manualPreset) {
      this.set(preferencesPresetAtom, manualPreset);
    }
    if (preferences) {
      this.set(scriptPreferencesAtom, preferences);
    }
  };
  setImmersive = (value) => {
    return this.set(setViewerImmersiveAtom, value);
  };
  setIsFullscreenPreferred = (value) => {
    return this.set(isFullscreenPreferredSettingsAtom, value);
  };
  toggleImmersive = () => {
    this.set(toggleImmersiveAtom);
  };
  toggleFullscreen = () => {
    this.set(toggleFullscreenAtom);
  };
  reloadErrored = () => {
    this.set(reloadErroredAtom);
  };
  unmount = () => {
    return this.get(rootAtom)?.unmount();
  };
  defaultElementKeyHandler = (event) => {
    if (maybeNotHotkey(event)) {
      return false;
    }
    const isHandled = this.handleElementKey(event);
    if (isHandled) {
      event.stopPropagation();
      event.preventDefault();
    }
    return isHandled;
  };
  defaultGlobalKeyHandler = (event) => {
    if (maybeNotHotkey(event)) {
      return false;
    }
    if (["KeyI", "Numpad0", "Enter"].includes(event.code)) {
      if (event.shiftKey) {
        this.toggleFullscreen();
      } else {
        this.toggleImmersive();
      }
      return true;
    }
    return false;
  };
  handleElementKey(event) {
    switch (event.key) {
      case "j":
      case "ArrowDown":
      case "q":
        this.goNext();
        return true;
      case "k":
      case "ArrowUp":
        this.goPrevious();
        return true;
      case "h":
      case "ArrowLeft":
        if (this.options.onPreviousSeries) {
          this.options.onPreviousSeries();
          return true;
        }
        return false;
      case "l":
      case "ArrowRight":
      case "w":
        if (this.options.onNextSeries) {
          this.options.onNextSeries();
          return true;
        }
        return false;
      case ";":
        this.downloader?.downloadAndSave();
        return true;
      case ",":
        void this.addSinglePageCount(-1);
        return true;
      case ".":
        void this.addSinglePageCount(1);
        return true;
      case "/":
        this.set(anchorSinglePageCountAtom);
        return true;
      case "'":
        this.reloadErrored();
        return true;
      default:
        return false;
    }
  }
  async addSinglePageCount(diff) {
    await this.setManualPreferences((preferences) => ({
      ...preferences,
      singlePageCount: this.effectivePreferences.singlePageCount + diff
    }));
  }
};
function maybeNotHotkey(event) {
  const { ctrlKey, altKey, metaKey } = event;
  return ctrlKey || altKey || metaKey || isTyping(event);
}
var setScrollElementAtom = (0, import_jotai.atom)(null, async (get, set, div) => {
  const previous = get(scrollElementStateAtom);
  if (previous?.div === div) {
    return;
  }
  previous?.resizeObserver.disconnect();
  if (div === null) {
    set(scrollElementStateAtom, null);
    return;
  }
  const setScrollElementSize = () => {
    const size = div.getBoundingClientRect();
    set(maxSizeAtom, size);
    set(correctScrollAtom);
  };
  const resizeObserver = new ResizeObserver(setScrollElementSize);
  resizeObserver.observe(div);
  resizeObserver.observe(div.firstElementChild);
  set(scrollElementStateAtom, { div, resizeObserver });
  setScrollElementSize();
  await get(isFullscreenPreferredPromiseAtom);
  await set(setViewerImmersiveAtom, get(wasImmersiveAtom));
});
var Svg = styled("svg", {
  opacity: "50%",
  filter: "drop-shadow(0 0 1px white) drop-shadow(0 0 1px white)",
  color: "black"
});
var downloadCss = { width: "40px" };
var fullscreenCss = {
  position: "absolute",
  right: "1%",
  bottom: "1%"
};
var IconButton = styled("button", {
  display: "flex",
  padding: 0,
  border: "none",
  background: "transparent",
  cursor: "pointer",
  "& > svg": {
    pointerEvents: "none"
  },
  "&:hover > svg": {
    opacity: "100%",
    transform: "scale(1.1)"
  },
  "&:focus > svg": {
    opacity: "100%"
  }
});
var DownloadButton = (props) =>  React.createElement(IconButton, { ...props },  React.createElement(
  Svg,
  {
    version: "1.1",
    xmlns: "http://www.w3.org/2000/svg",
    x: "0px",
    y: "0px",
    viewBox: "0 -34.51 122.88 122.87",
    css: downloadCss
  },
   React.createElement("g", null,  React.createElement("path", { d: "M58.29,42.08V3.12C58.29,1.4,59.7,0,61.44,0s3.15,1.4,3.15,3.12v38.96L79.1,29.4c1.3-1.14,3.28-1.02,4.43,0.27 s1.03,3.25-0.27,4.39L63.52,51.3c-1.21,1.06-3.01,1.03-4.18-0.02L39.62,34.06c-1.3-1.14-1.42-3.1-0.27-4.39 c1.15-1.28,3.13-1.4,4.43-0.27L58.29,42.08L58.29,42.08L58.29,42.08z M0.09,47.43c-0.43-1.77,0.66-3.55,2.43-3.98 c1.77-0.43,3.55,0.66,3.98,2.43c1.03,4.26,1.76,7.93,2.43,11.3c3.17,15.99,4.87,24.57,27.15,24.57h52.55 c20.82,0,22.51-9.07,25.32-24.09c0.67-3.6,1.4-7.5,2.44-11.78c0.43-1.77,2.21-2.86,3.98-2.43c1.77,0.43,2.85,2.21,2.43,3.98 c-0.98,4.02-1.7,7.88-2.36,11.45c-3.44,18.38-5.51,29.48-31.8,29.48H36.07C8.37,88.36,6.3,77.92,2.44,58.45 C1.71,54.77,0.98,51.08,0.09,47.43L0.09,47.43z" }))
));
var FullscreenButton = (props) =>  React.createElement(IconButton, { css: fullscreenCss, ...props },  React.createElement(
  Svg,
  {
    version: "1.1",
    xmlns: "http://www.w3.org/2000/svg",
    x: "0px",
    y: "0px",
    viewBox: "0 0 122.88 122.87",
    width: "40px"
  },
   React.createElement("g", null,  React.createElement("path", { d: "M122.88,77.63v41.12c0,2.28-1.85,4.12-4.12,4.12H77.33v-9.62h35.95c0-12.34,0-23.27,0-35.62H122.88L122.88,77.63z M77.39,9.53V0h41.37c2.28,0,4.12,1.85,4.12,4.12v41.18h-9.63V9.53H77.39L77.39,9.53z M9.63,45.24H0V4.12C0,1.85,1.85,0,4.12,0h41 v9.64H9.63V45.24L9.63,45.24z M45.07,113.27v9.6H4.12c-2.28,0-4.12-1.85-4.12-4.13V77.57h9.63v35.71H45.07L45.07,113.27z" }))
));
var ErrorIcon = styled("svg", {
  width: "10vmin",
  height: "10vmin",
  fill: "hsl(0, 50%, 20%)",
  margin: "2rem"
});
var CircledX = (props) => {
  return  React.createElement(
    ErrorIcon,
    {
      x: "0px",
      y: "0px",
      viewBox: "0 0 122.881 122.88",
      "enable-background": "new 0 0 122.881 122.88",
      ...props
    },
     React.createElement("g", null,  React.createElement("path", { d: "M61.44,0c16.966,0,32.326,6.877,43.445,17.996c11.119,11.118,17.996,26.479,17.996,43.444 c0,16.967-6.877,32.326-17.996,43.444C93.766,116.003,78.406,122.88,61.44,122.88c-16.966,0-32.326-6.877-43.444-17.996 C6.877,93.766,0,78.406,0,61.439c0-16.965,6.877-32.326,17.996-43.444C29.114,6.877,44.474,0,61.44,0L61.44,0z M80.16,37.369 c1.301-1.302,3.412-1.302,4.713,0c1.301,1.301,1.301,3.411,0,4.713L65.512,61.444l19.361,19.362c1.301,1.301,1.301,3.411,0,4.713 c-1.301,1.301-3.412,1.301-4.713,0L60.798,66.157L41.436,85.52c-1.301,1.301-3.412,1.301-4.713,0c-1.301-1.302-1.301-3.412,0-4.713 l19.363-19.362L36.723,42.082c-1.301-1.302-1.301-3.412,0-4.713c1.301-1.302,3.412-1.302,4.713,0l19.363,19.362L80.16,37.369 L80.16,37.369z M100.172,22.708C90.26,12.796,76.566,6.666,61.44,6.666c-15.126,0-28.819,6.13-38.731,16.042 C12.797,32.62,6.666,46.314,6.666,61.439c0,15.126,6.131,28.82,16.042,38.732c9.912,9.911,23.605,16.042,38.731,16.042 c15.126,0,28.82-6.131,38.732-16.042c9.912-9.912,16.043-23.606,16.043-38.732C116.215,46.314,110.084,32.62,100.172,22.708 L100.172,22.708z" }))
  );
};
var SettingsButton = (props) => {
  return  React.createElement(IconButton, { ...props },  React.createElement(
    Svg,
    {
      fill: "none",
      stroke: "currentColor",
      strokeLinecap: "round",
      strokeLinejoin: "round",
      strokeWidth: 2,
      viewBox: "0 0 24 24",
      height: "40px",
      width: "40px"
    },
     React.createElement("path", { d: "M15 12 A3 3 0 0 1 12 15 A3 3 0 0 1 9 12 A3 3 0 0 1 15 12 z" }),
     React.createElement("path", { d: "M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z" })
  ));
};
var RightArrow = (props) => {
  return  React.createElement(Svg, { viewBox: "0 0 330 330", ...props },  React.createElement("path", { d: "M250.606,154.389l-150-149.996c-5.857-5.858-15.355-5.858-21.213,0.001\n	c-5.857,5.858-5.857,15.355,0.001,21.213l139.393,139.39L79.393,304.394c-5.857,5.858-5.857,15.355,0.001,21.213\n	C82.322,328.536,86.161,330,90,330s7.678-1.464,10.607-4.394l149.999-150.004c2.814-2.813,4.394-6.628,4.394-10.606\n	C255,161.018,253.42,157.202,250.606,154.389z" }));
};
var LeftArrow = (props) => {
  return  React.createElement(RightArrow, { ...props, transform: "rotate(180)" });
};
var Container = styled("div", {
  position: "relative",
  height: "100%",
  overflow: "hidden",
  userSelect: "none",
  fontFamily: "Pretendard, NanumGothic, sans-serif",
  fontSize: "16px",
  color: "black",
  "& *:focus-visible": {
    outline: "none"
  },
  variants: {
    immersive: {
      true: {
        position: "fixed",
        top: 0,
        bottom: 0,
        left: 0,
        right: 0
      }
    }
  }
});
var OverlayScroller = styled("div", {
  position: "relative",
  width: "100%",
  height: "100%",
  "& .os-scrollbar": {
    zIndex: 1
  },
  "& .os-scrollbar-handle": {
    backdropFilter: "brightness(0.5)",
    background: "none",
    border: "#fff8 1px solid"
  },
  variants: {
    fullscreen: {
      true: {
        position: "fixed",
        top: 0,
        bottom: 0,
        overflow: "auto"
      }
    }
  }
});
var import_overlayscrollbars_react = require("overlayscrollbars-react");
GM.getResourceText("overlayscrollbars-css").then(insertCss);
var import_jotai3 = require("jotai");
var Backdrop = styled("div", {
  position: "absolute",
  top: 0,
  left: 0,
  width: "100%",
  height: "100%",
  display: "flex",
  alignItems: "center",
  justifyContent: "center",
  background: "rgba(0, 0, 0, 0.5)",
  transition: "0.2s",
  variants: {
    isOpen: {
      true: {
        opacity: 1,
        pointerEvents: "auto"
      },
      false: {
        opacity: 0,
        pointerEvents: "none"
      }
    }
  }
});
var CenterDialog = styled("div", {
  minWidth: "20em",
  minHeight: "20em",
  transition: "0.2s",
  background: "white",
  padding: "20px",
  borderRadius: "10px",
  boxShadow: "0 0 10px 0 rgba(0, 0, 0, 0.2)"
});
function BackdropDialog({ onClose, ...props }) {
  const [isOpen, setIsOpen] = (0, import_react3.useState)(false);
  const close = async () => {
    setIsOpen(false);
    await timeout(200);
    onClose();
  };
  const closeIfEnter = (event) => {
    if (event.key === "Enter") {
      close();
      event.stopPropagation();
    }
  };
  (0, import_react3.useEffect)(() => {
    setIsOpen(true);
  }, []);
  return  React.createElement(Backdrop, { isOpen, onClick: close, onKeyDown: closeIfEnter },  React.createElement(
    CenterDialog,
    {
      onClick: (event) => event.stopPropagation(),
      ...props
    }
  ));
}
var keyBindingsAtom = (0, import_jotai.atom)((get) => {
  const strings = get(i18nAtom);
  return [
    [
      strings.toggleViewer,
       React.createElement(React.Fragment, null,  React.createElement("kbd", null, "i"), ", ",  React.createElement("kbd", null, "Enter⏎"), ", ",  React.createElement("kbd", null, "NumPad0"))
    ],
    [
      strings.toggleFullscreenSetting,
       React.createElement(React.Fragment, null,  React.createElement("kbd", null, "⇧Shift"), "+(",  React.createElement("kbd", null, "i"), ", ",  React.createElement("kbd", null, "Enter⏎"), ", ",  React.createElement("kbd", null, "NumPad0"), ")")
    ],
    [
      strings.nextPage,
       React.createElement(React.Fragment, null,  React.createElement("kbd", null, "j"), ", ",  React.createElement("kbd", null, "↓"), ", ",  React.createElement("kbd", null, "q"))
    ],
    [
      strings.previousPage,
       React.createElement(React.Fragment, null,  React.createElement("kbd", null, "k"), ", ",  React.createElement("kbd", null, "↑"))
    ],
    [strings.download,  React.createElement("kbd", null, ";")],
    [strings.refresh,  React.createElement("kbd", null, "'")],
    [strings.decreaseSinglePageCount,  React.createElement("kbd", null, ",")],
    [strings.increaseSinglePageCount,  React.createElement("kbd", null, ".")],
    [strings.anchorSinglePageCount,  React.createElement("kbd", null, "/")]
  ];
});
var ActionName = styled("td", {
  paddingRight: "1em"
});
function HelpTab() {
  const keyBindings = (0, import_jotai.useAtomValue)(keyBindingsAtom);
  const strings = (0, import_jotai.useAtomValue)(i18nAtom);
  return  React.createElement(React.Fragment, null,  React.createElement("p", null, strings.keyBindings),  React.createElement("table", null, keyBindings.map(([action, keyBinding]) =>  React.createElement("tr", null,  React.createElement(ActionName, null, action),  React.createElement("td", null, keyBinding)))));
}
function SettingsTab() {
  const [maxZoomOutExponent, setMaxZoomOutExponent] = (0, import_jotai.useAtom)(maxZoomOutExponentAtom);
  const [maxZoomInExponent, setMaxZoomInExponent] = (0, import_jotai.useAtom)(maxZoomInExponentAtom);
  const [singlePageCount, setSinglePageCount] = (0, import_jotai.useAtom)(singlePageCountAtom);
  const [backgroundColor, setBackgroundColor] = (0, import_jotai.useAtom)(backgroundColorAtom);
  const [pageDirection, setPageDirection] = (0, import_jotai.useAtom)(pageDirectionAtom);
  const [isFullscreenPreferred, setIsFullscreenPreferred] = (0, import_jotai.useAtom)(
    isFullscreenPreferredSettingsAtom
  );
  const zoomOutExponentInputId = (0, import_react3.useId)();
  const zoomInExponentInputId = (0, import_react3.useId)();
  const singlePageCountInputId = (0, import_react3.useId)();
  const colorInputId = (0, import_react3.useId)();
  const pageDirectionInputId = (0, import_react3.useId)();
  const fullscreenInputId = (0, import_react3.useId)();
  const strings = (0, import_jotai.useAtomValue)(i18nAtom);
  const [isResetConfirming, setResetConfirming] = (0, import_react3.useState)(false);
  const maxZoomOut = formatMultiplier(maxZoomOutExponent);
  const maxZoomIn = formatMultiplier(maxZoomInExponent);
  function tryReset() {
    if (!isResetConfirming) {
      setResetConfirming(true);
      return;
    }
    setMaxZoomInExponent(import_utils.RESET);
    setMaxZoomOutExponent(import_utils.RESET);
    setSinglePageCount(import_utils.RESET);
    setBackgroundColor(import_utils.RESET);
    setPageDirection(import_utils.RESET);
    setIsFullscreenPreferred(import_utils.RESET);
    setResetConfirming(false);
  }
  return  React.createElement(ConfigSheet, null,  React.createElement(ConfigRow, null,  React.createElement(ConfigLabel, { htmlFor: zoomOutExponentInputId }, strings.maxZoomOut, ": ", maxZoomOut),  React.createElement(
    "input",
    {
      type: "number",
      min: 0,
      step: 0.1,
      id: zoomOutExponentInputId,
      value: maxZoomOutExponent,
      onChange: (event) => {
        setMaxZoomOutExponent(event.currentTarget.valueAsNumber || 0);
      }
    }
  )),  React.createElement(ConfigRow, null,  React.createElement(ConfigLabel, { htmlFor: zoomInExponentInputId }, strings.maxZoomIn, ": ", maxZoomIn),  React.createElement(
    "input",
    {
      type: "number",
      min: 0,
      step: 0.1,
      id: zoomInExponentInputId,
      value: maxZoomInExponent,
      onChange: (event) => {
        setMaxZoomInExponent(event.currentTarget.valueAsNumber || 0);
      }
    }
  )),  React.createElement(ConfigRow, null,  React.createElement(ConfigLabel, { htmlFor: singlePageCountInputId }, strings.singlePageCount),  React.createElement(
    "input",
    {
      type: "number",
      min: 0,
      step: 1,
      id: singlePageCountInputId,
      value: singlePageCount,
      onChange: (event) => {
        setSinglePageCount(event.currentTarget.valueAsNumber || 0);
      }
    }
  )),  React.createElement(ConfigRow, null,  React.createElement(ConfigLabel, { htmlFor: colorInputId }, strings.backgroundColor),  React.createElement(
    ColorInput,
    {
      type: "color",
      id: colorInputId,
      value: backgroundColor,
      onChange: (event) => {
        setBackgroundColor(event.currentTarget.value);
      }
    }
  )),  React.createElement(ConfigRow, null,  React.createElement("p", null, strings.useFullScreen),  React.createElement(Toggle, null,  React.createElement(
    HiddenInput,
    {
      type: "checkbox",
      id: fullscreenInputId,
      checked: isFullscreenPreferred,
      onChange: (event) => {
        setIsFullscreenPreferred(event.currentTarget.checked);
      }
    }
  ),  React.createElement("label", { htmlFor: fullscreenInputId }, strings.useFullScreen))),  React.createElement(ConfigRow, null,  React.createElement("p", null, strings.leftToRight),  React.createElement(Toggle, null,  React.createElement(
    HiddenInput,
    {
      type: "checkbox",
      id: pageDirectionInputId,
      checked: pageDirection === "leftToRight",
      onChange: (event) => {
        setPageDirection(event.currentTarget.checked ? "leftToRight" : "rightToLeft");
      }
    }
  ),  React.createElement("label", { htmlFor: pageDirectionInputId }, strings.leftToRight))),  React.createElement(ResetButton, { onClick: tryReset }, isResetConfirming ? strings.doYouReallyWantToReset : strings.reset));
}
function formatMultiplier(maxZoomOutExponent) {
  return Math.sqrt(2) ** maxZoomOutExponent === Infinity ? "∞" : `${(Math.sqrt(2) ** maxZoomOutExponent).toPrecision(2)}x`;
}
var ConfigLabel = styled("label", {
  margin: 0
});
var ResetButton = styled("button", {
  padding: "0.2em 0.5em",
  background: "none",
  border: "red 1px solid",
  borderRadius: "0.2em",
  color: "red",
  cursor: "pointer",
  transition: "0.3s",
  "&:hover": {
    background: "#ffe0e0"
  }
});
var ColorInput = styled("input", {
  height: "1.5em"
});
var ConfigRow = styled("div", {
  display: "flex",
  alignItems: "center",
  justifyContent: "space-between",
  gap: "10%",
  "&& > *": {
    fontSize: "1em",
    fontWeight: "medium",
    minWidth: 0
  },
  "& > input": {
    appearance: "meter",
    border: "gray 1px solid",
    borderRadius: "0.2em",
    textAlign: "center"
  },
  ":first-child": {
    flex: "2 1 0"
  },
  ":nth-child(2)": {
    flex: "1 1 0"
  }
});
var HiddenInput = styled("input", {
  opacity: 0,
  width: 0,
  height: 0
});
var Toggle = styled("span", {
  "--width": "60px",
  "label": {
    position: "relative",
    display: "inline-flex",
    margin: 0,
    width: "var(--width)",
    height: "calc(var(--width) / 2)",
    borderRadius: "calc(var(--width) / 2)",
    cursor: "pointer",
    textIndent: "-9999px",
    background: "grey"
  },
  "label:after": {
    position: "absolute",
    top: "calc(var(--width) * 0.025)",
    left: "calc(var(--width) * 0.025)",
    width: "calc(var(--width) * 0.45)",
    height: "calc(var(--width) * 0.45)",
    borderRadius: "calc(var(--width) * 0.45)",
    content: "",
    background: "#fff",
    transition: "0.3s"
  },
  "input:checked + label": {
    background: "#bada55"
  },
  "input:checked + label:after": {
    left: "calc(var(--width) * 0.975)",
    transform: "translateX(-100%)"
  },
  "label:active:after": {
    width: "calc(var(--width) * 0.65)"
  }
});
var ConfigSheet = styled("div", {
  display: "flex",
  flexFlow: "column nowrap",
  alignItems: "stretch",
  gap: "0.8em"
});
function ViewerDialog({ onClose }) {
  const strings = (0, import_jotai.useAtomValue)(i18nAtom);
  return  React.createElement(BackdropDialog, { onClose },  React.createElement(import_react2.Tab.Group, null,  React.createElement(import_react2.Tab.List, { as: TabList },  React.createElement(import_react2.Tab, { as: PlainTab }, strings.settings),  React.createElement(import_react2.Tab, { as: PlainTab }, strings.help)),  React.createElement(import_react2.Tab.Panels, { as: TabPanels },  React.createElement(import_react2.Tab.Panel, null,  React.createElement(SettingsTab, null)),  React.createElement(import_react2.Tab.Panel, null,  React.createElement(HelpTab, null)))));
}
var PlainTab = styled("button", {
  flex: 1,
  padding: "0.5em 1em",
  background: "transparent",
  border: "none",
  borderRadius: "0.5em",
  color: "#888",
  cursor: "pointer",
  fontSize: "1.2em",
  fontWeight: "bold",
  textAlign: "center",
  '&[data-headlessui-state="selected"]': {
    border: "1px solid black",
    color: "black"
  },
  "&:hover": {
    color: "black"
  }
});
var TabList = styled("div", {
  display: "flex",
  flexFlow: "row nowrap",
  gap: "0.5em"
});
var TabPanels = styled("div", {
  marginTop: "1em"
});
var LeftBottomFloat = styled("div", {
  position: "absolute",
  bottom: "1%",
  left: "1%",
  display: "flex",
  flexFlow: "column"
});
var MenuActions = styled("div", {
  display: "flex",
  flexFlow: "column nowrap",
  alignItems: "center",
  gap: "16px"
});
function LeftBottomControl() {
  const downloadAndSave = (0, import_jotai.useSetAtom)(downloadAndSaveAtom);
  const [isOpen, setIsOpen] = (0, import_react3.useState)(false);
  const scrollable = (0, import_jotai3.useAtomValue)(scrollElementAtom);
  const closeDialog = () => {
    setIsOpen(false);
    scrollable?.focus();
  };
  return  React.createElement(React.Fragment, null,  React.createElement(LeftBottomFloat, null,  React.createElement(MenuActions, null,  React.createElement(SettingsButton, { onClick: () => setIsOpen((value) => !value) }),  React.createElement(DownloadButton, { onClick: () => downloadAndSave() }))), isOpen &&  React.createElement(ViewerDialog, { onClose: closeDialog }));
}
var stretch = keyframes({
  "0%": {
    top: "8px",
    height: "64px"
  },
  "50%": {
    top: "24px",
    height: "32px"
  },
  "100%": {
    top: "24px",
    height: "32px"
  }
});
var SpinnerContainer = styled("div", {
  position: "absolute",
  left: "0",
  top: "0",
  right: "0",
  bottom: "0",
  margin: "auto",
  display: "flex",
  justifyContent: "center",
  alignItems: "center",
  div: {
    display: "inline-block",
    width: "16px",
    margin: "0 4px",
    background: "#fff",
    animation: `${stretch} 1.2s cubic-bezier(0, 0.5, 0.5, 1) infinite`
  },
  "div:nth-child(1)": {
    "animation-delay": "-0.24s"
  },
  "div:nth-child(2)": {
    "animation-delay": "-0.12s"
  },
  "div:nth-child(3)": {
    "animation-delay": "0"
  }
});
var Spinner = () =>  React.createElement(SpinnerContainer, null,  React.createElement("div", null),  React.createElement("div", null),  React.createElement("div", null));
var Overlay = styled("div", {
  position: "relative",
  maxWidth: "100%",
  height: "100vh",
  display: "flex",
  alignItems: "center",
  justifyContent: "center",
  "@media print": {
    margin: 0
  },
  variants: {
    fullWidth: {
      true: { width: "100%" }
    }
  }
});
var LinkColumn = styled("div", {
  display: "flex",
  flexFlow: "column nowrap",
  alignItems: "center",
  justifyContent: "center",
  cursor: "pointer",
  boxShadow: "1px 1px 3px",
  padding: "1rem 1.5rem",
  transition: "box-shadow 1s easeOutExpo",
  lineBreak: "anywhere",
  "&:hover": {
    boxShadow: "2px 2px 5px"
  },
  "&:active": {
    boxShadow: "0 0 2px"
  }
});
var Image = styled("img", {
  position: "relative",
  height: "100%",
  maxWidth: "100%",
  objectFit: "contain",
  variants: {
    originalSize: {
      true: { height: "auto" }
    }
  }
});
var Video = styled("video", {
  position: "relative",
  height: "100%",
  maxWidth: "100%",
  objectFit: "contain",
  variants: {
    originalSize: {
      true: { height: "auto" }
    }
  }
});
var Page = ({ atom: atom3, ...props }) => {
  const {
    imageProps,
    videoProps,
    fullWidth,
    reloadAtom,
    shouldBeOriginalSize,
    divCss,
    state: pageState,
    setDiv
  } = (0, import_jotai.useAtomValue)(atom3);
  const strings = (0, import_jotai.useAtomValue)(i18nAtom);
  const reload = (0, import_jotai.useSetAtom)(reloadAtom);
  const { status } = pageState;
  const reloadErrored = async (event) => {
    event.stopPropagation();
    await reload("load");
  };
  return  React.createElement(Overlay, { ref: setDiv, css: divCss, fullWidth }, status === "loading" &&  React.createElement(Spinner, null), status === "error" &&  React.createElement(LinkColumn, { onClick: reloadErrored },  React.createElement(CircledX, null),  React.createElement("p", null, strings.failedToLoadImage),  React.createElement("p", null, pageState.urls?.join("\n"))), videoProps &&  React.createElement(Video, { ...videoProps, originalSize: shouldBeOriginalSize, ...props }), imageProps &&  React.createElement(Image, { ...imageProps, originalSize: shouldBeOriginalSize, ...props }));
};
function useHorizontalSwipe({ element, onPrevious, onNext }) {
  const [swipeRatio, setSwipeRatio] = (0, import_react3.useState)(0);
  (0, import_react3.useEffect)(() => {
    if (!element || !onPrevious && !onNext)
      return;
    let lastX = null;
    let lastRatio = 0;
    let startTouch = null;
    const addTouchIfClean = (event) => {
      const newTouch = event.touches[0];
      if (startTouch !== null || !newTouch)
        return;
      startTouch = {
        identifier: newTouch.identifier,
        x: newTouch.clientX,
        y: newTouch.clientY,
        scrollTop: element.scrollTop
      };
      lastX = newTouch.clientX;
    };
    const throttledSetSwipeRatio = throttle(setSwipeRatio, 1e3 / 60);
    const updateSwipeRatio = (event) => {
      const continuedTouch = [...event.changedTouches].find(
        (touch) => touch.identifier === startTouch?.identifier
      );
      if (!continuedTouch || !startTouch || !lastX)
        return;
      const isVerticalScroll = element.scrollTop !== startTouch.scrollTop;
      if (isVerticalScroll) {
        resetTouch();
        return;
      }
      const ratioDelta = (continuedTouch.clientX - lastX) / 200;
      lastRatio = Math.max(-1, Math.min(lastRatio + ratioDelta, 1));
      throttledSetSwipeRatio(lastRatio);
      lastX = continuedTouch.clientX;
      const horizontalOffset = Math.abs(continuedTouch.clientX - startTouch.x);
      const verticalOffset = Math.abs(continuedTouch.clientY - startTouch.y);
      if (horizontalOffset > verticalOffset) {
        event.preventDefault();
      }
    };
    const resetSwipeRatioIfReleased = (event) => {
      const continuedTouch = [...event.touches].find(
        (touch) => touch.identifier === startTouch?.identifier
      );
      if (continuedTouch)
        return;
      if (Math.abs(lastRatio) < 0.7) {
        resetTouch();
        return;
      }
      if (lastRatio > 0) {
        onPrevious?.();
      } else {
        onNext?.();
      }
      resetTouch();
    };
    function resetTouch() {
      startTouch = null;
      lastX = null;
      lastRatio = 0;
      throttledSetSwipeRatio(0);
      throttledSetSwipeRatio.flush();
    }
    element.addEventListener("touchend", resetSwipeRatioIfReleased);
    element.addEventListener("touchcancel", resetSwipeRatioIfReleased);
    element.addEventListener("touchmove", updateSwipeRatio, { passive: false });
    element.addEventListener("touchstart", addTouchIfClean, { passive: true });
    return () => {
      element.removeEventListener("touchstart", addTouchIfClean);
      element.removeEventListener("touchmove", updateSwipeRatio);
      element.removeEventListener("touchcancel", resetSwipeRatioIfReleased);
      element.removeEventListener("touchend", resetSwipeRatioIfReleased);
    };
  }, [element]);
  return swipeRatio;
}
var sideButtonCss = {
  position: "absolute",
  top: 0,
  bottom: "60px",
  width: "10%",
  height: "100%",
  border: "none",
  backgroundColor: "transparent",
  "& > *": {
    transition: "transform 0.2s ease-in-out"
  },
  variants: {
    touchDevice: {
      true: {
        transition: "unset",
        pointerEvents: "none"
      }
    }
  }
};
var LeftSideHiddenButton = styled("button", {
  ...sideButtonCss,
  left: 0,
  "&:not(:hover) > *": {
    transform: "translateX(-60%)"
  },
  "&:hover > *, &:focus > *, &:focus-visible > *": {
    transform: "translateX(-20%)"
  }
});
var RightSideHiddenButton = styled("button", {
  ...sideButtonCss,
  right: 0,
  "&:not(:hover) > *": {
    transform: "translateX(+60%)"
  },
  "&:hover > *, &:focus > *, &:focus-visible > *": {
    transform: "translateX(+20%)"
  }
});
var FlexCenter = styled("div", {
  display: "flex",
  justifyContent: "center",
  alignItems: "center",
  width: "100%",
  height: "100%"
});
function SideSeriesButtons() {
  const { onNextSeries, onPreviousSeries } = (0, import_jotai.useAtomValue)(viewerOptionsAtom);
  const scrollElement = (0, import_jotai.useAtomValue)(scrollElementAtom);
  const swipeRatio = useHorizontalSwipe({
    element: scrollElement,
    onPrevious: onPreviousSeries,
    onNext: onNextSeries
  });
  const isTouchDevice = navigator.maxTouchPoints > 0;
  function forwardWheelEvent(event) {
    scrollElement?.scrollBy({ top: event.deltaY });
  }
  return  React.createElement(React.Fragment, null, onPreviousSeries &&  React.createElement(
    LeftSideHiddenButton,
    {
      onClick: onPreviousSeries,
      onWheel: forwardWheelEvent,
      touchDevice: isTouchDevice
    },
     React.createElement(
      FlexCenter,
      {
        style: swipeRatio <= 0 ? {} : { transform: `translateX(${swipeRatio * 40 - 60}%)` }
      },
       React.createElement(LeftArrow, { height: "3vmin", width: "3vmin" })
    )
  ), onNextSeries &&  React.createElement(
    RightSideHiddenButton,
    {
      onClick: onNextSeries,
      onWheel: forwardWheelEvent,
      touchDevice: isTouchDevice
    },
     React.createElement(
      FlexCenter,
      {
        style: swipeRatio >= 0 ? {} : { transform: `translateX(${swipeRatio * 40 + 60}%)` }
      },
       React.createElement(RightArrow, { height: "3vmin", width: "3vmin" })
    )
  ));
}
var Pages = styled("div", {
  display: "flex",
  justifyContent: "center",
  alignItems: "center",
  flexFlow: "row-reverse wrap",
  overflowY: "auto",
  variants: {
    ltr: {
      true: {
        flexFlow: "row wrap"
      }
    }
  }
});
var CenterText = styled("p", {
  position: "absolute",
  top: "50%",
  left: "50%",
  transform: "translate(-50%, -50%)",
  fontSize: "2em"
});
function InnerViewer(props) {
  const { options, onInitialized, ...otherProps } = props;
  const isFullscreen = (0, import_jotai.useAtomValue)(viewerFullscreenAtom);
  const backgroundColor = (0, import_jotai.useAtomValue)(backgroundColorAtom);
  const status = (0, import_jotai.useAtomValue)(viewerStatusAtom);
  const viewerOptions = (0, import_jotai.useAtomValue)(viewerOptionsAtom);
  const pageDirection = (0, import_jotai.useAtomValue)(pageDirectionAtom);
  const strings = (0, import_jotai.useAtomValue)(i18nAtom);
  const mode = (0, import_jotai.useAtomValue)(viewerModeAtom);
  const controller = (0, import_jotai.useAtomValue)(controllerAtom);
  const virtualContainerRef = (0, import_react3.useRef)(null);
  const virtualContainer = virtualContainerRef.current;
  const setScrollElement = (0, import_jotai.useSetAtom)(setScrollElementAtom);
  const setViewerOptions = (0, import_jotai.useSetAtom)(setViewerOptionsAtom);
  const pageAtoms = (0, import_jotai.useAtomValue)(pageAtomsAtom);
  const [initialize2] = (0, import_overlayscrollbars_react.useOverlayScrollbars)({
    defer: true,
    events: { scroll: (0, import_jotai.useSetAtom)(synchronizeScrollAtom), initialized: setupScroll }
  });
  (0, import_jotai.useAtomValue)(fullscreenSynchronizationAtom);
  useBeforeRepaint();
  async function setupScroll() {
    const selector = "div[data-overlayscrollbars-viewport]";
    await setScrollElement(virtualContainerRef.current?.querySelector(selector));
  }
  (0, import_react3.useEffect)(() => {
    if (controller) {
      onInitialized?.(controller);
    }
  }, [controller, onInitialized]);
  (0, import_react3.useEffect)(() => {
    setViewerOptions(options);
  }, [options]);
  (0, import_react3.useEffect)(() => {
    if (virtualContainer) {
      initialize2(virtualContainer);
    }
  }, [initialize2, virtualContainer]);
  return  React.createElement(
    Container,
    {
      ref: (0, import_jotai.useSetAtom)(setViewerElementAtom),
      css: { backgroundColor },
      immersive: mode === "window"
    },
     React.createElement(
      OverlayScroller,
      {
        tabIndex: 0,
        ref: virtualContainerRef,
        fullscreen: isFullscreen,
        onClick: (0, import_jotai.useSetAtom)(navigateAtom),
        onMouseDown: (0, import_jotai.useSetAtom)(blockSelectionAtom),
        ...otherProps
      },
       React.createElement(Pages, { ltr: pageDirection === "leftToRight" }, pageAtoms.map((atom3) =>  React.createElement(
        Page,
        {
          key: `${atom3}`,
          atom: atom3,
          ...viewerOptions.mediaProps
        }
      )))
    ),
     React.createElement(SideSeriesButtons, null),
    status === "loading" &&  React.createElement(CenterText, null, strings.loading),
    status === "error" &&  React.createElement(CenterText, null, strings.errorIsOccurred),
    status === "complete" &&  React.createElement(LeftBottomControl, null),
     React.createElement(FullscreenButton, { onClick: (0, import_jotai.useSetAtom)(toggleImmersiveAtom) }),
     React.createElement(import_react_toastify.ToastContainer, null)
  );
}
function initialize(options) {
  const store = (0, import_jotai.createStore)();
  const root = (0, import_react_dom.createRoot)(getDefaultRoot());
  store.set(rootAtom, root);
  return new Promise(
    (resolve) => root.render(
       React.createElement(import_jotai.Provider, { store },  React.createElement(InnerViewer, { options, onInitialized: resolve }))
    )
  );
}
var Viewer = (0, import_react3.forwardRef)(({ options, onInitialized }) => {
  const store = (0, import_react3.useMemo)(import_jotai.createStore, []);
  return  React.createElement(import_jotai.Provider, { store },  React.createElement(InnerViewer, { options, onInitialized }));
});
function getDefaultRoot() {
  const div = document.createElement("div");
  div.setAttribute("style", "width: 0; height: 0; z-index: 9999999; position: fixed;");
  document.body.append(div);
  return div;
}