Greasy Fork is available in English.

Manga OnlineViewer

Shows all pages at once in online view for these sites: Alandal, Asura Scans, Batoto, BilibiliComics, ComiCastle, Comick, Dynasty-Scans, INKR, InManga, KLManga, Leitor, LHTranslation, Local Files, LynxScans, MangaBuddy, MangaDemon, MangaDex, MangaFox, MangaHere, Mangago, MangaHosted, MangaHub, MangasIn, MangaKakalot, MangaNelo, MangaNato, MangaOni, MangaPark, Mangareader, MangaSee, Manga4life, MangaTigre, MangaToons, MangaTown, ManhuaScan, ManhwaWeb, MangaGeko.com, MangaGeko.cc, NaniScans, NineManga, OlympusScans, PandaManga, RawDevart, ReadComicsOnline, ReadManga Today, ReaperScans, SenManga(Raw), KLManga, TenManga, TuMangaOnline, TuManhwas, UnionMangas, WebNovel, WebToons, Manga33, YugenMangas, ZeroScans, MangaStream WordPress Plugin, Flame Comics, Realm Oasis, Voids-Scans, Luminous Scans, Shimada Scans, Night Scans, Manhwa-Freak, OzulScansEn, CypherScans, MangaGalaxy, LuaScans, Drake Scans, Rizzfables, FoOlSlide, Kireicake, Madara WordPress Plugin, MangaHaus, Isekai Scan, Comic Kiba, Zinmanga, mangatx, Toonily, Mngazuki, JaiminisBox, DisasterScans, ManhuaPlus, TopManhua, NovelMic, Reset-Scans, LeviatanScans, Dragon Tea, SetsuScans, ToonGod

Från och med 2024-09-15. Se den senaste versionen.

  1. // ==UserScript==
  2. // @name Manga OnlineViewer
  3. // @author Tago
  4. // @supportURL https://github.com/TagoDR/MangaOnlineViewer/issues
  5. // @namespace https://github.com/TagoDR
  6. // @description Shows all pages at once in online view for these sites: Alandal, Asura Scans, Batoto, BilibiliComics, ComiCastle, Comick, Dynasty-Scans, INKR, InManga, KLManga, Leitor, LHTranslation, Local Files, LynxScans, MangaBuddy, MangaDemon, MangaDex, MangaFox, MangaHere, Mangago, MangaHosted, MangaHub, MangasIn, MangaKakalot, MangaNelo, MangaNato, MangaOni, MangaPark, Mangareader, MangaSee, Manga4life, MangaTigre, MangaToons, MangaTown, ManhuaScan, ManhwaWeb, MangaGeko.com, MangaGeko.cc, NaniScans, NineManga, OlympusScans, PandaManga, RawDevart, ReadComicsOnline, ReadManga Today, ReaperScans, SenManga(Raw), KLManga, TenManga, TuMangaOnline, TuManhwas, UnionMangas, WebNovel, WebToons, Manga33, YugenMangas, ZeroScans, MangaStream WordPress Plugin, Flame Comics, Realm Oasis, Voids-Scans, Luminous Scans, Shimada Scans, Night Scans, Manhwa-Freak, OzulScansEn, CypherScans, MangaGalaxy, LuaScans, Drake Scans, Rizzfables, FoOlSlide, Kireicake, Madara WordPress Plugin, MangaHaus, Isekai Scan, Comic Kiba, Zinmanga, mangatx, Toonily, Mngazuki, JaiminisBox, DisasterScans, ManhuaPlus, TopManhua, NovelMic, Reset-Scans, LeviatanScans, Dragon Tea, SetsuScans, ToonGod
  7. // @version 2024.09.14
  8. // @license MIT
  9. // @icon https://cdn-icons-png.flaticon.com/32/2281/2281832.png
  10. // @run-at document-end
  11. // @grant unsafeWindow
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // @grant GM_listValues
  15. // @grant GM_deleteValue
  16. // @grant GM_xmlhttpRequest
  17. // @noframes on
  18. // @connect *
  19. // @require https://cdnjs.cloudflare.com/ajax/libs/tinycolor/1.6.0/tinycolor.min.js
  20. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery.imagesloaded/5.0.0/imagesloaded.pkgd.min.js
  21. // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.9.1/jszip.min.js
  22. // @require https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.js
  23. // @require https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/11.4.8/sweetalert2.min.js
  24. // @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js
  25. // @require https://cdn.jsdelivr.net/npm/hotkeys-js@3.13.7/dist/hotkeys.min.js
  26. // @require https://cdn.jsdelivr.net/npm/range-slider-input@2.4.4/dist/rangeslider.nostyle.umd.min.js
  27. // @require https://cdnjs.cloudflare.com/ajax/libs/UAParser.js/1.0.37/ua-parser.min.js
  28. // @require https://cdnjs.cloudflare.com/ajax/libs/blob-util/2.0.2/blob-util.min.js
  29. // @include /https?:\/\/alandal.com\/chapter\/.+\/\d+/
  30. // @include /https?:\/\/(www.)?(asuracomic).(net)\/.+/
  31. // @include /https?:\/\/(www\.)?bato.to\/(chapter|title).*/
  32. // @include /https?:\/\/(www\.)?(bilibilicomics).net\/episode\/.+/
  33. // @include /https?:\/\/comic\.nizamkomputer.com\/read\/.+\/\d+.*/
  34. // @include /https?:\/\/(www\.)?comick.io\/.+/
  35. // @include /https?:\/\/(www\.)?dynasty-scans.com\/chapters\/.+/
  36. // @include /https?:\/\/(comics\.)?inkr.com\/title\/.+\/chapter\/.+/
  37. // @include /https?:\/\/(www\.)?inmanga.com\/ver\/manga\/.+\/.+/
  38. // @include /https?:\/\/(www\.)?klmanga.com\/.+chapter.+/
  39. // @include /https?:\/\/(www\.)?leitor.net\/manga\/.+\/.+\/.+/
  40. // @include /https?:\/\/(www\.)?lhtranslation.net\/read.+/
  41. // @include /(file:\/\/\/.+(index)?.html)/
  42. // @include /https?:\/\/(www\.)?lynxscans.com\/comics?\/.+/
  43. // @include /https?:\/\/(www\.)?mangabuddy.com\/.+\/chapter.+/
  44. // @include /https?:\/\/(www\.)?demonicscans\.org\/title\/.+\/chapter\/.+/
  45. // @include /https?:\/\/(www\.)?mangadex.org/
  46. // @include /https?:\/\/(www\.)?(fanfox.net|mangahere.cc)\/manga\/.+\/.+\//
  47. // @include /https?:\/\/(www\.)?mangago.me\/.*\/.*\/.*/
  48. // @include /https?:\/\/(www\.)?mangahosted.com\/manga\/.+\/.+/
  49. // @include /https?:\/\/(www\.)?(mangahub).io\/chapter\/.+\/.+/
  50. // @include /https?:\/\/(www\.)?mangas.in\/manga\/.+\/.+\/\d+/
  51. // @include /https?:\/\/(www\.)?(read|chap)?(manganelo|mangakakalot|manganato).(com|to).*\/chapter.+/
  52. // @include /https?:\/\/(www\.)?manga-oni.com\/lector\/.+\/\d+\/cascada/
  53. // @include /https?:\/\/(www\.)?mangapark.(com|me|org|net)\/title\/.+\/.+/
  54. // @include /https?:\/\/(www\.)?mangareader.to\/read\/.+\/.+\/.+/
  55. // @include /https?:\/\/(www\.)?(mangasee123|manga4life).com\/read-online\/.+/
  56. // @include /https?:\/\/(www\.)?mangatigre.net\/.+\/.+\/.+/
  57. // @include /https?:\/\/.*mangatoon.mobi\/.+\/watch\/.+/
  58. // @include /https?:\/\/(www\.|m\.)?mangatown.com\/manga\/.+\/.+/
  59. // @include /https?:\/\/(www\.)?manhuascan.com\/manga\/.+\/chapter.+/
  60. // @include /https?:\/\/(www\.)?manhwaweb.com\/leer\/.+/
  61. // @include /https?:\/\/(www\.)?mgeko.(com|cc)?\/reader\/.*/
  62. // @include /https?:\/\/(www\.)?(naniscans).com\/chapters\/.+\/read/
  63. // @include /https?:\/\/(www\.)?ninemanga.com\/chapter\/.+\/.+\.html/
  64. // @include /https?:\/\/(www\.)?olympusscans.com\/capitulo\/.+\/.+/
  65. // @include /https?:\/\/(www\.)?pandamanga.xyz\/.+\/.+\/.+/
  66. // @include /https?:\/\/(www\.)?rawdevart.com\/comic\/.+\/.+\//
  67. // @include /https?:\/\/(www\.)?readcomicsonline.ru\/comic\/.*\/\d*/
  68. // @include /https?:\/\/(www\.)?readm.today\/.+\/\d+/
  69. // @include /https?:\/\/(www\.)?reaperscans\.com\/series\/.+\/chapter.+/
  70. // @include /https?:\/\/raw\.senmanga.com\/.+\/.+\/?/
  71. // @include /https?:\/\/(www\.)?tapas.io\/episode\/.+/
  72. // @include /https?:\/\/(www\.)?(tenmanga|gardenmanage).com\/(chapter|statuses)\/.+/
  73. // @include /https?:\/\/(www\.)?(.+).com\/(viewer|news)\/.+\/(paginated|cascade)/
  74. // @include /https?:\/\/(www\.)?tumanhwas.com\/news\/.+/
  75. // @include /https?:\/\/(www\.)?unionleitor.top\/leitor\/.+\/.+/
  76. // @include /https?:\/\/(www\.)?webnovel.com\/comic\/.+/
  77. // @include /https?:\/\/(www\.)?webtoons.com\/.+viewer.+/
  78. // @include /https?:\/\/(www\.)?(manga33).com\/manga\/.+/
  79. // @include /https?:\/\/(www\.)?(yugenmangas).(com|net|lat)\/series\/.+/
  80. // @include /https?:\/\/(www\.)?zscans.com\/comics\/.+/
  81. // @include /https?:\/\/[^/]*(scans|comic|realmoasis|hivetoon|rizzfables)[^/]*\/.+/
  82. // @include /^(?!.*jaiminisbox).*\/read\/.+/
  83. // @include /https?:\/\/.+\/(manga|series|manhua|comic|ch|novel|webtoon)\/.+\/.+/
  84. // @exclude /https?:\/\/(www\.)?tsumino.com\/.+/
  85. // @exclude /https?:\/\/(www\.)?pururin.io\/.+/
  86. // ==/UserScript==
  87. (function () {
  88. "use strict";
  89.  
  90. const alandal = {
  91. name: "Alandal",
  92. url: /https?:\/\/alandal.com\/chapter\/.+\/\d+/,
  93. homepage: "https://alandal.com/",
  94. language: ["English"],
  95. category: "manga",
  96. run() {
  97. const images = [...document.querySelectorAll('img[alt^="Page"]')];
  98. const chapter = document
  99. ?.querySelector('[aria-label="chapter list"]')
  100. ?.parentElement?.parentElement?.parentElement?.parentElement?.querySelectorAll(
  101. "a",
  102. );
  103. return {
  104. title: document.querySelector("title")?.textContent?.trim(),
  105. series: document
  106. .querySelector('a[href^="/series/"]')
  107. ?.getAttribute("href"),
  108. pages: images.length,
  109. prev: chapter?.item(0)?.getAttribute("href"),
  110. next: chapter?.item(1)?.getAttribute("href"),
  111. listImages: images.map((img) => img.getAttribute("src")),
  112. };
  113. },
  114. };
  115.  
  116. function findOneByContentStarts(selector, content) {
  117. return [...document.querySelectorAll(selector)].filter((e) =>
  118. e.textContent?.startsWith(content),
  119. )?.[0];
  120. }
  121. function findClosestByContentEq(selector, content, ancestor = "a") {
  122. return [...document.querySelectorAll(selector)]
  123. .filter((e) => e.textContent === content)?.[0]
  124. .closest(ancestor);
  125. }
  126. function findClosestByContentStarts(selector, content, ancestor = "a") {
  127. return [...document.querySelectorAll(selector)]
  128. .filter((e) => e.textContent?.startsWith(content))?.[0]
  129. .closest(ancestor);
  130. }
  131. function findClosestByContentEnds(selector, content, ancestor = "a") {
  132. return [...document.querySelectorAll(selector)]
  133. .filter((e) => e.textContent?.endsWith(content))?.[0]
  134. .closest(ancestor);
  135. }
  136.  
  137. const asura = {
  138. name: "Asura Scans",
  139. url: /https?:\/\/(www.)?(asuracomic).(net)\/.+/,
  140. homepage: "https://asuracomic.net/",
  141. language: ["English"],
  142. category: "manga",
  143. waitEle: 'img[alt*="chapter"]',
  144. waitTime: 2e3,
  145. run() {
  146. const images = [...document.querySelectorAll('img[alt*="chapter"]')];
  147. return {
  148. title: document.querySelector("h2")?.textContent?.trim(),
  149. series: findOneByContentStarts("p", "All chapters are in")
  150. ?.querySelector("a")
  151. ?.getAttribute("href"),
  152. pages: images.length,
  153. prev: findClosestByContentEq("h2", "Prev", "a")?.getAttribute("href"),
  154. next: findClosestByContentEq("h2", "Next", "a")?.getAttribute("href"),
  155. listImages: images.map((img) => img.getAttribute("src")),
  156. };
  157. },
  158. };
  159.  
  160. const batoto = {
  161. name: "Batoto",
  162. url: /https?:\/\/(www\.)?bato.to\/(chapter|title).*/,
  163. homepage: "https://bato.to/",
  164. language: ["English"],
  165. category: "manga",
  166. waitEle: 'div[name="image-item"] img, .page-img',
  167. run() {
  168. if (window.location.pathname.startsWith("/title")) {
  169. if (window.location.search !== "?load=2") {
  170. window.location.search = "?load=2";
  171. }
  172. const images2 = [
  173. ...document.querySelectorAll('div[name="image-item"] img'),
  174. ];
  175. return {
  176. title: document.querySelector("h6")?.textContent?.trim(),
  177. series: document.querySelector("h3 a")?.getAttribute("href"),
  178. pages: images2.length,
  179. prev: findClosestByContentEnds(
  180. "span",
  181. "Prev Chapter",
  182. "a",
  183. )?.getAttribute("href"),
  184. next: findClosestByContentStarts(
  185. "span",
  186. "Next Chapter",
  187. "a",
  188. )?.getAttribute("href"),
  189. listImages: images2.map((img) => img.getAttribute("src")),
  190. };
  191. }
  192. const images = [...document.querySelectorAll(".page-img")];
  193. return {
  194. title: document.querySelector(".nav-title a")?.textContent?.trim(),
  195. series: document.querySelector(".nav-title a")?.getAttribute("href"),
  196. pages: images.length,
  197. prev: document.querySelector(".nav-prev a")?.getAttribute("href"),
  198. next: document.querySelector(".nav-next a")?.getAttribute("href"),
  199. listImages: images.map((img) => img.getAttribute("src")),
  200. };
  201. },
  202. };
  203.  
  204. const bilibilicomics = {
  205. name: "BilibiliComics",
  206. url: /https?:\/\/(www\.)?(bilibilicomics).net\/episode\/.+/,
  207. homepage: "https://www.bilibilicomics.net/",
  208. language: ["English"],
  209. category: "manga",
  210. waitEle: "#__NUXT_DATA__",
  211. async run() {
  212. const json = JSON.parse(
  213. document.querySelector("#__NUXT_DATA__")?.innerHTML ?? "",
  214. );
  215. const images = json.filter(
  216. (x) =>
  217. typeof x === "string" && /.(png|jpg|jpeg|gif|bmp|webp)$/i.exec(x),
  218. );
  219. return {
  220. title: document.querySelector(".chapterTitle")?.textContent?.trim(),
  221. series: document.querySelector(".book-name")?.getAttribute("href"),
  222. pages: images.length,
  223. prev: document
  224. .querySelectorAll(".pre-next-btns")
  225. .item(0)
  226. ?.getAttribute("href"),
  227. next: document
  228. .querySelectorAll(".pre-next-btns")
  229. .item(2)
  230. ?.getAttribute("href"),
  231. listImages: images.map(
  232. (image) => `https://static.comicfans.io/${image}`,
  233. ),
  234. };
  235. },
  236. };
  237.  
  238. const comicastle = {
  239. name: "ComiCastle",
  240. url: /https?:\/\/comic\.nizamkomputer.com\/read\/.+\/\d+.*/,
  241. homepage: "https://comic.nizamkomputer.com/",
  242. language: ["English"],
  243. category: "comic",
  244. waitEle: ".form-control option:nth-child(1)",
  245. run() {
  246. const images = [
  247. ...document
  248. .querySelectorAll(".form-control")[1]
  249. .querySelectorAll("option"),
  250. ];
  251. const chapter = document
  252. .querySelectorAll(".form-control")[0]
  253. .querySelector("option:checked");
  254. return {
  255. title: chapter?.textContent?.trim(),
  256. series: document
  257. .querySelector(".navbar-header a")
  258. ?.getAttribute("href"),
  259. pages: images.length,
  260. prev: chapter?.previousElementSibling?.getAttribute("value"),
  261. next: chapter?.nextElementSibling?.getAttribute("value"),
  262. listImages: images.map((img) => img.getAttribute("alt")),
  263. };
  264. },
  265. };
  266.  
  267. const comick = {
  268. name: "Comick",
  269. url: /https?:\/\/(www\.)?comick.io\/.+/,
  270. homepage: "https://comick.io/home",
  271. language: ["English"],
  272. category: "manga",
  273. waitEle: "#__NEXT_DATA__",
  274. run() {
  275. const data = JSON.parse(
  276. document.querySelector("#__NEXT_DATA__")?.textContent ?? "",
  277. );
  278. return {
  279. title: data.props.pageProps.chapter.title,
  280. series: `/comic/${data.props.pageProps.chapter.md_comics.slug}`,
  281. pages: data.props.pageProps.chapter.md_images.length,
  282. prev: data.props.pageProps.prev?.href,
  283. next: data.props.pageProps.next?.href,
  284. listImages: data.props.pageProps.chapter.md_images.map(
  285. (img) => `https://s3.comick.ink/comick/${img.b2key}`,
  286. ),
  287. };
  288. },
  289. };
  290.  
  291. const dysnatyscans = {
  292. name: "Dynasty-Scans",
  293. url: /https?:\/\/(www\.)?dynasty-scans.com\/chapters\/.+/,
  294. homepage: "https://dynasty-scans.com/",
  295. language: ["English"],
  296. category: "manga",
  297. run() {
  298. return {
  299. title: document.querySelector("#chapter-title")?.textContent?.trim(),
  300. series: document
  301. .querySelector("#chapter-title a")
  302. ?.getAttribute("href"),
  303. pages: unsafeWindow.pages.length,
  304. prev: document.querySelector("#prev_link")?.getAttribute("href"),
  305. next: document.querySelector("#next_link")?.getAttribute("href"),
  306. listImages: unsafeWindow.pages.map((x) => x.image),
  307. };
  308. },
  309. };
  310.  
  311. const foolslide = {
  312. name: ["FoOlSlide", "Kireicake"],
  313. url: /^(?!.*jaiminisbox).*\/read\/.+/,
  314. homepage: [
  315. "https://github.com/saintly2k/FoOlSlideX",
  316. "https://reader.kireicake.com",
  317. ],
  318. language: ["English"],
  319. obs: "Any Site that uses FoOLSlide",
  320. category: "manga",
  321. waitEle: "img.open",
  322. run() {
  323. const chapter = [
  324. ...document.querySelectorAll(
  325. ".topbar_left .dropdown_parent:last-of-type li",
  326. ),
  327. ];
  328. const origin = chapter.findIndex((item) => {
  329. const url = item.querySelector("a")?.getAttribute("href");
  330. if (url) {
  331. return window.location.href.startsWith(url);
  332. }
  333. return false;
  334. });
  335. const pages = [
  336. ...document.querySelectorAll(".topbar_right .dropdown li"),
  337. ];
  338. const images = [...document.querySelectorAll(".inner img:not(.open)")];
  339. const num = images.length > 1 ? images.length : pages.length;
  340. return {
  341. title:
  342. chapter.at(origin)?.querySelector("a")?.textContent?.trim() ??
  343. document.querySelector("title")?.textContent?.trim(),
  344. series: document
  345. .querySelector("div.tbtitle div.text a")
  346. ?.getAttribute("href"),
  347. pages: num,
  348. prev: chapter
  349. .at(origin + 1)
  350. ?.querySelector("a")
  351. ?.getAttribute("href"),
  352. next: chapter
  353. .at(origin - 1)
  354. ?.querySelector("a")
  355. ?.getAttribute("href"),
  356. listPages:
  357. images.length > 1
  358. ? null
  359. : Array(num)
  360. .fill(0)
  361. .map(
  362. (_, i) =>
  363. `${window.location.href.replace(/\/\d+$/, "")}/${i + 1}`,
  364. ),
  365. listImages:
  366. images.length > 1
  367. ? images.map((img) => img.getAttribute("src"))
  368. : null,
  369. img: "img.open",
  370. };
  371. },
  372. };
  373.  
  374. const inkr = {
  375. name: "INKR",
  376. url: /https?:\/\/(comics\.)?inkr.com\/title\/.+\/chapter\/.+/,
  377. homepage: "https://inkr.com/",
  378. language: ["English"],
  379. category: "manga",
  380. waitFunc: () =>
  381. document.querySelector(
  382. '[data-container="file-horizontal-scroll-view"] img',
  383. )?.naturalWidth !== void 0 &&
  384. document.querySelectorAll(
  385. '[data-container="file-horizontal-scroll-view"] img',
  386. ).length > 2,
  387. run() {
  388. const images = [
  389. ...document.querySelectorAll(
  390. '[data-container="file-horizontal-scroll-view"] img',
  391. ),
  392. ];
  393. return {
  394. title: document.querySelector("title")?.textContent?.trim(),
  395. series: document
  396. .querySelector('[aria-label="Previous Chapter"] + div a')
  397. ?.getAttribute("href"),
  398. pages: images.length,
  399. prev: document
  400. .querySelector('a[aria-label="Previous Chapter"]')
  401. ?.getAttribute("href"),
  402. next: document
  403. .querySelector('a[aria-label="Next Chapter"]')
  404. ?.getAttribute("href"),
  405. listImages: images.map((img) =>
  406. img.getAttribute("src")?.replace("/t.", "/p."),
  407. ),
  408. };
  409. },
  410. };
  411.  
  412. const inmanga = {
  413. name: "InManga",
  414. url: /https?:\/\/(www\.)?inmanga.com\/ver\/manga\/.+\/.+/,
  415. homepage: "https://inmanga.com//",
  416. language: ["Spanish"],
  417. category: "manga",
  418. waitVar: "pageController",
  419. run() {
  420. const images = [...document.querySelectorAll("#PageList option")];
  421. const chapter = document.querySelector("#ChapList option:checked");
  422. const src = unsafeWindow.pageController._containers.pageUrl;
  423. return {
  424. title: document.querySelector("title")?.textContent?.trim(),
  425. series: `../${unsafeWindow.pageController._containers.mangaIdentification}`,
  426. pages: images.length,
  427. prev: chapter?.previousElementSibling?.getAttribute("value"),
  428. next: chapter?.nextElementSibling?.getAttribute("value"),
  429. listImages: images.map((img, index) =>
  430. src
  431. .replace("identification", img.getAttribute("value"))
  432. .replace("pageNumber", index),
  433. ),
  434. };
  435. },
  436. };
  437.  
  438. const klmanga = {
  439. name: "KLManga",
  440. url: /https?:\/\/(www\.)?klmanga.com\/.+chapter.+/,
  441. homepage: "https://klmanga.com/",
  442. language: ["Raw"],
  443. category: "manga",
  444. run() {
  445. const images = [...document.querySelectorAll(".chapter-content img")];
  446. const chapter = document
  447. .querySelectorAll(".form-control")[0]
  448. .querySelector("option:checked");
  449. return {
  450. title: document.querySelector("title")?.textContent?.trim(),
  451. series: document.querySelector(".navbar-brand")?.getAttribute("href"),
  452. pages: images.length,
  453. prev: chapter?.nextElementSibling?.getAttribute("value"),
  454. next: chapter?.previousElementSibling?.getAttribute("value"),
  455. listImages: images.map((img) => img.getAttribute("src")),
  456. };
  457. },
  458. };
  459.  
  460. const leitor = {
  461. name: "Leitor",
  462. url: /https?:\/\/(www\.)?leitor.net\/manga\/.+\/.+\/.+/,
  463. homepage: "https://leitor.net/",
  464. language: ["Portuguese"],
  465. category: "manga",
  466. async run() {
  467. const url = `https://leitor.net/leitor/pages/${unsafeWindow.READER_ID_RELEASE}.json?key=${unsafeWindow.READER_TOKEN}`;
  468. const api = await fetch(url).then(async (res) => res.json());
  469. const chapter = document.querySelector(".chapter-list .selected");
  470. return {
  471. title: document.querySelector("title")?.textContent?.trim(),
  472. series: document.querySelector(".series-cover a")?.getAttribute("href"),
  473. pages: api.images.length,
  474. prev: chapter?.nextElementSibling
  475. ?.querySelector("a")
  476. ?.getAttribute("href"),
  477. next: chapter?.previousElementSibling
  478. ?.querySelector("a")
  479. ?.getAttribute("href"),
  480. listImages: api.images.map((img) => img.avif ?? img.legacy),
  481. };
  482. },
  483. };
  484.  
  485. const lhtranslation = {
  486. name: "LHTranslation",
  487. url: /https?:\/\/(www\.)?lhtranslation.net\/read.+/,
  488. homepage: "https://lhtranslation.net/",
  489. language: ["English"],
  490. category: "manga",
  491. run() {
  492. const chapter = document.querySelector(".form-control option:checked");
  493. const images = [...document.querySelectorAll("img.chapter-img")];
  494. return {
  495. title: document
  496. .querySelector(".chapter-img.tieude font")
  497. ?.textContent?.trim(),
  498. series: document
  499. .querySelector(".navbar-brand.manga-name")
  500. ?.getAttribute("href"),
  501. pages: images.length,
  502. prev: chapter?.nextElementSibling?.getAttribute("value"),
  503. next: chapter?.previousElementSibling?.getAttribute("value"),
  504. listImages: images.map((img) => img.getAttribute("src")),
  505. };
  506. },
  507. };
  508.  
  509. const concatenateTemplateLiteralTag = (raw, ...keys) =>
  510. keys.length === 0 ? raw[0] : String.raw({ raw }, ...keys);
  511. const html = concatenateTemplateLiteralTag;
  512. const css = concatenateTemplateLiteralTag;
  513.  
  514. const colors = {
  515. dark: {
  516. name: "dark",
  517. 50: "#C1C2C5",
  518. 100: "#A6A7AB",
  519. 200: "#909296",
  520. 300: "#5c5f66",
  521. 400: "#373A40",
  522. 500: "#2C2E33",
  523. 600: "#25262b",
  524. 700: "#1A1B1E",
  525. 800: "#141517",
  526. 900: "#101113",
  527. },
  528. gray: {
  529. name: "gray",
  530. 50: "#f8f9fa",
  531. 100: "#f1f3f5",
  532. 200: "#e9ecef",
  533. 300: "#dee2e6",
  534. 400: "#ced4da",
  535. 500: "#adb5bd",
  536. 600: "#868e96",
  537. 700: "#495057",
  538. 800: "#343a40",
  539. 900: "#212529",
  540. },
  541. red: {
  542. name: "red",
  543. 50: "#fff5f5",
  544. 100: "#ffe3e3",
  545. 200: "#ffc9c9",
  546. 300: "#ffa8a8",
  547. 400: "#ff8787",
  548. 500: "#ff6b6b",
  549. 600: "#fa5252",
  550. 700: "#f03e3e",
  551. 800: "#e03131",
  552. 900: "#c92a2a",
  553. },
  554. pink: {
  555. name: "pink",
  556. 50: "#fff0f6",
  557. 100: "#ffdeeb",
  558. 200: "#fcc2d7",
  559. 300: "#faa2c1",
  560. 400: "#f783ac",
  561. 500: "#f06595",
  562. 600: "#e64980",
  563. 700: "#d6336c",
  564. 800: "#c2255c",
  565. 900: "#a61e4d",
  566. },
  567. grape: {
  568. name: "grape",
  569. 50: "#f8f0fc",
  570. 100: "#f3d9fa",
  571. 200: "#eebefa",
  572. 300: "#e599f7",
  573. 400: "#da77f2",
  574. 500: "#cc5de8",
  575. 600: "#be4bdb",
  576. 700: "#ae3ec9",
  577. 800: "#9c36b5",
  578. 900: "#862e9c",
  579. },
  580. violet: {
  581. name: "violet",
  582. 50: "#f3f0ff",
  583. 100: "#e5dbff",
  584. 200: "#d0bfff",
  585. 300: "#b197fc",
  586. 400: "#9775fa",
  587. 500: "#845ef7",
  588. 600: "#7950f2",
  589. 700: "#7048e8",
  590. 800: "#6741d9",
  591. 900: "#5f3dc4",
  592. },
  593. indigo: {
  594. name: "purple",
  595. 50: "#edf2ff",
  596. 100: "#dbe4ff",
  597. 200: "#bac8ff",
  598. 300: "#91a7ff",
  599. 400: "#748ffc",
  600. 500: "#5c7cfa",
  601. 600: "#4c6ef5",
  602. 700: "#4263eb",
  603. 800: "#3b5bdb",
  604. 900: "#364fc7",
  605. },
  606. blue: {
  607. name: "blue",
  608. 50: "#e7f5ff",
  609. 100: "#d0ebff",
  610. 200: "#a5d8ff",
  611. 300: "#74c0fc",
  612. 400: "#4dabf7",
  613. 500: "#339af0",
  614. 600: "#228be6",
  615. 700: "#1c7ed6",
  616. 800: "#1971c2",
  617. 900: "#1864ab",
  618. },
  619. cyan: {
  620. name: "cyan",
  621. 50: "#e3fafc",
  622. 100: "#c5f6fa",
  623. 200: "#99e9f2",
  624. 300: "#66d9e8",
  625. 400: "#3bc9db",
  626. 500: "#22b8cf",
  627. 600: "#15aabf",
  628. 700: "#1098ad",
  629. 800: "#0c8599",
  630. 900: "#0b7285",
  631. },
  632. teal: {
  633. name: "teal",
  634. 50: "#e6fcf5",
  635. 100: "#c3fae8",
  636. 200: "#96f2d7",
  637. 300: "#63e6be",
  638. 400: "#38d9a9",
  639. 500: "#20c997",
  640. 600: "#12b886",
  641. 700: "#0ca678",
  642. 800: "#099268",
  643. 900: "#087f5b",
  644. },
  645. green: {
  646. name: "green",
  647. 50: "#ebfbee",
  648. 100: "#d3f9d8",
  649. 200: "#b2f2bb",
  650. 300: "#8ce99a",
  651. 400: "#69db7c",
  652. 500: "#51cf66",
  653. 600: "#40c057",
  654. 700: "#37b24d",
  655. 800: "#2f9e44",
  656. 900: "#2b8a3e",
  657. },
  658. lime: {
  659. name: "lime",
  660. 50: "#f4fce3",
  661. 100: "#e9fac8",
  662. 200: "#d8f5a2",
  663. 300: "#c0eb75",
  664. 400: "#a9e34b",
  665. 500: "#94d82d",
  666. 600: "#82c91e",
  667. 700: "#74b816",
  668. 800: "#66a80f",
  669. 900: "#5c940d",
  670. },
  671. yellow: {
  672. name: "yellow",
  673. 50: "#fff9db",
  674. 100: "#fff3bf",
  675. 200: "#ffec99",
  676. 300: "#ffe066",
  677. 400: "#ffd43b",
  678. 500: "#fcc419",
  679. 600: "#fab005",
  680. 700: "#f59f00",
  681. 800: "#f08c00",
  682. 900: "#e67700",
  683. },
  684. orange: {
  685. name: "orange",
  686. 50: "#fff4e6",
  687. 100: "#ffe8cc",
  688. 200: "#ffd8a8",
  689. 300: "#ffc078",
  690. 400: "#ffa94d",
  691. 500: "#ff922b",
  692. 600: "#fd7e14",
  693. 700: "#f76707",
  694. 800: "#e8590c",
  695. 900: "#d9480f",
  696. },
  697. darkblue: {
  698. name: "darkblue",
  699. 50: "#E8F4F9",
  700. 100: "#D9DEE9",
  701. 200: "#B7C2DA",
  702. 300: "#6482C0",
  703. 400: "#4267B2",
  704. 500: "#385898",
  705. 600: "#314E89",
  706. 700: "#29487D",
  707. 800: "#223B67",
  708. 900: "#1E355B",
  709. },
  710. };
  711. const darkest = 10;
  712. const lightest = 95;
  713. function setLightness(hsl, lightness) {
  714. hsl.l = lightness;
  715. return tinycolor(hsl).toHexString();
  716. }
  717. function getTextColor(hex) {
  718. const color = tinycolor(hex);
  719. const hsl = color.toHsl();
  720. return setLightness(hsl, color.isDark() ? lightest : darkest);
  721. }
  722.  
  723. function svgToUrl(str) {
  724. const cleaned = str.replace(/[\t\n\r]/gim, "").replace(/\s\s+/g, " ");
  725. const encoded = encodeURIComponent(cleaned)
  726. .replace(/\(/g, "%28")
  727. .replace(/\)/g, "%29");
  728. return `data:image/svg+xml;charset=UTF-8,${encoded}`;
  729. }
  730. const rulerMarkerLength = (len) => {
  731. if (len % 100 === 0) {
  732. return 15;
  733. }
  734. if (len % 50 === 0) {
  735. return 10;
  736. }
  737. if (len % 25 === 0) {
  738. return 5;
  739. }
  740. return 2.5;
  741. };
  742. function rectangleRuler(width, height, bgColor, textColor) {
  743. let markers = "";
  744. for (let x = 0; x <= width; x += 5) {
  745. const tick = html` <line
  746. x1="${x}"
  747. y1="0"
  748. x2="${x}"
  749. y2="${rulerMarkerLength(x)}"
  750. />`;
  751. markers += tick;
  752. if (x !== 0 && x % 50 === 0) {
  753. const label = html` <text
  754. x="${x}"
  755. y="25"
  756. text-anchor="middle"
  757. font-size="${rulerMarkerLength(x)}px"
  758. >
  759. ${x}
  760. </text>`;
  761. markers += label;
  762. }
  763. }
  764. for (let y = 0; y <= height; y += 5) {
  765. const tick = html` <line
  766. x1="0"
  767. y1="${y}"
  768. x2="${rulerMarkerLength(y)}"
  769. y2="${y}"
  770. />`;
  771. markers += tick;
  772. if (y !== 0 && y % 50 === 0) {
  773. const label = html` <text
  774. x="25"
  775. y="${y}"
  776. text-anchor="middle"
  777. dominant-baseline="middle"
  778. font-size="${rulerMarkerLength(y)}px"
  779. >
  780. ${y}
  781. </text>`;
  782. markers += label;
  783. }
  784. }
  785. return html` <svg
  786. xmlns="http://www.w3.org/2000/svg"
  787. width="${width}"
  788. height="${height}"
  789. viewBox="0 0 ${width} ${height}"
  790. >
  791. <rect width="${width}" height="${height}" fill="${bgColor}" />
  792. <text
  793. fill="${textColor}"
  794. font-family="Verdana, Arial, Helvetica, sans-serif"
  795. font-size="30"
  796. dy="10.5"
  797. font-weight="bold"
  798. x="50%"
  799. y="50%"
  800. text-anchor="middle"
  801. >
  802. ${width}x${height}
  803. </text>
  804. <g
  805. stroke-width="1"
  806. font-family="Verdana, Arial, Helvetica, sans-serif"
  807. font-size="10px"
  808. font-weight="100"
  809. fill="${textColor}"
  810. stroke="${textColor}"
  811. >
  812. ${markers}
  813. </g>
  814. </svg>`;
  815. }
  816. function placeholder(
  817. width,
  818. height,
  819. bgColor = "#0F1C3F",
  820. textColor = "#ECEAD9",
  821. ) {
  822. const str = rectangleRuler(width, height, bgColor, textColor);
  823. return svgToUrl(str);
  824. }
  825. const backgrounds = Object.values(colors).map((i) => i["900"]);
  826. const widths = [400, 600, 900, 1200, 1400, 1600, 1970];
  827. const heights = [600, 800, 1e3, 1200, 1400, 2e3, 2600];
  828. function randomPlaceholder() {
  829. const randomWidth = Math.floor(Math.random() * widths.length);
  830. const randomHeight = Math.floor(Math.random() * heights.length);
  831. const randomColor = Math.floor(Math.random() * backgrounds.length);
  832. return placeholder(
  833. widths[randomWidth],
  834. heights[randomHeight],
  835. backgrounds[randomColor],
  836. );
  837. }
  838.  
  839. const localhost = {
  840. name: "Local Files",
  841. url: /(file:\/\/\/.+(index)?.html)/,
  842. homepage: "/index.html?raw=1",
  843. language: ["Raw"],
  844. category: "manga",
  845. run() {
  846. const num = parseInt(
  847. /\d+/.exec(window.location.search)?.toString() ?? "5",
  848. 10,
  849. );
  850. const comments = document.createElement("div");
  851. comments.innerHTML = Array(100).fill("Testing Comment<br/>").join("");
  852. return {
  853. title: "Placeholder Manga Loaded",
  854. series: "?reload",
  855. pages: num,
  856. begin: 1,
  857. prev: "?pages=50",
  858. next: "?pages=1",
  859. listImages: [
  860. placeholder(1970, 1400, "#2D1657"),
  861. placeholder(985, 1400, "#152C55"),
  862. placeholder(985, 1400, "#7A1420"),
  863. placeholder(985, 1400, "#0F5B30"),
  864. placeholder(1970, 1400, "#806D15"),
  865. ...Array(num).fill(0).map(randomPlaceholder),
  866. ],
  867. comments,
  868. };
  869. },
  870. };
  871.  
  872. const lynxscans = {
  873. name: "LynxScans",
  874. url: /https?:\/\/(www\.)?lynxscans.com\/comics?\/.+/,
  875. homepage: "https://lynxscans.com/",
  876. language: ["English"],
  877. category: "manga",
  878. waitAttr: ["#app", "data-page"],
  879. run() {
  880. const data = JSON.parse(
  881. document.querySelector("#app").getAttribute("data-page"),
  882. );
  883. return {
  884. title: document.querySelector("title")?.textContent?.trim(),
  885. series: data.props.home,
  886. pages: data.props.pages.length,
  887. prev: data.props.previousChapter,
  888. next: data.props.nextChapter,
  889. listImages: data.props.pages.map((img) => img.url),
  890. };
  891. },
  892. };
  893.  
  894. const imageRegex =
  895. /^([\t\n])*(https?:\/\/)?.+\.(jpg|jpeg|png|gif|bmp|webp).*$/;
  896. function findImages$1() {
  897. return [
  898. ...document.querySelectorAll(
  899. ".wp-manga-chapter-img, .blocks-gallery-item img, .reading-content img, #chapter-images img, #chapterContent img",
  900. ),
  901. ].map(
  902. (img) =>
  903. [...img.attributes]
  904. .filter(
  905. (attr) =>
  906. /.*(src|url).*/i.test(attr.name) &&
  907. !/^.*(blank|lazy|load).*$/.test(attr.value),
  908. )
  909. .find((attr) => imageRegex.test(attr.value))?.value ??
  910. img?.getAttribute("src"),
  911. );
  912. }
  913. const madarawp = {
  914. name: [
  915. "Madara WordPress Plugin",
  916. "MangaHaus",
  917. "Isekai Scan",
  918. "Comic Kiba",
  919. "Zinmanga",
  920. "mangatx",
  921. "Toonily",
  922. "Mngazuki",
  923. "JaiminisBox",
  924. "DisasterScans",
  925. "ManhuaPlus",
  926. "TopManhua",
  927. "NovelMic",
  928. "Reset-Scans",
  929. "LeviatanScans",
  930. "Dragon Tea",
  931. "SetsuScans",
  932. "ToonGod",
  933. ],
  934. url: /https?:\/\/.+\/(manga|series|manhua|comic|ch|novel|webtoon)\/.+\/.+/,
  935. homepage: [
  936. "https://mangabooth.com/",
  937. "https://manhuaus.com",
  938. "https://isekaiscan.com/",
  939. "https://comickiba.com/",
  940. "https://zinmanga.com/",
  941. "https://mangatx.com/",
  942. "https://toonily.net/",
  943. "https://mangazuki.me/",
  944. "https://jaiminisbox.net",
  945. "https://disasterscans.com/",
  946. "https://manhuaplus.org/",
  947. "https://www.topmanhua.com/",
  948. "https://novelmic.com/",
  949. "https://reset-scans.com/",
  950. "https://leviatanscans.com/",
  951. "https://dragontea.ink/",
  952. "https://setsuscans.com/",
  953. "https://toongod.org/home/",
  954. ],
  955. language: ["English"],
  956. obs: "Any Site that uses Madara Wordpress Plugin",
  957. category: "manga",
  958. waitFunc: () => {
  959. const images = findImages$1();
  960. return images.length > 0 && images.every((s) => s && imageRegex.test(s));
  961. },
  962. run() {
  963. const images = findImages$1();
  964. return {
  965. title: document.querySelector("#chapter-heading")?.textContent?.trim(),
  966. series: (
  967. document.querySelector(".breadcrumb li:nth-child(3) a") ??
  968. document.querySelector(".breadcrumb li:nth-child(2) a")
  969. )?.getAttribute("href"),
  970. pages: images.length,
  971. prev: document.querySelector(".prev_page")?.getAttribute("href"),
  972. next: document.querySelector(".next_page")?.getAttribute("href"),
  973. listImages: images,
  974. };
  975. },
  976. };
  977.  
  978. const mangabuddy = {
  979. name: "MangaBuddy",
  980. url: /https?:\/\/(www\.)?mangabuddy.com\/.+\/chapter.+/,
  981. homepage: "https://mangabuddy.com/",
  982. language: ["English"],
  983. category: "manga",
  984. waitVar: "chapImages",
  985. run() {
  986. const images = unsafeWindow.chapImages.split(",");
  987. return {
  988. title: document.querySelector(".chapter-info")?.textContent?.trim(),
  989. series: document
  990. .querySelector("#breadcrumbs-container div:nth-child(2) a")
  991. ?.getAttribute("href"),
  992. pages: images.length,
  993. prev: document.querySelector("a.prev")?.getAttribute("href"),
  994. next: document.querySelector("a.next")?.getAttribute("href"),
  995. listImages: images,
  996. };
  997. },
  998. };
  999.  
  1000. const mangademon = {
  1001. name: "MangaDemon",
  1002. url: /https?:\/\/(www\.)?demonicscans\.org\/title\/.+\/chapter\/.+/,
  1003. homepage: "https://demonicscans.org/",
  1004. language: ["English"],
  1005. category: "manga",
  1006. run() {
  1007. const images = [...document.querySelectorAll(".imgholder")];
  1008. return {
  1009. title: document.querySelector("title")?.textContent?.trim(),
  1010. series: document.querySelector("h1 a")?.getAttribute("href"),
  1011. pages: images.length,
  1012. prev: document.querySelector(".prevchap")?.getAttribute("href"),
  1013. next: document.querySelector(".nextchap")?.getAttribute("href"),
  1014. listImages: images.map(
  1015. (img) => img.getAttribute("data-src") || img.getAttribute("src"),
  1016. ),
  1017. };
  1018. },
  1019. };
  1020.  
  1021. const mangadex = {
  1022. name: "MangaDex",
  1023. url: /https?:\/\/(www\.)?mangadex.org/,
  1024. homepage: "https://mangadex.org/",
  1025. language: ["English"],
  1026. category: "manga",
  1027. waitEle: "#chapter-selector a",
  1028. async run() {
  1029. const chapterId = /\/chapter\/([^/]+)(\/\d+)?/
  1030. .exec(window.location.pathname)
  1031. ?.at(1);
  1032. const home = `https://api.mangadex.org/at-home/server/${chapterId}`;
  1033. const server = await fetch(home).then(async (res) => res.json());
  1034. const images = server.chapter.data;
  1035. const chapters = document.querySelectorAll("#chapter-selector a");
  1036. return {
  1037. title: document.querySelector("title")?.text.replace(" - MangaDex", ""),
  1038. series: document
  1039. .querySelector("a.text-primary[href^='/title/']")
  1040. ?.getAttribute("href"),
  1041. pages: images.length,
  1042. prev: chapters?.item(0)?.getAttribute("href"),
  1043. next: chapters?.item(1)?.getAttribute("href"),
  1044. listImages: images.map(
  1045. (img) => `${server.baseUrl}/data/${server.chapter.hash}/${img}`,
  1046. ),
  1047. };
  1048. },
  1049. };
  1050.  
  1051. const mangafox = {
  1052. name: ["MangaFox", "MangaHere"],
  1053. url: /https?:\/\/(www\.)?(fanfox.net|mangahere.cc)\/manga\/.+\/.+\//,
  1054. homepage: ["https://fanfox.net/", "https://www.mangahere.cc/"],
  1055. language: ["English"],
  1056. category: "manga",
  1057. waitVar: "chapterid",
  1058. async run() {
  1059. const key = document.querySelector("#dm5_key")?.getAttribute("value");
  1060. const options = {
  1061. method: "GET",
  1062. headers: {
  1063. "Content-Type": "text/plain",
  1064. },
  1065. };
  1066. const src = Array(unsafeWindow.imagecount)
  1067. .fill(0)
  1068. .map(async (_, i) => {
  1069. const url = `chapterfun.ashx?cid=${unsafeWindow.chapterid ?? unsafeWindow.chapter_id}&page=${i}&key=${key}`;
  1070. const api = await fetch(url, options).then(async (res) => res.text());
  1071. (0, eval)(api);
  1072. return d;
  1073. });
  1074. const images = await Promise.all(src);
  1075. return {
  1076. title: document
  1077. .querySelector(".reader-header-title div")
  1078. ?.textContent?.trim(),
  1079. series: document
  1080. .querySelector(".reader-header-title a")
  1081. ?.getAttribute("href"),
  1082. pages: unsafeWindow.imagecount,
  1083. prev: unsafeWindow.prechapterurl,
  1084. next: unsafeWindow.nextchapterurl,
  1085. listImages: images.map((img, i) => img[i === 0 ? 0 : 1]),
  1086. };
  1087. },
  1088. };
  1089.  
  1090. const mangago = {
  1091. name: "Mangago",
  1092. url: /https?:\/\/(www\.)?mangago.me\/.*\/.*\/.*/,
  1093. homepage: "https://www.mangago.me/",
  1094. language: ["English"],
  1095. category: "manga",
  1096. waitVar: "imgsrcs",
  1097. run() {
  1098. const key = CryptoJS.enc.Hex.parse("e11adc3949ba59abbe56e057f20f883e");
  1099. const iv = CryptoJS.enc.Hex.parse("1234567890abcdef1234567890abcdef");
  1100. const opinion = { iv, padding: CryptoJS.pad.ZeroPadding };
  1101. const images = CryptoJS.AES.decrypt(unsafeWindow.imgsrcs, key, opinion)
  1102. .toString(CryptoJS.enc.Utf8)
  1103. .split(",");
  1104. return {
  1105. title: `${unsafeWindow.manga_name} ${unsafeWindow.chapter_name}`,
  1106. series: unsafeWindow.mid,
  1107. pages: unsafeWindow.total_pages,
  1108. prev: document
  1109. .querySelector(".recom p:nth-child(5) a")
  1110. ?.getAttribute("href"),
  1111. next: unsafeWindow.next_c_url,
  1112. listImages: images,
  1113. before() {
  1114. if (images.some((s) => s === "")) {
  1115. document.querySelector("#nform")?.submit();
  1116. }
  1117. },
  1118. };
  1119. },
  1120. };
  1121.  
  1122. const mangahosted = {
  1123. name: "MangaHosted",
  1124. url: /https?:\/\/(www\.)?mangahosted.com\/manga\/.+\/.+/,
  1125. homepage: "https://mangahosted.com/",
  1126. language: ["Portuguese"],
  1127. category: "manga",
  1128. run() {
  1129. const images = [...document.querySelectorAll("picture img")];
  1130. return {
  1131. title: $(".breadcrumb li:eq(3)").text().trim(),
  1132. series: $(".breadcrumb li:eq(2) a").attr("href"),
  1133. pages: images.length,
  1134. prev: unsafeWindow.$read_prev,
  1135. next: unsafeWindow.$read_next,
  1136. listImages: images.map((img) => img.getAttribute("src")),
  1137. };
  1138. },
  1139. };
  1140.  
  1141. const mangahub = {
  1142. name: "MangaHub",
  1143. url: /https?:\/\/(www\.)?(mangahub).io\/chapter\/.+\/.+/,
  1144. homepage: "https://mangahub.io/",
  1145. language: ["English"],
  1146. category: "manga",
  1147. waitEle: "#select-chapter",
  1148. async run() {
  1149. function getCookie(name) {
  1150. const re = new RegExp(`${name}=([^;]+)`);
  1151. const value = re.exec(document.cookie);
  1152. return value != null ? decodeURIComponent(value[1]) : null;
  1153. }
  1154. const slug =
  1155. unsafeWindow.CURRENT_MANGA_SLUG ??
  1156. window.location.pathname.split("/")[2];
  1157. const number = window.location.pathname
  1158. .split("/")[3]
  1159. .replace("chapter-", "");
  1160. const data = {
  1161. query: `{chapter(x:m01,slug:"${slug}",number:${number}){pages}}`,
  1162. };
  1163. const options = {
  1164. method: "POST",
  1165. body: JSON.stringify(data),
  1166. headers: {
  1167. "Content-Type": "application/json",
  1168. "x-mhub-access": getCookie("mhub_access") ?? "",
  1169. },
  1170. };
  1171. const api = await fetch("https://api.mghcdn.com/graphql", options).then(
  1172. async (res) => res.json(),
  1173. );
  1174. const images = JSON.parse(api?.data.chapter.pages.toString());
  1175. return {
  1176. title: document.querySelector("#mangareader h3")?.textContent?.trim(),
  1177. series: document.querySelector("#mangareader a")?.getAttribute("href"),
  1178. pages: images.i.length,
  1179. prev: document.querySelector(".previous a")?.getAttribute("href"),
  1180. next: document.querySelector(".next a")?.getAttribute("href"),
  1181. listImages: images.i.map(
  1182. (i) => `https://imgx.mghcdn.com/${images.p + i}`,
  1183. ),
  1184. };
  1185. },
  1186. };
  1187.  
  1188. const mangasin = {
  1189. name: "MangasIn",
  1190. url: /https?:\/\/(www\.)?mangas.in\/manga\/.+\/.+\/\d+/,
  1191. homepage: "https://mangas.in/",
  1192. language: ["Spanish"],
  1193. category: "manga",
  1194. run() {
  1195. const images = [...document.querySelectorAll("#all img")];
  1196. const chapter = document.querySelector("#chapter-list li.active");
  1197. return {
  1198. title: document.querySelector("title")?.textContent?.trim(),
  1199. series: document
  1200. .querySelector("#navbar-collapse-1 ul:nth-child(2) a")
  1201. ?.getAttribute("href"),
  1202. pages: images.length,
  1203. prev: chapter?.nextElementSibling?.firstElementChild?.getAttribute(
  1204. "href",
  1205. ),
  1206. next: chapter?.previousElementSibling?.firstElementChild?.getAttribute(
  1207. "href",
  1208. ),
  1209. listImages: images.map((img) => img.getAttribute("data-src")),
  1210. };
  1211. },
  1212. };
  1213.  
  1214. const mangakakalot = {
  1215. name: ["MangaKakalot", "MangaNelo", "MangaNato"],
  1216. url: /https?:\/\/(www\.)?(read|chap)?(manganelo|mangakakalot|manganato).(com|to).*\/chapter.+/,
  1217. homepage: [
  1218. "https://mangakakalot.com/",
  1219. "https://www.manganelo.com/",
  1220. "https://www.manganato.com/",
  1221. ],
  1222. language: ["English"],
  1223. category: "manga",
  1224. run() {
  1225. const images = [
  1226. ...document.querySelectorAll(
  1227. "#vungdoc img, .container-chapter-reader img",
  1228. ),
  1229. ];
  1230. return {
  1231. title: document
  1232. .querySelector(
  1233. ".info-top-chapter h2, .imageOptions-chapter-info-top h1, .panel-chapter-info-top h1",
  1234. )
  1235. ?.textContent?.trim(),
  1236. series: document
  1237. .querySelectorAll("span a[title]")
  1238. .item(1)
  1239. .getAttribute("href"),
  1240. pages: images.length,
  1241. prev: document
  1242. .querySelector(".navi-change-chapter-btn-prev, .next")
  1243. ?.getAttribute("href"),
  1244. next: document
  1245. .querySelector(".navi-change-chapter-btn-next, .back")
  1246. ?.getAttribute("href"),
  1247. listImages: images.map((img) => img.getAttribute("src")),
  1248. };
  1249. },
  1250. };
  1251.  
  1252. const mangaoni = {
  1253. name: "MangaOni",
  1254. url: /https?:\/\/(www\.)?manga-oni.com\/lector\/.+\/\d+\/cascada/,
  1255. homepage: "https://manga-oni.com/",
  1256. language: ["Spanish"],
  1257. category: "manga",
  1258. run() {
  1259. document.querySelector("#c_list")?.dispatchEvent(new Event("mouseover"));
  1260. const chapter = document.querySelector("#c_list option:checked");
  1261. const images = [...document.querySelectorAll("#slider img")];
  1262. return {
  1263. title: document
  1264. .querySelector("title")
  1265. ?.text.replace(" — Manga en línea | MangaOni", ""),
  1266. pages: images?.length,
  1267. prev: chapter?.nextElementSibling?.getAttribute("value"),
  1268. next: chapter?.previousElementSibling?.getAttribute("value"),
  1269. listImages: images.map(
  1270. (img) => img.getAttribute("data-src") ?? img.getAttribute("src"),
  1271. ),
  1272. };
  1273. },
  1274. };
  1275.  
  1276. const mangapark = {
  1277. name: "MangaPark",
  1278. url: /https?:\/\/(www\.)?mangapark.(com|me|org|net)\/title\/.+\/.+/,
  1279. homepage: "https://mangapark.net/",
  1280. language: ["English"],
  1281. category: "manga",
  1282. waitEle: "main div div a.btn-primary",
  1283. run() {
  1284. const json = JSON.parse(
  1285. document.querySelector("#__NEXT_DATA__")?.innerHTML ?? "",
  1286. );
  1287. const data =
  1288. json.props.pageProps.dehydratedState.queries[0].state.data.data
  1289. .imageSet;
  1290. const images = data.httpLis.map(
  1291. (img, index) => `${img}?${data.wordLis[index]}`,
  1292. );
  1293. return {
  1294. title: [
  1295. ...document.querySelectorAll(
  1296. ".comic-detail h3 a, .comic-detail h6 span",
  1297. ),
  1298. ]
  1299. .map((e) => e.textContent?.trim())
  1300. .join(" "),
  1301. series: document.querySelector(".comic-detail a")?.getAttribute("href"),
  1302. pages: images.length,
  1303. prev: document
  1304. .querySelectorAll("main div div a.btn-primary")
  1305. ?.item(0)
  1306. ?.getAttribute("href"),
  1307. next: document
  1308. .querySelectorAll("main div div a.btn-primary")
  1309. ?.item(1)
  1310. ?.getAttribute("href"),
  1311. listImages: images,
  1312. };
  1313. },
  1314. };
  1315.  
  1316. const mangareader = {
  1317. name: "Mangareader",
  1318. url: /https?:\/\/(www\.)?mangareader.to\/read\/.+\/.+\/.+/,
  1319. homepage: "https://mangareader.to",
  1320. language: ["English"],
  1321. category: "manga",
  1322. obs: "Some galleries will not be usable",
  1323. waitEle: ".ds-image, .iv-card",
  1324. async run() {
  1325. const chapter = document.querySelector(".chapter-item.active");
  1326. const images = [
  1327. ...document.querySelectorAll(".ds-image[data-url], .iv-card[data-url]"),
  1328. ];
  1329. const src = images.map(async (img) => {
  1330. const url = img.getAttribute("data-url");
  1331. if (url && img.classList.contains("shuffled")) {
  1332. return (await imgReverser(url)).toDataURL();
  1333. }
  1334. return url;
  1335. });
  1336. return {
  1337. title: document.querySelector(".hr-manga h2")?.textContent?.trim(),
  1338. series: document.querySelector(".hr-manga")?.getAttribute("href"),
  1339. pages: src.length,
  1340. prev: chapter?.nextElementSibling
  1341. ?.querySelector("a")
  1342. ?.getAttribute("href"),
  1343. next: chapter?.previousElementSibling
  1344. ?.querySelector("a")
  1345. ?.getAttribute("href"),
  1346. listImages: await Promise.all(src),
  1347. };
  1348. },
  1349. };
  1350.  
  1351. const mangasee = {
  1352. name: ["MangaSee", "Manga4life"],
  1353. url: /https?:\/\/(www\.)?(mangasee123|manga4life).com\/read-online\/.+/,
  1354. homepage: ["https://mangasee123.com/", "https://manga4life.com/"],
  1355. language: ["English"],
  1356. category: "manga",
  1357. waitAttr: [".img-fluid", "src"],
  1358. run() {
  1359. const src =
  1360. document.querySelector(".img-fluid")?.getAttribute("src") ?? "";
  1361. const script = [
  1362. ...document.querySelectorAll("body script:not([src])"),
  1363. ].at(-1)?.textContent;
  1364. const textCurChapter = script?.match(/CurChapter = ({.+});/) ?? [];
  1365. const CurChapter = JSON.parse(textCurChapter[1]);
  1366. const textCHAPTERS = script?.match(/CHAPTERS = (\[\{.+}]);/) ?? [];
  1367. const CHAPTERS = JSON.parse(textCHAPTERS[1]);
  1368. const CurChapterIndex = CHAPTERS.findIndex(
  1369. (chap) => chap.Chapter === CurChapter.Chapter,
  1370. );
  1371. function ChapterURLEncode(reference) {
  1372. let ChapterString = CHAPTERS[CurChapterIndex + reference];
  1373. if (ChapterString === void 0) {
  1374. return "#";
  1375. }
  1376. ChapterString = ChapterString.Chapter;
  1377. let Index = "";
  1378. const IndexString = ChapterString.substring(0, 1);
  1379. if (IndexString !== "1") {
  1380. Index = `-index-${IndexString}`;
  1381. }
  1382. const Chapter = parseInt(ChapterString.slice(1, -1), 10);
  1383. let Odd = "";
  1384. const OddString = ChapterString[ChapterString.length - 1];
  1385. if (OddString !== "0") {
  1386. Odd = `.${OddString}`;
  1387. }
  1388. return window.location.href.replace(
  1389. /-chapter-.+/,
  1390. `-chapter-${Chapter}${Odd}${Index}.html`,
  1391. );
  1392. }
  1393. return {
  1394. title: document
  1395. .querySelector("title")
  1396. ?.textContent?.replace(/ Page .+/, "")
  1397. .trim(),
  1398. series: document
  1399. .querySelector(".MainContainer a")
  1400. ?.getAttribute("href"),
  1401. pages: parseInt(CurChapter.Page, 10),
  1402. prev: ChapterURLEncode(-1),
  1403. next: ChapterURLEncode(1),
  1404. listImages: Array(parseInt(CurChapter.Page, 10))
  1405. .fill(0)
  1406. .map((_, i) =>
  1407. src.replace(
  1408. /-\d\d\d.png/,
  1409. `-${String(i + 1)
  1410. .padStart(3, "0")
  1411. .slice(-3)}.png`,
  1412. ),
  1413. ),
  1414. };
  1415. },
  1416. };
  1417.  
  1418. const mangastreamwp = {
  1419. name: [
  1420. "MangaStream WordPress Plugin",
  1421. "Flame Comics",
  1422. "Realm Oasis",
  1423. "Voids-Scans",
  1424. "Luminous Scans",
  1425. "Shimada Scans",
  1426. "Night Scans",
  1427. "Manhwa-Freak",
  1428. "OzulScansEn",
  1429. "CypherScans",
  1430. "MangaGalaxy",
  1431. "LuaScans",
  1432. "Drake Scans",
  1433. "Rizzfables",
  1434. ],
  1435. url: /https?:\/\/[^/]*(scans|comic|realmoasis|hivetoon|rizzfables)[^/]*\/.+/,
  1436. homepage: [
  1437. "https://themesia.com/mangastream-wordpress-theme/",
  1438. "https://flamecomics.com/",
  1439. "https://realmoasis.com/",
  1440. "https://void-scans.com/",
  1441. "https://luminous-scans.com/",
  1442. "https://shimadascans.com/",
  1443. "https://night-scans.com/",
  1444. "https://freakcomic.com/",
  1445. "https://ozulscansen.com/",
  1446. "https://cypherscans.xyz/",
  1447. "https://mangagalaxy.me/",
  1448. "https://luascans.com/",
  1449. "https://drake-scans.com/",
  1450. "https://rizzfables.com/",
  1451. ],
  1452. language: ["English"],
  1453. category: "manga",
  1454. // waitTime: 2000,
  1455. waitEle: "#chapter option:nth-child(2)",
  1456. run() {
  1457. const chapterSelector = document.querySelector("#chapter option:checked");
  1458. const chapter = [
  1459. ...document.querySelectorAll(".nextprev").item(0).querySelectorAll("a"),
  1460. ];
  1461. const images = [
  1462. ...document.querySelectorAll(
  1463. '#readerarea img:not(.asurascans):not([src*="loader"])',
  1464. ),
  1465. ];
  1466. return {
  1467. title: document.querySelector(".entry-title")?.textContent?.trim(),
  1468. series:
  1469. document.querySelector(".allc a")?.getAttribute("href") ??
  1470. document
  1471. .querySelectorAll('[class*="breadcrumb"] a')
  1472. .item(1)
  1473. ?.getAttribute("href"),
  1474. pages: images.length,
  1475. prev:
  1476. (chapter.at(0)?.classList.contains("disabled")
  1477. ? void 0
  1478. : chapter.at(0)?.getAttribute("href")) ??
  1479. chapterSelector?.nextElementSibling?.getAttribute("value"),
  1480. next:
  1481. (chapter.at(1)?.classList.contains("disabled")
  1482. ? void 0
  1483. : chapter.at(1)?.getAttribute("href")) ??
  1484. chapterSelector?.previousElementSibling?.getAttribute("value"),
  1485. listImages: images.map(
  1486. (img) =>
  1487. img.getAttribute("data-src") ??
  1488. img.getAttribute("data-lazy-src") ??
  1489. img.getAttribute("src"),
  1490. ),
  1491. };
  1492. },
  1493. };
  1494.  
  1495. const mangatigre = {
  1496. name: "MangaTigre",
  1497. url: /https?:\/\/(www\.)?mangatigre.net\/.+\/.+\/.+/,
  1498. homepage: "https://www.mangatigre.net/",
  1499. language: ["Spanish"],
  1500. category: "manga",
  1501. run() {
  1502. const images = [...document.querySelectorAll(".chapter-content img")];
  1503. const chapter = document.querySelector(".form-control option:checked");
  1504. return {
  1505. title: document.querySelector(".page-title")?.textContent?.trim(),
  1506. series: document
  1507. .querySelector(".breadcrumb li:nth-child(3) a")
  1508. ?.getAttribute("href"),
  1509. pages: images.length,
  1510. prev: chapter?.nextElementSibling?.getAttribute("value"),
  1511. next: chapter?.previousElementSibling?.getAttribute("value"),
  1512. listImages: images.map(
  1513. (img) => img.getAttribute("data-src") ?? img.getAttribute("src"),
  1514. ),
  1515. };
  1516. },
  1517. };
  1518.  
  1519. const mangatoon = {
  1520. name: "MangaToons",
  1521. url: /https?:\/\/.*mangatoon.mobi\/.+\/watch\/.+/,
  1522. homepage: "https://mangatoon.mobi/",
  1523. language: ["English"],
  1524. category: "manga",
  1525. waitEle: ".pictures img:not(.cover)",
  1526. run() {
  1527. const images = [
  1528. ...document.querySelectorAll(".pictures img:not(.cover)"),
  1529. ];
  1530. return {
  1531. title: document.querySelector("title")?.textContent?.trim(),
  1532. series: document.querySelector(".top-left a")?.getAttribute("href"),
  1533. pages: images.length,
  1534. prev: document.querySelector(".page-icons-prev")?.getAttribute("href"),
  1535. next: document.querySelector(".page-icons-next")?.getAttribute("href"),
  1536. listImages: images.map((img) => img.getAttribute("data-src")),
  1537. };
  1538. },
  1539. };
  1540.  
  1541. const mangatown = {
  1542. name: "MangaTown",
  1543. url: /https?:\/\/(www\.|m\.)?mangatown.com\/manga\/.+\/.+/,
  1544. homepage: "https://www.mangatown.com/",
  1545. language: ["English"],
  1546. category: "manga",
  1547. waitVar: "chapter_id",
  1548. async run() {
  1549. const key = document.querySelector("#dm5_key")?.getAttribute("value");
  1550. const options = {
  1551. method: "GET",
  1552. headers: {
  1553. "Content-Type": "text/plain",
  1554. },
  1555. };
  1556. const src = Array(unsafeWindow.total_pages)
  1557. .fill(0)
  1558. .map(async (_, i) => {
  1559. const url = `chapterfun.ashx?cid=${unsafeWindow.chapter_id}&page=${i}&key=${key}`;
  1560. const api = await fetch(url, options).then(async (res) => res.text());
  1561. (0, eval)(api);
  1562. return d;
  1563. });
  1564. const images = await Promise.all(src);
  1565. const chapter = document.querySelector(
  1566. "#top_chapter_list option:checked",
  1567. );
  1568. return {
  1569. title: document.querySelector(".title h1")?.textContent,
  1570. series: unsafeWindow.series_url,
  1571. pages: images.length,
  1572. prev: chapter?.previousElementSibling?.getAttribute("value"),
  1573. next: chapter?.nextElementSibling?.getAttribute("value"),
  1574. listImages: images.map((img, i) => img[i === 0 ? 0 : 1]),
  1575. };
  1576. },
  1577. };
  1578.  
  1579. function findImages() {
  1580. return [...document.querySelectorAll(".chapter-image")]
  1581. .map((div) => div.querySelector("img"))
  1582. .map(
  1583. (img) =>
  1584. img?.getAttribute("src") ??
  1585. img?.getAttribute("data-src") ??
  1586. img?.getAttribute("data-full-url"),
  1587. )
  1588. .filter((src) => !src?.match(/loading/i));
  1589. }
  1590. const manhuascan = {
  1591. name: "ManhuaScan",
  1592. url: /https?:\/\/(www\.)?manhuascan.com\/manga\/.+\/chapter.+/,
  1593. homepage: "https://manhuascan.com/",
  1594. language: ["English"],
  1595. category: "manga",
  1596. waitFunc: () => {
  1597. const images = findImages();
  1598. return (
  1599. images.length > 0 &&
  1600. images.every(
  1601. (s) =>
  1602. s &&
  1603. /^([\t\n])*(https?:\/\/)?.+\.(jpg|jpeg|png|gif|bmp|webp).*$/.test(
  1604. s,
  1605. ),
  1606. )
  1607. );
  1608. },
  1609. run() {
  1610. const images = findImages();
  1611. return {
  1612. title: document.querySelector("title")?.textContent?.trim(),
  1613. series: document
  1614. .querySelector('#breadcrumbs-container div a[title="Plaything"]')
  1615. ?.getAttribute("href"),
  1616. pages: images.length,
  1617. prev: document
  1618. .querySelector("#chapter-list ~ div li:nth-of-type(2) a")
  1619. ?.getAttribute("href"),
  1620. next: document
  1621. .querySelector("#chapter-list ~ div li:nth-of-type(3) a")
  1622. ?.getAttribute("href"),
  1623. listImages: images,
  1624. };
  1625. },
  1626. };
  1627.  
  1628. const manhwaweb = {
  1629. name: "ManhwaWeb",
  1630. url: /https?:\/\/(www\.)?manhwaweb.com\/leer\/.+/,
  1631. homepage: "https://manhwaweb.com/",
  1632. language: ["Spanish"],
  1633. category: "manga",
  1634. async run() {
  1635. const slug = window.location.pathname.replace("/leer", "");
  1636. const api = await fetch(
  1637. `https://manhwawebbackend-production.up.railway.app/chapters/see${slug}`,
  1638. ).then(async (res) => res.json());
  1639. const data = await fetch(
  1640. `https://manhwawebbackend-production.up.railway.app/chapters/seeprevpost${slug}`,
  1641. ).then(async (res) => res.json());
  1642. return {
  1643. title: `${api.name} ${api.chapter.chapter}`,
  1644. series: [...document.querySelectorAll("div")]
  1645. .filter((i) => i.textContent === "Episodios")?.[0]
  1646. ?.parentElement?.getAttribute("href"),
  1647. pages: api.chapter.img.length,
  1648. prev: data.chapterAnterior,
  1649. next: data.chapterSiguiente,
  1650. listImages: api.chapter.img,
  1651. };
  1652. },
  1653. };
  1654.  
  1655. const mgeko = {
  1656. name: ["MangaGeko.com", "MangaGeko.cc"],
  1657. url: /https?:\/\/(www\.)?mgeko.(com|cc)?\/reader\/.*/,
  1658. homepage: ["https://www.mgeko.com/", "https://www.mgeko.cc/"],
  1659. language: ["English"],
  1660. category: "manga",
  1661. run() {
  1662. const images = [...document.querySelectorAll("#chapter-reader img")];
  1663. return {
  1664. title: document.querySelector(".titles")?.textContent?.trim(),
  1665. series: document.querySelector(".titles a")?.getAttribute("href"),
  1666. pages: images.length,
  1667. prev: document.querySelector(".chnav.prev")?.getAttribute("href"),
  1668. next: document.querySelector(".chnav.next")?.getAttribute("href"),
  1669. listImages: images.map((img) => img.getAttribute("src")),
  1670. };
  1671. },
  1672. };
  1673.  
  1674. const naniscans = {
  1675. name: "NaniScans",
  1676. url: /https?:\/\/(www\.)?(naniscans).com\/chapters\/.+\/read/,
  1677. homepage: "https://naniscans.com/",
  1678. language: ["English"],
  1679. category: "manga",
  1680. waitVar: "chapterListRoute",
  1681. async run() {
  1682. const api = await fetch(
  1683. window.location.href.replace("read", "json"),
  1684. ).then(async (res) => res.json());
  1685. return {
  1686. title: document.querySelector("title")?.textContent?.trim(),
  1687. series: document
  1688. .querySelector('a[href^="/titles/"]')
  1689. ?.getAttribute("href"),
  1690. pages: api.pages.length,
  1691. prev: unsafeWindow.previousChapterRoute,
  1692. next: unsafeWindow.nextChapterRoute,
  1693. listImages: api.pages.map((i) => i.address),
  1694. };
  1695. },
  1696. };
  1697.  
  1698. const ninemanga = {
  1699. name: "NineManga",
  1700. url: /https?:\/\/(www\.)?ninemanga.com\/chapter\/.+\/.+\.html/,
  1701. homepage: "https://ninemanga.com/",
  1702. language: ["English"],
  1703. category: "manga",
  1704. run() {
  1705. const chapter = document.querySelector("#chapter option:checked");
  1706. const pages = [
  1707. ...document.querySelector("#page").querySelectorAll("option"),
  1708. ];
  1709. return {
  1710. title: document.querySelector(".tip a")?.textContent?.trim(),
  1711. series: document
  1712. .querySelector(".subgiude > li:nth-child(2) > a")
  1713. ?.getAttribute("href"),
  1714. pages: pages.length,
  1715. prev: chapter?.nextElementSibling?.getAttribute("value"),
  1716. next: chapter?.previousElementSibling?.getAttribute("value"),
  1717. listPages: pages.map((item) => $(item).val()),
  1718. img: ".manga_pic",
  1719. };
  1720. },
  1721. };
  1722.  
  1723. const olympusscans = {
  1724. name: "OlympusScans",
  1725. url: /https?:\/\/(www\.)?olympusscans.com\/capitulo\/.+\/.+/,
  1726. homepage: "https://olympusscans.com/",
  1727. language: ["Spanish"],
  1728. category: "manga",
  1729. waitVar: "__NUXT__",
  1730. run() {
  1731. const images =
  1732. unsafeWindow.__NUXT__.data[window.location.pathname].chapter.pages;
  1733. return {
  1734. title: document.querySelector("title")?.textContent?.trim(),
  1735. series: document
  1736. .querySelector("h1")
  1737. ?.parentElement?.getAttribute("href"),
  1738. pages: images.length,
  1739. prev: document
  1740. .querySelector(".i-heroicons-chevron-left-20-solid")
  1741. ?.parentElement?.getAttribute("href"),
  1742. next: document
  1743. .querySelector(".i-heroicons-chevron-right-20-solid")
  1744. ?.parentElement?.getAttribute("href"),
  1745. listImages: images,
  1746. };
  1747. },
  1748. };
  1749.  
  1750. const pandamanga = {
  1751. name: "PandaManga",
  1752. url: /https?:\/\/(www\.)?pandamanga.xyz\/.+\/.+\/.+/,
  1753. homepage: "https://www.pandamanga.com/",
  1754. language: ["English"],
  1755. category: "manga",
  1756. run() {
  1757. const chapter = document.querySelector(".select-chapter option:checked");
  1758. const data = JSON.parse(
  1759. document.getElementById("__NEXT_DATA__").textContent,
  1760. );
  1761. const images = data.props.pageProps.mangaview.source
  1762. .split(",")
  1763. .filter((url) => url.length > 0);
  1764. return {
  1765. title: data.props.pageProps.mangaview.nameSeoChapter,
  1766. series: document.querySelector(".allc a")?.getAttribute("href"),
  1767. pages: images.length,
  1768. prev: chapter?.nextElementSibling?.getAttribute("value"),
  1769. next: chapter?.previousElementSibling?.getAttribute("value"),
  1770. listImages: images,
  1771. };
  1772. },
  1773. };
  1774.  
  1775. const rawdevart = {
  1776. name: "RawDevart",
  1777. url: /https?:\/\/(www\.)?rawdevart.com\/comic\/.+\/.+\//,
  1778. homepage: "https://rawdevart.com",
  1779. language: ["Raw"],
  1780. category: "manga",
  1781. waitVar: "rconfig",
  1782. waitEle: "#chapter-list select",
  1783. run() {
  1784. const chapter = document.querySelector("#chapter-list option:checked");
  1785. const images = [...document.querySelectorAll("#img-container img")];
  1786. return {
  1787. title: unsafeWindow.rconfig.chapterTitle,
  1788. series: unsafeWindow.rconfig.prefix,
  1789. pages: images.length,
  1790. prev: chapter?.nextElementSibling?.getAttribute("value"),
  1791. next: chapter?.previousElementSibling?.getAttribute("value"),
  1792. listImages: images.map(
  1793. (item) => $(item).attr("data-src") ?? $(item).attr("src"),
  1794. ),
  1795. };
  1796. },
  1797. };
  1798.  
  1799. const readcomicsonline = {
  1800. name: "ReadComicsOnline",
  1801. url: /https?:\/\/(www\.)?readcomicsonline.ru\/comic\/.*\/\d*/,
  1802. homepage: "https://readcomicsonline.ru/",
  1803. language: ["English"],
  1804. category: "comic",
  1805. run() {
  1806. const images = [...document.querySelectorAll("#all img")];
  1807. return {
  1808. title: unsafeWindow.title.replace(/ - Page \d+/, ""),
  1809. series: document.querySelector("div.pager-cnt a")?.getAttribute("href"),
  1810. pages: unsafeWindow.pages.length,
  1811. prev: unsafeWindow.prev_chapter,
  1812. next: unsafeWindow.next_chapter,
  1813. listImages: images.map((img) => img.getAttribute("data-src")),
  1814. };
  1815. },
  1816. };
  1817.  
  1818. const readmangatoday = {
  1819. name: ["ReadManga Today"],
  1820. url: /https?:\/\/(www\.)?readm.today\/.+\/\d+/,
  1821. homepage: ["https://readm.today/"],
  1822. language: ["English"],
  1823. category: "manga",
  1824. run() {
  1825. return {
  1826. title: document.querySelector(".page-title")?.textContent?.trim(),
  1827. series: document.querySelector(".page-title a")?.getAttribute("href"),
  1828. pages: unsafeWindow.chapter.pages.length,
  1829. prev: unsafeWindow.chapter.prev,
  1830. next: unsafeWindow.chapter.next,
  1831. listImages: unsafeWindow.chapter.pages.map((item) => item.src),
  1832. };
  1833. },
  1834. };
  1835.  
  1836. const reaperscans = {
  1837. name: "ReaperScans",
  1838. url: /https?:\/\/(www\.)?reaperscans\.com\/series\/.+\/chapter.+/,
  1839. homepage: "https://reaperscans.com/",
  1840. language: ["English"],
  1841. category: "manga",
  1842. waitEle: "#content .container img:not(.rounded)",
  1843. run() {
  1844. const images = [
  1845. ...document.querySelectorAll("#content .container img:not(.rounded)"),
  1846. ];
  1847. return {
  1848. title: document.querySelector("title")?.textContent?.trim(),
  1849. series: document
  1850. .querySelector("button .fa-house")
  1851. ?.closest("a")
  1852. ?.getAttribute("href"),
  1853. pages: images.length,
  1854. prev: document
  1855. .querySelector(".fa-chevron-left")
  1856. ?.closest("a")
  1857. ?.getAttribute("href"),
  1858. next: document
  1859. .querySelector(".fa-chevron-right")
  1860. ?.closest("a")
  1861. ?.getAttribute("href"),
  1862. listImages: images.map(
  1863. (img) => img.getAttribute("data-src") || img.getAttribute("src"),
  1864. ),
  1865. };
  1866. },
  1867. };
  1868.  
  1869. const senmanga = {
  1870. name: "SenManga(Raw)",
  1871. url: /https?:\/\/raw\.senmanga.com\/.+\/.+\/?/,
  1872. homepage: "https://raw.senmanga.com/",
  1873. language: ["Raw"],
  1874. category: "manga",
  1875. run() {
  1876. const url = `/${window.location.pathname.split("/")[1]}/${window.location.pathname.split("/")[2]}`;
  1877. const num = parseInt(
  1878. document
  1879. .querySelector(".page-list select option:last-child")
  1880. ?.getAttribute("value") ?? "0",
  1881. 10,
  1882. );
  1883. const chapter = [...document.querySelectorAll(".dropdown-chapter li")];
  1884. const origin = chapter.findIndex(
  1885. (item) =>
  1886. item.querySelector("a")?.getAttribute("href") ===
  1887. window.location.href,
  1888. );
  1889. return {
  1890. title: $(".title").text().trim(),
  1891. series: document
  1892. .querySelector(".breadcrumb li:nth-child(2) a")
  1893. ?.getAttribute("href"),
  1894. pages: num,
  1895. prev: chapter
  1896. .at(origin + 1)
  1897. ?.querySelector("a")
  1898. ?.getAttribute("href"),
  1899. next: chapter
  1900. .at(origin - 1)
  1901. ?.querySelector("a")
  1902. ?.getAttribute("href"),
  1903. listPages: Array(num)
  1904. .fill(0)
  1905. .map((_, i) => `${url}/${i + 1}/`),
  1906. img: ".picture",
  1907. };
  1908. },
  1909. };
  1910.  
  1911. const tapas = {
  1912. name: "KLManga",
  1913. url: /https?:\/\/(www\.)?tapas.io\/episode\/.+/,
  1914. homepage: "https://tapas.io/",
  1915. language: ["English"],
  1916. category: "manga",
  1917. run() {
  1918. const images = [
  1919. ...document.querySelectorAll(".viewer__body img.content__img"),
  1920. ];
  1921. const chapter = document.querySelector(
  1922. ".js-episodes .body__item--selected",
  1923. );
  1924. return {
  1925. title: document
  1926. .querySelector(".viewer__header .title")
  1927. ?.textContent?.trim(),
  1928. series: document.querySelector(".vw-nav__left a")?.getAttribute("href"),
  1929. pages: images.length,
  1930. prev: chapter?.previousElementSibling?.getAttribute("data-href"),
  1931. next: chapter?.nextElementSibling?.getAttribute("data-href"),
  1932. listImages: images.map(
  1933. (img) => img.getAttribute("data-src") ?? img.getAttribute("src"),
  1934. ),
  1935. };
  1936. },
  1937. };
  1938.  
  1939. const tenmanga = {
  1940. name: "TenManga",
  1941. url: /https?:\/\/(www\.)?(tenmanga|gardenmanage).com\/(chapter|statuses)\/.+/,
  1942. homepage: "https://www.tenmanga.com/",
  1943. language: ["English"],
  1944. category: "manga",
  1945. waitVar: "_pageCtrl",
  1946. run() {
  1947. const chapter = document.querySelector(
  1948. ".mangaread-pagenav select option:checked",
  1949. );
  1950. const images = unsafeWindow._pageCtrl.options.all_imgs_url;
  1951. return {
  1952. title: document.querySelector(".title h1")?.textContent?.trim(),
  1953. series: document
  1954. .querySelector(".title a:nth-child(2)")
  1955. ?.getAttribute("href"),
  1956. pages: images.length,
  1957. prev: chapter?.nextElementSibling?.getAttribute("value"),
  1958. next: chapter?.previousElementSibling?.getAttribute("value"),
  1959. listImages: images,
  1960. };
  1961. },
  1962. };
  1963.  
  1964. const tmofans = {
  1965. name: "TuMangaOnline",
  1966. url: /https?:\/\/(www\.)?(.+).com\/(viewer|news)\/.+\/(paginated|cascade)/,
  1967. homepage: "https://lectortmo.com/",
  1968. language: ["Spanish"],
  1969. category: "manga",
  1970. run() {
  1971. const images = [
  1972. ...document.querySelectorAll(
  1973. ".img-container img, .viewer-container img",
  1974. ),
  1975. ];
  1976. const pages = [
  1977. ...document.querySelectorAll(
  1978. "div.container:nth-child(4) select#viewer-pages-select option",
  1979. ),
  1980. ];
  1981. const num = images.length > 1 ? images.length : pages.length;
  1982. return {
  1983. title: document.querySelector("title")?.textContent?.trim(),
  1984. series: document
  1985. .querySelector('a[title="Volver"]')
  1986. ?.getAttribute("href"),
  1987. pages: num,
  1988. prev: document.querySelector(".chapter-prev a")?.getAttribute("href"),
  1989. next: document.querySelector(".chapter-next a")?.getAttribute("href"),
  1990. ...(images.length > 1
  1991. ? {
  1992. listImages: images.map((item) => $(item).attr("data-src")),
  1993. }
  1994. : {
  1995. listPages: Array(pages.length)
  1996. .fill(0)
  1997. .map(
  1998. (_, i) =>
  1999. `${window.location.href.replace(/\/\d+$/, "")}/${i + 1}`,
  2000. ),
  2001. img: "#viewer-container img, .viewer-page",
  2002. }),
  2003. };
  2004. },
  2005. };
  2006.  
  2007. const tumanhwas = {
  2008. name: "TuManhwas",
  2009. url: /https?:\/\/(www\.)?tumanhwas.com\/news\/.+/,
  2010. homepage: "https://tumanhwas.com/",
  2011. language: ["Spanish"],
  2012. category: "manga",
  2013. run() {
  2014. const images = [...document.querySelectorAll("#chapter_imgs img")];
  2015. return {
  2016. title: document.querySelector(".entry-title")?.textContent?.trim(),
  2017. series: document
  2018. .querySelector(".nextprev a:nth-child(2)")
  2019. ?.getAttribute("href"),
  2020. pages: images.length,
  2021. prev: document
  2022. .querySelector(".nextprev a:nth-child(1)")
  2023. ?.getAttribute("href"),
  2024. next: document
  2025. .querySelector(".nextprev a:nth-child(3)")
  2026. ?.getAttribute("href"),
  2027. listImages: images.map((item) => $(item).attr("src")),
  2028. };
  2029. },
  2030. };
  2031.  
  2032. const unionmangas = {
  2033. name: "UnionMangas",
  2034. url: /https?:\/\/(www\.)?unionleitor.top\/leitor\/.+\/.+/,
  2035. homepage: "https://unionleitor.top/",
  2036. language: ["Portuguese"],
  2037. category: "manga",
  2038. run() {
  2039. const chapter = document.querySelector("#capitulo_trocar option:checked");
  2040. const images = [...document.querySelectorAll(".img-manga")];
  2041. return {
  2042. title: document.querySelector(".titulo-leitura")?.textContent?.trim(),
  2043. series: document
  2044. .querySelector(".breadcrumbs a:nth-child(3)")
  2045. ?.getAttribute("href"),
  2046. pages: images.length,
  2047. prev: chapter?.previousElementSibling?.getAttribute("value"),
  2048. next: chapter?.nextElementSibling?.getAttribute("value"),
  2049. listImages: images.map((img) => img.getAttribute("src")),
  2050. };
  2051. },
  2052. };
  2053.  
  2054. const webnovel = {
  2055. name: "WebNovel",
  2056. url: /https?:\/\/(www\.)?webnovel.com\/comic\/.+/,
  2057. homepage: "https://www.webnovel.com/",
  2058. language: ["English"],
  2059. category: "manga",
  2060. waitVar: "g_data",
  2061. run() {
  2062. const images = unsafeWindow.g_data.chapter.chapterInfo.chapterPage.map(
  2063. (img) => img.url,
  2064. );
  2065. return {
  2066. title: document.querySelector("title")?.textContent?.trim(),
  2067. series: "./",
  2068. pages: images.length,
  2069. prev: `${unsafeWindow.g_data.chapter.chapterInfo.preChapterName}_${unsafeWindow.g_data.chapter.chapterInfo.preChapterId}`,
  2070. next: `${unsafeWindow.g_data.chapter.chapterInfo.nextChapterName}_${unsafeWindow.g_data.chapter.chapterInfo.nextChapterId}`,
  2071. listImages: images,
  2072. };
  2073. },
  2074. };
  2075.  
  2076. const webtoons = {
  2077. name: "WebToons",
  2078. url: /https?:\/\/(www\.)?webtoons.com\/.+viewer.+/,
  2079. homepage: "https://www.webtoons.com/",
  2080. language: ["English"],
  2081. category: "manga",
  2082. run() {
  2083. const images = [...document.querySelectorAll("#_imageList img")];
  2084. return {
  2085. title: document.querySelector(".subj_info")?.textContent?.trim(),
  2086. series: document.querySelector(".subj_info a")?.getAttribute("href"),
  2087. pages: images.length,
  2088. prev: document.querySelector("._prevEpisode")?.getAttribute("href"),
  2089. next: document.querySelector("._nextEpisode")?.getAttribute("href"),
  2090. listImages: images.map(
  2091. (img) =>
  2092. img.getAttribute("data-url") ??
  2093. img.getAttribute("data-src") ??
  2094. img.getAttribute("src"),
  2095. ),
  2096. };
  2097. },
  2098. };
  2099.  
  2100. const wpmanga = {
  2101. name: ["Manga33"],
  2102. url: /https?:\/\/(www\.)?(manga33).com\/manga\/.+/,
  2103. homepage: ["https://manga33.com/"],
  2104. language: ["English"],
  2105. category: "manga",
  2106. run() {
  2107. const images = [...document.querySelectorAll(".chapter-content img")];
  2108. return {
  2109. title: document.querySelector("title")?.textContent?.trim(),
  2110. series: document.querySelector(".navbar-brand")?.getAttribute("href"),
  2111. pages: images.length,
  2112. prev: document.querySelector("a.prev")?.getAttribute("href"),
  2113. next: document.querySelector("a.next")?.getAttribute("href"),
  2114. listImages: images.map((img) => img.getAttribute("src")),
  2115. before() {
  2116. if (/all.html$/.exec(window.location.pathname)) {
  2117. return;
  2118. }
  2119. if (/\d+-\d+.html$/.exec(window.location.pathname)) {
  2120. window.location.pathname = window.location.pathname.replace(
  2121. /-\d+.html$/,
  2122. "-all.html",
  2123. );
  2124. }
  2125. },
  2126. };
  2127. },
  2128. };
  2129.  
  2130. const yugenmangas = {
  2131. name: "YugenMangas",
  2132. url: /https?:\/\/(www\.)?(yugenmangas).(com|net|lat)\/series\/.+/,
  2133. homepage: "https://yugenmangas.lat/",
  2134. language: ["Spanish"],
  2135. category: "manga",
  2136. async run() {
  2137. const images = [...document.querySelectorAll("p.flex > img")];
  2138. return {
  2139. title: document.querySelector("title")?.textContent?.trim(),
  2140. series: document
  2141. .querySelector("div.justify-between:nth-child(2) > a:nth-child(2)")
  2142. ?.getAttribute("href"),
  2143. pages: images.length,
  2144. prev: document
  2145. .querySelector("div.justify-between:nth-child(2) > a:nth-child(1)")
  2146. ?.getAttribute("href"),
  2147. next: document
  2148. .querySelector("div.justify-between:nth-child(2) > a:nth-child(3)")
  2149. ?.getAttribute("href"),
  2150. listImages: images.map((img) =>
  2151. img.classList.contains("lazy")
  2152. ? img.getAttribute("data-src")
  2153. : img.getAttribute("src"),
  2154. ),
  2155. };
  2156. },
  2157. };
  2158.  
  2159. const zeroscans = {
  2160. name: "ZeroScans",
  2161. url: /https?:\/\/(www\.)?zscans.com\/comics\/.+/,
  2162. homepage: "https://zscans.com/",
  2163. language: ["English"],
  2164. category: "manga",
  2165. waitVar: "__ZEROSCANS__",
  2166. run() {
  2167. const images =
  2168. unsafeWindow.__ZEROSCANS__.data.at(0).current_chapter.high_quality;
  2169. const chapters = document.querySelectorAll(".v-btn--router");
  2170. return {
  2171. title: document.querySelector("title")?.textContent?.trim(),
  2172. series: document
  2173. .querySelector(".v-breadcrumbs li:nth-child(2) + a")
  2174. ?.getAttribute("href"),
  2175. pages: images.length,
  2176. prev: chapters[0]?.getAttribute("href"),
  2177. next: chapters[1]?.getAttribute("href"),
  2178. listImages: images,
  2179. };
  2180. },
  2181. };
  2182.  
  2183. const sites = [
  2184. alandal,
  2185. asura,
  2186. batoto,
  2187. bilibilicomics,
  2188. comicastle,
  2189. comick,
  2190. dysnatyscans,
  2191. inkr,
  2192. inmanga,
  2193. klmanga,
  2194. leitor,
  2195. lhtranslation,
  2196. // Leviatanscans,
  2197. localhost,
  2198. lynxscans,
  2199. mangabuddy,
  2200. mangademon,
  2201. mangadex,
  2202. mangafox,
  2203. mangago,
  2204. // mangafreak,
  2205. mangahosted,
  2206. mangahub,
  2207. mangasin,
  2208. mangakakalot,
  2209. mangaoni,
  2210. mangapark,
  2211. mangareader,
  2212. mangasee,
  2213. mangatigre,
  2214. mangatoon,
  2215. mangatown,
  2216. manhuascan,
  2217. manhwaweb,
  2218. mgeko,
  2219. naniscans,
  2220. ninemanga,
  2221. olympusscans,
  2222. pandamanga,
  2223. rawdevart,
  2224. readcomicsonline,
  2225. readmangatoday,
  2226. reaperscans,
  2227. senmanga,
  2228. // Resetscans, deprecated
  2229. tapas,
  2230. tenmanga,
  2231. tmofans,
  2232. tumanhwas,
  2233. unionmangas,
  2234. webnovel,
  2235. webtoons,
  2236. wpmanga,
  2237. yugenmangas,
  2238. zeroscans,
  2239. mangastreamwp,
  2240. // Must be at the end because is a generic check
  2241. foolslide,
  2242. // Must be at the end because is a generic check
  2243. madarawp,
  2244. // Must be at the end because is a generic check
  2245. ];
  2246.  
  2247. const rangeSliderStyles =
  2248. ".range-slider{touch-action:none;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;cursor:pointer;display:block;position:relative;width:100%;height:8px;background:#ddd;border-radius:4px}.range-slider[data-vertical]{height:100%;width:8px}.range-slider[data-disabled]{opacity:.5;cursor:not-allowed}.range-slider .range-slider__thumb{position:absolute;z-index:3;top:50%;width:24px;height:24px;transform:translate(-50%,-50%);border-radius:50%;background:#2196f3}.range-slider .range-slider__thumb:focus-visible{outline:0;box-shadow:0 0 0 6px rgba(33,150,243,.5)}.range-slider[data-vertical] .range-slider__thumb{left:50%}.range-slider .range-slider__thumb[data-disabled]{z-index:2}.range-slider .range-slider__range{position:absolute;z-index:1;transform:translate(0,-50%);top:50%;width:100%;height:100%;background:#51adf6}.range-slider[data-vertical] .range-slider__range{left:50%;transform:translate(-50%,0)}.range-slider input[type=range]{-webkit-appearance:none;pointer-events:none;position:absolute;z-index:2;top:0;left:0;width:0;height:0;background-color:transparent}.range-slider input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none}.range-slider input[type=range]::-moz-range-thumb{width:0;height:0;border:0}.range-slider input[type=range]:focus{outline:0}";
  2249.  
  2250. function logScript(...text) {
  2251. console.log("MangaOnlineViewer: ", ...text);
  2252. return text;
  2253. }
  2254. function logScriptVerbose(...text) {
  2255. return text;
  2256. }
  2257. const logScriptC = (x) => (y) => logScript(x, y)[1];
  2258. function getListGM() {
  2259. return typeof GM_listValues !== "undefined" ? GM_listValues() : [];
  2260. }
  2261. function removeValueGM(name) {
  2262. if (typeof GM_deleteValue !== "undefined") {
  2263. GM_deleteValue(name);
  2264. } else {
  2265. logScript("Removing: ", name);
  2266. }
  2267. }
  2268. const getInfoGM =
  2269. typeof GM_info !== "undefined"
  2270. ? GM_info
  2271. : {
  2272. scriptHandler: "Console",
  2273. script: {
  2274. name: "Debug",
  2275. version: "Testing",
  2276. },
  2277. };
  2278. function getValueGM(name, defaultValue = null) {
  2279. if (typeof GM_getValue !== "undefined") {
  2280. return GM_getValue(name, defaultValue);
  2281. }
  2282. logScript("Fake Getting: ", name, " = ", defaultValue);
  2283. return defaultValue;
  2284. }
  2285. function getJsonGM(name, defaultValue = null) {
  2286. const result = getValueGM(name, defaultValue);
  2287. if (typeof result === "string") {
  2288. return JSON.parse(result);
  2289. }
  2290. return result;
  2291. }
  2292. function getSettings(defaultSettings) {
  2293. return getJsonGM("settings", defaultSettings);
  2294. }
  2295. function setValueGM(name, value) {
  2296. try {
  2297. GM_setValue(name, value);
  2298. return value.toString();
  2299. } catch (e) {
  2300. logScript("Fake Setting: ", name, " = ", value);
  2301. return String(value);
  2302. }
  2303. }
  2304. function setSettings(value) {
  2305. return setValueGM("settings", value);
  2306. }
  2307. function getBrowser() {
  2308. let tem;
  2309. const M =
  2310. /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i.exec(
  2311. navigator.userAgent,
  2312. ) ?? [];
  2313. if (/trident/i.test(M[1])) {
  2314. tem = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) ?? [];
  2315. return `IE ${tem[1] ?? ""}`;
  2316. }
  2317. if (M[1] === "Chrome") {
  2318. tem = /\b(OPR|Edge)\/(\d+)/.exec(navigator.userAgent);
  2319. if (tem !== null) {
  2320. return tem.slice(1).join(" ").replace("OPR", "Opera");
  2321. }
  2322. }
  2323. const tempM = [M[1], M[2]];
  2324. tem = /version\/(\d+)/i.exec(navigator.userAgent);
  2325. if (tem !== null) {
  2326. tempM.splice(1, 1, tem[1]);
  2327. }
  2328. return tempM.join(" ");
  2329. }
  2330. function getEngine() {
  2331. return getInfoGM.scriptHandler ?? "Greasemonkey";
  2332. }
  2333. const parser = new UAParser();
  2334. const getDevice = () => {
  2335. const device = parser.getDevice().type;
  2336. if (device !== "mobile" && device !== "tablet") {
  2337. if (window.matchMedia("screen and (max-width: 600px)").matches)
  2338. return "mobile";
  2339. if (window.matchMedia("screen and (max-width: 992px)").matches)
  2340. return "tablet";
  2341. return "desktop";
  2342. }
  2343. return device;
  2344. };
  2345. const isMobile = () =>
  2346. // @ts-ignore
  2347. navigator?.userAgentData?.mobile ||
  2348. getDevice() === "mobile" ||
  2349. getDevice() === "tablet";
  2350.  
  2351. const diffObj = (changed, original) => {
  2352. const changes = (object, base) =>
  2353. _.transform(
  2354. object,
  2355. (result, value, key) => {
  2356. if (!_.isEqual(value, base[key])) {
  2357. if (_.isArray(value)) {
  2358. result[key] = _.difference(value, base[key]);
  2359. } else if (_.isObject(value) && _.isObject(base[key])) {
  2360. result[key] = changes(value, base[key]);
  2361. } else {
  2362. result[key] = value;
  2363. }
  2364. }
  2365. },
  2366. /* Omit accumulator */
  2367. );
  2368. return changes(changed, original);
  2369. };
  2370.  
  2371. const en_US = {
  2372. ID: "en_US",
  2373. NAME: "English (US)",
  2374. STARTING: "Starting<br>Manga OnlineViewer",
  2375. RESUME: "Resuming reading from Page ",
  2376. WAITING: "Please wait, 3 seconds...",
  2377. CHOOSE_BEGINNING: "Choose the Page to start from:",
  2378. BUTTON_START: "Start Manga OnlineViewer",
  2379. SETTINGS: "Settings",
  2380. LANGUAGE: "Language",
  2381. COLOR_SCHEME: "Color Scheme",
  2382. THEME: "Theme",
  2383. THEME_HUE: "Theme Primary Color Hue",
  2384. THEME_SHADE: "Theme Primary Color Shade",
  2385. DEFAULT_LOAD_MODE: "Default Load Mode",
  2386. LOAD_MODE_NORMAL: "Normal(Wait 3 sec)",
  2387. LOAD_MODE_ALWAYS: "Always(Immediately)",
  2388. LOAD_MODE_NEVER: "Never(Manually)",
  2389. LOAD_SPEED: "Load Speed Pages/Second",
  2390. DEFAULT_ZOOM: "Default Zoom (between 5 and 200)",
  2391. DEFAULT_ZOOM_MODE: "Default Zoom Mode",
  2392. MINIMUM_ZOOM:
  2393. "Minimum Zoom relative to the width of screen (between 30 and 100)",
  2394. ZOOM_STEP: "Zoom Change Step (between 5 and 50)",
  2395. DEFAULT_VIEW_MODE: "Default View Mode",
  2396. VIEW_MODE_VERTICAL: "Vertical",
  2397. VIEW_MODE_LEFT: "Left to Right",
  2398. VIEW_MODE_RIGHT: "Right to Left",
  2399. VIEW_MODE_WEBCOMIC: "WebComic",
  2400. FIT_WIDTH_OVERSIZED: "Fit Width if Oversized",
  2401. SHOW_THUMBNAILS: "Show Thumbnails",
  2402. ENABLE_COMMENTS: "Capture Comments (When available)",
  2403. HIDE_CONTROLS: "Always Hide Page Controls",
  2404. HEADER_TYPE: "Change Header Type",
  2405. HEADER_HOVER: "Hover",
  2406. HEADER_SCROLL: "Scroll",
  2407. HEADER_CLICK: "Click",
  2408. HEADER_FIXED: "Fixed",
  2409. HEADER_SIMPLE: "Simple",
  2410. BUTTON_DOWNLOAD: "Download",
  2411. DOWNLOAD_ZIP: "Download Zip file",
  2412. DOWNLOAD_IMAGES: "Download Images as Zip Automatically",
  2413. BUTTON_NEXT: "Next",
  2414. NEXT_CHAPTER: "Next Chapter",
  2415. BUTTON_PREVIOUS: "Previous",
  2416. PREVIOUS_CHAPTER: "Previous Chapter",
  2417. BOOKMARKS: "Bookmarks",
  2418. BOOKMARK: "Bookmark",
  2419. BOOKMARK_REMOVED: "Bookmark Removed",
  2420. BOOKMARK_SAVED: "Bookmark Saved",
  2421. BOOKMARK_MESSAGE:
  2422. "Next time you open this chapter it will resume from:<h4>Page ##num##</h4>(Only <i>ONCE</i> per Bookmark)",
  2423. KEYBINDINGS: "Keybindings",
  2424. EDIT_KEYBINDS: "Edit KeyBindings",
  2425. SAVE_KEYBINDS: "Save KeyBindings",
  2426. BUTTON_EDIT: "Edit",
  2427. BUTTON_SAVE: "Save",
  2428. KEYBIND_RULES: `
  2429. <h3>Supported Keys</h3>
  2430. Allowed modifiers: shift, option, alt, ctrl, control, command. </br>
  2431. Special keys: backspace, tab, clear, enter, return, esc, escape, space, up, down, left, right, home, end, pageup, pagedown, del, delete, f1 - f19, num_0 - num_9, num_multiply, num_add, num_enter, num_subtract, num_decimal, num_divide. </br>
  2432. Examples: <kbd>a</kbd>, <kbd>ctrl+a</kbd> , <kbd>shift+a</kbd> , <kbd>num_2</kbd> , <kbd>2</kbd>
  2433. `,
  2434. ATTENTION: "Attention",
  2435. WARNING: "Warning",
  2436. BUTTON_RESET_SETTINGS: "Reset Settings",
  2437. SETTINGS_RESET: "Settings have been reset, reload the page to take effect",
  2438. LANGUAGE_CHANGED:
  2439. "Language has been changed, reload the page to take effect",
  2440. AUTO_DOWNLOAD:
  2441. "Next time a chapter finish loading you will be prompted to save automatically",
  2442. LAZY_LOAD:
  2443. "Lazy load is incompatible with zip download, you will not be able to download with this setting ON.<br/> Suggestion: <span style='color:red;font-weight:bold'>Disable Thumbnails</span> to save Bandwidth/Memory.",
  2444. LAZY_LOAD_IMAGES_ENABLE: "Enable Lazy Load Images",
  2445. LAZY_LOAD_IMAGES: "Lazy Start From Page (between 5 and 100)",
  2446. RETURN_CHAPTER_LIST: "Return to Chapter List",
  2447. PAGES_LOADED: "Pages Loaded",
  2448. GO_TO_PAGE: "Go to Page",
  2449. ENLARGE: "Enlarge",
  2450. RESTORE: "Restore",
  2451. REDUCE: "Restore",
  2452. FIT_WIDTH: "Fit Width",
  2453. FIT_HEIGHT: "Fit Height",
  2454. PERCENT: "Percent",
  2455. TOGGLE_CONTROLS: "Toggle page controls",
  2456. ZOOM_IN: "Zoom In",
  2457. ZOOM_OUT: "Zoom Out",
  2458. ZOOM_RESET: "Zoom Reset",
  2459. ZOOM_WIDTH: "Zoom to Width",
  2460. ZOOM_HEIGHT: "Zoom to Height",
  2461. HIDE: "Hide",
  2462. RELOAD: "Reload",
  2463. SLOWLY: "Slowly",
  2464. NORMAL: "Normal",
  2465. FAST: "Fast",
  2466. EXTREME: "Extreme",
  2467. ALL_PAGES: "All Pages",
  2468. SPEED_WARNING: "Loading Speed too High",
  2469. SPEED_WARNING_MESSAGE:
  2470. "This speed is not recommended.<br> It may hurt some servers or get your IP marked as DDoS attacker.<br> Please use with caution!",
  2471. SCROLL_UP: "Scroll Up",
  2472. SCROLL_DOWN: "Scroll Down",
  2473. CLOSE: "Close",
  2474. LIST_EMPTY: "List Empty",
  2475. DISPLAY_COMMENTS: "Display Comments",
  2476. COMMENTS: "Comments Section",
  2477. SCROLL_START: "Toggle Auto Scroll",
  2478. AUTO_SCROLL_HEIGHT: "Auto Scroll Speed in Pixels",
  2479. VERTICAL_SEPARATOR: "Show Vertical Separators",
  2480. END: "End",
  2481. };
  2482.  
  2483. const pt_BR = {
  2484. ID: "pt_BR",
  2485. NAME: "Portugues (Brasil)",
  2486. STARTING: "Iniciando<br>Manga OnlineViewer",
  2487. RESUME: "Continuando leitura na Pagina ",
  2488. WAITING: "Por Favor espere, 3 segundos...",
  2489. CHOOSE_BEGINNING: "Escolha a pagina de onde começar:",
  2490. BUTTON_START: "Iniciar Manga OnlineViewer",
  2491. SETTINGS: "Configurações",
  2492. LANGUAGE: "Idioma",
  2493. COLOR_SCHEME: "Esquema de Color",
  2494. THEME: "Tema",
  2495. THEME_HUE: "Coloração primaria",
  2496. THEME_SHADE: "Saturação de Cor",
  2497. DEFAULT_LOAD_MODE: "Forma de Carregamento Padrão",
  2498. LOAD_MODE_NORMAL: "Normal(Esperando 3 sec)",
  2499. LOAD_MODE_ALWAYS: "Sempre(Imediatamente)",
  2500. LOAD_MODE_NEVER: "Nunca(Manualmente)",
  2501. LOAD_SPEED: "Velocidade de Carregamento Paginas/Segundo",
  2502. DEFAULT_ZOOM: "Zoom padrão (entre 5 e 200)",
  2503. DEFAULT_ZOOM_MODE: "Modo de Zoom padrão",
  2504. MINIMUM_ZOOM: "Zoom minimo, relativo ao tamanho da tela (entre 30 e 100)",
  2505. ZOOM_STEP: "Precisão da Mudança do Zoom (entre 5 e 50)",
  2506. DEFAULT_VIEW_MODE: "Modo de Visualização Padrão",
  2507. VIEW_MODE_VERTICAL: "Vertical",
  2508. VIEW_MODE_LEFT: "Esquerda para Direita",
  2509. VIEW_MODE_RIGHT: "Direita para Esquerda",
  2510. VIEW_MODE_WEBCOMIC: "WebComic",
  2511. FIT_WIDTH_OVERSIZED: "Encher a tela se grande demais",
  2512. SHOW_THUMBNAILS: "Mostra Miniaturas",
  2513. ENABLE_COMMENTS: "Capturar comentários (quando disponível)",
  2514. HIDE_CONTROLS: "Sempre esconder controles das paginas",
  2515. HEADER_TYPE: "Mudar Tipo de Cabeçalho",
  2516. HEADER_HOVER: "Passar por perto",
  2517. HEADER_SCROLL: "Rolagem do Mouse",
  2518. HEADER_CLICK: "Click",
  2519. HEADER_FIXED: "Fixo",
  2520. HEADER_SIMPLE: "Simples",
  2521. BUTTON_DOWNLOAD: "Download",
  2522. DOWNLOAD_ZIP: "Baixar arquivo Zip",
  2523. DOWNLOAD_IMAGES: "Download das Imagens como Zip Automaticamente",
  2524. BUTTON_NEXT: "Proximo",
  2525. NEXT_CHAPTER: "Proximo Capitulo",
  2526. BUTTON_PREVIOUS: "Anterior",
  2527. PREVIOUS_CHAPTER: "Capitulo Anterior",
  2528. BOOKMARKS: "Marca paginas",
  2529. BOOKMARK: "Marcar pagina",
  2530. BOOKMARK_REMOVED: "Marca pagina Removido",
  2531. BOOKMARK_SAVED: "Marca pagina Salvo",
  2532. BOOKMARK_MESSAGE:
  2533. "Proxima vez que abrir este capitulo continuará a partir da <h4>Pagina ##num##</h4>(Apenas <i>UMA VEZ</i> por marca pagina)",
  2534. KEYBINDINGS: "Atalhos",
  2535. EDIT_KEYBINDS: "Editar Atalhos",
  2536. SAVE_KEYBINDS: "Salvar Atalhos",
  2537. BUTTON_EDIT: "Editar",
  2538. BUTTON_SAVE: "Salvar",
  2539. KEYBIND_RULES: `
  2540. <h3>Teclas Suportadas</h3>
  2541. Modificadores permitidos: shift, option, alt, ctrl, control, command. </br>
  2542. Teclas Especiais: backspace, tab, clear, enter, return, esc, escape, space, up, down, left, right, home, end, pageup, pagedown, del, delete, f1 - f19, num_0 - num_9, num_multiply, num_add, num_enter, num_subtract, num_decimal, num_divide.</br>
  2543. Exemplos: <kbd>a</kbd>, <kbd>ctrl+a</kbd> , <kbd>shift+a</kbd> , <kbd>num_2</kbd> , <kbd>2</kbd>
  2544. `,
  2545. ATTENTION: "Atenção",
  2546. WARNING: "Alerta",
  2547. BUTTON_RESET_SETTINGS: "Limpar Configurações(Reset Settings)",
  2548. SETTINGS_RESET:
  2549. "Configurações foram limpas, recarregue o site para efetivar a alteração",
  2550. LANGUAGE_CHANGED:
  2551. "Idioma foi alterado, recarregue o site para efetivar a alteração",
  2552. AUTO_DOWNLOAD:
  2553. "Proxima vez que abrir um capitulo download iniciara automaticamente",
  2554. LAZY_LOAD:
  2555. "Carregamento preguiçoso não é compativel com download de zip, não conseguira com essa configuração ativa.<br/> Sugestão: <span style='color:red;font-weight:bold'>Desative Miniaturas</span> para economizar memoria e cota de internet.",
  2556. LAZY_LOAD_IMAGES_ENABLE: "Ativar Carregamento de imagens preguiçoso",
  2557. LAZY_LOAD_IMAGES:
  2558. "Carregamento de paginas preguiçoso começa a partir de (entre 5 e 100)",
  2559. RETURN_CHAPTER_LIST: "Voltar a lista de Capitulos",
  2560. PAGES_LOADED: "Paginas Carregadas",
  2561. GO_TO_PAGE: "Pular para",
  2562. ENLARGE: "Aumentar",
  2563. RESTORE: "Restaurar",
  2564. REDUCE: "Diminuir",
  2565. FIT_WIDTH: "Preencher Largura",
  2566. FIT_HEIGHT: "Preencher Altura ",
  2567. PERCENT: "Percentual",
  2568. TOGGLE_CONTROLS: "Mostar controles de pagina",
  2569. ZOOM_IN: "Mais Zoom",
  2570. ZOOM_OUT: "Menos Zoom",
  2571. ZOOM_RESET: "Resetar Zoom",
  2572. ZOOM_WIDTH: "Zoom para Largura",
  2573. ZOOM_HEIGHT: "Zoom para Altura",
  2574. HIDE: "Esconder",
  2575. RELOAD: "Recarregar",
  2576. SLOWLY: "Devagar",
  2577. NORMAL: "Normal",
  2578. FAST: "Rapido",
  2579. EXTREME: "Extremo",
  2580. ALL_PAGES: "Todas as Paginas",
  2581. SPEED_WARNING: "Velocidade de Carregamento muito alta",
  2582. SPEED_WARNING_MESSAGE:
  2583. "Essa velocidade não é recomendada.<br> Ela pode derrubar um servidor or marcar voce como um ataque hacker de DDoS.<br> Use com cuidado!",
  2584. SCROLL_UP: "Subir Pagina",
  2585. SCROLL_DOWN: "Descer Pagina",
  2586. CLOSE: "Fechar",
  2587. LIST_EMPTY: "Lista Vazia",
  2588. DISPLAY_COMMENTS: "Mostar Comentarios",
  2589. COMMENTS: "Seção de comentários",
  2590. SCROLL_START: "Ativar Rolagem Automatica",
  2591. AUTO_SCROLL_HEIGHT: "Velocidade da Rolagem Automatica em Pixels",
  2592. VERTICAL_SEPARATOR: "Mostrar Separadores Verticais",
  2593. END: "Fin",
  2594. };
  2595.  
  2596. const zh_CN = {
  2597. ID: "zh_CN",
  2598. NAME: "中文 (简体)",
  2599. STARTING: "正在启动<br>Manga OnlineViewer",
  2600. RESUME: "从页面继续阅读 ",
  2601. WAITING: "请等待3秒钟...",
  2602. CHOOSE_BEGINNING: "选择要开始的页数:",
  2603. BUTTON_START: "启动Manga OnlineViewer",
  2604. SETTINGS: "设置",
  2605. LANGUAGE: "语言",
  2606. COLOR_SCHEME: "配色方案",
  2607. THEME: "主题",
  2608. THEME_HUE: "主题色调",
  2609. THEME_SHADE: "主题阴影",
  2610. DEFAULT_LOAD_MODE: "默认加载模式",
  2611. LOAD_MODE_NORMAL: "等待模式(等待3秒自动加载 )",
  2612. LOAD_MODE_ALWAYS: "自动模式(无需等待)",
  2613. LOAD_MODE_NEVER: "手动模式(点击启动)",
  2614. LOAD_SPEED: "加载速度页数/秒",
  2615. DEFAULT_ZOOM: "默认缩放 (最小 5 最大 200)",
  2616. DEFAULT_ZOOM_MODE: "默认缩放模式",
  2617. MINIMUM_ZOOM: "相对于屏幕宽度的最小缩放 (最小 30 最大 100)",
  2618. ZOOM_STEP: "缩放级别 (最小 5 最大 50)",
  2619. DEFAULT_VIEW_MODE: "默认视图模式",
  2620. VIEW_MODE_VERTICAL: "垂直有缝",
  2621. VIEW_MODE_LEFT: "从左到右",
  2622. VIEW_MODE_RIGHT: "从右到左",
  2623. VIEW_MODE_WEBCOMIC: "垂直无缝",
  2624. FIT_WIDTH_OVERSIZED: "如果尺寸过大、则适合宽度",
  2625. SHOW_THUMBNAILS: "显示缩略图",
  2626. ENABLE_COMMENTS: "捕获评论(如果可用)",
  2627. HIDE_CONTROLS: "始终隐藏页面控件",
  2628. HEADER_TYPE: "更改标题显示方式",
  2629. HEADER_HOVER: "悬停",
  2630. HEADER_SCROLL: "滚动",
  2631. HEADER_CLICK: "点击",
  2632. HEADER_FIXED: "固定",
  2633. HEADER_SIMPLE: "简单",
  2634. BUTTON_DOWNLOAD: "下载",
  2635. DOWNLOAD_ZIP: "下载压缩文件",
  2636. DOWNLOAD_IMAGES: "自动将图片下载成ZIP",
  2637. BUTTON_NEXT: "下一页",
  2638. NEXT_CHAPTER: "下一章",
  2639. BUTTON_PREVIOUS: "上一页",
  2640. PREVIOUS_CHAPTER: "上一章",
  2641. BOOKMARKS: "书签",
  2642. BOOKMARK: "Bookmark",
  2643. BOOKMARK_REMOVED: "删除书签",
  2644. BOOKMARK_SAVED: "保存书签",
  2645. BOOKMARK_MESSAGE:
  2646. "下次打开本章时,将从:<h4>页码 ##num##</h4>(<i>仅一次</i> 每个书签)",
  2647. KEYBINDINGS: "快捷键",
  2648. EDIT_KEYBINDS: "编辑键绑定",
  2649. SAVE_KEYBINDS: "保存键绑定",
  2650. BUTTON_EDIT: "编辑",
  2651. BUTTON_SAVE: "救",
  2652. KEYBIND_RULES: `
  2653. <h3>支持的密钥</h3>
  2654. 允许的修饰符: shift, option, alt, ctrl, control, command. </br>
  2655. 特殊键: backspace, tab, clear, enter, return, esc, escape, space, up, down, left, right, home, end, pageup, pagedown, del, delete, f1 - f19, num_0 - num_9, num_multiply, num_add, num_enter, num_subtract, num_decimal, num_divide.</br>
  2656. 例子: <kbd>a</kbd>, <kbd>ctrl+a</kbd> , <kbd>shift+a</kbd> , <kbd>num_2</kbd> , <kbd>2</kbd>
  2657. `,
  2658. ATTENTION: "注意",
  2659. WARNING: "警告",
  2660. BUTTON_RESET_SETTINGS: "重置设置(Reset Settings)",
  2661. SETTINGS_RESET: "设置已重置、重新加载页面才能生效",
  2662. LANGUAGE_CHANGED: "语言已更改、重新加载页面才能生效",
  2663. AUTO_DOWNLOAD: "下次章节加载完成时、系统将提示您自动保存",
  2664. LAZY_LOAD:
  2665. "延迟加载与zip下载不兼容、您将无法使用此设置下载.<br/> 建议: <span style='color:red;font-weight:bold'>禁用缩略图</span> 以节省流量和内存.",
  2666. LAZY_LOAD_IMAGES_ENABLE: "启用延迟加载图像",
  2667. LAZY_LOAD_IMAGES: "惰性加载从页面 (最小 5 最大 100)",
  2668. RETURN_CHAPTER_LIST: "返回章节列表",
  2669. PAGES_LOADED: "已加载的页数",
  2670. GO_TO_PAGE: "转到页数",
  2671. ENLARGE: "放大",
  2672. RESTORE: "还原",
  2673. REDUCE: "缩小",
  2674. FIT_WIDTH: "适合宽度",
  2675. FIT_HEIGHT: "适合高度",
  2676. PERCENT: "百分之",
  2677. TOGGLE_CONTROLS: "显示隐藏页面控件",
  2678. ZOOM_IN: "放大",
  2679. ZOOM_OUT: "缩小",
  2680. ZOOM_RESET: "还原",
  2681. ZOOM_WIDTH: "适合宽度",
  2682. ZOOM_HEIGHT: "适合高度",
  2683. HIDE: "显示隐藏页面控件",
  2684. RELOAD: "重新加载",
  2685. SLOWLY: "慢速",
  2686. NORMAL: "正常",
  2687. FAST: "快速",
  2688. EXTREME: "极端",
  2689. ALL_PAGES: "所有页面",
  2690. SPEED_WARNING: "加载速度过高",
  2691. SPEED_WARNING_MESSAGE:
  2692. "不建议使用此速度.<br>它可能会伤害某些服务器或将您的 IP 标记为 DDoS 攻击者.<br>请谨慎使用!",
  2693. SCROLL_UP: "向上滚动",
  2694. SCROLL_DOWN: "向下滚动",
  2695. CLOSE: "关闭",
  2696. LIST_EMPTY: "没有收藏书签",
  2697. DISPLAY_COMMENTS: "显示注释",
  2698. COMMENTS: "评论部分",
  2699. SCROLL_START: "切换自动滚动",
  2700. AUTO_SCROLL_HEIGHT: "自动滚动速度(以像素为单位)",
  2701. VERTICAL_SEPARATOR: "显示垂直分隔符",
  2702. END: "结尾",
  2703. };
  2704.  
  2705. const es_ES = {
  2706. ID: "es_ES",
  2707. NAME: "Español (ES)",
  2708. STARTING: "Iniciando<br>Manga OnlineViewer",
  2709. RESUME: "Continuando lectura desde la Página ",
  2710. WAITING: "Por favor espere, 3 segundos...",
  2711. CHOOSE_BEGINNING: "Elija la página en la que comenzar:",
  2712. BUTTON_START: "Iniciar Manga OnlineViewer",
  2713. SETTINGS: "Ajustes",
  2714. LANGUAGE: "Idioma",
  2715. COLOR_SCHEME: "Esquema de color",
  2716. THEME: "Tema",
  2717. THEME_HUE: "Matiz del color primario",
  2718. THEME_SHADE: "Saturación del color primario",
  2719. DEFAULT_LOAD_MODE: "Modo de carga por defecto",
  2720. LOAD_MODE_NORMAL: "Normal (Espera 3s)",
  2721. LOAD_MODE_ALWAYS: "Siempre (Inmediatamente)",
  2722. LOAD_MODE_NEVER: "Nunca (Manualmente)",
  2723. LOAD_SPEED: "Velocidad carga página/segundo",
  2724. DEFAULT_ZOOM: "Zoom por defecto (entre 5 y 200)",
  2725. DEFAULT_ZOOM_MODE: "Modo de zoom por defecto",
  2726. MINIMUM_ZOOM: "Zoom mínimo relativo al ancho de la pantalla",
  2727. ZOOM_STEP: "Paso entre cambios de zoom (entre 5 y 50)",
  2728. DEFAULT_VIEW_MODE: "Modo de visualización por defecto",
  2729. VIEW_MODE_VERTICAL: "Vertical",
  2730. VIEW_MODE_LEFT: "Izquierda a derecha",
  2731. VIEW_MODE_RIGHT: "Derecha a izquierda",
  2732. VIEW_MODE_WEBCOMIC: "WebComic",
  2733. FIT_WIDTH_OVERSIZED: "Ajustar ancho si es demasiado grande",
  2734. SHOW_THUMBNAILS: "Mostrar miniaturas",
  2735. ENABLE_COMMENTS: "Capturar comentarios (cuando esté disponible)",
  2736. HIDE_CONTROLS: "Ocultar siempre la barra de controles",
  2737. HEADER_TYPE: "Cambiar tipo de cabecera",
  2738. HEADER_HOVER: "Pasar por encima",
  2739. HEADER_SCROLL: "Desplazamiento",
  2740. HEADER_CLICK: "Hacer click",
  2741. HEADER_FIXED: "Fijo",
  2742. HEADER_SIMPLE: "Sencillo",
  2743. BUTTON_DOWNLOAD: "Descargar",
  2744. DOWNLOAD_ZIP: "Descargar fichero Zip",
  2745. DOWNLOAD_IMAGES: "Autodescargar imágenes como Zip",
  2746. BUTTON_NEXT: "Siguiente",
  2747. NEXT_CHAPTER: "Siguiente capítulo",
  2748. BUTTON_PREVIOUS: "Anterior",
  2749. PREVIOUS_CHAPTER: "Capítulo anterior",
  2750. BOOKMARKS: "Marcadores",
  2751. BOOKMARK: "Marcador",
  2752. BOOKMARK_REMOVED: "Marcador eliminado",
  2753. BOOKMARK_SAVED: "Marcador guardado",
  2754. BOOKMARK_MESSAGE:
  2755. "La próxima vez que abra este capítulo, continuará desde la <h4>página ##num##</h4>(Sólo <i>UNA VEZ</i> por Marcador)",
  2756. KEYBINDINGS: "Atajos de teclado",
  2757. EDIT_KEYBINDS: "Editar atajos",
  2758. SAVE_KEYBINDS: "Guardar atajos",
  2759. BUTTON_EDIT: "Editar",
  2760. BUTTON_SAVE: "Guardar",
  2761. KEYBIND_RULES: `
  2762. <h3>Teclas soportadas</h3>
  2763. Modificadores permitidos: shift, option, alt, ctrl, control, command. </br>
  2764. Teclas especiales: backspace, tab, clear, enter, return, esc, escape, space, up, down, left, right, home, end, pageup, pagedown, del, delete, f1 - f19, num_0 - num_9, num_multiply, num_add, num_enter, num_subtract, num_decimal, num_divide. <br>
  2765. Ejemplos: <kbd>a</kbd>, <kbd>ctrl+a</kbd> , <kbd>shift+a</kbd> , <kbd>num_2</kbd> , <kbd>2</kbd>
  2766. `,
  2767. ATTENTION: "Atención",
  2768. WARNING: "Alerta",
  2769. BUTTON_RESET_SETTINGS: "Reiniciar ajustes(Reset Settings)",
  2770. SETTINGS_RESET:
  2771. "Se han restablecido los ajustes, vuelve a cargar la página para que surta efecto",
  2772. LANGUAGE_CHANGED:
  2773. "Se ha cambiado el idioma, vuelve a cargar la página para que surta efecto",
  2774. AUTO_DOWNLOAD:
  2775. "La próxima vez que termine de cargarse un capítulo, se le pedirá que guarde automáticamente",
  2776. LAZY_LOAD:
  2777. "La carga diferida es incompatible con la descarga zip, no podrá descargar con este ajuste activado.<br/> Sugerencia: <span style='color:red;font-weight:bold'>Desactivar miniaturas</span> para ahorrar Ancho de banda/Memoria.",
  2778. LAZY_LOAD_IMAGES_ENABLE: "Habilitar carga de imágenes diferida",
  2779. LAZY_LOAD_IMAGES:
  2780. "Empezar carga diferida a partir de la página (entre 5 y 100)",
  2781. RETURN_CHAPTER_LIST: "Regresar a la lista de capítulos",
  2782. PAGES_LOADED: "Páginas cargadas",
  2783. GO_TO_PAGE: "Ir a página",
  2784. ENLARGE: "Agrandar",
  2785. RESTORE: "Restaurar",
  2786. REDUCE: "Reducir",
  2787. FIT_WIDTH: "Ajustar al ancho",
  2788. FIT_HEIGHT: "Ajustar al alto",
  2789. PERCENT: "Porcentual",
  2790. TOGGLE_CONTROLS: "Alternar controles de página",
  2791. ZOOM_IN: "Acercar",
  2792. ZOOM_OUT: "Alejar",
  2793. ZOOM_RESET: "Restablecer zoom",
  2794. ZOOM_WIDTH: "Zoom al ancho",
  2795. ZOOM_HEIGHT: "Zoom al alto",
  2796. HIDE: "Ocultar",
  2797. RELOAD: "Recargar",
  2798. SLOWLY: "Lento",
  2799. NORMAL: "Normal",
  2800. FAST: "Rápido",
  2801. EXTREME: "Extremo",
  2802. ALL_PAGES: "Todas las páginas",
  2803. SPEED_WARNING: "Velocidad de carga muy alta",
  2804. SPEED_WARNING_MESSAGE:
  2805. "No se recomienda esta velocidad.<br> Puede dañar algunos servidores o marcar su IP como atacante DDoS.<br> ¡Utilícelo con precaución!",
  2806. SCROLL_UP: "Desplazar arriba",
  2807. SCROLL_DOWN: "Desplazar abajo",
  2808. CLOSE: "Cerrar",
  2809. LIST_EMPTY: "Lista vacía",
  2810. DISPLAY_COMMENTS: "Mostrar comentarios",
  2811. COMMENTS: "Sección de comentarios",
  2812. SCROLL_START: "Alternar desplazamiento automático",
  2813. AUTO_SCROLL_HEIGHT: "Velocidad de desplazamiento automático en píxeles",
  2814. VERTICAL_SEPARATOR: "Mostrar separadores verticales",
  2815. END: "Fin",
  2816. };
  2817.  
  2818. const locales = [en_US, es_ES, pt_BR, zh_CN];
  2819.  
  2820. const defaultSettings = {
  2821. locale: "en_US",
  2822. theme: "darkblue",
  2823. customTheme: "#263e3a",
  2824. themeShade: 600,
  2825. colorScheme: "dark",
  2826. fitWidthIfOversize: true,
  2827. showThumbnails: true,
  2828. enableComments: true,
  2829. downloadZip: false,
  2830. verticalSeparator: false,
  2831. throttlePageLoad: 1e3,
  2832. zoomMode: "percent",
  2833. defaultZoom: 100,
  2834. zoomStep: 25,
  2835. minZoom: 30,
  2836. loadMode: "wait",
  2837. viewMode: "WebComic",
  2838. bookmarks: [],
  2839. lazyLoadImages: false,
  2840. lazyStart: 50,
  2841. hidePageControls: false,
  2842. header: "hover",
  2843. maxReload: 5,
  2844. scrollHeight: 20,
  2845. keybinds: {
  2846. SCROLL_UP: ["up", "W", "num_8"],
  2847. SCROLL_DOWN: ["down", "S", "num_2"],
  2848. NEXT_CHAPTER: ["right", "/", "D", "num_6"],
  2849. PREVIOUS_CHAPTER: ["left", ";", "A", "num_4"],
  2850. ENLARGE: ["-", "num_add", "E"],
  2851. REDUCE: ["=", "num_subtract", "Q"],
  2852. RESTORE: ["9", "num_divide", "R"],
  2853. FIT_WIDTH: ["0", "num_multiply", "F"],
  2854. FIT_HEIGHT: ["H"],
  2855. SETTINGS: ["num_divide", "num_5", "X"],
  2856. VIEW_MODE_WEBCOMIC: ["C"],
  2857. VIEW_MODE_VERTICAL: ["V"],
  2858. VIEW_MODE_LEFT: ["N"],
  2859. VIEW_MODE_RIGHT: ["B"],
  2860. SCROLL_START: ["space"],
  2861. },
  2862. };
  2863. const getDefault = () =>
  2864. !isMobile()
  2865. ? defaultSettings
  2866. : _.defaultsDeep(
  2867. {
  2868. lazyLoadImages: true,
  2869. fitWidthIfOversize: true,
  2870. showThumbnails: false,
  2871. viewMode: "WebComic",
  2872. header: "click",
  2873. },
  2874. defaultSettings,
  2875. );
  2876. let settings$2 = _.defaultsDeep(getSettings(getDefault()), getDefault());
  2877. function getUserSettings() {
  2878. return settings$2;
  2879. }
  2880. function getLocaleString(name) {
  2881. const locale = locales.find((l) => l.ID === settings$2.locale);
  2882. if (locale?.[name]) {
  2883. return locale[name];
  2884. }
  2885. if (locales?.at(1)?.[name]) {
  2886. return locales[1][name];
  2887. }
  2888. return "##MISSING_STRING##";
  2889. }
  2890. function updateSettings(newValue) {
  2891. logScript(JSON.stringify(newValue));
  2892. settings$2 = { ...settings$2, ...newValue };
  2893. setSettings(diffObj(settings$2, getDefault()));
  2894. }
  2895. function resetSettings() {
  2896. getListGM().forEach((setting) => {
  2897. removeValueGM(setting);
  2898. });
  2899. updateSettings(getDefault());
  2900. }
  2901. function isBookmarked(url = window.location.href) {
  2902. return settings$2.bookmarks.find((el) => el.url === url)?.page;
  2903. }
  2904. const bookmarkTimeLimit = 1e3 * 60 * 60 * 24 * 30 * 12;
  2905. const refreshedBookmark = settings$2.bookmarks.filter(
  2906. (el) => Date.now() - new Date(el.date).valueOf() < bookmarkTimeLimit,
  2907. );
  2908. if (settings$2.bookmarks.length !== refreshedBookmark.length) {
  2909. updateSettings({ bookmarks: refreshedBookmark });
  2910. }
  2911.  
  2912. const IconArrowAutofitDown =
  2913. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-autofit-down" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M12 20h-6a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h8" />\n <path d="M18 4v17" />\n <path d="M15 18l3 3l3 -3" />\n</svg>\n\n\n';
  2914.  
  2915. const IconArrowAutofitHeight =
  2916. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-autofit-height" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M12 20h-6a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h6" />\n <path d="M18 14v7" />\n <path d="M18 3v7" />\n <path d="M15 18l3 3l3 -3" />\n <path d="M15 6l3 -3l3 3" />\n</svg>\n\n\n';
  2917.  
  2918. const IconArrowAutofitLeft =
  2919. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-autofit-left" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M4 12v-6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v8" />\n <path d="M20 18h-17" />\n <path d="M6 15l-3 3l3 3" />\n</svg>\n\n\n';
  2920.  
  2921. const IconArrowAutofitRight =
  2922. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-autofit-right" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M20 12v-6a2 2 0 0 0 -2 -2h-12a2 2 0 0 0 -2 2v8" />\n <path d="M4 18h17" />\n <path d="M18 15l3 3l-3 3" />\n</svg>\n\n\n';
  2923.  
  2924. const IconArrowAutofitWidth =
  2925. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-autofit-width" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M4 12v-6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v6" />\n <path d="M10 18h-7" />\n <path d="M21 18h-7" />\n <path d="M6 15l-3 3l3 3" />\n <path d="M18 15l3 3l-3 3" />\n</svg>\n\n\n';
  2926.  
  2927. const IconArrowBigLeft =
  2928. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-big-left" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M20 15h-8v3.586a1 1 0 0 1 -1.707 .707l-6.586 -6.586a1 1 0 0 1 0 -1.414l6.586 -6.586a1 1 0 0 1 1.707 .707v3.586h8a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1z" />\n</svg>\n\n\n';
  2929.  
  2930. const IconArrowBigRight =
  2931. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-big-right" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M4 9h8v-3.586a1 1 0 0 1 1.707 -.707l6.586 6.586a1 1 0 0 1 0 1.414l-6.586 6.586a1 1 0 0 1 -1.707 -.707v-3.586h-8a1 1 0 0 1 -1 -1v-4a1 1 0 0 1 1 -1z" />\n</svg>\n\n\n';
  2932.  
  2933. const IconBookmark =
  2934. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-bookmark" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M18 7v14l-6 -4l-6 4v-14a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4z" />\n</svg>\n\n\n';
  2935.  
  2936. const IconBookmarkOff =
  2937. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-bookmark-off" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M7.708 3.721a3.982 3.982 0 0 1 2.292 -.721h4a4 4 0 0 1 4 4v7m0 4v3l-6 -4l-6 4v-14c0 -.308 .035 -.609 .1 -.897" />\n <path d="M3 3l18 18" />\n</svg>\n\n\n';
  2938.  
  2939. const IconBookmarks =
  2940. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-bookmarks" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M15 10v11l-5 -3l-5 3v-11a3 3 0 0 1 3 -3h4a3 3 0 0 1 3 3z" />\n <path d="M11 3h5a3 3 0 0 1 3 3v11" />\n</svg>\n\n\n';
  2941.  
  2942. const IconCategory =
  2943. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-category" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M4 4h6v6h-6z" />\n <path d="M14 4h6v6h-6z" />\n <path d="M4 14h6v6h-6z" />\n <path d="M17 17m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" />\n</svg>\n\n\n';
  2944.  
  2945. const IconCheck =
  2946. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-check" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M5 12l5 5l10 -10" />\n</svg>\n\n\n';
  2947.  
  2948. const IconDeviceFloppy =
  2949. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-device-floppy" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M6 4h10l4 4v10a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2" />\n <path d="M12 14m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />\n <path d="M14 4l0 4l-6 0l0 -4" />\n</svg>\n\n\n';
  2950.  
  2951. const IconExternalLink =
  2952. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-external-link" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M12 6h-6a2 2 0 0 0 -2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-6" />\n <path d="M11 13l9 -9" />\n <path d="M15 4h5v5" />\n</svg>\n\n\n';
  2953.  
  2954. const IconEye =
  2955. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-eye" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />\n <path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" />\n</svg>\n\n\n';
  2956.  
  2957. const IconEyeOff =
  2958. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-eye-off" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M10.585 10.587a2 2 0 0 0 2.829 2.828" />\n <path d="M16.681 16.673a8.717 8.717 0 0 1 -4.681 1.327c-3.6 0 -6.6 -2 -9 -6c1.272 -2.12 2.712 -3.678 4.32 -4.674m2.86 -1.146a9.055 9.055 0 0 1 1.82 -.18c3.6 0 6.6 2 9 6c-.666 1.11 -1.379 2.067 -2.138 2.87" />\n <path d="M3 3l18 18" />\n</svg>\n\n\n';
  2959.  
  2960. const IconFileDownload =
  2961. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-download" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M14 3v4a1 1 0 0 0 1 1h4" />\n <path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z" />\n <path d="M12 17v-6" />\n <path d="M9.5 14.5l2.5 2.5l2.5 -2.5" />\n</svg>\n\n\n';
  2962.  
  2963. const IconKeyboard =
  2964. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-keyboard" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M2 6m0 2a2 2 0 0 1 2 -2h16a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-16a2 2 0 0 1 -2 -2z" />\n <path d="M6 10l0 .01" />\n <path d="M10 10l0 .01" />\n <path d="M14 10l0 .01" />\n <path d="M18 10l0 .01" />\n <path d="M6 14l0 .01" />\n <path d="M18 14l0 .01" />\n <path d="M10 14l4 .01" />\n</svg>\n\n\n';
  2965.  
  2966. const IconListNumbers =
  2967. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-list-numbers" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M11 6h9" />\n <path d="M11 12h9" />\n <path d="M12 18h8" />\n <path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4" />\n <path d="M6 10v-6l-2 2" />\n</svg>\n\n\n';
  2968.  
  2969. const IconLoader2 =
  2970. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-loader-2" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M12 3a9 9 0 1 0 9 9" />\n</svg>\n\n\n';
  2971.  
  2972. const IconMenu2 =
  2973. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-menu-2" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M4 6l16 0" />\n <path d="M4 12l16 0" />\n <path d="M4 18l16 0" />\n</svg>\n\n\n';
  2974.  
  2975. const IconMessage =
  2976. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-message" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M8 9h8" />\n <path d="M8 13h6" />\n <path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12z" />\n</svg>\n\n\n';
  2977.  
  2978. const IconMoon =
  2979. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-moon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" />\n</svg>\n\n\n';
  2980.  
  2981. const IconPalette =
  2982. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-palette" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M12 21a9 9 0 0 1 0 -18c4.97 0 9 3.582 9 8c0 1.06 -.474 2.078 -1.318 2.828c-.844 .75 -1.989 1.172 -3.182 1.172h-2.5a2 2 0 0 0 -1 3.75a1.3 1.3 0 0 1 -1 2.25" />\n <path d="M8.5 10.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />\n <path d="M12.5 7.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />\n <path d="M16.5 10.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />\n</svg>\n\n\n';
  2983.  
  2984. const IconPencil =
  2985. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-pencil" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4" />\n <path d="M13.5 6.5l4 4" />\n</svg>\n\n\n';
  2986.  
  2987. const IconPhoto =
  2988. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-photo" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M15 8h.01" />\n <path d="M3 6a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v12a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-12z" />\n <path d="M3 16l5 -5c.928 -.893 2.072 -.893 3 0l5 5" />\n <path d="M14 14l1 -1c.928 -.893 2.072 -.893 3 0l3 3" />\n</svg>\n\n\n';
  2989.  
  2990. const IconPhotoOff =
  2991. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-photo-off" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M15 8h.01" />\n <path d="M7 3h11a3 3 0 0 1 3 3v11m-.856 3.099a2.991 2.991 0 0 1 -2.144 .901h-12a3 3 0 0 1 -3 -3v-12c0 -.845 .349 -1.608 .91 -2.153" />\n <path d="M3 16l5 -5c.928 -.893 2.072 -.893 3 0l5 5" />\n <path d="M16.33 12.338c.574 -.054 1.155 .166 1.67 .662l3 3" />\n <path d="M3 3l18 18" />\n</svg>\n\n\n';
  2992.  
  2993. const IconRefresh =
  2994. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4" />\n <path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4" />\n</svg>\n\n\n';
  2995.  
  2996. const IconSettings =
  2997. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-settings" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />\n <path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />\n</svg>\n\n\n';
  2998.  
  2999. const IconSpacingVertical =
  3000. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-spacing-vertical" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M4 20v-2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v2" />\n <path d="M4 4v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2" />\n <path d="M16 12h-8" />\n</svg>\n\n\n';
  3001.  
  3002. const IconSun =
  3003. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-sun" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" />\n <path d="M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7" />\n</svg>\n\n\n';
  3004.  
  3005. const IconTrash =
  3006. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-trash" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M4 7l16 0" />\n <path d="M10 11l0 6" />\n <path d="M14 11l0 6" />\n <path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12" />\n <path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3" />\n</svg>\n\n\n';
  3007.  
  3008. const IconX =
  3009. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-x" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M18 6l-12 12" />\n <path d="M6 6l12 12" />\n</svg>\n\n\n';
  3010.  
  3011. const IconZoomCancel =
  3012. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-zoom-cancel" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" />\n <path d="M8 8l4 4" />\n <path d="M12 8l-4 4" />\n <path d="M21 21l-6 -6" />\n</svg>\n\n\n';
  3013.  
  3014. const IconZoomIn =
  3015. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-zoom-in" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" />\n <path d="M7 10l6 0" />\n <path d="M10 7l0 6" />\n <path d="M21 21l-6 -6" />\n</svg>\n\n\n';
  3016.  
  3017. const IconZoomInArea =
  3018. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-zoom-in-area" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M15 13v4" />\n <path d="M13 15h4" />\n <path d="M15 15m-5 0a5 5 0 1 0 10 0a5 5 0 1 0 -10 0" />\n <path d="M22 22l-3 -3" />\n <path d="M6 18h-1a2 2 0 0 1 -2 -2v-1" />\n <path d="M3 11v-1" />\n <path d="M3 6v-1a2 2 0 0 1 2 -2h1" />\n <path d="M10 3h1" />\n <path d="M15 3h1a2 2 0 0 1 2 2v1" />\n</svg>\n\n\n';
  3019.  
  3020. const IconZoomOut =
  3021. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-zoom-out" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" />\n <path d="M7 10l6 0" />\n <path d="M21 21l-6 -6" />\n</svg>\n\n\n';
  3022.  
  3023. const IconZoomOutArea =
  3024. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-zoom-out-area" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M13 15h4" />\n <path d="M15 15m-5 0a5 5 0 1 0 10 0a5 5 0 1 0 -10 0" />\n <path d="M22 22l-3 -3" />\n <path d="M6 18h-1a2 2 0 0 1 -2 -2v-1" />\n <path d="M3 11v-1" />\n <path d="M3 6v-1a2 2 0 0 1 2 -2h1" />\n <path d="M10 3h1" />\n <path d="M15 3h1a2 2 0 0 1 2 2v1" />\n</svg>\n\n\n';
  3025.  
  3026. const IconZoomPan =
  3027. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-zoom-pan" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M12 12m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" />\n <path d="M17 17l-2.5 -2.5" />\n <path d="M10 5l2 -2l2 2" />\n <path d="M19 10l2 2l-2 2" />\n <path d="M5 10l-2 2l2 2" />\n <path d="M10 19l2 2l2 -2" />\n</svg>\n\n\n';
  3028.  
  3029. const IconPlayerPlay =
  3030. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-player-play" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M7 4v16l13 -8z" />\n</svg>\n\n\n';
  3031.  
  3032. const IconPlayerPause =
  3033. '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-player-pause" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">\n <path stroke="none" d="M0 0h24v24H0z" fill="none"/>\n <path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" />\n <path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" />\n</svg>\n\n\n';
  3034.  
  3035. const styles =
  3036. ":root {\n --theme-body-background: #25262b;\n --theme-body-text-color: #c1c2c5;\n --theme-text-color: #c1c2c5;\n --theme-primary-color: #1a1b1e;\n --theme-primary-text-color: #c1c2c5;\n --theme-background-color: #25262b;\n --theme-hightlight-color: #2c2e33;\n --theme-border-color: #373a40;\n}\n\n#MangaOnlineViewer {\n text-decoration: none;\n color: var(--theme-body-text-color);\n background-color: var(--theme-body-background);\n}\n\n#MangaOnlineViewer #Chapter {\n display: grid;\n grid-template-columns: repeat(1, 1fr);\n min-width: 225px;\n}\n\n#MangaOnlineViewer #Chapter.Vertical:has(+ #Navigation:not(.disabled)),\n#MangaOnlineViewer #Chapter.WebComic:has(+ #Navigation:not(.disabled)) {\n padding-bottom: 31px;\n}\n\n#MangaOnlineViewer #Chapter.Vertical .PageContent {\n margin-bottom: 8px;\n margin-top: 8px;\n}\n\n#MangaOnlineViewer .closeButton {\n width: fit-content;\n height: fit-content;\n position: absolute;\n right: 10px;\n top: 10px;\n}\n\n#MangaOnlineViewer .overlay {\n position: fixed;\n display: none;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(0, 0, 0, 0.5);\n z-index: 950;\n cursor: pointer;\n}\n\n#MangaOnlineViewer .overlay.visible {\n display: block;\n}\n\n#MangaOnlineViewer select {\n height: 20px;\n padding: 0;\n margin-bottom: 5px;\n}\n\n#MangaOnlineViewer .ControlButton,\n#MangaOnlineViewer .simpleButton {\n cursor: pointer;\n border-radius: 5px;\n border-width: 1px;\n border-style: solid;\n padding: 2px;\n min-height: 32px;\n color: var(--theme-primary-text-color);\n background-color: var(--theme-primary-color);\n border-color: var(--theme-border-color);\n}\n\n#MangaOnlineViewer .ControlButton:active,\n#MangaOnlineViewer .ControlButton:hover {\n opacity: 0.8;\n}\n\n#MangaOnlineViewer .simpleButton {\n font-size: initial;\n min-width: 32px;\n}\n\n#MangaOnlineViewer .panel .simpleButton {\n position: absolute;\n top: 10px;\n left: 10px;\n}\n\n#MangaOnlineViewer .panel {\n padding: 5px;\n position: inherit;\n border-radius: 5px;\n background-color: var(--theme-background-color);\n}\n\n#MangaOnlineViewer :not(.FluidRTL, .FluidLTR).fitWidthIfOversize .PageContent .PageImg {\n max-width: 100%;\n}\n\n#MangaOnlineViewer .ControlButton.hidden,\n#MangaOnlineViewer.light #ColorScheme > .icon-tabler-sun,\n#MangaOnlineViewer:not(.light) #ColorScheme > .icon-tabler-moon,\n#MangaOnlineViewer .light + #CommentsColorScheme > .icon-tabler-sun,\n#MangaOnlineViewer .dark + #CommentsColorScheme > .icon-tabler-moon,\n#MangaOnlineViewer .ChapterControl #download.loading > .icon-tabler-file-download,\n#MangaOnlineViewer .ChapterControl #download:not(.loading) > .icon-tabler-loader-2,\n#MangaOnlineViewer .MangaPage.hide .ControlButton.Hide > .icon-tabler-eye-off,\n#MangaOnlineViewer .MangaPage:not(.hide) .ControlButton.Hide > .icon-tabler-eye,\n#MangaOnlineViewer.bookmarked .Bookmark > .icon-tabler-bookmark,\n#MangaOnlineViewer:not(.bookmarked) .Bookmark > .icon-tabler-bookmark-off,\n#MangaOnlineViewer #AutoScroll.running > .icon-tabler-player-play,\n#MangaOnlineViewer #AutoScroll:not(.running) > .icon-tabler-player-pause {\n display: none;\n}\n\n#MangaOnlineViewer.hideControls .PageFunctions {\n visibility: hidden;\n}\n";
  3037.  
  3038. const icons =
  3039. ".icon-tabler {\n height: 1rem;\n width: 1rem;\n vertical-align: sub;\n}\n\n.icon-tabler-file-download > :nth-child(n + 4) {\n /* 4, 5 */\n color: gold;\n}\n.icon-tabler-arrow-autofit-width > :nth-child(n + 3) {\n /* 3,4,5,6 */\n color: yellow;\n}\n.icon-tabler-arrow-autofit-height > :nth-child(n + 3) {\n /* 3,4,5,6 */\n color: yellow;\n}\n.icon-tabler-zoom-in-area > :nth-child(2),\n.icon-tabler-zoom-in-area > :nth-child(3) {\n color: lime;\n}\n.icon-tabler-zoom-out-area > :nth-child(2) {\n color: red;\n}\n.icon-tabler-zoom-pan > :nth-child(n + 4) {\n color: #9966ff;\n}\n.icon-tabler-arrow-autofit-down > :nth-child(n + 3) {\n color: #28ffbf;\n}\n.icon-tabler-arrow-autofit-left > :nth-child(n + 3) {\n color: #28ffbf;\n}\n.icon-tabler-arrow-autofit-right > :nth-child(n + 3) {\n color: #28ffbf;\n}\n.icon-tabler-spacing-vertical > :nth-child(4) {\n color: fuchsia;\n}\n.icon-tabler-list-numbers > :nth-child(n + 5) {\n color: #e48900;\n}\n.icon-tabler-bookmarks > :nth-child(n + 2) {\n color: orange;\n}\n.icon-tabler-bookmark > * {\n color: orange;\n}\n.icon-tabler-bookmark-off > * {\n color: orange;\n}\n.icon-tabler-bookmark-off > :nth-child(3) {\n color: red;\n}\n.icon-tabler-eye-off > :nth-child(4) {\n color: red;\n}\n.icon-tabler-zoom-cancel > :nth-child(3),\n.icon-tabler-zoom-cancel > :nth-child(4) {\n color: #9966ff;\n}\n.icon-tabler-zoom-in > :nth-child(3),\n.icon-tabler-zoom-in > :nth-child(4) {\n color: lime;\n}\n.icon-tabler-zoom-out > :nth-child(3) {\n color: red;\n}\n.icon-tabler-refresh > :nth-child(n + 2) {\n color: cyan;\n}\n.icon-tabler-photo > * {\n color: silver;\n}\n.icon-tabler-photo-off > * {\n color: silver;\n}\n.icon-tabler-photo-off > :nth-child(5) {\n color: orange;\n}\n.icon-tabler-message > :nth-child(2),\n.icon-tabler-message > :nth-child(3) {\n color: greenyellow;\n}\n";
  3040.  
  3041. const normalize$1 =
  3042. "/* Simple Normalizer */\nhtml {\n font-size: 100%;\n}\n\nbody {\n margin: 0;\n font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;\n font-size: 14px;\n line-height: 20px;\n color: var(--theme-body-text-color);\n background-color: var(--theme-body-background);\n padding: 0;\n}\n\na,\na:link,\na:visited,\na:active,\na:focus {\n color: var(--theme-body-text-color);\n text-decoration: none;\n}\n\nimg {\n height: auto;\n vertical-align: middle;\n border: 0 none;\n}\n";
  3043.  
  3044. const media =
  3045. "#MangaOnlineViewer.mobile #Header,\n#MangaOnlineViewer.tablet #Header {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n}\n\n#MangaOnlineViewer.mobile .ViewerTitle,\n#MangaOnlineViewer.tablet .ViewerTitle {\n order: 1;\n min-height: auto;\n padding: 0;\n margin: 0;\n flex-grow: 1;\n flex-shrink: 1;\n flex-basis: 100%;\n}\n\n#MangaOnlineViewer.mobile #GlobalFunctions,\n#MangaOnlineViewer.tablet #GlobalFunctions {\n width: auto;\n order: 2;\n padding: 5px;\n}\n\n#MangaOnlineViewer.mobile #ChapterNavigation,\n#MangaOnlineViewer.tablet #ChapterNavigation {\n order: 3;\n}\n\n#MangaOnlineViewer.mobile #GlobalFunctions #ZoomSlider,\n#MangaOnlineViewer.tablet #GlobalFunctions #ZoomSlider,\n#MangaOnlineViewer.mobile #GlobalFunctions .ControlButton:not(.tablets, .phones),\n#MangaOnlineViewer.tablet #GlobalFunctions .ControlButton:not(.tablets, .phones) {\n display: none;\n}\n\n#MangaOnlineViewer.mobile #Header {\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: center;\n align-items: center;\n}\n\n#MangaOnlineViewer.mobile #Header.click + #Chapter:not(.webcomic, .vertical) {\n position: sticky;\n}\n\n#MangaOnlineViewer.mobile #MangaTitle {\n word-wrap: anywhere;\n}\n\n#MangaOnlineViewer.mobile .ViewerTitle {\n order: 1;\n margin-top: 0;\n height: auto;\n padding: 0;\n}\n\n#MangaOnlineViewer.mobile #GlobalFunctions {\n order: 2;\n padding: 0;\n width: auto;\n flex-basis: 35px;\n}\n\n#MangaOnlineViewer.mobile #ChapterNavigation {\n order: 3;\n width: min-content;\n min-width: 205px;\n}\n\n#MangaOnlineViewer.mobile .ChapterControl {\n flex-direction: row;\n flex-wrap: wrap;\n}\n\n#MangaOnlineViewer.mobile .ChapterControl .NavigationControlButton {\n flex-grow: 1;\n}\n\n#MangaOnlineViewer.mobile .PageFunctions {\n padding: 0;\n}\n\n#MangaOnlineViewer.mobile .PageFunctions .ControlButton.Bookmark {\n opacity: 1;\n}\n\n#MangaOnlineViewer.mobile #Navigation,\n#MangaOnlineViewer.mobile #GlobalFunctions #ZoomSlider,\n#MangaOnlineViewer.mobile #GlobalFunctions .ControlButton:not(.phones),\n#MangaOnlineViewer.mobile .PageFunctions .ControlButton:not(.Bookmark),\n#MangaOnlineViewer.mobile #SettingsPanel .DefaultZoomMode,\n#MangaOnlineViewer.mobile #SettingsPanel .DefaultZoom,\n#MangaOnlineViewer.mobile #SettingsPanel .fitIfOversize,\n#MangaOnlineViewer.mobile #SettingsPanel .showThumbnails,\n#MangaOnlineViewer.mobile #SettingsPanel .lazyLoadImages,\n#MangaOnlineViewer.mobile #SettingsPanel .downloadZip,\n#MangaOnlineViewer.mobile #SettingsPanel .minZoom,\n#MangaOnlineViewer.mobile #SettingsPanel .zoomStep,\n#MangaOnlineViewer.mobile #SettingsPanel .headerType,\n#MangaOnlineViewer.mobile #SettingsPanel .autoScroll,\n#MangaOnlineViewer.mobile #KeybindingsPanel,\n#MangaOnlineViewer.mobile .ChapterControl .download,\n#MangaOnlineViewer.mobile #Counters {\n display: none;\n}\n";
  3046.  
  3047. const animation =
  3048. "@-webkit-keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n@-webkit-keyframes spin-reverse {\n 0% {\n transform: rotate(360deg);\n }\n\n to {\n transform: rotate(0);\n }\n}\n\n@keyframes spin-reverse {\n 0% {\n transform: rotate(360deg);\n }\n\n to {\n transform: rotate(0);\n }\n}\n\n.icon-tabler-loader-2,\n.animate-spin {\n -webkit-animation: spin 1s linear infinite;\n animation: spin 1s linear infinite;\n}\n\n.animate-spin-reverse {\n -webkit-animation: spin-reverse 1s linear infinite;\n animation: spin-reverse 1s linear infinite;\n}\n";
  3049.  
  3050. const header =
  3051. "#MangaOnlineViewer #gotoPage {\n min-width: 35px;\n}\n\n#MangaOnlineViewer #Header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex-flow: row nowrap;\n transition: transform 0.3s ease-in;\n position: sticky;\n top: 0;\n left: 0;\n right: 0;\n background-color: inherit;\n z-index: 900;\n}\n\n#MangaOnlineViewer #Header.click {\n padding-left: 40px;\n}\n\n@keyframes headroom {\n from {\n transform: translateY(-100%);\n position: sticky;\n top: 0;\n }\n to {\n transform: translateY(0%);\n position: sticky;\n top: 0;\n }\n}\n\n#MangaOnlineViewer #Header:not(.visible, .headroom-top, .fixed, .simple) {\n animation: headroom 0.3s ease-in reverse;\n transform: translateY(-100%);\n position: sticky;\n top: 0;\n}\n\n#MangaOnlineViewer #Header.click:has(+ #Chapter.FluidLTR, + #Chapter.FluidRTL) {\n position: fixed;\n padding-left: 40px;\n top: -100%;\n}\n\n#MangaOnlineViewer #Header.scroll.headroom-hide {\n animation: none;\n transform: translateY(-100%);\n position: sticky;\n top: 0;\n}\n\n#MangaOnlineViewer #Header.scroll.headroom-show,\n#MangaOnlineViewer #Header.headroom-end,\n#MangaOnlineViewer #Header.click:has(+ #Chapter.FluidLTR, + #Chapter.FluidRTL).visible,\n#MangaOnlineViewer #Header.visible {\n animation: headroom 0.3s ease-in;\n transform: translateY(0%);\n position: sticky;\n top: 0;\n}\n\n#MangaOnlineViewer #Header.headroom-top {\n animation: none;\n}\n\n#MangaOnlineViewer #Header.fixed {\n position: sticky;\n animation: none;\n top: 0;\n transform: translateY(0%);\n}\n\n#MangaOnlineViewer #Header.simple {\n position: static;\n animation: none;\n top: 0;\n transform: translateY(0%);\n}\n\n#MangaOnlineViewer #menu {\n position: fixed;\n z-index: 1;\n color: var(--theme-body-text-color);\n height: 40px;\n width: 40px;\n}\n\n#MangaOnlineViewer #menu .icon-tabler {\n position: relative;\n top: 4px;\n left: 4px;\n height: 32px;\n width: 32px;\n stroke-width: 1.25;\n}\n\n#MangaOnlineViewer #menu:not(.click, .hover),\n#MangaOnlineViewer #menu.hide {\n display: none;\n}\n\n#MangaOnlineViewer #menu.click {\n z-index: 901;\n}\n\n#MangaOnlineViewer #MangaTitle {\n padding: 2px;\n margin: 0;\n font-size: 1.2rem;\n font-weight: 400;\n}\n\n#MangaOnlineViewer #GlobalFunctions {\n display: flex;\n gap: 3px;\n padding: 3px 3px 3px 0;\n flex-wrap: wrap;\n width: 300px;\n z-index: 100;\n}\n\n#MangaOnlineViewer #GlobalFunctions span,\n#MangaOnlineViewer .ChapterControl span {\n display: flex;\n flex-wrap: nowrap;\n justify-content: space-evenly;\n}\n\n#MangaOnlineViewer .ChapterControl span {\n flex-grow: 1;\n}\n\n#MangaOnlineViewer .ChapterControl span > * {\n flex-basis: 50%;\n}\n\n#MangaOnlineViewer #ScrollControl .icon-tabler,\n#MangaOnlineViewer #GlobalFunctions .icon-tabler {\n width: 25px;\n height: 25px;\n}\n\n#MangaOnlineViewer #GlobalFunctions #ZoomSlider {\n display: flex;\n align-items: center;\n}\n\n#MangaOnlineViewer #GlobalFunctions #Zoom {\n margin: 2px 5px;\n width: 160px;\n}\n\n#MangaOnlineViewer #GlobalFunctions #ZoomVal {\n width: 40px;\n display: inline-block;\n color: var(--theme-primary-text-color);\n line-height: 20px;\n text-align: center;\n border-radius: 3px;\n background: var(--theme-primary-color);\n padding: 2px 5px;\n}\n\n#MangaOnlineViewer #ChapterNavigation {\n display: flex;\n flex-flow: column nowrap;\n justify-content: center;\n align-items: end;\n padding: 5px;\n max-width: 350px;\n}\n\n#MangaOnlineViewer #Counters {\n padding-right: 5px;\n}\n\n#MangaOnlineViewer #ChapterControl {\n display: flex;\n}\n\n#MangaOnlineViewer #ChapterControl .NavigationControlButton {\n display: inline-flex;\n margin: 2px;\n justify-content: center;\n align-items: center;\n padding: 3px;\n gap: 0.5em;\n}\n\n#MangaOnlineViewer #ChapterControl .NavigationControlButton .icon-tabler {\n flex-shrink: 0;\n align-self: center;\n width: 1rem;\n height: 1rem;\n}\n\n#MangaOnlineViewer #ChapterControl .NavigationControlButton[href='#'],\n#MangaOnlineViewer #ChapterControl .NavigationControlButton[href=''],\n#MangaOnlineViewer #ChapterControl .NavigationControlButton[href='undefined'] {\n visibility: hidden;\n}\n\n#MangaOnlineViewer #ChapterControl #download.loading {\n cursor: not-allowed;\n pointer-events: none;\n opacity: 0.6;\n}\n\n#MangaOnlineViewer #ChapterControl .NavigationControlButton.disabled {\n pointer-events: none;\n filter: grayscale(0.9);\n}\n\n#MangaOnlineViewer .ViewerTitle {\n text-align: center;\n min-height: 60px;\n display: flex;\n justify-content: center;\n align-items: center;\n flex-direction: column;\n padding: 5px;\n flex-basis: 60%;\n}\n";
  3052.  
  3053. const keybindings$1 =
  3054. "#MangaOnlineViewer #KeybindingsPanel {\n padding: 10px;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n transition: transform 0.3s ease-in-out;\n transform: translateX(100%);\n line-height: 1.5em;\n z-index: 1000;\n overflow-y: auto;\n width: 360px;\n max-width: 100vw;\n}\n\n#MangaOnlineViewer #KeybindingsPanel.visible {\n transform: translateX(0);\n display: block;\n}\n\n#MangaOnlineViewer #KeybindingsPanel #KeybindingsList {\n display: grid;\n grid-template-columns: 1fr 2fr;\n gap: 5px;\n}\n\n#MangaOnlineViewer #KeybindingsPanel .ControlButton {\n margin-left: 3px;\n justify-content: center;\n align-items: center;\n padding: 5px 10px;\n gap: 0.5em;\n}\n\n#MangaOnlineViewer #KeybindingsPanel label {\n display: ruby;\n}\n#MangaOnlineViewer #KeybindingsPanel input {\n display: inline-block;\n width: 100%;\n}\n\n#MangaOnlineViewer #KeybindingsPanel #HotKeysRules {\n grid-column: span 2;\n}\n";
  3055.  
  3056. const page =
  3057. "#MangaOnlineViewer .MangaPage {\n width: 100%;\n display: inline-block;\n text-align: center;\n line-height: 0;\n min-height: 22px;\n min-width: 100%;\n}\n\n#MangaOnlineViewer .PageContent {\n text-align: center;\n display: inline-block;\n overflow-x: auto;\n max-width: 100%;\n transition: all 0.3s ease-in-out;\n height: 100%;\n overflow-y: hidden;\n}\n\n#MangaOnlineViewer .MangaPage.hide .PageContent {\n height: 0;\n}\n\n#MangaOnlineViewer .PageContent .PageImg[src=''],\n#MangaOnlineViewer .PageContent .PageImg:not([src]) {\n width: 40vw;\n height: 80vh;\n display: inline-block;\n background-position: center;\n background-repeat: no-repeat;\n background-size: 20%;\n background-color: var(--theme-hightlight-color);\n}\n\n#MangaOnlineViewer .PageContent .PageImg.imgBroken {\n width: 40vw;\n height: 80vh;\n display: inline-block;\n background-position: center;\n background-repeat: no-repeat;\n background-size: 20%;\n background-color: var(--theme-hightlight-color);\n}\n\n#MangaOnlineViewer .PageFunctions {\n font-family: monospace;\n display: flex;\n justify-content: flex-end;\n align-items: center;\n margin: 0;\n padding: 0;\n gap: 3px;\n position: absolute;\n right: 0;\n}\n\n#MangaOnlineViewer .PageFunctions > .PageIndex {\n background-color: var(--theme-primary-color);\n color: var(--theme-primary-text-color);\n min-width: 20px;\n text-align: center;\n display: inline-block;\n padding: 3px 5px;\n line-height: 1rem;\n border-radius: 5px;\n}\n\n#MangaOnlineViewer .PageFunctions .ControlButton {\n padding: 3px;\n display: flex;\n justify-content: center;\n align-items: center;\n margin: 0;\n border-width: 0;\n min-height: auto;\n opacity: 0.5;\n}\n\n#MangaOnlineViewer .PageFunctions:hover .ControlButton {\n opacity: 1;\n}\n\n#MangaOnlineViewer .PageFunctions .ControlButton:hover {\n opacity: 0.9;\n}\n\n#MangaOnlineViewer #Chapter.Vertical .separator {\n display: flex;\n align-items: center;\n text-align: center;\n font-style: italic;\n}\n\n#MangaOnlineViewer #Chapter.Vertical .separator::before,\n#MangaOnlineViewer #Chapter.Vertical .separator::after {\n content: '';\n flex: 1;\n border-bottom: 1px solid var(--theme-text-color);\n}\n\n#MangaOnlineViewer #Chapter.Vertical.separator:not(:empty)::before {\n margin-right: 0.25em;\n}\n\n#MangaOnlineViewer #Chapter.Vertical.separator:not(:empty)::after {\n margin-left: 0.25em;\n}\n\n#MangaOnlineViewer #Chapter:not(.separator) .separator,\n#MangaOnlineViewer #Chapter:not(.Vertical) .separator {\n display: none;\n}\n";
  3058.  
  3059. const fluid =
  3060. "#MangaOnlineViewer #Chapter.FluidLTR,\n#MangaOnlineViewer #Chapter.FluidRTL {\n display: flex;\n overflow-x: auto;\n min-width: auto;\n\n .ZoomWidth {\n display: none;\n }\n\n .PageImg {\n min-width: unset;\n }\n\n .MangaPage {\n width: initial;\n min-width: fit-content;\n position: relative;\n max-height: 100%;\n }\n\n .MangaPage.DoublePage {\n grid-column: span 2;\n }\n}\n\n#MangaOnlineViewer #Chapter.FluidLTR {\n flex-direction: row;\n\n .MangaPage .PageFunctions {\n right: auto;\n left: 0;\n direction: rtl;\n }\n}\n\n#MangaOnlineViewer #Chapter.FluidRTL {\n flex-direction: row-reverse;\n}\n";
  3061.  
  3062. const settings$1 =
  3063. "#MangaOnlineViewer #SettingsPanel {\n color: var(--theme-text-color);\n padding: 10px;\n position: fixed;\n top: 0;\n left: 0;\n bottom: 0;\n z-index: 1000;\n transition: transform 0.3s ease-in,\n background-color 0.3s linear;\n transform: translateX(-100%);\n display: flex;\n flex-flow: column;\n gap: 5px;\n overflow-y: auto;\n max-width: 100vw;\n width: 305px;\n}\n\n#MangaOnlineViewer #SettingsPanel.visible {\n transform: translateX(0);\n}\n\n#MangaOnlineViewer #SettingsPanel .ControlLabel {\n display: flex;\n flex-flow: row wrap;\n justify-content: space-between;\n align-items: center;\n}\n\n#MangaOnlineViewer #SettingsPanel .ControlLabelItem {\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n#MangaOnlineViewer #SettingsPanel .ControlLabelItem:not(.show) {\n display: none;\n}\n\n#MangaOnlineViewer #SettingsPanel input[type='range'] {\n width: 100%;\n}\n\n#MangaOnlineViewer #SettingsPanel .RangeValue {\n display: inline-block;\n color: var(--theme-primary-text-color);\n line-height: 20px;\n text-align: center;\n border-radius: 3px;\n background: var(--theme-primary-color);\n padding: 2px 5px;\n margin-left: 8px;\n}\n\n#MangaOnlineViewer #SettingsPanel datalist {\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n align-items: center;\n writing-mode: vertical-lr;\n width: 100%;\n}\n\n#MangaOnlineViewer #SettingsPanel datalist option {\n padding: 0;\n}\n\n#MangaOnlineViewer #ThemeSection {\n border: 1px solid var(--theme-body-text-color);\n border-radius: 10px;\n padding: 10px;\n}\n\n#MangaOnlineViewer .ThemeRadio {\n border: 1px solid var(--theme-text-color);\n color: var(--theme-primary-text-color);\n background-color: var(--theme-primary-color);\n height: 20px;\n width: 20px;\n border-radius: 50%;\n padding: 1px;\n margin: 2px 5px;\n position: relative;\n}\n\n#MangaOnlineViewer .ThemeRadio svg {\n position: absolute;\n top: 15%;\n right: 15%;\n}\n\n#MangaOnlineViewer .ThemeRadio.selected .icon-tabler-check {\n display: inline;\n}\n\n#MangaOnlineViewer .ThemeRadio:not(.selected) .icon-tabler-check {\n display: none;\n}\n\n#MangaOnlineViewer #ThemeSelector {\n width: 110px;\n}\n\n#MangaOnlineViewer #Chapter:not(.Vertical) ~ #SettingsPanel .verticalSeparator {\n display: none;\n}\n";
  3064.  
  3065. const thumbnails =
  3066. "#MangaOnlineViewer .Thumbnail .ThumbnailImg[src=''],\n#MangaOnlineViewer .Thumbnail .ThumbnailImg:not([src]) {\n width: 100px;\n height: 150px;\n display: inline-block;\n background-position: center;\n background-repeat: no-repeat;\n background-size: 20%;\n}\n\n#MangaOnlineViewer #NavigationCounters {\n margin: 5px;\n width: 100%;\n line-height: 1rem;\n}\n\n#MangaOnlineViewer #Navigation {\n color: var(--theme-text-color);\n background-color: var(--theme-hightlight-color);\n bottom: -180px;\n height: 185px;\n overflow-x: hidden;\n overflow-y: hidden;\n padding-bottom: 20px;\n position: fixed;\n white-space: nowrap;\n width: 100%;\n text-align: center;\n transition:\n transform 0.3s ease-in,\n background-color 0.3s linear;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n line-height: 0;\n}\n\n#MangaOnlineViewer #Navigation #Thumbnails {\n overflow-x: auto;\n overflow-y: hidden;\n margin-right: 10px;\n}\n\n#MangaOnlineViewer #Navigation:hover {\n transform: translateY(-180px);\n}\n\n#MangaOnlineViewer #Navigation.disabled {\n display: none;\n}\n\n#MangaOnlineViewer #Navigation.visible {\n transform: translateY(-180px);\n}\n\n#MangaOnlineViewer #Navigation .Thumbnail {\n display: inline-block;\n height: 150px;\n margin: 0 5px;\n border: 1px solid var(--theme-primary-color);\n}\n\n#MangaOnlineViewer #Navigation .Thumbnail .ThumbnailIndex {\n color: var(--theme-text-color);\n background-color: var(--theme-hightlight-color);\n display: block;\n opacity: 0.8;\n position: relative;\n bottom: 25%;\n width: 100%;\n line-height: 1rem;\n}\n\n#MangaOnlineViewer #Navigation .Thumbnail .ThumbnailImg {\n cursor: pointer;\n display: inline-block;\n max-height: 150px;\n min-height: 150px;\n min-width: 80px;\n max-width: 160px;\n}\n";
  3067.  
  3068. const bookmarks$1 =
  3069. "#MangaOnlineViewer #BookmarksPanel {\n position: fixed;\n top: 10%;\n width: 50%;\n left: 25%;\n right: 25%;\n text-align: center;\n max-height: 70%;\n transition: transform 0.3s ease-in-out;\n transform: scaleY(0);\n z-index: 1000;\n}\n\n#MangaOnlineViewer #BookmarksPanel.visible {\n transform: scaleY(1);\n display: block;\n}\n\n#MangaOnlineViewer #BookmarksList {\n padding: 0 15px;\n overflow: auto;\n max-height: 60vh;\n}\n\n#MangaOnlineViewer #BookmarksList .BookmarkItem {\n display: flex;\n flex-flow: row;\n justify-content: space-between;\n align-items: center;\n padding: 2px;\n}\n\n#MangaOnlineViewer #BookmarksList .bookmarkColumnLarge {\n flex-basis: 90%;\n}\n\n#MangaOnlineViewer #BookmarksList .bookmarkColumnSmall {\n width: 90px;\n}\n\n#MangaOnlineViewer #BookmarksList .bookmarkFunctions {\n width: 75px;\n}\n\n#MangaOnlineViewer #BookmarksList .bookmarkURl {\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n flex-basis: 55%;\n}\n";
  3070.  
  3071. const comments =
  3072. "#MangaOnlineViewer #CommentsPanel {\n position: static;\n width: 90%;\n height: 0;\n top: 5%;\n left: 5%;\n text-align: center;\n transition: transform 0.3s ease-in-out;\n transform: scaleY(0);\n z-index: 1000;\n overflow-y: initial;\n background-color: var(--theme-body-background);\n opacity: 0;\n}\n\n#MangaOnlineViewer #CommentsPanel.visible {\n position: fixed;\n height: 90%;\n transform: scaleY(1);\n display: flex;\n justify-content: center;\n align-items: center;\n flex-direction: column;\n opacity: 1;\n}\n\n#MangaOnlineViewer #CommentsArea {\n overflow-y: auto;\n overscroll-behavior: contain;\n height: 100%;\n width: 100%;\n background-color: var(--theme-body-background);\n}\n";
  3073.  
  3074. const cssStyles = css`
  3075. :root,
  3076. .dark,
  3077. .dark .default,
  3078. [data-theme="dark"] {
  3079. --theme-body-background: ${colors.dark["600"]};
  3080. --theme-body-text-color: ${colors.dark["50"]};
  3081. --theme-text-color: ${colors.dark["50"]};
  3082. --theme-primary-color: ${colors.dark["700"]};
  3083. --theme-primary-text-color: ${colors.dark["50"]};
  3084. --theme-background-color: ${colors.dark["600"]};
  3085. --theme-hightlight-color: ${colors.dark["500"]};
  3086. --theme-border-color: ${colors.dark["400"]};
  3087. }
  3088.  
  3089. .light,
  3090. .light .default,
  3091. [data-theme="light"] {
  3092. --theme-body-background: ${colors.gray["50"]};
  3093. --theme-body-text-color: ${colors.gray["900"]};
  3094. --theme-text-color: ${colors.gray["900"]};
  3095. --theme-primary-color: ${colors.gray["300"]};
  3096. --theme-primary-text-color: ${colors.gray["900"]};
  3097. --theme-background-color: ${colors.gray["50"]};
  3098. --theme-hightlight-color: ${colors.gray["500"]};
  3099. --theme-border-color: ${colors.gray["100"]};
  3100. }
  3101.  
  3102. #MangaOnlineViewer .PageContent .PageImg[src=""],
  3103. #MangaOnlineViewer .PageContent .PageImg:not([src]) {
  3104. background-image: url("${svgToUrl(IconPhoto)}");
  3105. }
  3106.  
  3107. #MangaOnlineViewer .PageContent .PageImg.imgBroken {
  3108. background-image: url("${svgToUrl(IconPhotoOff)}");
  3109. }
  3110.  
  3111. #MangaOnlineViewer .Thumbnail .ThumbnailImg[src=""],
  3112. #MangaOnlineViewer .Thumbnail .ThumbnailImg:not([src]) {
  3113. background-image: url("${svgToUrl(IconPhoto)}");
  3114. }
  3115.  
  3116. #MangaOnlineViewer .ThemeRadio.custom {
  3117. /*background-image: url("${svgToUrl(IconPalette)}");*/
  3118. }
  3119.  
  3120. ${normalize$1}
  3121. ${styles}
  3122. ${header}
  3123. ${icons}
  3124. ${keybindings$1}
  3125. ${page}
  3126. ${fluid}
  3127. ${settings$1}
  3128. ${thumbnails}
  3129. ${bookmarks$1}
  3130. ${comments}
  3131. ${media}
  3132. ${animation}
  3133. `;
  3134.  
  3135. function createStyleElement(id, content) {
  3136. const style = document.createElement("style");
  3137. style.id = id;
  3138. style.appendChild(document.createTextNode(content));
  3139. return style;
  3140. }
  3141. function appendStyleSheet(id, content) {
  3142. if (!document.querySelector(`#${id}`)) {
  3143. const head = document.head ?? document.querySelector("head");
  3144. head.appendChild(createStyleElement(id, content));
  3145. }
  3146. }
  3147. function removeStyleSheet(id) {
  3148. document.querySelectorAll(`style[id="${id}"]`).forEach((elem) => {
  3149. elem.remove();
  3150. });
  3151. }
  3152. function replaceStyleSheet(id, content) {
  3153. removeStyleSheet(id);
  3154. appendStyleSheet(id, content);
  3155. }
  3156. function wrapStyle(id, css) {
  3157. return html`
  3158. <style type="text/css" id="${id}">
  3159. ${css}
  3160. </style>
  3161. `;
  3162. }
  3163.  
  3164. function generateThemeCSS(name, primary, text) {
  3165. return css`
  3166. .${name}, [data-theme="${name}"] {
  3167. --theme-primary-color: ${primary};
  3168. --theme-primary-text-color: ${text};
  3169. }
  3170. `;
  3171. }
  3172. function getNormalThemeCSS(theme) {
  3173. return generateThemeCSS(
  3174. theme.name,
  3175. theme[getUserSettings().themeShade],
  3176. getUserSettings().themeShade < 500 ? theme["900"] : theme["50"],
  3177. );
  3178. }
  3179. function getCustomThemeCSS(hex) {
  3180. return generateThemeCSS("custom", hex, getTextColor(hex));
  3181. }
  3182. function addTheme(theme) {
  3183. return wrapStyle(theme.name, getNormalThemeCSS(theme));
  3184. }
  3185. function addCustomTheme(hex) {
  3186. replaceStyleSheet("custom", getCustomThemeCSS(hex));
  3187. }
  3188. const themes = () => Object.values(colors);
  3189. function refreshThemes() {
  3190. themes().forEach((theme) => {
  3191. replaceStyleSheet(theme.name, getNormalThemeCSS(theme));
  3192. });
  3193. replaceStyleSheet(
  3194. "custom",
  3195. getCustomThemeCSS(getUserSettings().customTheme),
  3196. );
  3197. }
  3198. const themesCSS =
  3199. themes().map(addTheme).join("") +
  3200. wrapStyle("custom", getCustomThemeCSS(getUserSettings().customTheme));
  3201.  
  3202. const sweetalert =
  3203. '.swal2-popup.swal2-toast{box-sizing:border-box;grid-column:1/4!important;grid-row:1/4!important;grid-template-columns:1fr 99fr 1fr;padding:1em;overflow-y:hidden;background:#fff;box-shadow:0 0 1px rgba(0,0,0,.075),0 1px 2px rgba(0,0,0,.075),1px 2px 4px rgba(0,0,0,.075),1px 3px 8px rgba(0,0,0,.075),2px 4px 16px rgba(0,0,0,.075);pointer-events:all}.swal2-popup.swal2-toast>*{grid-column:2}.swal2-popup.swal2-toast .swal2-title{margin:.5em 1em;padding:0;font-size:1em;text-align:initial}.swal2-popup.swal2-toast .swal2-loading{justify-content:center}.swal2-popup.swal2-toast .swal2-input{height:2em;margin:.5em;font-size:1em}.swal2-popup.swal2-toast .swal2-validation-message{font-size:1em}.swal2-popup.swal2-toast .swal2-footer{margin:.5em 0 0;padding:.5em 0 0;font-size:.8em}.swal2-popup.swal2-toast .swal2-close{grid-column:3/3;grid-row:1/99;align-self:center;width:.8em;height:.8em;margin:0;font-size:2em}.swal2-popup.swal2-toast .swal2-html-container{margin:.5em 1em;padding:0;font-size:1em;text-align:initial}.swal2-popup.swal2-toast .swal2-html-container:empty{padding:0}.swal2-popup.swal2-toast .swal2-loader{grid-column:1;grid-row:1/99;align-self:center;width:2em;height:2em;margin:.25em}.swal2-popup.swal2-toast .swal2-icon{grid-column:1;grid-row:1/99;align-self:center;width:2em;min-width:2em;height:2em;margin:0 .5em 0 0}.swal2-popup.swal2-toast .swal2-icon .swal2-icon-content{display:flex;align-items:center;font-size:1.8em;font-weight:700}.swal2-popup.swal2-toast .swal2-icon.swal2-success .swal2-success-ring{width:2em;height:2em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line]{top:.875em;width:1.375em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left]{left:.3125em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right]{right:.3125em}.swal2-popup.swal2-toast .swal2-actions{justify-content:flex-start;height:auto;margin:0;margin-top:.5em;padding:0 .5em}.swal2-popup.swal2-toast .swal2-styled{margin:.25em .5em;padding:.4em .6em;font-size:1em}.swal2-popup.swal2-toast .swal2-success{border-color:#a5dc86}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line]{position:absolute;width:1.6em;height:3em;transform:rotate(45deg);border-radius:50%}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=left]{top:-.8em;left:-.5em;transform:rotate(-45deg);transform-origin:2em 2em;border-radius:4em 0 0 4em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=right]{top:-.25em;left:.9375em;transform-origin:0 1.5em;border-radius:0 4em 4em 0}.swal2-popup.swal2-toast .swal2-success .swal2-success-ring{width:2em;height:2em}.swal2-popup.swal2-toast .swal2-success .swal2-success-fix{top:0;left:.4375em;width:.4375em;height:2.6875em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line]{height:.3125em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line][class$=tip]{top:1.125em;left:.1875em;width:.75em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line][class$=long]{top:.9375em;right:.1875em;width:1.375em}.swal2-popup.swal2-toast .swal2-success.swal2-icon-show .swal2-success-line-tip{-webkit-animation:swal2-toast-animate-success-line-tip .75s;animation:swal2-toast-animate-success-line-tip .75s}.swal2-popup.swal2-toast .swal2-success.swal2-icon-show .swal2-success-line-long{-webkit-animation:swal2-toast-animate-success-line-long .75s;animation:swal2-toast-animate-success-line-long .75s}.swal2-popup.swal2-toast.swal2-show{-webkit-animation:swal2-toast-show .5s;animation:swal2-toast-show .5s}.swal2-popup.swal2-toast.swal2-hide{-webkit-animation:swal2-toast-hide .1s forwards;animation:swal2-toast-hide .1s forwards}.swal2-container{display:grid;position:fixed;z-index:1060;top:0;right:0;bottom:0;left:0;box-sizing:border-box;grid-template-areas:"top-start top top-end" "center-start center center-end" "bottom-start bottom-center bottom-end";grid-template-rows:minmax(-webkit-min-content,auto) minmax(-webkit-min-content,auto) minmax(-webkit-min-content,auto);grid-template-rows:minmax(min-content,auto) minmax(min-content,auto) minmax(min-content,auto);height:100%;padding:.625em;overflow-x:hidden;transition:background-color .1s;-webkit-overflow-scrolling:touch}.swal2-container.swal2-backdrop-show,.swal2-container.swal2-noanimation{background:rgba(0,0,0,.4)}.swal2-container.swal2-backdrop-hide{background:0 0!important}.swal2-container.swal2-bottom-start,.swal2-container.swal2-center-start,.swal2-container.swal2-top-start{grid-template-columns:minmax(0,1fr) auto auto}.swal2-container.swal2-bottom,.swal2-container.swal2-center,.swal2-container.swal2-top{grid-template-columns:auto minmax(0,1fr) auto}.swal2-container.swal2-bottom-end,.swal2-container.swal2-center-end,.swal2-container.swal2-top-end{grid-template-columns:auto auto minmax(0,1fr)}.swal2-container.swal2-top-start>.swal2-popup{align-self:start}.swal2-container.swal2-top>.swal2-popup{grid-column:2;align-self:start;justify-self:center}.swal2-container.swal2-top-end>.swal2-popup,.swal2-container.swal2-top-right>.swal2-popup{grid-column:3;align-self:start;justify-self:end}.swal2-container.swal2-center-left>.swal2-popup,.swal2-container.swal2-center-start>.swal2-popup{grid-row:2;align-self:center}.swal2-container.swal2-center>.swal2-popup{grid-column:2;grid-row:2;align-self:center;justify-self:center}.swal2-container.swal2-center-end>.swal2-popup,.swal2-container.swal2-center-right>.swal2-popup{grid-column:3;grid-row:2;align-self:center;justify-self:end}.swal2-container.swal2-bottom-left>.swal2-popup,.swal2-container.swal2-bottom-start>.swal2-popup{grid-column:1;grid-row:3;align-self:end}.swal2-container.swal2-bottom>.swal2-popup{grid-column:2;grid-row:3;justify-self:center;align-self:end}.swal2-container.swal2-bottom-end>.swal2-popup,.swal2-container.swal2-bottom-right>.swal2-popup{grid-column:3;grid-row:3;align-self:end;justify-self:end}.swal2-container.swal2-grow-fullscreen>.swal2-popup,.swal2-container.swal2-grow-row>.swal2-popup{grid-column:1/4;width:100%}.swal2-container.swal2-grow-column>.swal2-popup,.swal2-container.swal2-grow-fullscreen>.swal2-popup{grid-row:1/4;align-self:stretch}.swal2-container.swal2-no-transition{transition:none!important}.swal2-popup{display:none;position:relative;box-sizing:border-box;grid-template-columns:minmax(0,100%);width:32em;max-width:100%;padding:0 0 1.25em;border:none;border-radius:5px;background:#fff;color:#545454;font-family:inherit;font-size:1rem}.swal2-popup:focus{outline:0}.swal2-popup.swal2-loading{overflow-y:hidden}.swal2-title{position:relative;max-width:100%;margin:0;padding:.8em 1em 0;color:inherit;font-size:1.875em;font-weight:600;text-align:center;text-transform:none;word-wrap:break-word}.swal2-actions{display:flex;z-index:1;box-sizing:border-box;flex-wrap:wrap;align-items:center;justify-content:center;width:auto;margin:1.25em auto 0;padding:0}.swal2-actions:not(.swal2-loading) .swal2-styled[disabled]{opacity:.4}.swal2-actions:not(.swal2-loading) .swal2-styled:hover{background-image:linear-gradient(rgba(0,0,0,.1),rgba(0,0,0,.1))}.swal2-actions:not(.swal2-loading) .swal2-styled:active{background-image:linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,.2))}.swal2-loader{display:none;align-items:center;justify-content:center;width:2.2em;height:2.2em;margin:0 1.875em;-webkit-animation:swal2-rotate-loading 1.5s linear 0s infinite normal;animation:swal2-rotate-loading 1.5s linear 0s infinite normal;border-width:.25em;border-style:solid;border-radius:100%;border-color:#2778c4 transparent #2778c4 transparent}.swal2-styled{margin:.3125em;padding:.625em 1.1em;transition:box-shadow .1s;box-shadow:0 0 0 3px transparent;font-weight:500}.swal2-styled:not([disabled]){cursor:pointer}.swal2-styled.swal2-confirm{border:0;border-radius:.25em;background:initial;background-color:#7066e0;color:#fff;font-size:1em}.swal2-styled.swal2-confirm:focus{box-shadow:0 0 0 3px rgba(112,102,224,.5)}.swal2-styled.swal2-deny{border:0;border-radius:.25em;background:initial;background-color:#dc3741;color:#fff;font-size:1em}.swal2-styled.swal2-deny:focus{box-shadow:0 0 0 3px rgba(220,55,65,.5)}.swal2-styled.swal2-cancel{border:0;border-radius:.25em;background:initial;background-color:#6e7881;color:#fff;font-size:1em}.swal2-styled.swal2-cancel:focus{box-shadow:0 0 0 3px rgba(110,120,129,.5)}.swal2-styled.swal2-default-outline:focus{box-shadow:0 0 0 3px rgba(100,150,200,.5)}.swal2-styled:focus{outline:0}.swal2-styled::-moz-focus-inner{border:0}.swal2-footer{justify-content:center;margin:1em 0 0;padding:1em 1em 0;border-top:1px solid #eee;color:inherit;font-size:1em}.swal2-timer-progress-bar-container{position:absolute;right:0;bottom:0;left:0;grid-column:auto!important;overflow:hidden;border-bottom-right-radius:5px;border-bottom-left-radius:5px}.swal2-timer-progress-bar{width:100%;height:.25em;background:rgba(0,0,0,.2)}.swal2-image{max-width:100%;margin:2em auto 1em}.swal2-close{z-index:2;align-items:center;justify-content:center;width:1.2em;height:1.2em;margin-top:0;margin-right:0;margin-bottom:-1.2em;padding:0;overflow:hidden;transition:color .1s,box-shadow .1s;border:none;border-radius:5px;background:0 0;color:#ccc;font-family:serif;font-family:monospace;font-size:2.5em;cursor:pointer;justify-self:end}.swal2-close:hover{transform:none;background:0 0;color:#f27474}.swal2-close:focus{outline:0;box-shadow:inset 0 0 0 3px rgba(100,150,200,.5)}.swal2-close::-moz-focus-inner{border:0}.swal2-html-container{z-index:1;justify-content:center;margin:1em 1.6em .3em;padding:0;overflow:auto;color:inherit;font-size:1.125em;font-weight:400;line-height:normal;text-align:center;word-wrap:break-word;word-break:break-word}.swal2-checkbox,.swal2-file,.swal2-input,.swal2-radio,.swal2-select,.swal2-textarea{margin:1em 2em 3px}.swal2-file,.swal2-input,.swal2-textarea{box-sizing:border-box;width:auto;transition:border-color .1s,box-shadow .1s;border:1px solid #d9d9d9;border-radius:.1875em;background:inherit;box-shadow:inset 0 1px 1px rgba(0,0,0,.06),0 0 0 3px transparent;color:inherit;font-size:1.125em}.swal2-file.swal2-inputerror,.swal2-input.swal2-inputerror,.swal2-textarea.swal2-inputerror{border-color:#f27474!important;box-shadow:0 0 2px #f27474!important}.swal2-file:focus,.swal2-input:focus,.swal2-textarea:focus{border:1px solid #b4dbed;outline:0;box-shadow:inset 0 1px 1px rgba(0,0,0,.06),0 0 0 3px rgba(100,150,200,.5)}.swal2-file::-moz-placeholder,.swal2-input::-moz-placeholder,.swal2-textarea::-moz-placeholder{color:#ccc}.swal2-file:-ms-input-placeholder,.swal2-input:-ms-input-placeholder,.swal2-textarea:-ms-input-placeholder{color:#ccc}.swal2-file::placeholder,.swal2-input::placeholder,.swal2-textarea::placeholder{color:#ccc}.swal2-range{margin:1em 2em 3px;background:#fff}.swal2-range input{width:80%}.swal2-range output{width:20%;color:inherit;font-weight:600;text-align:center}.swal2-range input,.swal2-range output{height:2.625em;padding:0;font-size:1.125em;line-height:2.625em}.swal2-input{height:2.625em;padding:0 .75em}.swal2-file{width:75%;margin-right:auto;margin-left:auto;background:inherit;font-size:1.125em}.swal2-textarea{height:6.75em;padding:.75em}.swal2-select{min-width:50%;max-width:100%;padding:.375em .625em;background:inherit;color:inherit;font-size:1.125em}.swal2-checkbox,.swal2-radio{align-items:center;justify-content:center;background:#fff;color:inherit}.swal2-checkbox label,.swal2-radio label{margin:0 .6em;font-size:1.125em}.swal2-checkbox input,.swal2-radio input{flex-shrink:0;margin:0 .4em}.swal2-input-label{display:flex;justify-content:center;margin:1em auto 0}.swal2-validation-message{align-items:center;justify-content:center;margin:1em 0 0;padding:.625em;overflow:hidden;background:#f0f0f0;color:#666;font-size:1em;font-weight:300}.swal2-validation-message::before{content:"!";display:inline-block;width:1.5em;min-width:1.5em;height:1.5em;margin:0 .625em;border-radius:50%;background-color:#f27474;color:#fff;font-weight:600;line-height:1.5em;text-align:center}.swal2-icon{position:relative;box-sizing:content-box;justify-content:center;width:5em;height:5em;margin:2.5em auto .6em;border:.25em solid transparent;border-radius:50%;border-color:#000;font-family:inherit;line-height:5em;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.swal2-icon .swal2-icon-content{display:flex;align-items:center;font-size:3.75em}.swal2-icon.swal2-error{border-color:#f27474;color:#f27474}.swal2-icon.swal2-error .swal2-x-mark{position:relative;flex-grow:1}.swal2-icon.swal2-error [class^=swal2-x-mark-line]{display:block;position:absolute;top:2.3125em;width:2.9375em;height:.3125em;border-radius:.125em;background-color:#f27474}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left]{left:1.0625em;transform:rotate(45deg)}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right]{right:1em;transform:rotate(-45deg)}.swal2-icon.swal2-error.swal2-icon-show{-webkit-animation:swal2-animate-error-icon .5s;animation:swal2-animate-error-icon .5s}.swal2-icon.swal2-error.swal2-icon-show .swal2-x-mark{-webkit-animation:swal2-animate-error-x-mark .5s;animation:swal2-animate-error-x-mark .5s}.swal2-icon.swal2-warning{border-color:#facea8;color:#f8bb86}.swal2-icon.swal2-warning.swal2-icon-show{-webkit-animation:swal2-animate-error-icon .5s;animation:swal2-animate-error-icon .5s}.swal2-icon.swal2-warning.swal2-icon-show .swal2-icon-content{-webkit-animation:swal2-animate-i-mark .5s;animation:swal2-animate-i-mark .5s}.swal2-icon.swal2-info{border-color:#9de0f6;color:#3fc3ee}.swal2-icon.swal2-info.swal2-icon-show{-webkit-animation:swal2-animate-error-icon .5s;animation:swal2-animate-error-icon .5s}.swal2-icon.swal2-info.swal2-icon-show .swal2-icon-content{-webkit-animation:swal2-animate-i-mark .8s;animation:swal2-animate-i-mark .8s}.swal2-icon.swal2-question{border-color:#c9dae1;color:#87adbd}.swal2-icon.swal2-question.swal2-icon-show{-webkit-animation:swal2-animate-error-icon .5s;animation:swal2-animate-error-icon .5s}.swal2-icon.swal2-question.swal2-icon-show .swal2-icon-content{-webkit-animation:swal2-animate-question-mark .8s;animation:swal2-animate-question-mark .8s}.swal2-icon.swal2-success{border-color:#a5dc86;color:#a5dc86}.swal2-icon.swal2-success [class^=swal2-success-circular-line]{position:absolute;width:3.75em;height:7.5em;transform:rotate(45deg);border-radius:50%}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=left]{top:-.4375em;left:-2.0635em;transform:rotate(-45deg);transform-origin:3.75em 3.75em;border-radius:7.5em 0 0 7.5em}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=right]{top:-.6875em;left:1.875em;transform:rotate(-45deg);transform-origin:0 3.75em;border-radius:0 7.5em 7.5em 0}.swal2-icon.swal2-success .swal2-success-ring{position:absolute;z-index:2;top:-.25em;left:-.25em;box-sizing:content-box;width:100%;height:100%;border:.25em solid rgba(165,220,134,.3);border-radius:50%}.swal2-icon.swal2-success .swal2-success-fix{position:absolute;z-index:1;top:.5em;left:1.625em;width:.4375em;height:5.625em;transform:rotate(-45deg)}.swal2-icon.swal2-success [class^=swal2-success-line]{display:block;position:absolute;z-index:2;height:.3125em;border-radius:.125em;background-color:#a5dc86}.swal2-icon.swal2-success [class^=swal2-success-line][class$=tip]{top:2.875em;left:.8125em;width:1.5625em;transform:rotate(45deg)}.swal2-icon.swal2-success [class^=swal2-success-line][class$=long]{top:2.375em;right:.5em;width:2.9375em;transform:rotate(-45deg)}.swal2-icon.swal2-success.swal2-icon-show .swal2-success-line-tip{-webkit-animation:swal2-animate-success-line-tip .75s;animation:swal2-animate-success-line-tip .75s}.swal2-icon.swal2-success.swal2-icon-show .swal2-success-line-long{-webkit-animation:swal2-animate-success-line-long .75s;animation:swal2-animate-success-line-long .75s}.swal2-icon.swal2-success.swal2-icon-show .swal2-success-circular-line-right{-webkit-animation:swal2-rotate-success-circular-line 4.25s ease-in;animation:swal2-rotate-success-circular-line 4.25s ease-in}.swal2-progress-steps{flex-wrap:wrap;align-items:center;max-width:100%;margin:1.25em auto;padding:0;background:inherit;font-weight:600}.swal2-progress-steps li{display:inline-block;position:relative}.swal2-progress-steps .swal2-progress-step{z-index:20;flex-shrink:0;width:2em;height:2em;border-radius:2em;background:#2778c4;color:#fff;line-height:2em;text-align:center}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step{background:#2778c4}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step{background:#add8e6;color:#fff}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step-line{background:#add8e6}.swal2-progress-steps .swal2-progress-step-line{z-index:10;flex-shrink:0;width:2.5em;height:.4em;margin:0 -1px;background:#2778c4}[class^=swal2]{-webkit-tap-highlight-color:transparent}.swal2-show{-webkit-animation:swal2-show .3s;animation:swal2-show .3s}.swal2-hide{-webkit-animation:swal2-hide .15s forwards;animation:swal2-hide .15s forwards}.swal2-noanimation{transition:none}.swal2-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}.swal2-rtl .swal2-close{margin-right:initial;margin-left:0}.swal2-rtl .swal2-timer-progress-bar{right:0;left:auto}@-webkit-keyframes swal2-toast-show{0%{transform:translateY(-.625em) rotateZ(2deg)}33%{transform:translateY(0) rotateZ(-2deg)}66%{transform:translateY(.3125em) rotateZ(2deg)}100%{transform:translateY(0) rotateZ(0)}}@keyframes swal2-toast-show{0%{transform:translateY(-.625em) rotateZ(2deg)}33%{transform:translateY(0) rotateZ(-2deg)}66%{transform:translateY(.3125em) rotateZ(2deg)}100%{transform:translateY(0) rotateZ(0)}}@-webkit-keyframes swal2-toast-hide{100%{transform:rotateZ(1deg);opacity:0}}@keyframes swal2-toast-hide{100%{transform:rotateZ(1deg);opacity:0}}@-webkit-keyframes swal2-toast-animate-success-line-tip{0%{top:.5625em;left:.0625em;width:0}54%{top:.125em;left:.125em;width:0}70%{top:.625em;left:-.25em;width:1.625em}84%{top:1.0625em;left:.75em;width:.5em}100%{top:1.125em;left:.1875em;width:.75em}}@keyframes swal2-toast-animate-success-line-tip{0%{top:.5625em;left:.0625em;width:0}54%{top:.125em;left:.125em;width:0}70%{top:.625em;left:-.25em;width:1.625em}84%{top:1.0625em;left:.75em;width:.5em}100%{top:1.125em;left:.1875em;width:.75em}}@-webkit-keyframes swal2-toast-animate-success-line-long{0%{top:1.625em;right:1.375em;width:0}65%{top:1.25em;right:.9375em;width:0}84%{top:.9375em;right:0;width:1.125em}100%{top:.9375em;right:.1875em;width:1.375em}}@keyframes swal2-toast-animate-success-line-long{0%{top:1.625em;right:1.375em;width:0}65%{top:1.25em;right:.9375em;width:0}84%{top:.9375em;right:0;width:1.125em}100%{top:.9375em;right:.1875em;width:1.375em}}@-webkit-keyframes swal2-show{0%{transform:scale(.7)}45%{transform:scale(1.05)}80%{transform:scale(.95)}100%{transform:scale(1)}}@keyframes swal2-show{0%{transform:scale(.7)}45%{transform:scale(1.05)}80%{transform:scale(.95)}100%{transform:scale(1)}}@-webkit-keyframes swal2-hide{0%{transform:scale(1);opacity:1}100%{transform:scale(.5);opacity:0}}@keyframes swal2-hide{0%{transform:scale(1);opacity:1}100%{transform:scale(.5);opacity:0}}@-webkit-keyframes swal2-animate-success-line-tip{0%{top:1.1875em;left:.0625em;width:0}54%{top:1.0625em;left:.125em;width:0}70%{top:2.1875em;left:-.375em;width:3.125em}84%{top:3em;left:1.3125em;width:1.0625em}100%{top:2.8125em;left:.8125em;width:1.5625em}}@keyframes swal2-animate-success-line-tip{0%{top:1.1875em;left:.0625em;width:0}54%{top:1.0625em;left:.125em;width:0}70%{top:2.1875em;left:-.375em;width:3.125em}84%{top:3em;left:1.3125em;width:1.0625em}100%{top:2.8125em;left:.8125em;width:1.5625em}}@-webkit-keyframes swal2-animate-success-line-long{0%{top:3.375em;right:2.875em;width:0}65%{top:3.375em;right:2.875em;width:0}84%{top:2.1875em;right:0;width:3.4375em}100%{top:2.375em;right:.5em;width:2.9375em}}@keyframes swal2-animate-success-line-long{0%{top:3.375em;right:2.875em;width:0}65%{top:3.375em;right:2.875em;width:0}84%{top:2.1875em;right:0;width:3.4375em}100%{top:2.375em;right:.5em;width:2.9375em}}@-webkit-keyframes swal2-rotate-success-circular-line{0%{transform:rotate(-45deg)}5%{transform:rotate(-45deg)}12%{transform:rotate(-405deg)}100%{transform:rotate(-405deg)}}@keyframes swal2-rotate-success-circular-line{0%{transform:rotate(-45deg)}5%{transform:rotate(-45deg)}12%{transform:rotate(-405deg)}100%{transform:rotate(-405deg)}}@-webkit-keyframes swal2-animate-error-x-mark{0%{margin-top:1.625em;transform:scale(.4);opacity:0}50%{margin-top:1.625em;transform:scale(.4);opacity:0}80%{margin-top:-.375em;transform:scale(1.15)}100%{margin-top:0;transform:scale(1);opacity:1}}@keyframes swal2-animate-error-x-mark{0%{margin-top:1.625em;transform:scale(.4);opacity:0}50%{margin-top:1.625em;transform:scale(.4);opacity:0}80%{margin-top:-.375em;transform:scale(1.15)}100%{margin-top:0;transform:scale(1);opacity:1}}@-webkit-keyframes swal2-animate-error-icon{0%{transform:rotateX(100deg);opacity:0}100%{transform:rotateX(0);opacity:1}}@keyframes swal2-animate-error-icon{0%{transform:rotateX(100deg);opacity:0}100%{transform:rotateX(0);opacity:1}}@-webkit-keyframes swal2-rotate-loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes swal2-rotate-loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@-webkit-keyframes swal2-animate-question-mark{0%{transform:rotateY(-360deg)}100%{transform:rotateY(0)}}@keyframes swal2-animate-question-mark{0%{transform:rotateY(-360deg)}100%{transform:rotateY(0)}}@-webkit-keyframes swal2-animate-i-mark{0%{transform:rotateZ(45deg);opacity:0}25%{transform:rotateZ(-25deg);opacity:.4}50%{transform:rotateZ(15deg);opacity:.8}75%{transform:rotateZ(-5deg);opacity:1}100%{transform:rotateX(0);opacity:1}}@keyframes swal2-animate-i-mark{0%{transform:rotateZ(45deg);opacity:0}25%{transform:rotateZ(-25deg);opacity:.4}50%{transform:rotateZ(15deg);opacity:.8}75%{transform:rotateZ(-5deg);opacity:1}100%{transform:rotateX(0);opacity:1}}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown){overflow:hidden}body.swal2-height-auto{height:auto!important}body.swal2-no-backdrop .swal2-container{background-color:transparent!important;pointer-events:none}body.swal2-no-backdrop .swal2-container .swal2-popup{pointer-events:all}body.swal2-no-backdrop .swal2-container .swal2-modal{box-shadow:0 0 10px rgba(0,0,0,.4)}@media print{body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown){overflow-y:scroll!important}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown)>[aria-hidden=true]{display:none}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown) .swal2-container{position:static!important}}body.swal2-toast-shown .swal2-container{box-sizing:border-box;width:360px;max-width:100%;background-color:transparent;pointer-events:none}body.swal2-toast-shown .swal2-container.swal2-top{top:0;right:auto;bottom:auto;left:50%;transform:translateX(-50%)}body.swal2-toast-shown .swal2-container.swal2-top-end,body.swal2-toast-shown .swal2-container.swal2-top-right{top:0;right:0;bottom:auto;left:auto}body.swal2-toast-shown .swal2-container.swal2-top-left,body.swal2-toast-shown .swal2-container.swal2-top-start{top:0;right:auto;bottom:auto;left:0}body.swal2-toast-shown .swal2-container.swal2-center-left,body.swal2-toast-shown .swal2-container.swal2-center-start{top:50%;right:auto;bottom:auto;left:0;transform:translateY(-50%)}body.swal2-toast-shown .swal2-container.swal2-center{top:50%;right:auto;bottom:auto;left:50%;transform:translate(-50%,-50%)}body.swal2-toast-shown .swal2-container.swal2-center-end,body.swal2-toast-shown .swal2-container.swal2-center-right{top:50%;right:0;bottom:auto;left:auto;transform:translateY(-50%)}body.swal2-toast-shown .swal2-container.swal2-bottom-left,body.swal2-toast-shown .swal2-container.swal2-bottom-start{top:auto;right:auto;bottom:0;left:0}body.swal2-toast-shown .swal2-container.swal2-bottom{top:auto;right:auto;bottom:0;left:50%;transform:translateX(-50%)}body.swal2-toast-shown .swal2-container.swal2-bottom-end,body.swal2-toast-shown .swal2-container.swal2-bottom-right{top:auto;right:0;bottom:0;left:auto}';
  3204.  
  3205. const normalize =
  3206. '/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n ========================================================================== */\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n line-height: 1.15; /* 1 */\n -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n margin: 0;\n}\n\n/**\n * Render the `main` element consistently in IE.\n */\n\nmain {\n display: block;\n}\n\n/**\n * Correct the font size and margin on `h1` elements within `section` and\n * `article` contexts in Chrome, Firefox, and Safari.\n */\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n/* Grouping content\n ========================================================================== */\n\n/**\n * 1. Add the correct box sizing in Firefox.\n * 2. Show the overflow in Edge and IE.\n */\n\nhr {\n box-sizing: content-box; /* 1 */\n height: 0; /* 1 */\n overflow: visible; /* 2 */\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\npre {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/* Text-level semantics\n ========================================================================== */\n\n/**\n * Remove the gray background on active links in IE 10.\n */\n\na {\n background-color: transparent;\n}\n\n/**\n * 1. Remove the bottom border in Chrome 57-\n * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n */\n\nabbr[title] {\n border-bottom: none; /* 1 */\n text-decoration: underline; /* 2 */\n text-decoration: underline dotted; /* 2 */\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n font-weight: bolder;\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n font-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in\n * all browsers.\n */\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -0.25em;\n}\n\nsup {\n top: -0.5em;\n}\n\n/* Embedded content\n ========================================================================== */\n\n/**\n * Remove the border on images inside links in IE 10.\n */\n\nimg {\n border-style: none;\n}\n\n/* Forms\n ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n font-family: inherit; /* 1 */\n font-size: 100%; /* 1 */\n line-height: 1.15; /* 1 */\n margin: 0; /* 2 */\n}\n\n/**\n * Show the overflow in IE.\n * 1. Show the overflow in Edge.\n */\n\nbutton,\ninput { /* 1 */\n overflow: visible;\n}\n\n/**\n * Remove the inheritance of text transform in Edge, Firefox, and IE.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect { /* 1 */\n text-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type="button"],\n[type="reset"],\n[type="submit"] {\n -webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type="button"]::-moz-focus-inner,\n[type="reset"]::-moz-focus-inner,\n[type="submit"]::-moz-focus-inner {\n border-style: none;\n padding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type="button"]:-moz-focusring,\n[type="reset"]:-moz-focusring,\n[type="submit"]:-moz-focusring {\n outline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n padding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * 1. Correct the text wrapping in Edge and IE.\n * 2. Correct the color inheritance from `fieldset` elements in IE.\n * 3. Remove the padding so developers are not caught out when they zero out\n * `fieldset` elements in all browsers.\n */\n\nlegend {\n box-sizing: border-box; /* 1 */\n color: inherit; /* 2 */\n display: table; /* 1 */\n max-width: 100%; /* 1 */\n padding: 0; /* 3 */\n white-space: normal; /* 1 */\n}\n\n/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */\n\nprogress {\n vertical-align: baseline;\n}\n\n/**\n * Remove the default vertical scrollbar in IE 10+.\n */\n\ntextarea {\n overflow: auto;\n}\n\n/**\n * 1. Add the correct box sizing in IE 10.\n * 2. Remove the padding in IE 10.\n */\n\n[type="checkbox"],\n[type="radio"] {\n box-sizing: border-box; /* 1 */\n padding: 0; /* 2 */\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Chrome.\n */\n\n[type="number"]::-webkit-inner-spin-button,\n[type="number"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type="search"] {\n -webkit-appearance: textfield; /* 1 */\n outline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type="search"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n -webkit-appearance: button; /* 1 */\n font: inherit; /* 2 */\n}\n\n/* Interactive\n ========================================================================== */\n\n/*\n * Add the correct display in Edge, IE 10+, and Firefox.\n */\n\ndetails {\n display: block;\n}\n\n/*\n * Add the correct display in all browsers.\n */\n\nsummary {\n display: list-item;\n}\n\n/* Misc\n ========================================================================== */\n\n/**\n * Add the correct display in IE 10+.\n */\n\ntemplate {\n display: none;\n}\n\n/**\n * Add the correct display in IE 10.\n */\n\n[hidden] {\n display: none;\n}\n';
  3207.  
  3208. const nprogress =
  3209. "/* Make clicks pass-through */\n#nprogress {\n pointer-events: none;\n}\n\n#nprogress .bar {\n background: #29d;\n\n position: fixed;\n z-index: 1031;\n top: 0;\n left: 0;\n\n width: 100%;\n height: 2px;\n}\n\n/* Fancy blur effect */\n#nprogress .peg {\n display: block;\n position: absolute;\n right: 0px;\n width: 100px;\n height: 100%;\n box-shadow: 0 0 10px #29d, 0 0 5px #29d;\n opacity: 1.0;\n\n -webkit-transform: rotate(3deg) translate(0px, -4px);\n -ms-transform: rotate(3deg) translate(0px, -4px);\n transform: rotate(3deg) translate(0px, -4px);\n}\n\n/* Remove these to get rid of the spinner */\n#nprogress .spinner {\n display: block;\n position: fixed;\n z-index: 1031;\n top: 15px;\n right: 15px;\n}\n\n#nprogress .spinner-icon {\n width: 18px;\n height: 18px;\n box-sizing: border-box;\n\n border: solid 2px transparent;\n border-top-color: #29d;\n border-left-color: #29d;\n border-radius: 50%;\n\n -webkit-animation: nprogress-spinner 400ms linear infinite;\n animation: nprogress-spinner 400ms linear infinite;\n}\n\n.nprogress-custom-parent {\n overflow: hidden;\n position: relative;\n}\n\n.nprogress-custom-parent #nprogress .spinner,\n.nprogress-custom-parent #nprogress .bar {\n position: absolute;\n}\n\n@-webkit-keyframes nprogress-spinner {\n 0% { -webkit-transform: rotate(0deg); }\n 100% { -webkit-transform: rotate(360deg); }\n}\n@keyframes nprogress-spinner {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n}\n\n";
  3210.  
  3211. const keyscss =
  3212. '/**\r\n * KEYS.css\r\n *\r\n * A simple stylesheet for rendering beautiful keyboard-style elements.\r\n *\r\n * Author: Michael Hüneburg\r\n * Website: http://michaelhue.com/keyscss\r\n * License: MIT License (see LICENSE.txt)\r\n */\r\n\r\nkbd,\r\n.key {\r\n display: inline;\r\n display: inline-block;\r\n white-space: nowrap;\r\n min-width: 1em;\r\n padding: .3em .4em .2em .3em;\r\n font-style: normal;\r\n font-family: "Lucida Grande", Lucida, Arial, sans-serif;\r\n text-align: center;\r\n text-decoration: none;\r\n border-radius: .3em;\r\n border: none;\r\n background-color: #505050;\r\n background-color: gradient(linear, left top, left bottom, from(#3c3c3c), to(#505050));\r\n color: #fafafa;\r\n text-shadow: -1px -1px 0 #464646;\r\n -webkit-box-shadow: inset 0 0 1px #969696, inset 0 -0.05em 0.4em #505050, 0 0.1em 0 #1e1e1e, 0 0.1em 0.1em rgba(0, 0, 0, 0.3);\r\n box-shadow: inset 0 0 1px #969696, inset 0 -0.05em 0.4em #505050, 0 0.1em 0 #1e1e1e, 0 0.1em 0.1em rgba(0, 0, 0, 0.3);\r\n font-size: .85em;\r\n line-height: 1;\r\n cursor: default;\r\n -webkit-user-select: none;\r\n -moz-user-select: none;\r\n -ms-user-select: none;\r\n user-select: none;\r\n}\r\nkbd[title],\r\n.key[title] {\r\n cursor: help;\r\n}\r\nkbd.dark,\r\n.dark-keys kbd,\r\n.key.dark,\r\n.dark-keys .key {\r\n display: inline;\r\n display: inline-block;\r\n white-space: nowrap;\r\n min-width: 1em;\r\n padding: .3em .4em .2em .3em;\r\n font-style: normal;\r\n font-family: "Lucida Grande", Lucida, Arial, sans-serif;\r\n text-align: center;\r\n text-decoration: none;\r\n border-radius: .3em;\r\n border: none;\r\n background-color: #505050;\r\n background-color: gradient(linear, left top, left bottom, from(#3c3c3c), to(#505050));\r\n color: #fafafa;\r\n text-shadow: -1px -1px 0 #464646;\r\n -webkit-box-shadow: inset 0 0 1px #969696, inset 0 -0.05em 0.4em #505050, 0 0.1em 0 #1e1e1e, 0 0.1em 0.1em rgba(0, 0, 0, 0.3);\r\n box-shadow: inset 0 0 1px #969696, inset 0 -0.05em 0.4em #505050, 0 0.1em 0 #1e1e1e, 0 0.1em 0.1em rgba(0, 0, 0, 0.3);\r\n}\r\nkbd.light,\r\n.light-keys kbd,\r\n.key.light,\r\n.light-keys .key {\r\n display: inline;\r\n display: inline-block;\r\n white-space: nowrap;\r\n min-width: 1em;\r\n padding: .3em .4em .2em .3em;\r\n font-style: normal;\r\n font-family: "Lucida Grande", Lucida, Arial, sans-serif;\r\n text-align: center;\r\n text-decoration: none;\r\n border-radius: .3em;\r\n border: none;\r\n background-color: #fafafa;\r\n background-color: gradient(linear, left top, left bottom, from(#d2d2d2), to(#ffffff));\r\n color: #323232;\r\n text-shadow: 0 0 2px #ffffff;\r\n -webkit-box-shadow: inset 0 0 1px #ffffff, inset 0 0 0.4em #c8c8c8, 0 0.1em 0 #828282, 0 0.11em 0 rgba(0, 0, 0, 0.4), 0 0.1em 0.11em rgba(0, 0, 0, 0.9);\r\n box-shadow: inset 0 0 1px #ffffff, inset 0 0 0.4em #c8c8c8, 0 0.1em 0 #828282, 0 0.11em 0 rgba(0, 0, 0, 0.4), 0 0.1em 0.11em rgba(0, 0, 0, 0.9);\r\n}\r\nkbd.so,\r\n.so-keys kbd,\r\n.key.so,\r\n.so-keys .key {\r\n display: inline;\r\n display: inline-block;\r\n white-space: nowrap;\r\n min-width: 1em;\r\n padding: .3em .4em .2em .3em;\r\n font-style: normal;\r\n font-family: "Lucida Grande", Lucida, Arial, sans-serif;\r\n text-align: center;\r\n text-decoration: none;\r\n border-radius: .3em;\r\n border: none;\r\n margin: 0 .1em;\r\n padding: .1em .6em;\r\n font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;\r\n line-height: 1.4;\r\n color: #242729;\r\n text-shadow: 0 1px 0 #FFF;\r\n background-color: #e1e3e5;\r\n border: 1px solid #adb3b9;\r\n border-radius: 0.27272727em;\r\n -webkit-box-shadow: 0 1px 0 rgba(12, 13, 14, 0.2), 0 0 0 2px #FFF inset;\r\n box-shadow: 0 1px 0 rgba(12, 13, 14, 0.2), 0 0 0 2px #FFF inset;\r\n}\r\nkbd.github,\r\n.github-keys kbd,\r\n.key.github,\r\n.github-keys .key {\r\n display: inline;\r\n display: inline-block;\r\n white-space: nowrap;\r\n min-width: 1em;\r\n padding: .3em .4em .2em .3em;\r\n font-style: normal;\r\n font-family: "Lucida Grande", Lucida, Arial, sans-serif;\r\n text-align: center;\r\n text-decoration: none;\r\n border-radius: .3em;\r\n border: none;\r\n padding: 0.27272727em 0.45454545em;\r\n font-size: 68.75%;\r\n line-height: 0.90909091;\r\n color: #444d56;\r\n vertical-align: middle;\r\n background-color: #fafbfc;\r\n border: solid 1px #c6cbd1;\r\n border-bottom-color: #959da5;\r\n border-radius: 0.27272727em;\r\n -webkit-box-shadow: inset 0 -1px 0 #959da5;\r\n box-shadow: inset 0 -1px 0 #959da5;\r\n font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;\r\n -webkit-box-sizing: border-box;\r\n box-sizing: border-box;\r\n text-shadow: none;\r\n}\r\n\r\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImtleXMuY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztFQUVFLGdCQUFnQjtFQUNoQixzQkFBc0I7RUFDdEIsb0JBQW9CO0VBQ3BCLGVBQWU7RUFDZiw2QkFBNkI7RUFDN0IsbUJBQW1CO0VBQ25CLHdEQUF3RDtFQUN4RCxtQkFBbUI7RUFDbkIsc0JBQXNCO0VBQ3RCLG9CQUFvQjtFQUNwQixhQUFhO0VBQ2IsMEJBQTBCO0VBQzFCLHNGQUFzRjtFQUN0RixlQUFlO0VBQ2YsaUNBQWlDO0VBQ2pDLDhIQUFzSDtVQUF0SCxzSEFBc0g7RUFDdEgsaUJBQWlCO0VBQ2pCLGVBQWU7RUFDZixnQkFBZ0I7RUFDaEIsMEJBQWtCO0tBQWxCLHVCQUFrQjtNQUFsQixzQkFBa0I7VUFBbEIsa0JBQWtCO0NBQ25CO0FBQ0Q7O0VBRUUsYUFBYTtDQUNkO0FBQ0Q7Ozs7RUFJRSxnQkFBZ0I7RUFDaEIsc0JBQXNCO0VBQ3RCLG9CQUFvQjtFQUNwQixlQUFlO0VBQ2YsNkJBQTZCO0VBQzdCLG1CQUFtQjtFQUNuQix3REFBd0Q7RUFDeEQsbUJBQW1CO0VBQ25CLHNCQUFzQjtFQUN0QixvQkFBb0I7RUFDcEIsYUFBYTtFQUNiLDBCQUEwQjtFQUMxQixzRkFBc0Y7RUFDdEYsZUFBZTtFQUNmLGlDQUFpQztFQUNqQyw4SEFBc0g7VUFBdEgsc0hBQXNIO0NBQ3ZIO0FBQ0Q7Ozs7RUFJRSxnQkFBZ0I7RUFDaEIsc0JBQXNCO0VBQ3RCLG9CQUFvQjtFQUNwQixlQUFlO0VBQ2YsNkJBQTZCO0VBQzdCLG1CQUFtQjtFQUNuQix3REFBd0Q7RUFDeEQsbUJBQW1CO0VBQ25CLHNCQUFzQjtFQUN0QixvQkFBb0I7RUFDcEIsYUFBYTtFQUNiLDBCQUEwQjtFQUMxQixzRkFBc0Y7RUFDdEYsZUFBZTtFQUNmLDZCQUE2QjtFQUM3Qix3SkFBZ0o7VUFBaEosZ0pBQWdKO0NBQ2pKO0FBQ0Q7Ozs7RUFJRSxnQkFBZ0I7RUFDaEIsc0JBQXNCO0VBQ3RCLG9CQUFvQjtFQUNwQixlQUFlO0VBQ2YsNkJBQTZCO0VBQzdCLG1CQUFtQjtFQUNuQix3REFBd0Q7RUFDeEQsbUJBQW1CO0VBQ25CLHNCQUFzQjtFQUN0QixvQkFBb0I7RUFDcEIsYUFBYTtFQUNiLGVBQWU7RUFDZixtQkFBbUI7RUFDbkIsNERBQTREO0VBQzVELGlCQUFpQjtFQUNqQixlQUFlO0VBQ2YsMEJBQTBCO0VBQzFCLDBCQUEwQjtFQUMxQiwwQkFBMEI7RUFDMUIsNEJBQTRCO0VBQzVCLHdFQUFnRTtVQUFoRSxnRUFBZ0U7Q0FDakU7QUFDRDs7OztFQUlFLGdCQUFnQjtFQUNoQixzQkFBc0I7RUFDdEIsb0JBQW9CO0VBQ3BCLGVBQWU7RUFDZiw2QkFBNkI7RUFDN0IsbUJBQW1CO0VBQ25CLHdEQUF3RDtFQUN4RCxtQkFBbUI7RUFDbkIsc0JBQXNCO0VBQ3RCLG9CQUFvQjtFQUNwQixhQUFhO0VBQ2IsbUNBQW1DO0VBQ25DLGtCQUFrQjtFQUNsQix3QkFBd0I7RUFDeEIsZUFBZTtFQUNmLHVCQUF1QjtFQUN2QiwwQkFBMEI7RUFDMUIsMEJBQTBCO0VBQzFCLDZCQUE2QjtFQUM3Qiw0QkFBNEI7RUFDNUIsMkNBQW1DO1VBQW5DLG1DQUFtQztFQUNuQyxzRkFBc0Y7RUFDdEYsK0JBQXVCO1VBQXZCLHVCQUF1QjtFQUN2QixrQkFBa0I7Q0FDbkIiLCJmaWxlIjoidG1wMi5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyJrYmQsXG4ua2V5IHtcbiAgZGlzcGxheTogaW5saW5lO1xuICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gIHdoaXRlLXNwYWNlOiBub3dyYXA7XG4gIG1pbi13aWR0aDogMWVtO1xuICBwYWRkaW5nOiAuM2VtIC40ZW0gLjJlbSAuM2VtO1xuICBmb250LXN0eWxlOiBub3JtYWw7XG4gIGZvbnQtZmFtaWx5OiBcIkx1Y2lkYSBHcmFuZGVcIiwgTHVjaWRhLCBBcmlhbCwgc2Fucy1zZXJpZjtcbiAgdGV4dC1hbGlnbjogY2VudGVyO1xuICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG4gIGJvcmRlci1yYWRpdXM6IC4zZW07XG4gIGJvcmRlcjogbm9uZTtcbiAgYmFja2dyb3VuZC1jb2xvcjogIzUwNTA1MDtcbiAgYmFja2dyb3VuZC1jb2xvcjogZ3JhZGllbnQobGluZWFyLCBsZWZ0IHRvcCwgbGVmdCBib3R0b20sIGZyb20oIzNjM2MzYyksIHRvKCM1MDUwNTApKTtcbiAgY29sb3I6ICNmYWZhZmE7XG4gIHRleHQtc2hhZG93OiAtMXB4IC0xcHggMCAjNDY0NjQ2O1xuICBib3gtc2hhZG93OiBpbnNldCAwIDAgMXB4ICM5Njk2OTYsIGluc2V0IDAgLTAuMDVlbSAwLjRlbSAjNTA1MDUwLCAwIDAuMWVtIDAgIzFlMWUxZSwgMCAwLjFlbSAwLjFlbSByZ2JhKDAsIDAsIDAsIDAuMyk7XG4gIGZvbnQtc2l6ZTogLjg1ZW07XG4gIGxpbmUtaGVpZ2h0OiAxO1xuICBjdXJzb3I6IGRlZmF1bHQ7XG4gIHVzZXItc2VsZWN0OiBub25lO1xufVxua2JkW3RpdGxlXSxcbi5rZXlbdGl0bGVdIHtcbiAgY3Vyc29yOiBoZWxwO1xufVxua2JkLmRhcmssXG4uZGFyay1rZXlzIGtiZCxcbi5rZXkuZGFyayxcbi5kYXJrLWtleXMgLmtleSB7XG4gIGRpc3BsYXk6IGlubGluZTtcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICB3aGl0ZS1zcGFjZTogbm93cmFwO1xuICBtaW4td2lkdGg6IDFlbTtcbiAgcGFkZGluZzogLjNlbSAuNGVtIC4yZW0gLjNlbTtcbiAgZm9udC1zdHlsZTogbm9ybWFsO1xuICBmb250LWZhbWlseTogXCJMdWNpZGEgR3JhbmRlXCIsIEx1Y2lkYSwgQXJpYWwsIHNhbnMtc2VyaWY7XG4gIHRleHQtYWxpZ246IGNlbnRlcjtcbiAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICBib3JkZXItcmFkaXVzOiAuM2VtO1xuICBib3JkZXI6IG5vbmU7XG4gIGJhY2tncm91bmQtY29sb3I6ICM1MDUwNTA7XG4gIGJhY2tncm91bmQtY29sb3I6IGdyYWRpZW50KGxpbmVhciwgbGVmdCB0b3AsIGxlZnQgYm90dG9tLCBmcm9tKCMzYzNjM2MpLCB0bygjNTA1MDUwKSk7XG4gIGNvbG9yOiAjZmFmYWZhO1xuICB0ZXh0LXNoYWRvdzogLTFweCAtMXB4IDAgIzQ2NDY0NjtcbiAgYm94LXNoYWRvdzogaW5zZXQgMCAwIDFweCAjOTY5Njk2LCBpbnNldCAwIC0wLjA1ZW0gMC40ZW0gIzUwNTA1MCwgMCAwLjFlbSAwICMxZTFlMWUsIDAgMC4xZW0gMC4xZW0gcmdiYSgwLCAwLCAwLCAwLjMpO1xufVxua2JkLmxpZ2h0LFxuLmxpZ2h0LWtleXMga2JkLFxuLmtleS5saWdodCxcbi5saWdodC1rZXlzIC5rZXkge1xuICBkaXNwbGF5OiBpbmxpbmU7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgd2hpdGUtc3BhY2U6IG5vd3JhcDtcbiAgbWluLXdpZHRoOiAxZW07XG4gIHBhZGRpbmc6IC4zZW0gLjRlbSAuMmVtIC4zZW07XG4gIGZvbnQtc3R5bGU6IG5vcm1hbDtcbiAgZm9udC1mYW1pbHk6IFwiTHVjaWRhIEdyYW5kZVwiLCBMdWNpZGEsIEFyaWFsLCBzYW5zLXNlcmlmO1xuICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gIHRleHQtZGVjb3JhdGlvbjogbm9uZTtcbiAgYm9yZGVyLXJhZGl1czogLjNlbTtcbiAgYm9yZGVyOiBub25lO1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZmFmYWZhO1xuICBiYWNrZ3JvdW5kLWNvbG9yOiBncmFkaWVudChsaW5lYXIsIGxlZnQgdG9wLCBsZWZ0IGJvdHRvbSwgZnJvbSgjZDJkMmQyKSwgdG8oI2ZmZmZmZikpO1xuICBjb2xvcjogIzMyMzIzMjtcbiAgdGV4dC1zaGFkb3c6IDAgMCAycHggI2ZmZmZmZjtcbiAgYm94LXNoYWRvdzogaW5zZXQgMCAwIDFweCAjZmZmZmZmLCBpbnNldCAwIDAgMC40ZW0gI2M4YzhjOCwgMCAwLjFlbSAwICM4MjgyODIsIDAgMC4xMWVtIDAgcmdiYSgwLCAwLCAwLCAwLjQpLCAwIDAuMWVtIDAuMTFlbSByZ2JhKDAsIDAsIDAsIDAuOSk7XG59XG5rYmQuc28sXG4uc28ta2V5cyBrYmQsXG4ua2V5LnNvLFxuLnNvLWtleXMgLmtleSB7XG4gIGRpc3BsYXk6IGlubGluZTtcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICB3aGl0ZS1zcGFjZTogbm93cmFwO1xuICBtaW4td2lkdGg6IDFlbTtcbiAgcGFkZGluZzogLjNlbSAuNGVtIC4yZW0gLjNlbTtcbiAgZm9udC1zdHlsZTogbm9ybWFsO1xuICBmb250LWZhbWlseTogXCJMdWNpZGEgR3JhbmRlXCIsIEx1Y2lkYSwgQXJpYWwsIHNhbnMtc2VyaWY7XG4gIHRleHQtYWxpZ246IGNlbnRlcjtcbiAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICBib3JkZXItcmFkaXVzOiAuM2VtO1xuICBib3JkZXI6IG5vbmU7XG4gIG1hcmdpbjogMCAuMWVtO1xuICBwYWRkaW5nOiAuMWVtIC42ZW07XG4gIGZvbnQtZmFtaWx5OiBBcmlhbCwgXCJIZWx2ZXRpY2EgTmV1ZVwiLCBIZWx2ZXRpY2EsIHNhbnMtc2VyaWY7XG4gIGxpbmUtaGVpZ2h0OiAxLjQ7XG4gIGNvbG9yOiAjMjQyNzI5O1xuICB0ZXh0LXNoYWRvdzogMCAxcHggMCAjRkZGO1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZTFlM2U1O1xuICBib3JkZXI6IDFweCBzb2xpZCAjYWRiM2I5O1xuICBib3JkZXItcmFkaXVzOiAwLjI3MjcyNzI3ZW07XG4gIGJveC1zaGFkb3c6IDAgMXB4IDAgcmdiYSgxMiwgMTMsIDE0LCAwLjIpLCAwIDAgMCAycHggI0ZGRiBpbnNldDtcbn1cbmtiZC5naXRodWIsXG4uZ2l0aHViLWtleXMga2JkLFxuLmtleS5naXRodWIsXG4uZ2l0aHViLWtleXMgLmtleSB7XG4gIGRpc3BsYXk6IGlubGluZTtcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICB3aGl0ZS1zcGFjZTogbm93cmFwO1xuICBtaW4td2lkdGg6IDFlbTtcbiAgcGFkZGluZzogLjNlbSAuNGVtIC4yZW0gLjNlbTtcbiAgZm9udC1zdHlsZTogbm9ybWFsO1xuICBmb250LWZhbWlseTogXCJMdWNpZGEgR3JhbmRlXCIsIEx1Y2lkYSwgQXJpYWwsIHNhbnMtc2VyaWY7XG4gIHRleHQtYWxpZ246IGNlbnRlcjtcbiAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICBib3JkZXItcmFkaXVzOiAuM2VtO1xuICBib3JkZXI6IG5vbmU7XG4gIHBhZGRpbmc6IDAuMjcyNzI3MjdlbSAwLjQ1NDU0NTQ1ZW07XG4gIGZvbnQtc2l6ZTogNjguNzUlO1xuICBsaW5lLWhlaWdodDogMC45MDkwOTA5MTtcbiAgY29sb3I6ICM0NDRkNTY7XG4gIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7XG4gIGJhY2tncm91bmQtY29sb3I6ICNmYWZiZmM7XG4gIGJvcmRlcjogc29saWQgMXB4ICNjNmNiZDE7XG4gIGJvcmRlci1ib3R0b20tY29sb3I6ICM5NTlkYTU7XG4gIGJvcmRlci1yYWRpdXM6IDAuMjcyNzI3MjdlbTtcbiAgYm94LXNoYWRvdzogaW5zZXQgMCAtMXB4IDAgIzk1OWRhNTtcbiAgZm9udC1mYW1pbHk6IFwiU0ZNb25vLVJlZ3VsYXJcIiwgQ29uc29sYXMsIFwiTGliZXJhdGlvbiBNb25vXCIsIE1lbmxvLCBDb3VyaWVyLCBtb25vc3BhY2U7XG4gIGJveC1zaXppbmc6IGJvcmRlci1ib3g7XG4gIHRleHQtc2hhZG93OiBub25lO1xufVxuIl19 */';
  3213.  
  3214. const fix =
  3215. "#nprogress .bar {\n background: #29d;\n position: fixed;\n z-index: 1031;\n top: 0;\n left: 0;\n width: 100%;\n height: 4px;\n}\n\n#pagesSlider {\n margin: 10px 0;\n}\n\n#pageInputs {\n display: flex;\n gap: 5px;\n align-items: center;\n justify-content: center;\n}\n\n#swal2-html-container .pageInput {\n border: 1px darkblue dashed;\n border-radius: 5px;\n text-align: center;\n background-color: aliceblue;\n color: black;\n max-width: 40%;\n}\n\n#swal2-title {\n color: navy;\n}\n\nbutton.swal2-styled {\n position: inherit;\n transform: inherit;\n}\n";
  3216.  
  3217. const sweetalertStyle = [normalize, sweetalert, fix, nprogress, keyscss].join(
  3218. "\n",
  3219. );
  3220.  
  3221. function head(manga) {
  3222. return html`
  3223. <title>${manga.title}</title>
  3224. <meta charset="UTF-8" />
  3225. ${wrapStyle("externals", sweetalertStyle)}
  3226. ${wrapStyle("reader", cssStyles)} ${themesCSS}
  3227. ${wrapStyle(
  3228. "MinZoom",
  3229. `#MangaOnlineViewer .PageContent .PageImg {min-width: ${getUserSettings().minZoom}vw;}`,
  3230. )}
  3231. `;
  3232. }
  3233.  
  3234. const localeSelector = () =>
  3235. locales
  3236. .map(
  3237. (locale) => html`
  3238. <option
  3239. value="${locale.ID}"
  3240. ${getUserSettings().locale === locale.ID ? "selected" : ""}
  3241. >
  3242. ${locale.NAME}
  3243. </option>
  3244. `,
  3245. )
  3246. .join("");
  3247. const themesSelector = () =>
  3248. [...Object.keys(colors).map((color) => colors[color].name)]
  3249. .map(
  3250. (theme2) => html`
  3251. <span
  3252. title="${theme2}"
  3253. class="${theme2} ThemeRadio ${getUserSettings().theme === theme2
  3254. ? "selected"
  3255. : ""}"
  3256. >
  3257. ${IconCheck}
  3258. </span>
  3259. `,
  3260. )
  3261. .join("");
  3262. const language = html` <div class="ControlLabel locale">
  3263. ${getLocaleString("LANGUAGE")}
  3264. <select id="locale">
  3265. ${localeSelector()}
  3266. </select>
  3267. </div>`;
  3268. const theme = html`
  3269. <div id="ThemeSection">
  3270. <div class="ControlLabel ColorSchemeSelector">
  3271. ${getLocaleString("COLOR_SCHEME")}
  3272. <button id="ColorScheme" class="ControlButton">
  3273. ${IconSun} ${IconMoon}
  3274. </button>
  3275. </div>
  3276. <div class="ControlLabel ThemeSelector">
  3277. ${getLocaleString("THEME")}
  3278. <span
  3279. class="custom ThemeRadio
  3280. ${getUserSettings().theme === "custom" ? "selected" : ""}"
  3281. title="custom"
  3282. >
  3283. ${IconPalette} ${IconCheck}
  3284. </span>
  3285. ${themesSelector()}
  3286. </div>
  3287. <div
  3288. id="Hue"
  3289. class="ControlLabel CustomTheme ControlLabelItem
  3290. ${getUserSettings().theme.startsWith("custom") ? "show" : ""}"
  3291. >
  3292. ${getLocaleString("THEME_HUE")}
  3293. <input
  3294. id="CustomThemeHue"
  3295. type="color"
  3296. value="${getUserSettings().customTheme}"
  3297. class="colorpicker CustomTheme"
  3298. />
  3299. </div>
  3300. <div
  3301. id="Shade"
  3302. class="ControlLabel CustomTheme ControlLabelItem
  3303. ${getUserSettings().theme.startsWith("custom") ? "" : "show"}"
  3304. >
  3305. <span>
  3306. ${getLocaleString("THEME_SHADE")}
  3307. <output id="themeShadeVal" class="RangeValue" for="ThemeShade">
  3308. ${getUserSettings().themeShade}
  3309. </output>
  3310. </span>
  3311. <input
  3312. type="range"
  3313. value="${getUserSettings().themeShade}"
  3314. name="ThemeShade"
  3315. id="ThemeShade"
  3316. min="100"
  3317. max="900"
  3318. step="100"
  3319. oninput="themeShadeVal.value = this.value"
  3320. />
  3321. </div>
  3322. </div>
  3323. `;
  3324. const loadMode = html`
  3325. <div class="ControlLabel loadMode">
  3326. ${getLocaleString("DEFAULT_LOAD_MODE")}
  3327. <select id="loadMode">
  3328. <option
  3329. value="wait"
  3330. ${getUserSettings().loadMode === "wait" ? "selected" : ""}
  3331. >
  3332. ${getLocaleString("LOAD_MODE_NORMAL")}
  3333. </option>
  3334. <option
  3335. value="always"
  3336. ${getUserSettings().loadMode === "always" ? "selected" : ""}
  3337. >
  3338. ${getLocaleString("LOAD_MODE_ALWAYS")}
  3339. </option>
  3340. <option
  3341. value="never"
  3342. ${getUserSettings().loadMode === "never" ? "selected" : ""}
  3343. >
  3344. ${getLocaleString("LOAD_MODE_NEVER")}
  3345. </option>
  3346. </select>
  3347. </div>
  3348. `;
  3349. const loadSpeed = html`
  3350. <div class="ControlLabel PagesPerSecond">
  3351. ${getLocaleString("LOAD_SPEED")}
  3352. <select id="PagesPerSecond">
  3353. <option
  3354. value="3000"
  3355. ${getUserSettings().throttlePageLoad === 3e3 ? "selected" : ""}
  3356. >
  3357. 0.3(${getLocaleString("SLOWLY")})
  3358. </option>
  3359. <option
  3360. value="2000"
  3361. ${getUserSettings().throttlePageLoad === 2e3 ? "selected" : ""}
  3362. >
  3363. 0.5
  3364. </option>
  3365. <option
  3366. value="1000"
  3367. ${getUserSettings().throttlePageLoad === 1e3 ? "selected" : ""}
  3368. >
  3369. 01(${getLocaleString("NORMAL")})
  3370. </option>
  3371. <option
  3372. value="500"
  3373. ${getUserSettings().throttlePageLoad === 500 ? "selected" : ""}
  3374. >
  3375. 02
  3376. </option>
  3377. <option
  3378. value="250"
  3379. ${getUserSettings().throttlePageLoad === 250 ? "selected" : ""}
  3380. >
  3381. 04(${getLocaleString("FAST")})
  3382. </option>
  3383. <option
  3384. value="125"
  3385. ${getUserSettings().throttlePageLoad === 125 ? "selected" : ""}
  3386. >
  3387. 08
  3388. </option>
  3389. <option
  3390. value="100"
  3391. ${getUserSettings().throttlePageLoad === 100 ? "selected" : ""}
  3392. >
  3393. 10(${getLocaleString("EXTREME")})
  3394. </option>
  3395. <option
  3396. value="1"
  3397. ${getUserSettings().throttlePageLoad === 1 ? "selected" : ""}
  3398. >
  3399. ${getLocaleString("ALL_PAGES")}
  3400. </option>
  3401. </select>
  3402. </div>
  3403. `;
  3404. const defaultZoomMode = html` <div class="ControlLabel DefaultZoomMode">
  3405. ${getLocaleString("DEFAULT_ZOOM_MODE")}
  3406. <select id="DefaultZoomMode">
  3407. <option
  3408. value="percent"
  3409. ${getUserSettings().zoomMode === "percent" ? "selected" : ""}
  3410. >
  3411. ${getLocaleString("PERCENT")}
  3412. </option>
  3413. <option
  3414. value="width"
  3415. ${getUserSettings().zoomMode === "width" ? "selected" : ""}
  3416. >
  3417. ${getLocaleString("FIT_WIDTH")}
  3418. </option>
  3419. <option
  3420. value="height"
  3421. ${getUserSettings().zoomMode === "height" ? "selected" : ""}
  3422. >
  3423. ${getLocaleString("FIT_HEIGHT")}
  3424. </option>
  3425. </select>
  3426. </div>`;
  3427. const defaultZoom = html`
  3428. <div
  3429. class="ControlLabel DefaultZoom ControlLabelItem
  3430. ${getUserSettings().zoomMode === "percent" ? "show" : ""}"
  3431. >
  3432. <span>
  3433. ${getLocaleString("DEFAULT_ZOOM")}
  3434. <output id="defaultZoomVal" class="RangeValue" for="DefaultZoom">
  3435. ${getUserSettings().defaultZoom}%
  3436. </output>
  3437. </span>
  3438. <input
  3439. type="range"
  3440. value="${getUserSettings().defaultZoom}"
  3441. name="DefaultZoom"
  3442. id="DefaultZoom"
  3443. min="5"
  3444. max="200"
  3445. step="5"
  3446. list="tickmarks"
  3447. oninput='defaultZoomVal.value = this.value + "%"'
  3448. />
  3449. <datalist id="tickmarks">
  3450. <option value="5">5</option>
  3451. <option value="25">25</option>
  3452. <option value="50">50</option>
  3453. <option value="75">75</option>
  3454. <option value="100">100</option>
  3455. <option value="125">125</option>
  3456. <option value="150">150</option>
  3457. <option value="175">175</option>
  3458. <option value="200">200</option>
  3459. </datalist>
  3460. </div>
  3461. `;
  3462. const minZoom = html`
  3463. <div class="ControlLabel minZoom">
  3464. <span>
  3465. ${getLocaleString("MINIMUM_ZOOM")}
  3466. <output id="minZoomVal" class="RangeValue" for="minZoom">
  3467. ${getUserSettings().minZoom}%
  3468. </output>
  3469. </span>
  3470. <input
  3471. type="range"
  3472. value="${getUserSettings().minZoom}"
  3473. name="minZoom"
  3474. id="minZoom"
  3475. min="30"
  3476. max="100"
  3477. step="10"
  3478. oninput='minZoomVal.value = this.value + "%"'
  3479. />
  3480. </div>
  3481. `;
  3482. const zoomStep = html`
  3483. <div class="ControlLabel zoomStep">
  3484. <span>
  3485. ${getLocaleString("ZOOM_STEP")}
  3486. <output id="zoomStepVal" class="RangeValue" for="zoomStep">
  3487. ${getUserSettings().zoomStep}%
  3488. </output>
  3489. </span>
  3490. <input
  3491. type="range"
  3492. value="${getUserSettings().zoomStep}"
  3493. name="zoomStep"
  3494. id="zoomStep"
  3495. min="5"
  3496. max="50"
  3497. step="5"
  3498. oninput='zoomStepVal.value = this.value + "%"'
  3499. />
  3500. </div>
  3501. `;
  3502. const viewMode$1 = html`
  3503. <div class="ControlLabel viewMode">
  3504. ${getLocaleString("DEFAULT_VIEW_MODE")}
  3505. <select id="viewMode">
  3506. <option
  3507. value="Vertical"
  3508. ${getUserSettings().viewMode === "Vertical" ? "selected" : ""}
  3509. >
  3510. ${getLocaleString("VIEW_MODE_VERTICAL")}
  3511. </option>
  3512. <option
  3513. value="WebComic"
  3514. ${getUserSettings().viewMode === "WebComic" ? "selected" : ""}
  3515. >
  3516. ${getLocaleString("VIEW_MODE_WEBCOMIC")}
  3517. </option>
  3518. <option
  3519. value="FluidLTR"
  3520. ${getUserSettings().viewMode === "FluidLTR" ? "selected" : ""}
  3521. >
  3522. ${getLocaleString("VIEW_MODE_LEFT")}
  3523. </option>
  3524. <option
  3525. value="FluidRTL"
  3526. ${getUserSettings().viewMode === "FluidRTL" ? "selected" : ""}
  3527. >
  3528. ${getLocaleString("VIEW_MODE_RIGHT")}
  3529. </option>
  3530. </select>
  3531. </div>
  3532. `;
  3533. const lazyLoad$1 = html`
  3534. <div
  3535. class="ControlLabel lazyStart ControlLabelItem
  3536. ${getUserSettings().lazyLoadImages ? "show" : ""}"
  3537. >
  3538. <span>
  3539. ${getLocaleString("LAZY_LOAD_IMAGES")}
  3540. <output id="lazyStartVal" for="lazyStart">
  3541. ${getUserSettings().lazyStart}
  3542. </output>
  3543. </span>
  3544. <input
  3545. type="range"
  3546. value="${getUserSettings().lazyStart}"
  3547. name="lazyStart"
  3548. id="lazyStart"
  3549. min="5"
  3550. max="100"
  3551. step="5"
  3552. oninput="lazyStartVal.value = this.value"
  3553. />
  3554. </div>
  3555. `;
  3556. const headerType = html`
  3557. <div class="ControlLabel headerType">
  3558. ${getLocaleString("HEADER_TYPE")}
  3559. <select id="headerType">
  3560. <option
  3561. value="hover"
  3562. ${getUserSettings().header === "hover" ? "selected" : ""}
  3563. >
  3564. ${getLocaleString("HEADER_HOVER")}
  3565. </option>
  3566. <option
  3567. value="scroll"
  3568. ${getUserSettings().header === "scroll" ? "selected" : ""}
  3569. >
  3570. ${getLocaleString("HEADER_SCROLL")}
  3571. </option>
  3572. <option
  3573. value="click"
  3574. ${getUserSettings().header === "click" ? "selected" : ""}
  3575. >
  3576. ${getLocaleString("HEADER_CLICK")}
  3577. </option>
  3578. <option
  3579. value="fixed"
  3580. ${getUserSettings().header === "fixed" ? "selected" : ""}
  3581. >
  3582. ${getLocaleString("HEADER_FIXED")}
  3583. </option>
  3584. <option
  3585. value="simple"
  3586. ${getUserSettings().header === "simple" ? "selected" : ""}
  3587. >
  3588. ${getLocaleString("HEADER_SIMPLE")}
  3589. </option>
  3590. </select>
  3591. </div>
  3592. `;
  3593. const checkboxOptions = html`
  3594. <div class="ControlLabel verticalSeparator">
  3595. ${getLocaleString("VERTICAL_SEPARATOR")}
  3596. <input
  3597. type="checkbox"
  3598. value="true"
  3599. name="verticalSeparator"
  3600. id="verticalSeparator"
  3601. ${getUserSettings().verticalSeparator ? "checked" : ""}
  3602. />
  3603. </div>
  3604. <div class="ControlLabel fitIfOversize">
  3605. ${getLocaleString("FIT_WIDTH_OVERSIZED")}
  3606. <input
  3607. type="checkbox"
  3608. value="true"
  3609. name="fitIfOversize"
  3610. id="fitIfOversize"
  3611. ${getUserSettings().fitWidthIfOversize ? "checked" : ""}
  3612. />
  3613. </div>
  3614. <div class="ControlLabel showThumbnails">
  3615. ${getLocaleString("SHOW_THUMBNAILS")}
  3616. <input
  3617. type="checkbox"
  3618. value="true"
  3619. name="showThumbnails"
  3620. id="showThumbnails"
  3621. ${getUserSettings().showThumbnails ? "checked" : ""}
  3622. />
  3623. </div>
  3624. <div class="ControlLabel enableComments">
  3625. ${getLocaleString("ENABLE_COMMENTS")}
  3626. <input
  3627. type="checkbox"
  3628. value="true"
  3629. name="enableComments"
  3630. id="enableComments"
  3631. ${getUserSettings().enableComments ? "checked" : ""}
  3632. />
  3633. </div>
  3634. <div class="ControlLabel lazyLoadImages">
  3635. ${getLocaleString("LAZY_LOAD_IMAGES_ENABLE")}
  3636. <input
  3637. type="checkbox"
  3638. value="true"
  3639. name="lazyLoadImages"
  3640. id="lazyLoadImages"
  3641. ${getUserSettings().lazyLoadImages ? "checked" : ""}
  3642. />
  3643. </div>
  3644. ${lazyLoad$1}
  3645. <div class="ControlLabel downloadZip">
  3646. ${getLocaleString("DOWNLOAD_IMAGES")}
  3647. <input
  3648. type="checkbox"
  3649. value="false"
  3650. name="downloadZip"
  3651. id="downloadZip"
  3652. ${getUserSettings().downloadZip ? "checked" : ""}
  3653. />
  3654. </div>
  3655. <div class="ControlLabel hidePageControls">
  3656. ${getLocaleString("HIDE_CONTROLS")}
  3657. <input
  3658. type="checkbox"
  3659. value="false"
  3660. name="hidePageControls"
  3661. id="hidePageControls"
  3662. ${getUserSettings().hidePageControls ? "checked" : ""}
  3663. />
  3664. </div>
  3665. `;
  3666. const autoScroll = html`
  3667. <div class="ControlLabel autoScroll">
  3668. <span>
  3669. ${getLocaleString("AUTO_SCROLL_HEIGHT")}
  3670. <output id="scrollHeightVal" for="scrollHeight">
  3671. ${getUserSettings().scrollHeight} </output
  3672. >px
  3673. </span>
  3674. <input
  3675. type="range"
  3676. value="${getUserSettings().scrollHeight}"
  3677. name="scrollHeight"
  3678. id="scrollHeight"
  3679. min="1"
  3680. max="100"
  3681. step="1"
  3682. oninput="scrollHeightVal.value = this.value"
  3683. />
  3684. </div>
  3685. `;
  3686. const SettingsPanel = () => html`
  3687. <div id="SettingsPanel" class="panel">
  3688. <h2>${getLocaleString("SETTINGS")}</h2>
  3689. <button
  3690. id="CloseSettings"
  3691. class="closeButton"
  3692. title="${getLocaleString("CLOSE")}"
  3693. >
  3694. ${IconX}
  3695. </button>
  3696. <button id="ResetSettings" class="ControlButton">
  3697. ${getLocaleString("BUTTON_RESET_SETTINGS")}
  3698. </button>
  3699. ${language} ${theme} ${loadMode} ${loadSpeed} ${defaultZoomMode}
  3700. ${defaultZoom} ${minZoom} ${zoomStep} ${viewMode$1} ${checkboxOptions}
  3701. ${headerType} ${autoScroll}
  3702. </div>
  3703. `;
  3704.  
  3705. const keybindList = () =>
  3706. Object.keys(getUserSettings().keybinds).map((kb) => {
  3707. const keys = getUserSettings().keybinds[kb]?.length
  3708. ? getUserSettings()
  3709. .keybinds[kb]?.map((key) => html`<kbd class="dark">${key}</kbd>`)
  3710. .join(" / ")
  3711. : "";
  3712. return html`<span>${getLocaleString(kb)}:</span> <span>${keys}</span>`;
  3713. });
  3714. const keybindEditor = () =>
  3715. Object.keys(getUserSettings().keybinds)
  3716. .map(
  3717. // Language=html
  3718. (kb) =>
  3719. html`<label for="${kb}">${getLocaleString(kb)}:</label>
  3720. <input
  3721. type="text"
  3722. class="KeybindInput"
  3723. id="${kb}"
  3724. name="${kb}"
  3725. value="${getUserSettings().keybinds[kb]?.join(" , ") ?? ""}"
  3726. />`,
  3727. )
  3728. .concat(
  3729. html`<div id="HotKeysRules">${getLocaleString("KEYBIND_RULES")}</div>`,
  3730. );
  3731. const KeybindingsPanel = () => html`
  3732. <div id="KeybindingsPanel" class="panel">
  3733. <h2>${getLocaleString("KEYBINDINGS")}</h2>
  3734. <button
  3735. id="CloseKeybindings"
  3736. class="closeButton"
  3737. title="${getLocaleString("CLOSE")}"
  3738. >
  3739. ${IconX}
  3740. </button>
  3741. <div class="controls">
  3742. <button
  3743. id="EditKeybindings"
  3744. class="ControlButton"
  3745. type="button"
  3746. title="${getLocaleString("EDIT_KEYBINDS")}"
  3747. >
  3748. ${IconPencil} ${getLocaleString("BUTTON_EDIT")}
  3749. </button>
  3750. <button
  3751. id="SaveKeybindings"
  3752. class="ControlButton hidden"
  3753. type="button"
  3754. title="${getLocaleString("SAVE_KEYBINDS")}"
  3755. >
  3756. ${IconDeviceFloppy} ${getLocaleString("BUTTON_SAVE")}
  3757. </button>
  3758. </div>
  3759. <div id="KeybindingsList">${keybindList().join("\n")}</div>
  3760. </div>
  3761. `;
  3762.  
  3763. function indexList(repeat, begin = 1) {
  3764. return Array(repeat)
  3765. .fill(0)
  3766. .map((_, i) => i + 1)
  3767. .filter((i) => i >= begin);
  3768. }
  3769.  
  3770. const ThumbnailsPanel = (manga) => html`
  3771. <nav
  3772. id="Navigation"
  3773. class="panel ${getUserSettings().showThumbnails ? "" : "disabled"}"
  3774. >
  3775. <div id="NavigationCounters" class="ControlLabel">
  3776. ${IconCategory}
  3777. <i>0</i> /
  3778. <b
  3779. >${manga.begin > 1 ? manga.pages - (manga.begin - 1) : manga.pages}</b
  3780. >
  3781. ${getLocaleString("PAGES_LOADED")}
  3782. </div>
  3783. <div id="Thumbnails">
  3784. ${indexList(manga.pages, manga.begin)
  3785. .map(
  3786. (index) => html`
  3787. <div id="Thumbnail${index}" class="Thumbnail">
  3788. <img
  3789. id="ThumbnailImg${index}"
  3790. alt=""
  3791. class="ThumbnailImg"
  3792. src=""
  3793. />
  3794. <span class="ThumbnailIndex">${index}</span>
  3795. </div>
  3796. `,
  3797. )
  3798. .join("")}
  3799. </div>
  3800. </nav>
  3801. `;
  3802.  
  3803. function isEmpty(value) {
  3804. return (
  3805. value === null || // Check for null
  3806. typeof value === "undefined" ||
  3807. value === void 0 || // Check for undefined
  3808. (typeof value === "string" && value === "") || // Check for empty string
  3809. (Array.isArray(value) && value.length === 0) || // Check for empty array
  3810. (typeof value === "object" && Object.keys(value).length === 0)
  3811. );
  3812. }
  3813. function isNothing(value) {
  3814. const isEmptyObject = (a) => {
  3815. if (!Array.isArray(a)) {
  3816. const hasNonempty = Object.keys(a).some(
  3817. (element) => !isNothing(a[element]),
  3818. );
  3819. return hasNonempty ? false : isEmptyObject(Object.keys(a));
  3820. }
  3821. return !a.some(
  3822. (element) => element instanceof Promise || !isNothing(element),
  3823. );
  3824. };
  3825. return (
  3826. !value ||
  3827. value === 0 ||
  3828. isEmpty(value) ||
  3829. (typeof value === "object" && isEmptyObject(value))
  3830. );
  3831. }
  3832.  
  3833. const listBookmarks = () => {
  3834. if (isEmpty(getUserSettings().bookmarks)) {
  3835. return [getLocaleString("LIST_EMPTY")];
  3836. }
  3837. return getUserSettings().bookmarks.map(
  3838. (mark, index) => html`
  3839. <div id="Bookmark${index + 1}" class="BookmarkItem">
  3840. <span class="bookmarkColumnLarge">
  3841. <span class="bookmarkName">${mark.name}</span>
  3842. <br />
  3843. <a class="bookmarkURl" href="${mark.url}" target="_blank"
  3844. >${mark.url}</a
  3845. >
  3846. </span>
  3847. <span class="bookmarkColumnSmall">
  3848. <span class="bookmarkDate">
  3849. ${new Date(mark.date).toISOString().slice(0, 10)}</span
  3850. >
  3851. <br />
  3852. <span class="bookmarkPage">Page: ${mark.page}</span>
  3853. </span>
  3854. <span class="bookmarkFunctions">
  3855. <a class="" href="${mark.url}" target="_blank">
  3856. <button
  3857. class="ControlButton open"
  3858. title="Open Bookmark"
  3859. type="button"
  3860. >
  3861. ${IconExternalLink}
  3862. </button>
  3863. </a>
  3864. <button
  3865. class="ControlButton erase"
  3866. title="Delete Bookmark"
  3867. type="button"
  3868. value="${mark.url}"
  3869. >
  3870. ${IconTrash}
  3871. </button>
  3872. </span>
  3873. </div>
  3874. `,
  3875. );
  3876. };
  3877. const BookmarkPanel = () => html`
  3878. <div id="BookmarksPanel" class="panel">
  3879. <button
  3880. id="CloseBookmarks"
  3881. class="closeButton"
  3882. title="${getLocaleString("CLOSE")}"
  3883. >
  3884. ${IconX}
  3885. </button>
  3886. <button
  3887. class="Bookmark simpleButton"
  3888. title="${getLocaleString("BOOKMARK")}"
  3889. >
  3890. ${IconBookmark} ${IconBookmarkOff}
  3891. </button>
  3892. <h2>${getLocaleString("BOOKMARKS")}</h2>
  3893. <div id="BookmarksList"></div>
  3894. </div>
  3895. `;
  3896. function reloadBookmarks() {
  3897. const list = document.getElementById("BookmarksList");
  3898. if (list) {
  3899. list.innerHTML = listBookmarks().join("");
  3900. }
  3901. }
  3902.  
  3903. const listOptions = (times, begin) =>
  3904. indexList(times, begin).map(
  3905. (index) => html` <option value="${index}">${index}</option>`,
  3906. );
  3907. const Header = (manga) => html`
  3908. <header id="Header" class="${getUserSettings().header} headroom-top">
  3909. <aside id="GlobalFunctions">
  3910. <span>
  3911. <button
  3912. id="enlarge"
  3913. title="${getLocaleString("ENLARGE")}"
  3914. class="ControlButton"
  3915. >
  3916. ${IconZoomInArea}
  3917. </button>
  3918. <button
  3919. id="restore"
  3920. title="${getLocaleString("RESTORE")}"
  3921. class="ControlButton"
  3922. >
  3923. ${IconZoomPan}
  3924. </button>
  3925. <button
  3926. id="reduce"
  3927. title="${getLocaleString("REDUCE")}"
  3928. class="ControlButton"
  3929. >
  3930. ${IconZoomOutArea}
  3931. </button>
  3932. <button
  3933. id="fitWidth"
  3934. title="${getLocaleString("FIT_WIDTH")}"
  3935. class="ControlButton"
  3936. >
  3937. ${IconArrowAutofitWidth}
  3938. </button>
  3939. <button
  3940. id="fitHeight"
  3941. title="${getLocaleString("FIT_HEIGHT")}"
  3942. class="ControlButton"
  3943. >
  3944. ${IconArrowAutofitHeight}
  3945. </button>
  3946. <button
  3947. id="keybindings"
  3948. title="${getLocaleString("KEYBINDINGS")}"
  3949. class="ControlButton"
  3950. >
  3951. ${IconKeyboard}
  3952. </button>
  3953. <button
  3954. id="AutoScroll"
  3955. title="${getLocaleString("SCROLL_START")}"
  3956. class="ControlButton phones"
  3957. >
  3958. ${IconPlayerPlay} ${IconPlayerPause}
  3959. </button>
  3960. </span>
  3961. <span>
  3962. <button
  3963. id="ltrMode"
  3964. title="${getLocaleString("VIEW_MODE_LEFT")}"
  3965. class="ControlButton"
  3966. >
  3967. ${IconArrowAutofitRight}
  3968. </button>
  3969. <button
  3970. id="verticalMode"
  3971. title="${getLocaleString("VIEW_MODE_VERTICAL")}"
  3972. class="ControlButton tablets"
  3973. >
  3974. ${IconArrowAutofitDown}
  3975. </button>
  3976. <button
  3977. id="webComic"
  3978. title="${getLocaleString("VIEW_MODE_WEBCOMIC")}"
  3979. class="ControlButton tablets"
  3980. >
  3981. ${IconSpacingVertical}
  3982. </button>
  3983. <button
  3984. id="rtlMode"
  3985. title="${getLocaleString("VIEW_MODE_RIGHT")}"
  3986. class="ControlButton"
  3987. >
  3988. ${IconArrowAutofitLeft}
  3989. </button>
  3990. <button
  3991. id="pageControls"
  3992. title="${getLocaleString("TOGGLE_CONTROLS")}"
  3993. class="ControlButton tablets"
  3994. >
  3995. ${IconListNumbers}
  3996. </button>
  3997. <button
  3998. id="bookmarks"
  3999. title="${getLocaleString("BOOKMARKS")}"
  4000. class="ControlButton tablets"
  4001. >
  4002. ${IconBookmarks}
  4003. </button>
  4004. <button
  4005. id="settings"
  4006. title="${getLocaleString("SETTINGS")}"
  4007. class="ControlButton tablets phones"
  4008. >
  4009. ${IconSettings}
  4010. </button>
  4011. </span>
  4012. <span id="ZoomSlider">
  4013. <output id="ZoomVal" class="RangeValue" for="Zoom">
  4014. ${getUserSettings().zoomMode === "percent"
  4015. ? `${getUserSettings().defaultZoom}%`
  4016. : getUserSettings().zoomMode}
  4017. </output>
  4018. <input
  4019. type="range"
  4020. value="${getUserSettings().defaultZoom}"
  4021. name="Zoom"
  4022. id="Zoom"
  4023. min="1"
  4024. max="200"
  4025. />
  4026. </span>
  4027. </aside>
  4028. <div class="ViewerTitle">
  4029. <h1 id="MangaTitle">${manga.title}</h1>
  4030. <a id="series" href="${manga.series ?? ""}">
  4031. (${getLocaleString("RETURN_CHAPTER_LIST")})
  4032. </a>
  4033. </div>
  4034. <nav id="ChapterNavigation">
  4035. <div id="Counters" class="ControlLabel">
  4036. ${getLocaleString("PAGES_LOADED")}:
  4037. <i>0</i> /
  4038. <b
  4039. >${manga.begin > 1
  4040. ? manga.pages - (manga.begin - 1)
  4041. : manga.pages}</b
  4042. >
  4043. <span class="ControlLabel"> ${getLocaleString("GO_TO_PAGE")}: </span>
  4044. <select id="gotoPage">
  4045. <option selected>#</option>
  4046. ${listOptions(manga.pages, manga.begin).join("")}
  4047. </select>
  4048. </div>
  4049. <div id="ChapterControl" class="ChapterControl">
  4050. <span>
  4051. <button
  4052. id="CommentsButton"
  4053. class="NavigationControlButton ControlButton ${manga.comments
  4054. ? ""
  4055. : "disabled"}"
  4056. title="${getLocaleString("DISPLAY_COMMENTS")}"
  4057. >
  4058. ${IconMessage} ${getLocaleString("DISPLAY_COMMENTS")}
  4059. </button>
  4060. <button
  4061. id="download"
  4062. class="NavigationControlButton ControlButton disabled"
  4063. type="button"
  4064. title="${getLocaleString("DOWNLOAD_ZIP")}"
  4065. >
  4066. ${IconFileDownload} ${IconLoader2}
  4067. ${getLocaleString("BUTTON_DOWNLOAD")}
  4068. </button></span
  4069. >
  4070. <span>
  4071. <a
  4072. id="prev"
  4073. class="NavigationControlButton ControlButton"
  4074. type="button"
  4075. href="${manga.prev ?? ""}"
  4076. title="${getLocaleString("PREVIOUS_CHAPTER")}"
  4077. >
  4078. ${IconArrowBigLeft} ${getLocaleString("BUTTON_PREVIOUS")}
  4079. </a>
  4080. <a
  4081. id="next"
  4082. class="NavigationControlButton ControlButton"
  4083. type="button"
  4084. href="${manga.next ?? ""}"
  4085. title="${getLocaleString("NEXT_CHAPTER")}"
  4086. >
  4087. ${getLocaleString("BUTTON_NEXT")} ${IconArrowBigRight}
  4088. </a>
  4089. </span>
  4090. </div>
  4091. </nav>
  4092. </header>
  4093. `;
  4094.  
  4095. const listPages = (times, begin) =>
  4096. indexList(times, begin).map(
  4097. // Language=html
  4098. (index) => html`
  4099. <div id="Page${index}" class="MangaPage">
  4100. <div class="PageFunctions">
  4101. <button
  4102. class="Bookmark ControlButton"
  4103. title="${getLocaleString("BOOKMARK")}"
  4104. >
  4105. ${IconBookmark} ${IconBookmarkOff}
  4106. </button>
  4107. <button
  4108. class="ZoomIn ControlButton"
  4109. title="${getLocaleString("ZOOM_IN")}"
  4110. >
  4111. ${IconZoomIn}
  4112. </button>
  4113. <button
  4114. class="ZoomRestore ControlButton"
  4115. title="${getLocaleString("ZOOM_RESET")}"
  4116. >
  4117. ${IconZoomCancel}
  4118. </button>
  4119. <button
  4120. class="ZoomOut ControlButton"
  4121. title="${getLocaleString("ZOOM_OUT")}"
  4122. >
  4123. ${IconZoomOut}
  4124. </button>
  4125. <button
  4126. class="ZoomWidth ControlButton"
  4127. title="${getLocaleString("ZOOM_WIDTH")}"
  4128. >
  4129. ${IconArrowAutofitWidth}
  4130. </button>
  4131. <button
  4132. class="ZoomHeight ControlButton"
  4133. title="${getLocaleString("ZOOM_HEIGHT")}"
  4134. >
  4135. ${IconArrowAutofitHeight}
  4136. </button>
  4137. <button
  4138. class="Hide ControlButton"
  4139. title="${getLocaleString("HIDE")}"
  4140. >
  4141. ${IconEye} ${IconEyeOff}
  4142. </button>
  4143. <button
  4144. class="Reload ControlButton"
  4145. title="${getLocaleString("RELOAD")}"
  4146. >
  4147. ${IconRefresh}
  4148. </button>
  4149. <span class="PageIndex">${index}</span>
  4150. </div>
  4151. <div class="PageContent">
  4152. <img id="PageImg${index}" alt="" class="PageImg" />
  4153. </div>
  4154. </div>
  4155. <div class="separator">
  4156. [ ${index === times ? getLocaleString("END") : `${index} / ${times}`}
  4157. ]
  4158. </div>
  4159. `,
  4160. );
  4161.  
  4162. const Reader = (manga) => html`
  4163. <main
  4164. id="Chapter"
  4165. class="${getUserSettings().fitWidthIfOversize ? "fitWidthIfOversize" : ""}
  4166. ${getUserSettings().verticalSeparator ? "separator" : ""}
  4167. ${getUserSettings().viewMode}"
  4168. >
  4169. ${listPages(manga.pages, manga.begin).join("")}
  4170. </main>
  4171. `;
  4172.  
  4173. const commentsPanel = () => html`
  4174. <div id="CommentsPanel" class="panel">
  4175. <button
  4176. id="CloseComments"
  4177. class="closeButton"
  4178. title="${getLocaleString("CLOSE")}"
  4179. >
  4180. ${IconX}
  4181. </button>
  4182. <h2>${getLocaleString("COMMENTS")}</h2>
  4183. <div id="CommentsArea" class="${getUserSettings().colorScheme}"></div>
  4184. <button id="CommentsColorScheme" class="simpleButton ColorScheme">
  4185. ${IconSun} ${IconMoon}
  4186. </button>
  4187. </div>
  4188. `;
  4189.  
  4190. function scrollToElement(ele) {
  4191. const chapter = document.querySelector("#Chapter");
  4192. if (
  4193. chapter?.classList.contains("FluidLTR") ||
  4194. chapter?.classList.contains("FluidRTL")
  4195. ) {
  4196. chapter?.scroll(ele?.offsetLeft ?? 0, ele?.offsetTop ?? 0);
  4197. } else {
  4198. window?.scroll(ele?.offsetLeft ?? 0, ele?.offsetTop ?? 0);
  4199. }
  4200. }
  4201. function addEvent(ev, fn) {
  4202. return (elem) => elem.addEventListener(ev, fn);
  4203. }
  4204. function transformScrollToHorizontal(event) {
  4205. if (!event.deltaY) {
  4206. return;
  4207. }
  4208. event.currentTarget.scrollLeft += event.deltaY + event.deltaX;
  4209. event.preventDefault();
  4210. }
  4211. function transformScrollToHorizontalReverse(event) {
  4212. if (!event.deltaY) {
  4213. return;
  4214. }
  4215. event.currentTarget.scrollLeft -= event.deltaY + event.deltaX;
  4216. event.preventDefault();
  4217. }
  4218.  
  4219. function buttonBookmarksClose() {
  4220. document.querySelector("#BookmarksPanel")?.classList.remove("visible");
  4221. document.querySelector("#Overlay")?.classList.remove("visible");
  4222. }
  4223. function removeURLBookmark(url = window.location.href) {
  4224. if (!isNothing(isBookmarked(url))) {
  4225. logScript(`Bookmark Removed ${url}`);
  4226. updateSettings({
  4227. bookmarks: getUserSettings().bookmarks.filter((el) => el.url !== url),
  4228. });
  4229. if (url === window.location.href) {
  4230. document
  4231. .querySelector("#MangaOnlineViewer")
  4232. ?.classList.remove("bookmarked");
  4233. }
  4234. }
  4235. }
  4236. function buttonEraseBookmarks(event) {
  4237. const target = event.currentTarget.value;
  4238. logScript(`Bookmark Removed ${target}`);
  4239. Swal.fire({
  4240. title: getLocaleString("BOOKMARK_REMOVED"),
  4241. timer: 1e4,
  4242. icon: "error",
  4243. });
  4244. removeURLBookmark(target);
  4245. reloadBookmarks();
  4246. document
  4247. .querySelectorAll(".bookmarkFunctions .erase")
  4248. ?.forEach(addEvent("click", buttonEraseBookmarks));
  4249. }
  4250. function buttonBookmarksOpen() {
  4251. reloadBookmarks();
  4252. document
  4253. .querySelectorAll(".bookmarkFunctions .erase")
  4254. ?.forEach(addEvent("click", buttonEraseBookmarks));
  4255. document.querySelector("#BookmarksPanel")?.classList.add("visible");
  4256. document.querySelector("#Overlay")?.classList.add("visible");
  4257. }
  4258. function buttonBookmark(event) {
  4259. document
  4260. .querySelector("#MangaOnlineViewer")
  4261. ?.classList.toggle("bookmarked");
  4262. const pagesDistance = [...document.querySelectorAll(".MangaPage")].map(
  4263. (element) => Math.abs(element.offsetTop - window.scrollY),
  4264. );
  4265. const currentPage = parseInt(
  4266. event.currentTarget.parentElement?.querySelector(".PageIndex")
  4267. ?.textContent ?? "0",
  4268. 10,
  4269. );
  4270. const num =
  4271. currentPage || pagesDistance.indexOf(Math.min(...pagesDistance)) + 1;
  4272. const mark = {
  4273. name:
  4274. document
  4275. .querySelector("title")
  4276. ?.textContent?.trim()
  4277. .replace(/^\(\d+%\) */, "") ?? "",
  4278. url: window.location.href,
  4279. page: num,
  4280. date: /* @__PURE__ */ new Date().toISOString().slice(0, 10),
  4281. };
  4282. if (isBookmarked(mark.url)) {
  4283. updateSettings({
  4284. bookmarks: getUserSettings().bookmarks.filter(
  4285. (el) => el.url !== mark.url,
  4286. ),
  4287. });
  4288. Swal.fire({
  4289. title: getLocaleString("BOOKMARK_REMOVED"),
  4290. timer: 1e4,
  4291. icon: "error",
  4292. });
  4293. } else {
  4294. updateSettings({ bookmarks: [...getUserSettings().bookmarks, mark] });
  4295. Swal.fire({
  4296. title: getLocaleString("BOOKMARK_SAVED"),
  4297. html: getLocaleString("BOOKMARK_SAVED").replace(
  4298. "##NUM##",
  4299. num.toString(),
  4300. ),
  4301. icon: "success",
  4302. });
  4303. }
  4304. reloadBookmarks();
  4305. document
  4306. .querySelectorAll(".bookmarkFunctions .erase")
  4307. ?.forEach(addEvent("click", buttonEraseBookmarks));
  4308. }
  4309. function bookmarks() {
  4310. document
  4311. .querySelector("#bookmarks")
  4312. ?.addEventListener("click", buttonBookmarksOpen);
  4313. document
  4314. .querySelectorAll(".closeButton")
  4315. ?.forEach(addEvent("click", buttonBookmarksClose));
  4316. document
  4317. .querySelector("#Overlay")
  4318. ?.addEventListener("click", buttonBookmarksClose);
  4319. document
  4320. .querySelectorAll(".bookmarkFunctions .erase")
  4321. ?.forEach(addEvent("click", buttonEraseBookmarks));
  4322. document
  4323. .querySelectorAll(".Bookmark")
  4324. ?.forEach(addEvent("click", buttonBookmark));
  4325. document
  4326. .querySelector(".AddBookmark")
  4327. ?.addEventListener("click", buttonBookmark);
  4328. }
  4329.  
  4330. var commonjsGlobal =
  4331. typeof globalThis !== "undefined"
  4332. ? globalThis
  4333. : typeof window !== "undefined"
  4334. ? window
  4335. : typeof global !== "undefined"
  4336. ? global
  4337. : typeof self !== "undefined"
  4338. ? self
  4339. : {};
  4340.  
  4341. var FileSaver_min = { exports: {} };
  4342.  
  4343. (function (module, exports) {
  4344. (function (a, b) {
  4345. b();
  4346. })(commonjsGlobal, function () {
  4347. function b(a, b) {
  4348. return (
  4349. "undefined" == typeof b
  4350. ? (b = { autoBom: !1 })
  4351. : "object" != typeof b &&
  4352. (console.warn(
  4353. "Deprecated: Expected third argument to be a object",
  4354. ),
  4355. (b = { autoBom: !b })),
  4356. b.autoBom &&
  4357. /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(
  4358. a.type,
  4359. )
  4360. ? new Blob(["\uFEFF", a], { type: a.type })
  4361. : a
  4362. );
  4363. }
  4364. function c(a, b, c) {
  4365. var d = new XMLHttpRequest();
  4366. d.open("GET", a),
  4367. (d.responseType = "blob"),
  4368. (d.onload = function () {
  4369. g(d.response, b, c);
  4370. }),
  4371. (d.onerror = function () {
  4372. console.error("could not download file");
  4373. }),
  4374. d.send();
  4375. }
  4376. function d(a) {
  4377. var b = new XMLHttpRequest();
  4378. b.open("HEAD", a, !1);
  4379. try {
  4380. b.send();
  4381. } catch (a) {}
  4382. return 200 <= b.status && 299 >= b.status;
  4383. }
  4384. function e(a) {
  4385. try {
  4386. a.dispatchEvent(new MouseEvent("click"));
  4387. } catch (c) {
  4388. var b = document.createEvent("MouseEvents");
  4389. b.initMouseEvent(
  4390. "click",
  4391. !0,
  4392. !0,
  4393. window,
  4394. 0,
  4395. 0,
  4396. 0,
  4397. 80,
  4398. 20,
  4399. !1,
  4400. !1,
  4401. !1,
  4402. !1,
  4403. 0,
  4404. null,
  4405. ),
  4406. a.dispatchEvent(b);
  4407. }
  4408. }
  4409. var f =
  4410. "object" == typeof window && window.window === window
  4411. ? window
  4412. : "object" == typeof self && self.self === self
  4413. ? self
  4414. : "object" == typeof commonjsGlobal &&
  4415. commonjsGlobal.global === commonjsGlobal
  4416. ? commonjsGlobal
  4417. : void 0,
  4418. a =
  4419. f.navigator &&
  4420. /Macintosh/.test(navigator.userAgent) &&
  4421. /AppleWebKit/.test(navigator.userAgent) &&
  4422. !/Safari/.test(navigator.userAgent),
  4423. g =
  4424. f.saveAs ||
  4425. ("object" != typeof window || window !== f
  4426. ? function () {}
  4427. : "download" in HTMLAnchorElement.prototype && !a
  4428. ? function (b, g, h) {
  4429. var i = f.URL || f.webkitURL,
  4430. j = document.createElement("a");
  4431. (g = g || b.name || "download"),
  4432. (j.download = g),
  4433. (j.rel = "noopener"),
  4434. "string" == typeof b
  4435. ? ((j.href = b),
  4436. j.origin === location.origin
  4437. ? e(j)
  4438. : d(j.href)
  4439. ? c(b, g, h)
  4440. : e(j, (j.target = "_blank")))
  4441. : ((j.href = i.createObjectURL(b)),
  4442. setTimeout(function () {
  4443. i.revokeObjectURL(j.href);
  4444. }, 4e4),
  4445. setTimeout(function () {
  4446. e(j);
  4447. }, 0));
  4448. }
  4449. : "msSaveOrOpenBlob" in navigator
  4450. ? function (f, g, h) {
  4451. if (((g = g || f.name || "download"), "string" != typeof f))
  4452. navigator.msSaveOrOpenBlob(b(f, h), g);
  4453. else if (d(f)) c(f, g, h);
  4454. else {
  4455. var i = document.createElement("a");
  4456. (i.href = f),
  4457. (i.target = "_blank"),
  4458. setTimeout(function () {
  4459. e(i);
  4460. });
  4461. }
  4462. }
  4463. : function (b, d, e, g) {
  4464. if (
  4465. ((g = g || open("", "_blank")),
  4466. g &&
  4467. (g.document.title = g.document.body.innerText =
  4468. "downloading..."),
  4469. "string" == typeof b)
  4470. )
  4471. return c(b, d, e);
  4472. var h = "application/octet-stream" === b.type,
  4473. i = /constructor/i.test(f.HTMLElement) || f.safari,
  4474. j = /CriOS\/[\d]+/.test(navigator.userAgent);
  4475. if (
  4476. (j || (h && i) || a) &&
  4477. "undefined" != typeof FileReader
  4478. ) {
  4479. var k = new FileReader();
  4480. (k.onloadend = function () {
  4481. var a = k.result;
  4482. (a = j
  4483. ? a
  4484. : a.replace(/^data:[^;]*;/, "data:attachment/file;")),
  4485. g ? (g.location.href = a) : (location = a),
  4486. (g = null);
  4487. }),
  4488. k.readAsDataURL(b);
  4489. } else {
  4490. var l = f.URL || f.webkitURL,
  4491. m = l.createObjectURL(b);
  4492. g ? (g.location = m) : (location.href = m),
  4493. (g = null),
  4494. setTimeout(function () {
  4495. l.revokeObjectURL(m);
  4496. }, 4e4);
  4497. }
  4498. });
  4499. (f.saveAs = g.saveAs = g), (module.exports = g);
  4500. });
  4501. })(FileSaver_min);
  4502.  
  4503. var FileSaver_minExports = FileSaver_min.exports;
  4504.  
  4505. const objectURLRegex = /^blob:(.+?)\/(.+)$/;
  4506. function getDataFromBase64(src) {
  4507. return src.slice(src.indexOf(";base64,") + 8);
  4508. }
  4509. function isBase64ImageUrl(imageUrl) {
  4510. const base64Pattern = /^data:image\/(png|jpg|jpeg|gif);base64,/;
  4511. return base64Pattern.test(imageUrl);
  4512. }
  4513. function isObjectURL(url) {
  4514. return objectURLRegex.test(url);
  4515. }
  4516. function getExtension(url) {
  4517. const parts = url.split("?");
  4518. const filename = parts[0].split("/").pop();
  4519. const extensionMatch = filename?.match(/\.[A-Za-z]{2,4}$/);
  4520. return extensionMatch ? extensionMatch[0].slice(1) : "";
  4521. }
  4522. const getExtensionBase64 = (base64) => {
  4523. const c = base64.substring(
  4524. base64.indexOf("/") + 1,
  4525. base64.indexOf(";base64"),
  4526. );
  4527. switch (c) {
  4528. case "/":
  4529. return "jpg";
  4530. case "R":
  4531. return "gif";
  4532. case "U":
  4533. return "webp";
  4534. case "i":
  4535. default:
  4536. return "png";
  4537. }
  4538. };
  4539.  
  4540. let zip;
  4541. const getFilename = (name, index, total, ext) =>
  4542. `${name}${(index + 1).toString().padStart(Math.floor(Math.log10(total)) + 1, "0")}.${ext.replace(
  4543. "jpeg",
  4544. "jpg",
  4545. )}`;
  4546. async function getImage(src) {
  4547. return new Promise((resolve, reject) => {
  4548. logScript(`Getting Image data: ${src}`);
  4549. GM_xmlhttpRequest({
  4550. method: "GET",
  4551. url: src,
  4552. headers: {
  4553. referer: window.location.host,
  4554. origin: window.location.host,
  4555. },
  4556. responseType: "blob",
  4557. onload(response) {
  4558. if (response.status !== 200) {
  4559. reject(response);
  4560. }
  4561. resolve(response);
  4562. },
  4563. });
  4564. });
  4565. }
  4566. async function getImageData(img, index, array) {
  4567. const src = img.getAttribute("src") ?? img.getAttribute("data-src") ?? "";
  4568. if (isObjectURL(src)) {
  4569. throw new Error("Image source unusable");
  4570. }
  4571. if (isBase64ImageUrl(src)) {
  4572. return Promise.resolve({
  4573. name: getFilename(
  4574. "Page-",
  4575. index,
  4576. array.length,
  4577. getExtensionBase64(src),
  4578. ),
  4579. data: getDataFromBase64(src) ?? "",
  4580. });
  4581. }
  4582. return new Promise((resolve) => {
  4583. getImage(src)
  4584. .then((res) => {
  4585. resolve({
  4586. name: getFilename("Page-", index, array.length, getExtension(src)),
  4587. data: res.response,
  4588. });
  4589. })
  4590. .catch(logScriptC("Image not Available"));
  4591. });
  4592. }
  4593. function addZip(img) {
  4594. logScript(`${img.name} Added to Zip from Base64 Image`);
  4595. zip.file(img.name, img.data, {
  4596. base64: true,
  4597. createFolders: true,
  4598. compression: "DEFLATE",
  4599. });
  4600. }
  4601. async function generateZip() {
  4602. zip = new JSZip();
  4603. const images = [...document.querySelectorAll(".PageImg")];
  4604. Promise.all(images.map(getImageData))
  4605. .then((data) => {
  4606. data.forEach(addZip);
  4607. logScript("Generating Zip");
  4608. zip
  4609. .generateAsync(
  4610. {
  4611. type: "blob",
  4612. },
  4613. // LogScript, progress
  4614. )
  4615. .then((content) => {
  4616. logScript("Download Ready");
  4617. const zipName = `${document.querySelector("#MangaTitle")?.textContent?.trim()}.zip`;
  4618. FileSaver_minExports.saveAs(content, zipName, { autoBom: false });
  4619. document.getElementById("download")?.classList.remove("loading");
  4620. })
  4621. .catch(logScript);
  4622. })
  4623. .catch((msg) =>
  4624. logScript("One or more images couldn't be Downloaded", msg),
  4625. );
  4626. }
  4627.  
  4628. function buttonStartDownload(event) {
  4629. const button = event.currentTarget;
  4630. if (button.classList.contains("loading")) {
  4631. return;
  4632. }
  4633. logScript("Downloading Chapter");
  4634. button.classList.add("loading");
  4635. generateZip().catch((err) => logScript("Error downloading chapter", err));
  4636. }
  4637. function buttonGlobalHideImageControls() {
  4638. document
  4639. .querySelector("#MangaOnlineViewer")
  4640. ?.classList.toggle("hideControls");
  4641. }
  4642. function buttonRedirectURL(event) {
  4643. const element = event.target;
  4644. const url = element.getAttribute("value") ?? element.getAttribute("href");
  4645. if (event.button !== 1 && !event.ctrlKey) {
  4646. if (url && url !== "#") {
  4647. window.location.href = url;
  4648. } else if (element.id === "series") {
  4649. window.history.back();
  4650. }
  4651. }
  4652. }
  4653. function buttonCommentsOpen() {
  4654. document.querySelector("#CommentsPanel")?.classList.add("visible");
  4655. document.querySelector("#Overlay")?.classList.add("visible");
  4656. }
  4657. function buttonCommentsClose() {
  4658. document.querySelector("#CommentsPanel")?.classList.remove("visible");
  4659. document.querySelector("#Overlay")?.classList.remove("visible");
  4660. }
  4661. function changeCommentsColor() {
  4662. const elem = document.querySelector("#CommentsArea");
  4663. elem?.classList.toggle("light");
  4664. elem?.classList.toggle("dark");
  4665. }
  4666. function globals() {
  4667. document
  4668. .querySelector("#download")
  4669. ?.addEventListener("click", buttonStartDownload);
  4670. document
  4671. .querySelector("#pageControls")
  4672. ?.addEventListener("click", buttonGlobalHideImageControls);
  4673. document
  4674. .querySelector("#next")
  4675. ?.addEventListener("click", buttonRedirectURL);
  4676. document
  4677. .querySelector("#prev")
  4678. ?.addEventListener("click", buttonRedirectURL);
  4679. document
  4680. .querySelector("#series")
  4681. ?.addEventListener("click", buttonRedirectURL);
  4682. document
  4683. .querySelector("#CommentsButton")
  4684. ?.addEventListener("click", buttonCommentsOpen);
  4685. document
  4686. .querySelector("#CommentsColorScheme")
  4687. ?.addEventListener("click", changeCommentsColor);
  4688. document
  4689. .querySelectorAll(".closeButton")
  4690. ?.forEach(addEvent("click", buttonCommentsClose));
  4691. document
  4692. .querySelector("#Overlay")
  4693. ?.addEventListener("click", buttonCommentsClose);
  4694. }
  4695.  
  4696. function headroom(showEnd = 0) {
  4697. let prevOffset = 0;
  4698. const setScrollDirection = (classSuffix) => {
  4699. const header = document.querySelector("#Header");
  4700. header.classList.remove(
  4701. "headroom-end",
  4702. "headroom-hide",
  4703. "headroom-show",
  4704. "headroom-top",
  4705. );
  4706. if (classSuffix) {
  4707. header.classList.add(`headroom-${classSuffix}`);
  4708. }
  4709. };
  4710. function toggleScrollDirection() {
  4711. const { scrollY } = window;
  4712. if (
  4713. showEnd &&
  4714. getUserSettings().zoomMode !== "height" &&
  4715. scrollY + window.innerHeight + showEnd > document.body.scrollHeight
  4716. ) {
  4717. setScrollDirection("end");
  4718. } else if (scrollY > prevOffset && scrollY > 50) {
  4719. setScrollDirection("hide");
  4720. } else if (scrollY < prevOffset && scrollY > 50) {
  4721. setScrollDirection("show");
  4722. } else if (scrollY <= 100) {
  4723. setScrollDirection("top");
  4724. } else {
  4725. setScrollDirection("");
  4726. }
  4727. prevOffset = scrollY;
  4728. }
  4729. window.addEventListener("scroll", _.debounce(toggleScrollDirection, 50));
  4730. }
  4731.  
  4732. const doClick = (selector) =>
  4733. document.querySelector(selector)?.dispatchEvent(new Event("click"));
  4734. function doScrolling(sign) {
  4735. const chapter = document.querySelector("#Chapter");
  4736. if (
  4737. chapter?.classList.contains("FluidLTR") ||
  4738. chapter?.classList.contains("FluidRTL")
  4739. ) {
  4740. chapter.scrollBy({
  4741. left:
  4742. (window.innerWidth / 2) *
  4743. sign *
  4744. (chapter?.classList.contains("FluidRTL") ? -1 : 1),
  4745. behavior: "smooth",
  4746. });
  4747. } else if (getUserSettings().zoomMode === "height") {
  4748. const pages = [...document.querySelectorAll(".MangaPage")];
  4749. const distance = pages.map((element) =>
  4750. Math.abs(element.offsetTop - window.scrollY),
  4751. );
  4752. const currentPage = _.indexOf(distance, _.min(distance));
  4753. const target = currentPage + sign;
  4754. const header = document.querySelector("#Header");
  4755. if (target < 0) {
  4756. scrollToElement(header);
  4757. } else if (target >= pages.length) {
  4758. header.classList.add("headroom-end");
  4759. } else {
  4760. logScript(
  4761. `Current array page ${currentPage},`,
  4762. `Scrolling to page ${target}`,
  4763. );
  4764. scrollToElement(pages.at(target));
  4765. }
  4766. } else {
  4767. window.scrollBy({
  4768. top: (sign * window.innerHeight) / 2,
  4769. behavior: "smooth",
  4770. });
  4771. }
  4772. }
  4773. const actions = {
  4774. SCROLL_UP() {
  4775. doScrolling(-1);
  4776. },
  4777. SCROLL_DOWN() {
  4778. doScrolling(1);
  4779. },
  4780. NEXT_CHAPTER() {
  4781. doClick("#next");
  4782. },
  4783. PREVIOUS_CHAPTER() {
  4784. doClick("#prev");
  4785. },
  4786. ENLARGE() {
  4787. doClick("#enlarge");
  4788. },
  4789. REDUCE() {
  4790. doClick("#reduce");
  4791. },
  4792. RESTORE() {
  4793. doClick("#restore");
  4794. },
  4795. FIT_WIDTH() {
  4796. doClick("#fitWidth");
  4797. },
  4798. FIT_HEIGHT() {
  4799. doClick("#fitHeight");
  4800. },
  4801. SETTINGS() {
  4802. doClick("#settings");
  4803. },
  4804. VIEW_MODE_WEBCOMIC() {
  4805. doClick("#webComic");
  4806. },
  4807. VIEW_MODE_VERTICAL() {
  4808. doClick("#verticalMode");
  4809. },
  4810. VIEW_MODE_LEFT() {
  4811. doClick("#rtlMode");
  4812. },
  4813. VIEW_MODE_RIGHT() {
  4814. doClick("#ltrMode");
  4815. },
  4816. SCROLL_START() {
  4817. doClick("#AutoScroll");
  4818. },
  4819. };
  4820. function keybindings() {
  4821. document.onkeydown = null;
  4822. document.onkeyup = null;
  4823. window.onkeydown = null;
  4824. window.onkeyup = null;
  4825. window.onload = null;
  4826. document.body.onload = null;
  4827. hotkeys.unbind();
  4828. Object.keys(getUserSettings().keybinds).forEach((key) => {
  4829. hotkeys(
  4830. getUserSettings().keybinds[key]?.join(",") ?? "",
  4831. _.throttle((event) => {
  4832. event.preventDefault();
  4833. event.stopImmediatePropagation();
  4834. event.stopPropagation();
  4835. actions[key]();
  4836. }, 100),
  4837. );
  4838. });
  4839. }
  4840.  
  4841. function isImagesManga(manga) {
  4842. return "listImages" in manga && !isNothing(manga.listImages);
  4843. }
  4844. function isPagesManga(manga) {
  4845. return "listPages" in manga && !isNothing(manga.listPages);
  4846. }
  4847. function isBruteforceManga(manga) {
  4848. return "bruteForce" in manga && !isNothing(manga.bruteForce);
  4849. }
  4850.  
  4851. async function fetchText(url, format) {
  4852. return new Promise((resolve) => {
  4853. logScript("Fetching page: ", url);
  4854. fetch(url)
  4855. .then(async (response) =>
  4856. // When the page is loaded convert it to text
  4857. response.text(),
  4858. )
  4859. .then((html) => {
  4860. const parser = new DOMParser();
  4861. const doc = parser.parseFromString(html, format);
  4862. resolve(doc);
  4863. })
  4864. .catch((err) => {
  4865. logScript("Failed to fetch page: ", err);
  4866. });
  4867. });
  4868. }
  4869. async function fetchHtml(url) {
  4870. return fetchText(url, "text/html");
  4871. }
  4872. async function getElementAttribute(url, selector, attribute) {
  4873. return fetchHtml(url).then((doc) =>
  4874. doc.querySelector(selector)?.getAttribute(attribute),
  4875. );
  4876. }
  4877.  
  4878. const settings = {
  4879. threshold: 2e3,
  4880. throttle: 500,
  4881. lazyAttribute: "data-src",
  4882. targetAttribute: "src",
  4883. };
  4884. let listElements = [];
  4885. let setup = false;
  4886. function filterInView(value) {
  4887. const { element } = value;
  4888. const rect = element.getBoundingClientRect();
  4889. const target =
  4890. (window.innerHeight || document.documentElement.clientHeight) +
  4891. settings.threshold;
  4892. return rect.top <= target || rect.bottom <= target;
  4893. }
  4894. async function showElement(item) {
  4895. let value = item.element.getAttribute(settings.lazyAttribute) ?? "";
  4896. if (value) {
  4897. if (
  4898. !isObjectURL(value) &&
  4899. !isBase64ImageUrl(value) &&
  4900. item.fetchOptions
  4901. ) {
  4902. value = await fetch(value, item.fetchOptions)
  4903. .then((resp) => resp.blob())
  4904. .then((blob) => blobUtil.blobToDataURL(blob));
  4905. }
  4906. item.element.setAttribute(settings.targetAttribute, value);
  4907. }
  4908. item.callback(item.element)?.catch(logScript);
  4909. }
  4910. function executeCheck() {
  4911. const inView = listElements.filter(filterInView);
  4912. listElements = listElements.filter((item) => !filterInView(item));
  4913. inView.forEach(showElement);
  4914. }
  4915. const observerEvent = _.throttle(executeCheck, settings.throttle);
  4916. function lazyLoad(element, callback, fetchOptions) {
  4917. if (!setup) {
  4918. window.addEventListener("scroll", observerEvent, {
  4919. passive: true,
  4920. });
  4921. window.addEventListener("touchmove", observerEvent, {
  4922. passive: true,
  4923. });
  4924. window.addEventListener("resize", observerEvent, {
  4925. passive: true,
  4926. });
  4927. setup = true;
  4928. }
  4929. listElements.push({ element, callback, fetchOptions });
  4930. observerEvent();
  4931. }
  4932.  
  4933. function applyZoom(
  4934. zoom = getUserSettings().zoomMode,
  4935. pages = ".PageContent img",
  4936. ) {
  4937. const pg = [...document.querySelectorAll(pages)];
  4938. pg.forEach((img) => {
  4939. img.removeAttribute("width");
  4940. img.removeAttribute("height");
  4941. img.removeAttribute("style");
  4942. if (zoom === "width") {
  4943. img.style.width = `${window.innerWidth}px`;
  4944. } else if (zoom === "height") {
  4945. const nextHeight =
  4946. window.innerHeight + (getUserSettings().showThumbnails ? -29 : 0);
  4947. img.style.height = `${nextHeight}px`;
  4948. img.style.minWidth = "unset";
  4949. } else if (zoom === "percent") {
  4950. img.style.width = `${img.naturalWidth * (getUserSettings().defaultZoom / 100)}px`;
  4951. } else if (zoom >= 0 && zoom !== 100) {
  4952. img.style.width = `${img.naturalWidth * (zoom / 100)}px`;
  4953. }
  4954. });
  4955. }
  4956. function invalidateImageCache(src, repeat) {
  4957. const url = src.replace(/[?&]cache=\d+$/, "");
  4958. const symbol = !url.includes("?") ? "?" : "&";
  4959. return `${url + symbol}cache=${repeat}`;
  4960. }
  4961. function getRepeatValue(src) {
  4962. let repeat = 1;
  4963. const cache = src?.match(/cache=(\d+)$/);
  4964. if (cache?.at(1)) {
  4965. repeat = parseInt(cache[1], 10) + 1;
  4966. }
  4967. return repeat;
  4968. }
  4969. function reloadImage(img) {
  4970. const src = img.getAttribute("src");
  4971. if (!src) {
  4972. return;
  4973. }
  4974. img.removeAttribute("src");
  4975. if (isBase64ImageUrl(src) || isObjectURL(src)) {
  4976. img.setAttribute("src", src);
  4977. } else {
  4978. img.setAttribute("src", invalidateImageCache(src, getRepeatValue(src)));
  4979. }
  4980. }
  4981. function onImagesDone() {
  4982. logScript("Images Loading Complete");
  4983. if (getUserSettings().downloadZip) {
  4984. document.getElementById("download")?.dispatchEvent(new Event("click"));
  4985. }
  4986. document.getElementById("download")?.classList.remove("disabled");
  4987. }
  4988. function updateProgress() {
  4989. const total = document.querySelectorAll(".PageContent .PageImg").length;
  4990. const loaded = document.querySelectorAll(
  4991. ".PageContent .PageImg.imgLoaded",
  4992. ).length;
  4993. const percentage = Math.floor((loaded / total) * 100);
  4994. const title = document.querySelector("title");
  4995. if (title) {
  4996. title.innerHTML = html`(${percentage}%)
  4997. ${document.querySelector("#MangaTitle")?.textContent}`;
  4998. }
  4999. document
  5000. .querySelectorAll("#Counters i, #NavigationCounters i")
  5001. .forEach((ele) => {
  5002. ele.textContent = loaded.toString();
  5003. });
  5004. NProgress.configure({
  5005. showSpinner: false,
  5006. }).set(loaded / total);
  5007. logScript(`Progress: ${percentage}%`);
  5008. if (loaded === total) {
  5009. onImagesDone();
  5010. }
  5011. }
  5012. const applyLastGlobalZoom = (pages = ".PageContent img") => {
  5013. const zoomVal = document.querySelector("#ZoomVal")?.textContent?.trim();
  5014. if (zoomVal?.match(/^\d+%$/)) {
  5015. applyZoom(parseInt(zoomVal, 10), pages);
  5016. } else {
  5017. applyZoom(zoomVal, pages);
  5018. }
  5019. };
  5020. function onImagesSuccess() {
  5021. return (instance) => {
  5022. instance.images.forEach((image) => {
  5023. image.img.classList.add("imgLoaded");
  5024. image.img.classList.remove("imgBroken");
  5025. const thumbId = image.img.id.replace("PageImg", "ThumbnailImg");
  5026. const thumb = document.getElementById(thumbId);
  5027. if (thumb) {
  5028. thumb.setAttribute("src", image.img.getAttribute("src"));
  5029. }
  5030. applyLastGlobalZoom(`#${image.img.id}`);
  5031. updateProgress();
  5032. });
  5033. };
  5034. }
  5035. function onImagesFail(manga) {
  5036. return (instance) => {
  5037. instance.images.forEach((image) => {
  5038. image.img.classList.add("imgBroken");
  5039. const src = image.img.getAttribute("src");
  5040. if (src && getRepeatValue(src) <= getUserSettings().maxReload) {
  5041. setTimeout(async () => {
  5042. if (manga.reload) {
  5043. const id = parseInt(`0${/\d+/.exec(image.img.id)}`, 10);
  5044. const alt = await manga.reload(id);
  5045. image.img.setAttribute("src", alt);
  5046. } else {
  5047. reloadImage(image.img);
  5048. }
  5049. const imgLoad = imagesLoaded(image.img.parentElement);
  5050. imgLoad.on("done", onImagesSuccess());
  5051. imgLoad.on("fail", onImagesFail(manga));
  5052. }, 2e3);
  5053. }
  5054. });
  5055. };
  5056. }
  5057. function normalizeUrl(url) {
  5058. if (url) {
  5059. let uri = url.trim();
  5060. if (uri.startsWith("//")) {
  5061. uri = `https:${uri}`;
  5062. }
  5063. return uri;
  5064. }
  5065. return "";
  5066. }
  5067. function addImg(manga, index, imageSrc, position) {
  5068. const relativePosition = position - manga.begin;
  5069. let src = normalizeUrl(imageSrc);
  5070. const img = document.querySelector(`#PageImg${index}`);
  5071. if (img) {
  5072. if (
  5073. !getUserSettings().lazyLoadImages ||
  5074. relativePosition <= getUserSettings().lazyStart
  5075. ) {
  5076. setTimeout(
  5077. async () => {
  5078. if (
  5079. !isObjectURL(src) &&
  5080. !isBase64ImageUrl(src) &&
  5081. manga.fetchOptions
  5082. ) {
  5083. src = await fetch(src, manga.fetchOptions)
  5084. .then((resp) => resp.blob())
  5085. .then((blob) => blobUtil.blobToDataURL(blob));
  5086. }
  5087. const imgLoad = imagesLoaded(img.parentElement);
  5088. imgLoad.on("done", onImagesSuccess());
  5089. imgLoad.on("fail", onImagesFail(manga));
  5090. img.setAttribute("src", src);
  5091. logScript("Loaded Image:", index, "Source:", src);
  5092. },
  5093. (manga.timer ?? getUserSettings().throttlePageLoad) *
  5094. relativePosition,
  5095. );
  5096. } else {
  5097. img.setAttribute("data-src", normalizeUrl(src));
  5098. lazyLoad(
  5099. img,
  5100. () => {
  5101. const imgLoad = imagesLoaded(img.parentElement);
  5102. imgLoad.on("done", onImagesSuccess());
  5103. imgLoad.on("fail", onImagesFail(manga));
  5104. logScript(
  5105. "Lazy Image: ",
  5106. index,
  5107. " Source: ",
  5108. img.getAttribute("src"),
  5109. );
  5110. },
  5111. manga.fetchOptions,
  5112. );
  5113. }
  5114. if (manga.pages === position) removeURLBookmark();
  5115. }
  5116. }
  5117. function findPage(manga, index, pageUrl, lazy) {
  5118. return async () => {
  5119. const src = await getElementAttribute(
  5120. pageUrl,
  5121. manga.img,
  5122. manga.lazyAttr ?? "src",
  5123. );
  5124. const img = document.querySelector(`#PageImg${index}`);
  5125. if (src && img) {
  5126. img.style.width = "auto";
  5127. const imgLoad = imagesLoaded(img.parentElement);
  5128. imgLoad.on("done", onImagesSuccess());
  5129. imgLoad.on("fail", onImagesFail(manga));
  5130. img.setAttribute("src", src);
  5131. logScript(
  5132. `${lazy && "Lazy "}Page: `,
  5133. index,
  5134. " Source: ",
  5135. img.getAttribute("src"),
  5136. );
  5137. }
  5138. };
  5139. }
  5140. async function addPage(manga, index, pageUrl, position) {
  5141. const relativePosition = position - manga.begin;
  5142. const img = document.querySelector(`#PageImg${index}`);
  5143. if (img) {
  5144. if (
  5145. !getUserSettings().lazyLoadImages ||
  5146. relativePosition <= getUserSettings().lazyStart
  5147. ) {
  5148. setTimeout(
  5149. () => {
  5150. findPage(manga, index, pageUrl, false)().catch(logScript);
  5151. },
  5152. (manga.timer ?? getUserSettings().throttlePageLoad) *
  5153. relativePosition,
  5154. );
  5155. } else {
  5156. img.setAttribute(
  5157. "data-src",
  5158. "data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",
  5159. );
  5160. lazyLoad(img, findPage(manga, index, pageUrl, true));
  5161. }
  5162. if (manga.pages === position) removeURLBookmark();
  5163. }
  5164. }
  5165. function loadMangaPages(begin, manga) {
  5166. indexList(manga.pages, begin).forEach((index, position) => {
  5167. addPage(manga, index, manga.listPages[index - 1], position).catch(
  5168. logScript,
  5169. );
  5170. });
  5171. }
  5172. function loadMangaImages(begin, manga) {
  5173. indexList(manga.pages, begin).forEach((index, position) => {
  5174. addImg(manga, index, manga.listImages[index - 1], position);
  5175. });
  5176. }
  5177. function loadManga(manga, begin = 1) {
  5178. getUserSettings().lazyLoadImages =
  5179. manga.lazy ?? getUserSettings().lazyLoadImages;
  5180. logScript("Loading Images");
  5181. logScript(
  5182. `Intervals: ${manga.timer ?? getUserSettings().throttlePageLoad ?? "Default(1000)"}`,
  5183. );
  5184. logScript(
  5185. `Lazy: ${getUserSettings().lazyLoadImages}, Starting from: ${getUserSettings().lazyStart}`,
  5186. );
  5187. if (isImagesManga(manga)) {
  5188. logScript("Method: Images:", manga.listImages);
  5189. loadMangaImages(begin, manga);
  5190. } else if (isPagesManga(manga)) {
  5191. logScript("Method: Pages:", manga.listPages);
  5192. loadMangaPages(begin, manga);
  5193. } else if (isBruteforceManga(manga)) {
  5194. logScript("Method: Brute Force");
  5195. manga.bruteForce({
  5196. begin,
  5197. addImg,
  5198. loadImages(list) {
  5199. loadMangaImages(begin, { ...manga, listImages: list });
  5200. },
  5201. loadPages(list, img, lazyAttr) {
  5202. loadMangaPages(begin, {
  5203. ...manga,
  5204. listPages: list,
  5205. img,
  5206. lazyAttr,
  5207. });
  5208. },
  5209. wait: getUserSettings().throttlePageLoad,
  5210. });
  5211. } else {
  5212. logScript("No Loading Method Found");
  5213. }
  5214. }
  5215.  
  5216. function buttonReloadPage(event) {
  5217. const img =
  5218. event.currentTarget.parentElement?.parentElement?.querySelector(
  5219. ".PageImg",
  5220. );
  5221. reloadImage(img);
  5222. }
  5223. function buttonHidePage(event) {
  5224. const img = event.currentTarget.parentElement?.parentElement;
  5225. img.classList.toggle("hide");
  5226. }
  5227. function individual() {
  5228. document
  5229. .querySelectorAll(".Reload")
  5230. ?.forEach(addEvent("click", buttonReloadPage));
  5231. document
  5232. .querySelectorAll(".Hide")
  5233. ?.forEach(addEvent("click", buttonHidePage));
  5234. }
  5235.  
  5236. function selectGoToPage(event) {
  5237. const target = event.currentTarget.value;
  5238. applyZoom();
  5239. scrollToElement(document.querySelector(`#Page${target}`));
  5240. }
  5241. function clickThumbnail(event) {
  5242. applyZoom();
  5243. scrollToElement(
  5244. document.querySelector(
  5245. `#Page${event.currentTarget.querySelector(".ThumbnailIndex")?.textContent}`,
  5246. ),
  5247. );
  5248. }
  5249. function navigation() {
  5250. document
  5251. .querySelector("#gotoPage")
  5252. ?.addEventListener("change", selectGoToPage);
  5253. document
  5254. .querySelectorAll(".Thumbnail")
  5255. ?.forEach(addEvent("click", clickThumbnail));
  5256. document
  5257. .querySelector("#Thumbnails")
  5258. ?.addEventListener("wheel", transformScrollToHorizontal);
  5259. }
  5260.  
  5261. function buttonResetSettings() {
  5262. resetSettings();
  5263. const elem = document.getElementById("MangaOnlineViewer");
  5264. elem?.removeAttribute("locale");
  5265. elem?.dispatchEvent(new Event("locale"));
  5266. }
  5267. function changeLocale(event) {
  5268. const locale = event.currentTarget.value;
  5269. updateSettings({ locale });
  5270. const elem = document.getElementById("MangaOnlineViewer");
  5271. elem?.setAttribute("locale", locale);
  5272. elem?.dispatchEvent(new Event("locale"));
  5273. }
  5274. function changeLoadMode(event) {
  5275. const mode = event.currentTarget.value;
  5276. updateSettings({ loadMode: mode });
  5277. }
  5278. function checkFitWidthOversize(event) {
  5279. document.querySelector("#Chapter")?.classList.toggle("fitWidthIfOversize");
  5280. updateSettings({ fitWidthIfOversize: event.currentTarget.checked });
  5281. }
  5282. function checkVerticalSeparator(event) {
  5283. document.querySelector("#Chapter")?.classList.toggle("separator");
  5284. updateSettings({ verticalSeparator: event.currentTarget.checked });
  5285. }
  5286. function checkShowThumbnails(event) {
  5287. document.querySelector("#Navigation")?.classList.toggle("disabled");
  5288. updateSettings({ showThumbnails: event.currentTarget.checked });
  5289. applyZoom();
  5290. }
  5291. function checkEnableComments(event) {
  5292. document.querySelector("#CommentsButton")?.classList.toggle("disabled");
  5293. updateSettings({ enableComments: event.currentTarget.checked });
  5294. applyZoom();
  5295. }
  5296. function changeAutoDownload(event) {
  5297. updateSettings({ downloadZip: event.currentTarget.checked });
  5298. if (event.currentTarget.checked) {
  5299. Swal.fire({
  5300. title: getLocaleString("ATTENTION"),
  5301. text: getLocaleString("AUTO_DOWNLOAD"),
  5302. timer: 1e4,
  5303. icon: "info",
  5304. });
  5305. }
  5306. }
  5307. function checkLazyLoad(event) {
  5308. updateSettings({ lazyLoadImages: event.currentTarget.checked });
  5309. const start = document.querySelector(".lazyStart");
  5310. if (getUserSettings().lazyLoadImages) {
  5311. start?.classList.add("show");
  5312. } else {
  5313. start?.classList.remove("show");
  5314. }
  5315. if (event.currentTarget.checked) {
  5316. Swal.fire({
  5317. title: getLocaleString("WARNING"),
  5318. html: getLocaleString("LAZY_LOAD"),
  5319. icon: "warning",
  5320. });
  5321. }
  5322. }
  5323. function changeLazyStart(event) {
  5324. const start = event.currentTarget.value;
  5325. updateSettings({ lazyStart: parseInt(start, 10) });
  5326. }
  5327. function changePagesPerSecond(event) {
  5328. const timer = parseInt(event.currentTarget.value, 10);
  5329. updateSettings({ throttlePageLoad: timer });
  5330. if (timer < 100) {
  5331. Swal.fire({
  5332. title: getLocaleString("SPEED_WARNING"),
  5333. html: getLocaleString("SPEED_WARNING_MESSAGE"),
  5334. icon: "warning",
  5335. });
  5336. }
  5337. }
  5338. function changeZoomStep(event) {
  5339. const step = event.currentTarget.value;
  5340. updateSettings({ zoomStep: parseInt(step, 10) });
  5341. }
  5342. function changeMinZoom(event) {
  5343. const min = event.currentTarget.value;
  5344. replaceStyleSheet(
  5345. "MinZoom",
  5346. `#MangaOnlineViewer .PageContent .PageImg {min-width: ${min}vw;}`,
  5347. );
  5348. updateSettings({ minZoom: parseInt(min, 10) });
  5349. }
  5350. function checkHideImageControls(event) {
  5351. document
  5352. .querySelector("#MangaOnlineViewer")
  5353. ?.classList.toggle("hideControls");
  5354. updateSettings({ hidePageControls: event.currentTarget.checked });
  5355. }
  5356. function updateHeaderType(mode) {
  5357. const header = document.querySelector("#Header");
  5358. if (!header?.classList.contains(mode)) {
  5359. const menu = document.querySelector("#menu");
  5360. header?.classList.remove(
  5361. "scroll",
  5362. "click",
  5363. "hover",
  5364. "fixed",
  5365. "simple",
  5366. "visible",
  5367. );
  5368. menu?.classList.remove(
  5369. "scroll",
  5370. "click",
  5371. "hover",
  5372. "fixed",
  5373. "simple",
  5374. "hide",
  5375. );
  5376. header?.classList.add(mode);
  5377. menu?.classList.add(mode);
  5378. }
  5379. }
  5380. function changeHeaderType(event) {
  5381. const headerType = event.currentTarget.value;
  5382. updateHeaderType(headerType);
  5383. updateSettings({ header: headerType });
  5384. }
  5385. function changeScrollHeight(event) {
  5386. const { value } = event.currentTarget;
  5387. updateSettings({ scrollHeight: parseInt(value, 10) });
  5388. }
  5389. function options() {
  5390. document
  5391. .querySelector("#ResetSettings")
  5392. ?.addEventListener("click", buttonResetSettings);
  5393. document.querySelector("#locale")?.addEventListener("change", changeLocale);
  5394. document
  5395. .querySelector("#fitIfOversize")
  5396. ?.addEventListener("change", checkFitWidthOversize);
  5397. document
  5398. .querySelector("#verticalSeparator")
  5399. ?.addEventListener("change", checkVerticalSeparator);
  5400. document
  5401. .querySelector("#loadMode")
  5402. ?.addEventListener("change", changeLoadMode);
  5403. document
  5404. .querySelector("#showThumbnails")
  5405. ?.addEventListener("change", checkShowThumbnails);
  5406. document
  5407. .querySelector("#enableComments")
  5408. ?.addEventListener("change", checkEnableComments);
  5409. document
  5410. .querySelector("#downloadZip")
  5411. ?.addEventListener("change", changeAutoDownload);
  5412. document
  5413. .querySelector("#lazyLoadImages")
  5414. ?.addEventListener("change", checkLazyLoad);
  5415. document
  5416. .querySelector("#lazyStart")
  5417. ?.addEventListener("change", changeLazyStart);
  5418. document
  5419. .querySelector("#PagesPerSecond")
  5420. ?.addEventListener("change", changePagesPerSecond);
  5421. document
  5422. .querySelector("#zoomStep")
  5423. ?.addEventListener("change", changeZoomStep);
  5424. document
  5425. .querySelector("#minZoom")
  5426. ?.addEventListener("input", changeMinZoom);
  5427. document
  5428. .querySelector("#hidePageControls")
  5429. ?.addEventListener("change", checkHideImageControls);
  5430. document
  5431. .querySelector("#headerType")
  5432. ?.addEventListener("change", changeHeaderType);
  5433. document
  5434. .querySelector("#scrollHeight")
  5435. ?.addEventListener("change", changeScrollHeight);
  5436. }
  5437.  
  5438. function toggleFunction(open, close) {
  5439. let isOpen = true;
  5440. return () => {
  5441. const func = isOpen ? open : close;
  5442. func();
  5443. isOpen = !isOpen;
  5444. };
  5445. }
  5446. function buttonHeaderClick() {
  5447. const header = document.querySelector("#Header");
  5448. if (header?.classList.contains("click")) {
  5449. header?.classList.toggle("visible");
  5450. }
  5451. }
  5452. function isMouseInsideRegion(event, headerWidth, headerHeight) {
  5453. return (
  5454. event.clientX >= 0 &&
  5455. event.clientX <= headerWidth &&
  5456. event.clientY >= 0 &&
  5457. event.clientY <= headerHeight
  5458. );
  5459. }
  5460. function headerHover(event) {
  5461. const header = document.querySelector("#Header");
  5462. if (header?.classList.contains("hover")) {
  5463. if (isMouseInsideRegion(event, header.clientWidth, header.clientHeight)) {
  5464. document.querySelector("#menu")?.classList.add("hide");
  5465. header?.classList.add("visible");
  5466. } else {
  5467. document.querySelector("#menu")?.classList.remove("hide");
  5468. header?.classList.remove("visible");
  5469. }
  5470. }
  5471. }
  5472. function buttonSettingsOpen() {
  5473. document.querySelector("#SettingsPanel")?.classList.add("visible");
  5474. document.querySelector("#Navigation")?.classList.add("visible");
  5475. document.querySelector("#Header")?.classList.add("visible");
  5476. document.querySelector("#Overlay")?.classList.add("visible");
  5477. }
  5478. function buttonSettingsClose() {
  5479. document.querySelector("#SettingsPanel")?.classList.remove("visible");
  5480. document.querySelector("#Navigation")?.classList.remove("visible");
  5481. document.querySelector("#Header")?.classList.remove("visible");
  5482. document.querySelector("#Overlay")?.classList.remove("visible");
  5483. }
  5484. function buttonKeybindingsOpen() {
  5485. document.querySelector("#KeybindingsList").innerHTML =
  5486. keybindList().join("\n");
  5487. document.querySelector("#SaveKeybindings")?.classList.add("hidden");
  5488. document.querySelector("#EditKeybindings")?.classList.remove("hidden");
  5489. document.querySelector("#KeybindingsPanel")?.classList.add("visible");
  5490. document.querySelector("#Overlay")?.classList.add("visible");
  5491. }
  5492. function buttonKeybindingsClose() {
  5493. document.querySelector("#SaveKeybindings")?.classList.add("hidden");
  5494. document.querySelector("#EditKeybindings")?.classList.remove("hidden");
  5495. document.querySelector("#KeybindingsPanel")?.classList.remove("visible");
  5496. document.querySelector("#Overlay")?.classList.remove("visible");
  5497. }
  5498. function saveKeybindings() {
  5499. const newkeybinds = getUserSettings().keybinds;
  5500. Object.keys(getUserSettings().keybinds).forEach((kb) => {
  5501. const keys = document
  5502. .querySelector(`#${kb}`)
  5503. ?.value.split(",")
  5504. ?.map((value) => value.trim());
  5505. newkeybinds[kb] = isNothing(keys) ? void 0 : keys;
  5506. });
  5507. updateSettings({ keybinds: newkeybinds });
  5508. document.querySelector("#KeybindingsList").innerHTML =
  5509. keybindList().join("\n");
  5510. document.querySelector("#SaveKeybindings")?.classList.add("hidden");
  5511. document.querySelector("#EditKeybindings")?.classList.remove("hidden");
  5512. keybindings();
  5513. }
  5514. function editKeybindings() {
  5515. document.querySelector("#KeybindingsList").innerHTML =
  5516. keybindEditor().join("\n");
  5517. document.querySelector("#SaveKeybindings")?.classList.remove("hidden");
  5518. document.querySelector("#EditKeybindings")?.classList.add("hidden");
  5519. }
  5520. function panels() {
  5521. document
  5522. .querySelector("#menu")
  5523. ?.addEventListener("click", buttonHeaderClick);
  5524. document.addEventListener("mousemove", _.throttle(headerHover, 300));
  5525. document
  5526. .querySelector("#settings")
  5527. ?.addEventListener(
  5528. "click",
  5529. toggleFunction(buttonSettingsOpen, buttonSettingsClose),
  5530. );
  5531. document
  5532. .querySelectorAll(".closeButton")
  5533. ?.forEach(addEvent("click", buttonSettingsClose));
  5534. document
  5535. .querySelector("#Overlay")
  5536. ?.addEventListener("click", buttonSettingsClose);
  5537. document
  5538. .querySelector("#keybindings")
  5539. ?.addEventListener("click", buttonKeybindingsOpen);
  5540. document
  5541. .querySelectorAll(".closeButton")
  5542. ?.forEach(addEvent("click", buttonKeybindingsClose));
  5543. document
  5544. .querySelector("#Overlay")
  5545. ?.addEventListener("click", buttonKeybindingsClose);
  5546. document
  5547. .querySelector("#EditKeybindings")
  5548. ?.addEventListener("click", editKeybindings);
  5549. document
  5550. .querySelector("#SaveKeybindings")
  5551. ?.addEventListener("click", saveKeybindings);
  5552. }
  5553.  
  5554. function buttonZoomIn(event) {
  5555. const img =
  5556. event.currentTarget.parentElement?.parentElement?.querySelector(
  5557. ".PageImg",
  5558. );
  5559. const ratio =
  5560. (img.width / img.naturalWidth) * (100 + getUserSettings().zoomStep);
  5561. applyZoom(ratio, `#${img.getAttribute("id")}`);
  5562. }
  5563. function buttonZoomOut(event) {
  5564. const img =
  5565. event.currentTarget.parentElement?.parentElement?.querySelector(
  5566. ".PageImg",
  5567. );
  5568. const ratio =
  5569. (img.width / img.naturalWidth) * (100 - getUserSettings().zoomStep);
  5570. applyZoom(ratio, `#${img.getAttribute("id")}`);
  5571. }
  5572. function buttonRestoreZoom() {
  5573. document.querySelector(".PageContent .PageImg")?.removeAttribute("width");
  5574. }
  5575. function buttonZoomWidth(event) {
  5576. const page = event.currentTarget.parentElement?.parentElement;
  5577. const img = page?.querySelector(".PageImg");
  5578. applyZoom("width", `#${img.getAttribute("id")}`);
  5579. page?.classList.toggle("DoublePage");
  5580. }
  5581. function buttonZoomHeight(event) {
  5582. const img =
  5583. event.currentTarget.parentElement?.parentElement?.querySelector(
  5584. ".PageImg",
  5585. );
  5586. applyZoom("height", `#${img.getAttribute("id")}`);
  5587. }
  5588. function size() {
  5589. document
  5590. .querySelectorAll(".ZoomIn")
  5591. ?.forEach(addEvent("click", buttonZoomIn));
  5592. document
  5593. .querySelectorAll(".ZoomOut")
  5594. ?.forEach(addEvent("click", buttonZoomOut));
  5595. document
  5596. .querySelectorAll(".ZoomRestore")
  5597. ?.forEach(addEvent("click", buttonRestoreZoom));
  5598. document
  5599. .querySelectorAll(".ZoomWidth")
  5600. ?.forEach(addEvent("click", buttonZoomWidth));
  5601. document
  5602. .querySelectorAll(".ZoomHeight")
  5603. ?.forEach(addEvent("click", buttonZoomHeight));
  5604. }
  5605.  
  5606. function changeColorScheme() {
  5607. const isDark = getUserSettings().colorScheme === "dark";
  5608. updateSettings({ colorScheme: isDark ? "light" : "dark" });
  5609. const elem = document.getElementById("MangaOnlineViewer");
  5610. elem?.classList.remove(isDark ? "dark" : "light");
  5611. elem?.classList.add(getUserSettings().colorScheme);
  5612. }
  5613. function buttonSelectTheme(event) {
  5614. const target = event.currentTarget;
  5615. [...document.querySelectorAll(".ThemeRadio")].forEach((theme) => {
  5616. theme.classList.remove("selected");
  5617. });
  5618. target.classList.add("selected");
  5619. document
  5620. .getElementById("MangaOnlineViewer")
  5621. ?.setAttribute("data-theme", target.title);
  5622. updateSettings({ theme: target.title });
  5623. const hue = document.querySelector("#Hue");
  5624. const shade = document.querySelector("#Shade");
  5625. if (target.title.startsWith("custom")) {
  5626. hue?.classList.add("show");
  5627. shade?.classList.remove("show");
  5628. } else {
  5629. hue?.classList.remove("show");
  5630. shade?.classList.add("show");
  5631. }
  5632. }
  5633. function changeCustomTheme(event) {
  5634. const target = event.currentTarget.value;
  5635. updateSettings({ customTheme: target });
  5636. addCustomTheme(target);
  5637. }
  5638. function changeThemeShade(event) {
  5639. const target = parseInt(event.currentTarget.value, 10);
  5640. updateSettings({ themeShade: target });
  5641. refreshThemes();
  5642. }
  5643. function theming() {
  5644. document
  5645. .querySelector("#ColorScheme")
  5646. ?.addEventListener("click", changeColorScheme);
  5647. document
  5648. .querySelectorAll(".ThemeRadio")
  5649. .forEach(addEvent("click", buttonSelectTheme));
  5650. document
  5651. .querySelector("#CustomThemeHue")
  5652. ?.addEventListener("change", changeCustomTheme);
  5653. document
  5654. .querySelector("#ThemeShade")
  5655. ?.addEventListener("input", changeThemeShade);
  5656. }
  5657.  
  5658. function changeGlobalZoom(value) {
  5659. return () => {
  5660. if (typeof value !== "number") {
  5661. getUserSettings().zoomMode = value;
  5662. } else {
  5663. getUserSettings().zoomMode = "percent";
  5664. }
  5665. if (value === "height") {
  5666. updateHeaderType("click");
  5667. } else {
  5668. updateHeaderType(getUserSettings().header);
  5669. }
  5670. const globalZoomVal = document.querySelector("#ZoomVal");
  5671. if (Number.isInteger(value)) {
  5672. globalZoomVal.textContent = `${value}%`;
  5673. document.querySelector("#Zoom").value = value.toString();
  5674. } else {
  5675. globalZoomVal.textContent = value;
  5676. }
  5677. applyZoom(value);
  5678. };
  5679. }
  5680. function changeZoomByStep(sign = 1) {
  5681. return () => {
  5682. const globalZoom = document.querySelector("#Zoom");
  5683. const ratio =
  5684. parseInt(globalZoom.value, 10) + sign * getUserSettings().zoomStep;
  5685. globalZoom.value = ratio.toString();
  5686. globalZoom?.dispatchEvent(new Event("input", { bubbles: true }));
  5687. };
  5688. }
  5689. function changeDefaultZoomMode(event) {
  5690. const target = event.currentTarget.value;
  5691. updateSettings({ zoomMode: target });
  5692. changeGlobalZoom(target)();
  5693. const percent = document.querySelector(".DefaultZoom");
  5694. if (getUserSettings().zoomMode === "percent") {
  5695. percent?.classList.add("show");
  5696. } else {
  5697. percent?.classList.remove("show");
  5698. }
  5699. }
  5700. function changeDefaultZoom(event) {
  5701. const target = parseInt(event.currentTarget.value, 10);
  5702. updateSettings({ defaultZoom: target });
  5703. changeGlobalZoom(target)();
  5704. }
  5705. function changeZoom(event) {
  5706. const target = parseInt(event.currentTarget.value, 10);
  5707. changeGlobalZoom(target)();
  5708. document.querySelector("#ZoomVal").textContent = `${target}%`;
  5709. }
  5710. function zoom() {
  5711. document
  5712. .querySelector("#DefaultZoomMode")
  5713. ?.addEventListener("change", changeDefaultZoomMode);
  5714. document
  5715. .querySelector("#DefaultZoom")
  5716. ?.addEventListener("input", changeDefaultZoom);
  5717. document.querySelector("#Zoom")?.addEventListener("input", changeZoom);
  5718. document
  5719. .querySelector("#enlarge")
  5720. ?.addEventListener("click", changeZoomByStep());
  5721. document
  5722. .querySelector("#reduce")
  5723. ?.addEventListener("click", changeZoomByStep(-1));
  5724. document
  5725. .querySelector("#restore")
  5726. ?.addEventListener("click", changeGlobalZoom(100));
  5727. document
  5728. .querySelector("#fitWidth")
  5729. ?.addEventListener("click", changeGlobalZoom("width"));
  5730. document
  5731. .querySelector("#fitHeight")
  5732. ?.addEventListener("click", changeGlobalZoom("height"));
  5733. }
  5734.  
  5735. function setupFluid(mode) {
  5736. const chapter = document.querySelector("#Chapter");
  5737. document.querySelector("#Header")?.classList.remove("visible");
  5738. document.querySelector("#menu")?.classList.remove("hide");
  5739. changeGlobalZoom("height")();
  5740. scrollToElement(chapter);
  5741. chapter?.addEventListener(
  5742. "wheel",
  5743. mode === "FluidLTR"
  5744. ? transformScrollToHorizontal
  5745. : transformScrollToHorizontalReverse,
  5746. );
  5747. }
  5748. function updateViewMode(mode) {
  5749. return () => {
  5750. const chapter = document.querySelector("#Chapter");
  5751. chapter?.classList.remove("Vertical", "WebComic", "FluidLTR", "FluidRTL");
  5752. chapter?.classList.add(mode);
  5753. chapter?.removeEventListener("wheel", transformScrollToHorizontal);
  5754. chapter?.removeEventListener("wheel", transformScrollToHorizontalReverse);
  5755. if (mode === "FluidLTR" || mode === "FluidRTL") {
  5756. setupFluid(mode);
  5757. } else {
  5758. document.querySelector("#Header").className = getUserSettings().header;
  5759. document.querySelector("#menu").className = getUserSettings().header;
  5760. changeGlobalZoom(100)();
  5761. }
  5762. };
  5763. }
  5764. function changeViewMode(event) {
  5765. const mode = event.currentTarget.value;
  5766. updateViewMode(mode)();
  5767. updateSettings({ viewMode: mode });
  5768. }
  5769. function viewMode() {
  5770. document
  5771. .querySelector("#viewMode")
  5772. ?.addEventListener("change", changeViewMode);
  5773. document
  5774. .querySelector("#webComic")
  5775. ?.addEventListener("click", updateViewMode("WebComic"));
  5776. document
  5777. .querySelector("#ltrMode")
  5778. ?.addEventListener("click", updateViewMode("FluidLTR"));
  5779. document
  5780. .querySelector("#rtlMode")
  5781. ?.addEventListener("click", updateViewMode("FluidRTL"));
  5782. document
  5783. .querySelector("#verticalMode")
  5784. ?.addEventListener("click", updateViewMode("Vertical"));
  5785. if (
  5786. getUserSettings().viewMode === "FluidLTR" ||
  5787. getUserSettings().viewMode === "FluidRTL"
  5788. ) {
  5789. setupFluid(getUserSettings().viewMode);
  5790. }
  5791. }
  5792.  
  5793. let scrollInterval;
  5794. function scroll() {
  5795. const chapter = document.querySelector("#Chapter");
  5796. if (
  5797. chapter?.classList.contains("FluidLTR") ||
  5798. chapter?.classList.contains("FluidRTL")
  5799. ) {
  5800. chapter?.scrollBy({
  5801. top: 0,
  5802. left:
  5803. getUserSettings().scrollHeight *
  5804. (chapter?.classList.contains("FluidRTL") ? -1 : 1),
  5805. behavior: "smooth",
  5806. });
  5807. } else {
  5808. window.scrollBy({
  5809. top: getUserSettings().scrollHeight,
  5810. left: 0,
  5811. behavior: "smooth",
  5812. });
  5813. }
  5814. if (document.querySelector("#Header")?.classList.contains("headroom-end")) {
  5815. clearInterval(scrollInterval);
  5816. scrollInterval = void 0;
  5817. document.querySelector("#ScrollControl")?.classList.remove("running");
  5818. logScript("Finished auto scroll");
  5819. }
  5820. }
  5821. function toggleAutoScroll() {
  5822. const control = document.querySelector("#AutoScroll");
  5823. if (scrollInterval) {
  5824. clearInterval(scrollInterval);
  5825. scrollInterval = void 0;
  5826. control?.classList.remove("running");
  5827. logScript("Stopped auto scroll");
  5828. } else {
  5829. scroll();
  5830. scrollInterval = setInterval(scroll, 1e3 / 60);
  5831. control?.classList.add("running");
  5832. logScript("Start auto scroll");
  5833. }
  5834. }
  5835. let resume = false;
  5836. const debounceAutoScroll = _.debounce(() => {
  5837. toggleAutoScroll();
  5838. resume = false;
  5839. }, 500);
  5840. function manualScroll() {
  5841. if (!resume && scrollInterval) {
  5842. toggleAutoScroll();
  5843. resume = true;
  5844. }
  5845. if (resume && !scrollInterval) {
  5846. debounceAutoScroll();
  5847. }
  5848. }
  5849. function autoscroll() {
  5850. window.addEventListener("wheel", _.throttle(manualScroll, 500));
  5851. document
  5852. .querySelector("#AutoScroll")
  5853. ?.addEventListener("click", toggleAutoScroll);
  5854. }
  5855.  
  5856. let setupEvents = false;
  5857. function events() {
  5858. if (!setupEvents) {
  5859. headroom(100);
  5860. keybindings();
  5861. individual();
  5862. size();
  5863. window.addEventListener("resize", () => {
  5864. const reader = document.querySelector("#MangaOnlineViewer");
  5865. reader?.classList.remove("mobile", "tablet", "desktop");
  5866. reader?.classList.add(getDevice());
  5867. });
  5868. setupEvents = true;
  5869. }
  5870. bookmarks();
  5871. globals();
  5872. navigation();
  5873. options();
  5874. panels();
  5875. theming();
  5876. viewMode();
  5877. zoom();
  5878. autoscroll();
  5879. }
  5880.  
  5881. let loadedManga;
  5882. function hydrateApp() {
  5883. updateViewMode(getUserSettings().viewMode)();
  5884. const elements = {
  5885. "#Header": Header(loadedManga),
  5886. "#CommentsPanel": commentsPanel(),
  5887. "#SettingsPanel": SettingsPanel(),
  5888. "#KeybindingsPanel": KeybindingsPanel(),
  5889. "#Bookmarks": BookmarkPanel(),
  5890. };
  5891. if (
  5892. document.querySelector("#ScrollControl")?.classList.contains("running")
  5893. ) {
  5894. toggleAutoScroll();
  5895. }
  5896. const outer = document.querySelector("#MangaOnlineViewer");
  5897. if (outer) {
  5898. outer.className = `${getUserSettings().colorScheme}
  5899. ${getUserSettings().hidePageControls ? "hideControls" : ""}
  5900. ${isBookmarked() ? "bookmarked" : ""}
  5901. ${getDevice()}`;
  5902. outer.setAttribute("data-theme", getUserSettings().theme);
  5903. }
  5904. const reader = document.querySelector("#Chapter");
  5905. if (reader) {
  5906. reader.className = `${getUserSettings().fitWidthIfOversize ? "fitWidthIfOversize" : ""} ${getUserSettings().viewMode}`;
  5907. }
  5908. Object.entries(elements).forEach(([id, component]) => {
  5909. const tag = document.querySelector(id);
  5910. if (tag) {
  5911. tag.outerHTML = component;
  5912. }
  5913. });
  5914. document.querySelector("#Overlay")?.dispatchEvent(new Event("click"));
  5915. events();
  5916. }
  5917. const app = (manga) => {
  5918. loadedManga = manga;
  5919. return html`
  5920. <div
  5921. id="MangaOnlineViewer"
  5922. class="${getUserSettings().colorScheme}
  5923. ${getUserSettings().hidePageControls ? "hideControls" : ""}
  5924. ${isBookmarked() ? "bookmarked" : ""}
  5925. ${getDevice()}"
  5926. data-theme="${getUserSettings().theme}"
  5927. >
  5928. <div id="menu" class="${getUserSettings().header}">${IconMenu2}</div>
  5929. ${Header(manga)} ${Reader(manga)} ${ThumbnailsPanel(manga)}
  5930. <div id="Overlay" class="overlay"></div>
  5931. ${commentsPanel()} ${KeybindingsPanel()} ${BookmarkPanel()}
  5932. ${SettingsPanel()}
  5933. </div>
  5934. `;
  5935. };
  5936.  
  5937. function display(manga) {
  5938. document.head.innerHTML = head(manga);
  5939. document.body.innerHTML = app(manga);
  5940. events();
  5941. loadManga(manga);
  5942. document
  5943. .querySelector("#MangaOnlineViewer")
  5944. ?.addEventListener("locale", hydrateApp);
  5945. if (manga.comments)
  5946. document.querySelector("#CommentsArea")?.append(manga.comments);
  5947. }
  5948.  
  5949. const cleanUpElement = (...ele) =>
  5950. ele.forEach((element) => {
  5951. element.getAttributeNames().forEach((attr) => {
  5952. element.removeAttribute(attr);
  5953. });
  5954. });
  5955.  
  5956. function waitForElm(selector, target = document.body) {
  5957. return new Promise((resolve) => {
  5958. if (document.querySelector(selector)) {
  5959. resolve(document.querySelector(selector));
  5960. return;
  5961. }
  5962. const observer = new MutationObserver(() => {
  5963. if (document.querySelector(selector)) {
  5964. resolve(document.querySelector(selector));
  5965. observer.disconnect();
  5966. }
  5967. });
  5968. observer.observe(target, {
  5969. childList: true,
  5970. subtree: true,
  5971. attributes: true,
  5972. });
  5973. });
  5974. }
  5975. function waitForFunc(fn, timer = 250) {
  5976. return new Promise((resolve) => {
  5977. const intervalId = setInterval(() => {
  5978. if (fn()) {
  5979. clearInterval(intervalId);
  5980. resolve(true);
  5981. }
  5982. }, timer);
  5983. });
  5984. }
  5985. function waitForAtb(selector, attribute, target = document.body) {
  5986. return new Promise((resolve) => {
  5987. if (target.querySelector(selector)?.getAttribute(attribute)) {
  5988. resolve(target.querySelector(selector)?.getAttribute(attribute) ?? "");
  5989. return;
  5990. }
  5991. const observer = new MutationObserver(() => {
  5992. if (target.querySelector(selector)?.getAttribute(attribute)) {
  5993. resolve(
  5994. target.querySelector(selector)?.getAttribute(attribute) ?? "",
  5995. );
  5996. observer.disconnect();
  5997. }
  5998. });
  5999. observer.observe(target, {
  6000. childList: true,
  6001. subtree: true,
  6002. attributes: true,
  6003. attributeFilter: [attribute],
  6004. });
  6005. });
  6006. }
  6007. function waitForVar(name, target = document.body) {
  6008. return new Promise((resolve) => {
  6009. if (!isNothing(unsafeWindow[name])) {
  6010. resolve(unsafeWindow[name]);
  6011. return;
  6012. }
  6013. const observer = new MutationObserver(() => {
  6014. if (!isNothing(unsafeWindow[name])) {
  6015. resolve(unsafeWindow[name]);
  6016. observer.disconnect();
  6017. }
  6018. });
  6019. observer.observe(target, {
  6020. childList: true,
  6021. subtree: true,
  6022. attributes: true,
  6023. });
  6024. });
  6025. }
  6026. function waitForTimer(millis = 1e3, result = true) {
  6027. return new Promise((resolve) => {
  6028. setTimeout(() => resolve(result), millis);
  6029. });
  6030. }
  6031. async function waitWithTimeout(promise, timeout = 5e3) {
  6032. return Promise.race([promise, waitForTimer(timeout, false)]);
  6033. }
  6034.  
  6035. async function captureComments() {
  6036. if (!getUserSettings().enableComments) return null;
  6037. let comments = document.querySelector("#disqus_thread, #fb-comments");
  6038. if (comments) {
  6039. logScript(`Waiting to Comments to load`, comments);
  6040. window.scrollTo(0, document.body.scrollHeight);
  6041. await waitWithTimeout(
  6042. waitForFunc(() => {
  6043. comments = document.querySelector("#disqus_thread, #fb-comments");
  6044. const iframe = comments?.querySelector(
  6045. "iframe:not(#indicator-north, #indicator-south)",
  6046. );
  6047. return (
  6048. iframe?.contentWindow?.document.readyState === "complete" &&
  6049. iframe?.contentWindow?.document?.body?.textContent?.length
  6050. );
  6051. }),
  6052. );
  6053. if (comments.children.length) {
  6054. logScript(`Got Comments`, comments);
  6055. } else {
  6056. logScript(`Timeout Comments`);
  6057. }
  6058. }
  6059. window.scrollTo(0, 0);
  6060. return comments;
  6061. }
  6062. async function viewer(manga) {
  6063. if (manga.before !== void 0) {
  6064. await manga.before(manga.begin);
  6065. }
  6066. if (getUserSettings().enableComments && !manga.comments) {
  6067. manga.comments = await captureComments();
  6068. }
  6069. setTimeout(() => {
  6070. try {
  6071. cleanUpElement(document.documentElement, document.head, document.body);
  6072. window.scrollTo(0, 0);
  6073. logScriptVerbose(`Page Cleaned Up`);
  6074. display(manga);
  6075. } catch (e) {
  6076. logScript(e);
  6077. }
  6078. }, 50);
  6079. }
  6080.  
  6081. const startButton =
  6082. "#StartMOV {\n all: revert;\n backface-visibility: hidden;\n font-size: 2rem;\n color: #fff;\n cursor: pointer;\n margin: 0 auto;\n padding: 0.5rem 1rem;\n text-align: center;\n border: none;\n border-radius: 10px;\n min-height: 50px;\n width: 80%;\n position: fixed;\n right: 0;\n left: 0;\n bottom: 0;\n z-index: 105000;\n transition: all 0.4s ease-in-out;\n background-size: 300% 100%;\n background-image: linear-gradient(to right, #667eea, #764ba2, #6b8dd6, #8e37d7);\n box-shadow: 0 4px 15px 0 rgba(116, 79, 168, 0.75);\n}\n\n#StartMOV:hover {\n background-position: 100% 0;\n transition: all 0.4s ease-in-out;\n}\n\n#StartMOV:focus {\n outline: none;\n}\n";
  6083.  
  6084. async function testAttribute(site) {
  6085. if (site.waitAttr !== void 0) {
  6086. logScript(
  6087. `Waiting for Attribute ${site.waitAttr[1]} of ${site.waitAttr[0]}`,
  6088. );
  6089. const wait = await waitForAtb(site.waitAttr[0], site.waitAttr[1]);
  6090. logScript(
  6091. `Found Attribute ${site.waitAttr[1]} of ${site.waitAttr[0]} = ${wait}`,
  6092. );
  6093. }
  6094. }
  6095. async function testElement(site) {
  6096. if (site.waitEle !== void 0) {
  6097. logScript(`Waiting for Element ${site.waitEle}`);
  6098. const wait = await waitForElm(site.waitEle);
  6099. logScript(`Found Element ${site.waitEle} = `, wait);
  6100. }
  6101. }
  6102. async function testVariable(site) {
  6103. if (site.waitVar !== void 0) {
  6104. logScript(`Waiting for Variable ${site.waitVar}`);
  6105. const wait = await waitForVar(site.waitVar);
  6106. logScript(`Found Variable ${site.waitVar} = ${wait}`);
  6107. }
  6108. }
  6109. async function testFunc(site) {
  6110. if (site.waitFunc !== void 0) {
  6111. logScript(`Waiting to pass Function check ${site.waitFunc}`);
  6112. const wait = await waitForFunc(site.waitFunc);
  6113. logScript(`Found Function check ${site.waitFunc} = ${wait}`);
  6114. }
  6115. }
  6116. async function testTime(site) {
  6117. if (site.waitTime !== void 0) {
  6118. logScript(`Waiting to for ${site.waitTime} milliseconds`);
  6119. await new Promise((resolve) => {
  6120. setTimeout(resolve, site.waitTime);
  6121. });
  6122. logScript("Continuing after timer");
  6123. }
  6124. }
  6125.  
  6126. const fileTypes = [
  6127. "image/apng",
  6128. "image/bmp",
  6129. "image/gif",
  6130. "image/jpeg",
  6131. "image/pjpeg",
  6132. "image/png",
  6133. "image/svg+xml",
  6134. "image/tiff",
  6135. "image/webp",
  6136. "image/x-icon",
  6137. ];
  6138. const fileImageExt = /.(png|jpg|jpeg|gif|bmp|webp)$/i;
  6139. const orderFiles = (a, b) =>
  6140. a.localeCompare(b, navigator.languages[0] || navigator.language, {
  6141. numeric: true,
  6142. ignorePunctuation: true,
  6143. });
  6144. function validFileType(file) {
  6145. return fileTypes.includes(file.type);
  6146. }
  6147. const getImageBlob = (content) => {
  6148. const buffer = new Uint8Array(content);
  6149. const blob = new Blob([buffer.buffer]);
  6150. return URL.createObjectURL(blob);
  6151. };
  6152. async function loadZipFile(filePath) {
  6153. const zip = await JSZip.loadAsync(filePath);
  6154. const files = zip
  6155. .filter((_, file) => !file.dir && fileImageExt.test(file.name))
  6156. .sort((a, b) => orderFiles(a.name, b.name));
  6157. logScript("Files in zip:", zip.files);
  6158. return Promise.all(
  6159. files.map((file) => file.async("arraybuffer").then(getImageBlob)),
  6160. );
  6161. }
  6162. function displayUploadedFiles(title, listImages) {
  6163. viewer({
  6164. title,
  6165. series: "?reload",
  6166. pages: listImages.length,
  6167. begin: 1,
  6168. prev: "#",
  6169. next: "#",
  6170. lazy: false,
  6171. listImages,
  6172. }).then(() => logScript("Page loaded"));
  6173. }
  6174. async function loadMangaFromZip(zipFile) {
  6175. const listImages = await loadZipFile(zipFile);
  6176. displayUploadedFiles(
  6177. typeof zipFile === "string" ? zipFile : zipFile.name,
  6178. listImages,
  6179. );
  6180. }
  6181. function openFileImages(evt) {
  6182. const input = evt.target;
  6183. const files = Array.from(input.files)
  6184. .filter(validFileType)
  6185. .sort((a, b) =>
  6186. orderFiles(
  6187. a.webkitRelativePath || a.name,
  6188. b.webkitRelativePath || b.name,
  6189. ),
  6190. );
  6191. logScript(
  6192. "Local Files: ",
  6193. files,
  6194. files.map((f) => f.webkitRelativePath || f.name),
  6195. );
  6196. if (input.files?.[0]) {
  6197. displayUploadedFiles(
  6198. input.files[0].webkitRelativePath.split("/")[0] || "Local Images",
  6199. files.map(URL.createObjectURL),
  6200. );
  6201. }
  6202. }
  6203. function allowUpload() {
  6204. if (localhost.url.test(window.location.href)) {
  6205. if (document.querySelector("#MangaOnlineViewer, #LocalTest")) {
  6206. document
  6207. .querySelector("#LocalTest")
  6208. ?.setAttribute("style", "display:none");
  6209. document.querySelector("#file")?.addEventListener("change", (evt) => {
  6210. const input = evt.target;
  6211. if (input.files?.[0]) loadMangaFromZip(input.files[0]);
  6212. });
  6213. document
  6214. .querySelector("#folder")
  6215. ?.addEventListener("change", openFileImages);
  6216. document
  6217. .querySelector("#images")
  6218. ?.addEventListener("change", openFileImages);
  6219. logScript(`Waiting for zip/images upload`);
  6220. }
  6221. return true;
  6222. }
  6223. return false;
  6224. }
  6225.  
  6226. function validateMin(valBegin, endPage, rs) {
  6227. let val = valBegin;
  6228. if (Number.isNaN(val) || val < rs.min()) {
  6229. val = rs.min();
  6230. } else if (val > rs.max()) {
  6231. val = rs.max();
  6232. } else if (val > endPage) {
  6233. val = endPage;
  6234. }
  6235. return val;
  6236. }
  6237. function validateMax(valEnd, beginPage, rs) {
  6238. let val = valEnd;
  6239. if (Number.isNaN(val) || val > rs.max()) {
  6240. val = rs.max();
  6241. } else if (val < rs.min()) {
  6242. val = rs.min();
  6243. } else if (val < beginPage) {
  6244. val = beginPage;
  6245. }
  6246. return val;
  6247. }
  6248. async function lateStart(site, begin = 1) {
  6249. const manga = await site.run();
  6250. logScript("LateStart");
  6251. let beginPage = begin;
  6252. let endPage = manga.pages;
  6253. const options = {
  6254. title: getLocaleString("STARTING"),
  6255. // Language=html
  6256. html: html`
  6257. ${getLocaleString("CHOOSE_BEGINNING")}
  6258. <div id="pageInputGroup">
  6259. <div id="pageInputs">
  6260. <input
  6261. type="number"
  6262. id="pageBegin"
  6263. class="pageInput"
  6264. min="1"
  6265. inputmode="numeric"
  6266. pattern="[0-9]*"
  6267. max="${manga.pages}"
  6268. value="${beginPage}"
  6269. />
  6270. -
  6271. <input
  6272. type="number"
  6273. id="pageEnd"
  6274. class="pageInput"
  6275. min="1"
  6276. inputmode="numeric"
  6277. pattern="[0-9]*"
  6278. max="${manga.pages}"
  6279. value="${endPage}"
  6280. />
  6281. </div>
  6282. <div id="pagesSlider"></div>
  6283. </div>
  6284. `,
  6285. showCancelButton: true,
  6286. cancelButtonColor: "#d33",
  6287. reverseButtons: true,
  6288. icon: "question",
  6289. didOpen() {
  6290. const pageBeginInput = document.querySelector("#pageBegin");
  6291. const pageEndInput = document.querySelector("#pageEnd");
  6292. const rangeSliderElement = rangeSlider(
  6293. document.getElementById("pagesSlider"),
  6294. {
  6295. min: 1,
  6296. max: manga.pages,
  6297. value: [beginPage, endPage],
  6298. onInput(value, userInteraction) {
  6299. if (userInteraction) {
  6300. [beginPage, endPage] = value;
  6301. if (pageBeginInput) {
  6302. pageBeginInput.value = beginPage.toString();
  6303. }
  6304. if (pageEndInput) {
  6305. pageEndInput.value = endPage.toString();
  6306. }
  6307. }
  6308. },
  6309. },
  6310. );
  6311. function changedInput() {
  6312. if (pageBeginInput.value === "" || pageEndInput.value === "") {
  6313. return;
  6314. }
  6315. const valBegin = validateMin(
  6316. parseInt(pageBeginInput.value, 10),
  6317. endPage,
  6318. rangeSliderElement,
  6319. );
  6320. const valEnd = validateMax(
  6321. parseInt(pageEndInput.value, 10),
  6322. beginPage,
  6323. rangeSliderElement,
  6324. );
  6325. pageBeginInput.value = valBegin.toString();
  6326. pageEndInput.value = valEnd.toString();
  6327. beginPage = valBegin;
  6328. endPage = valEnd;
  6329. rangeSliderElement.value([valBegin, valEnd]);
  6330. }
  6331. const observerEvent = _.debounce(changedInput, 600);
  6332. ["change", "mouseup", "keyup", "touchend"].forEach((event) => {
  6333. pageBeginInput?.addEventListener(event, observerEvent);
  6334. pageEndInput?.addEventListener(event, observerEvent);
  6335. });
  6336. },
  6337. };
  6338. Swal.fire(options).then((result) => {
  6339. if (result.value) {
  6340. logScript(`Choice: ${beginPage} - ${endPage}`);
  6341. manga.begin = beginPage;
  6342. manga.pages = endPage;
  6343. viewer(manga).then(() => logScript("Page loaded"));
  6344. } else {
  6345. logScript(result.dismiss);
  6346. }
  6347. });
  6348. }
  6349. function createLateStartButton(site, beginning) {
  6350. const button = document.createElement("button");
  6351. button.innerText = getLocaleString("BUTTON_START");
  6352. button.id = "StartMOV";
  6353. button.onclick = () => {
  6354. lateStart(site, beginning).catch(logScript);
  6355. };
  6356. document.body.appendChild(button);
  6357. const style = document.createElement("style");
  6358. style.appendChild(document.createTextNode(startButton + rangeSliderStyles));
  6359. document.head.appendChild(style);
  6360. logScript("Start Button added to page", button);
  6361. }
  6362. function showWaitPopup(site, manga) {
  6363. Swal.fire({
  6364. title: getLocaleString("STARTING"),
  6365. html: html`${manga.begin > 1
  6366. ? `${getLocaleString("RESUME")}${manga.begin}.<br/>`
  6367. : ""}${getLocaleString("WAITING")}`,
  6368. showCancelButton: true,
  6369. cancelButtonColor: "#d33",
  6370. reverseButtons: true,
  6371. timer: 3e3,
  6372. }).then((result) => {
  6373. if (result.value || result.dismiss === Swal.DismissReason.timer) {
  6374. viewer(manga).then(() => logScript("Page loaded"));
  6375. } else {
  6376. createLateStartButton(site, manga.begin);
  6377. logScript(result.dismiss);
  6378. }
  6379. });
  6380. }
  6381. async function preparePage([site, manga]) {
  6382. logScript(`Found Pages: ${manga.pages} in ${site.name}`);
  6383. if (!manga.title) {
  6384. manga.title = document.querySelector("title")?.textContent?.trim();
  6385. }
  6386. manga.begin = isBookmarked() ?? manga.begin ?? 1;
  6387. const style = document.createElement("style");
  6388. style.appendChild(document.createTextNode(sweetalertStyle));
  6389. document.body.appendChild(style);
  6390. unsafeWindow.MOV = (startPage, endPage) => {
  6391. if (startPage !== void 0) {
  6392. manga.begin = startPage;
  6393. }
  6394. if (endPage !== void 0) {
  6395. manga.pages = endPage;
  6396. }
  6397. viewer(manga).then(() => logScript("Page loaded"));
  6398. };
  6399. switch (site.start ?? getUserSettings()?.loadMode) {
  6400. case "never":
  6401. createLateStartButton(site, manga.begin);
  6402. break;
  6403. case "always":
  6404. viewer(manga).then(() => logScript("Page loaded"));
  6405. break;
  6406. case "wait":
  6407. default:
  6408. showWaitPopup(site, manga);
  6409. break;
  6410. }
  6411. }
  6412. async function start(sites) {
  6413. logScript(
  6414. `Starting ${getInfoGM.script.name} ${getInfoGM.script.version} on ${getBrowser()} with ${getEngine()}`,
  6415. );
  6416. if (allowUpload()) return;
  6417. logScript(sites.length, "Known Manga Sites:", sites);
  6418. const foundSites = sites.filter((s) => s.url.test(window.location.href));
  6419. logScript(foundSites.length, "Found sites:", foundSites);
  6420. const testedSites = foundSites.map(async (site) => {
  6421. logScript(`Testing site: ${site.name}`);
  6422. return new Promise((resolve, reject) => {
  6423. Promise.all([
  6424. testTime(site),
  6425. testElement(site),
  6426. testAttribute(site),
  6427. testVariable(site),
  6428. testFunc(site),
  6429. ])
  6430. .then(async () => site.run())
  6431. .then((manga) =>
  6432. manga.pages > 0
  6433. ? resolve([site, manga])
  6434. : reject(new Error(`${site.name} found ${manga.pages} pages`)),
  6435. );
  6436. });
  6437. });
  6438. Promise.race(
  6439. testedSites.map((promise, index) => promise.then(() => index)),
  6440. ).then((fastestIndex) => {
  6441. testedSites.forEach((_promise, i) => {
  6442. if (i !== fastestIndex)
  6443. logScript(`Failed/Skipped: ${foundSites[i].name}`);
  6444. });
  6445. testedSites[fastestIndex].then((result) => {
  6446. preparePage(result);
  6447. });
  6448. });
  6449. }
  6450.  
  6451. start(sites).catch(logScript);
  6452. })();