// ==UserScript==
// @name Manga OnlineViewer
// @author Tago
// @namespace https://github.com/TagoDR
// @description Shows all pages at once in online view for these sites: Asura Scans, Flame Scans, Realm Scans, Batoto, ComiCastle, Dynasty-Scans, InManga, KLManga, Leitor, LHTranslation, MangaBuddy, MangaDex, MangaFox, MangaHere, MangaFreak, Mangago, mangahosted, MangaHub, MangaKakalot, MangaNelo, MangaNato, MangaPark, MangaRaw, Mangareader, MangaSee, Manga4life, MangaTigre, MangaTown, ManhuaScan, NineManga, PandaManga, RawDevart, ReadComicsOnline, ReadManga Today, Funmanga, MangaDoom, MangaInn, SenManga(Raw), ShimadaScans, TenManga, TuMangaOnline, UnionMangas, WebToons, Manga33, ZeroScans, FoOlSlide, Kireicake, Yuri-ism, Sense-Scans, Madara WordPress Plugin, MangaHaus, Isekai Scan, Comic Kiba, Zinmanga, mangatx, Toonily, Mngazuki, ReaperScans, JaiminisBox, DisasterScans, ManhuaPlus
// @version 2022.07.29
// @license MIT
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_listValues
// @grant GM_deleteValue
// @grant GM_xmlhttpRequest
// @connect *
// @require https://cdnjs.cloudflare.com/ajax/libs/tinycolor/1.4.2/tinycolor.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery.imagesloaded/5.0.0/imagesloaded.pkgd.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.9.1/jszip.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/11.4.23/sweetalert2.min.js
// @include /https?:\/\/(www.)?(asurascans|flamescans|realmscans).(com|org)\/.+/
// @include /https?:\/\/(www.)?bato.to\/chapter.*/
// @include /https?:\/\/(www.)?comicastle.org\/read\/.+\/[0-9]+.*/
// @include /https?:\/\/(www.)?dynasty-scans.com\/chapters\/.+/
// @include /https?:\/\/(www.)?inmanga.com\/ver\/manga\/.+\/.+/
// @include /https?:\/\/(www.)?klmanga.com\/.+chapter.+/
// @include /https?:\/\/(www.)?leitor.net\/manga\/.+\/.+\/.+/
// @include /https?:\/\/(www.)?lhtranslation.net\/read.+/
// @include /https?:\/\/(www.)?mangabuddy.com\/.+\/chapter.+/
// @include /https?:\/\/(www.)?mangadex.org\/chapter\/.+(\/.+)?/
// @include /https?:\/\/(www.)?(fanfox.net|mangahere.cc)\/manga\/.+\/.+\//
// @include /https?:\/\/.{3,4}?(mangafreak).net\/Read.+/
// @include /https?:\/\/(www.)?mangago.me\/.*\/.*\/.*/
// @include /https?:\/\/(www.)?mangahosted.com\/manga\/.+\/.+/
// @include /https?:\/\/(www.)?(mangahub).io\/chapter\/.+\/.+/
// @include /https?:\/\/(www.)?((manganelo|mangakakalot).com\/chapter\/.+\/.+|(manganato|readmanganato).com\/manga-\w\w\d+\/chapter-\d+)/
// @include /https?:\/\/(www.)?mangapark.(com|me|org|net)\/(manga|chapter|comic)\/.+\/.+/
// @include /https?:\/\/(www.)?mcreader.net\/reader\/.*/
// @include /https?:\/\/(www.)?mangareader.to\/read\/.+\/.+\/.+/
// @include /https?:\/\/(www.)?(mangasee123|manga4life).com\/read-online\/.+/
// @include /https?:\/\/(www.)?mangatigre.net\/.+\/.+\/.+/
// @include /https?:\/\/(www.|m.)?mangatown.com\/manga\/.+\/.+/
// @include /https?:\/\/(www.)?manhuascan.io\/.+chapter.+/
// @include /https?:\/\/(www.)?ninemanga.com\/chapter\/.+\/.+\.html/
// @include /https?:\/\/(www.)?pandamanga.xyz\/.+\/.+\/.+/
// @include /https?:\/\/(www.)?rawdevart.com\/comic\/.+\/.+\//
// @include /https?:\/\/(www.)?readcomicsonline.ru\/comic\/.*\/\d*/
// @include /https?:\/\/(www.)?(funmanga|mngdoom|readmng|mangainn).(com|net)\/.+\/\d+/
// @include /https?:\/\/raw.senmanga.com\/.+\/.+\/?/
// @include /https?:\/\/(www.)?shimadascans.com\/.+series.+/
// @include /https?:\/\/(www.)?(tenmanga|gardenmanage).com\/(chapter|statuses)\/.+/
// @include /https?:\/\/(www.)?(tmofans|lectortmo|followmanga).com\/.+\/.+\/(paginated|cascade)/
// @include /https?:\/\/(www.)?unionleitor.top\/leitor\/.+\/.+/
// @include /https?:\/\/(www.)?webtoons.com\/.+viewer.+/
// @include /https?:\/\/(www.)?(manga33).com\/manga\/.+/
// @include /https?:\/\/(www.)?zeroscans.com\/comics\/.+/
// @include /^(?!.*jaiminisbox).*\/read\/.+/
// @include /https?:\/\/.+\/(manga|series)\/.+\/.+/
// @exclude /https?:\/\/(www.)?tsumino.com\/.+/
// @exclude /https?:\/\/(www.)?pururin.io\/.+/
// ==/UserScript==
(function () {
'use strict';
// == AsuraScans and FlameScans ====================================================================
var asurasflamecans = {
name: ['Asura Scans', 'Flame Scans', 'Realm Scans'],
url: /https?:\/\/(www.)?(asurascans|flamescans|realmscans).(com|org)\/.+/,
homepage: ['https://www.asurascans.com/', 'https://flamescans.org/', 'https://realmscans.com/'],
language: ['English'],
category: 'manga',
waitEle: '#chapter option:nth-child(2)',
run() {
const chapter = document.querySelector('#chapter option:checked');
const images = [...document.querySelectorAll('#readerarea img')];
return {
title: document.querySelector('.entry-title')?.textContent?.trim(),
series: document.querySelector('.allc a')?.getAttribute('href'),
pages: images.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listImages: images.map((img) => img.getAttribute('data-src') || img.getAttribute('src')),
};
},
};
// == Batoto =======================================================================================
var batoto = {
name: 'Batoto',
url: /https?:\/\/(www.)?bato.to\/chapter.*/,
homepage: 'http://bato.to/',
language: ['English'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('.page-img')];
return {
title: document.querySelector('.nav-title a')?.textContent?.trim(),
series: document.querySelector('.nav-title a')?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('.nav-prev a')?.getAttribute('href'),
next: document.querySelector('.nav-next a')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
// == ComiCastle ===================================================================================
var comicastle = {
name: 'ComiCastle',
url: /https?:\/\/(www.)?comicastle.org\/read\/.+\/[0-9]+.*/,
homepage: 'http://www.comicastle.org/',
language: ['English'],
category: 'comic',
waitEle: '.form-control option:nth-child(1)',
run() {
const images = [...document.querySelectorAll('.form-control')[1].querySelectorAll('option')];
const chapter = document.querySelectorAll('.form-control')[0].querySelector('option:checked');
return {
title: chapter?.textContent?.trim(),
series: document.querySelector('.navbar-header a')?.getAttribute('href'),
pages: images.length,
prev: chapter?.previousElementSibling?.getAttribute('value'),
next: chapter?.nextElementSibling?.getAttribute('value'),
listImages: images.map((img) => img.getAttribute('alt')),
};
},
};
// == DynastyScans =================================================================================
var dysnatyscans = {
name: 'Dynasty-Scans',
url: /https?:\/\/(www.)?dynasty-scans.com\/chapters\/.+/,
homepage: 'https://dynasty-scans.com/',
language: ['English'],
category: 'manga',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
return {
title: document.querySelector('#chapter-title')?.textContent?.trim(),
series: document.querySelector('#chapter-title a')?.getAttribute('href'),
pages: W.pages.length,
prev: document.querySelector('#prev_link')?.getAttribute('href'),
next: document.querySelector('#next_link')?.getAttribute('href'),
listImages: W.pages.map((x) => x.image),
};
},
};
// == InManga ===================================================================================
/* eslint-disable no-underscore-dangle */
var inmanga = {
name: 'InManga',
url: /https?:\/\/(www.)?inmanga.com\/ver\/manga\/.+\/.+/,
homepage: 'https://inmanga.com//',
language: ['Spanish'],
category: 'manga',
waitVar: 'pageController',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const images = [...document.querySelectorAll('#PageList option')];
const chapter = document.querySelector('#ChapList option:checked');
const src = W.pageController._containers.pageUrl;
return {
title: document.querySelector('title')?.textContent?.trim(),
series: `../${W.pageController._containers.mangaIdentification}`,
pages: images.length,
prev: chapter?.previousElementSibling?.getAttribute('value'),
next: chapter?.nextElementSibling?.getAttribute('value'),
listImages: images.map((img, index) => src.replace('identification', img.getAttribute('value')).replace('pageNumber', index)),
};
},
};
// == FoOlSlide ====================================================================================
var foolslide = {
name: ['FoOlSlide', 'Kireicake', 'Yuri-ism', 'Sense-Scans'],
url: /^(?!.*jaiminisbox).*\/read\/.+/,
homepage: [
'#',
'https://reader.kireicake.com',
'https://www.yuri-ism.net',
'https://sensescans.com/',
],
language: ['English'],
obs: 'Any Site that uses FoOLSlide',
category: 'manga',
waitEle: 'img.open',
run() {
const chapter = [...document.querySelectorAll('.topbar_left .dropdown_parent:last-of-type li')];
const origin = chapter.findIndex((item) => {
const url = item.querySelector('a')?.getAttribute('href');
if (url)
return window.location.href.startsWith(url);
return false;
});
const pages = [...document.querySelectorAll('.topbar_right .dropdown li')];
const images = [...document.querySelectorAll('.inner img:not(.open)')];
const num = images.length > 1 ? images.length : pages.length;
return {
title: chapter.at(origin)?.querySelector('a')?.textContent?.trim(),
series: document.querySelector('div.tbtitle div.text a')?.getAttribute('href'),
pages: num,
prev: chapter
.at(origin + 1)
?.querySelector('a')
?.getAttribute('href'),
next: chapter
.at(origin - 1)
?.querySelector('a')
?.getAttribute('href'),
listPages: images.length > 1
? null
: Array(num)
.fill(0)
.map((_, i) => `${window.location.href.replace(/\/\d+$/, '')}/${i + 1}`),
listImages: images.length > 1 ? images.map((img) => img.getAttribute('src')) : null,
img: 'img.open',
};
},
};
// == KLManga ======================================================================================
var klmanga = {
name: 'KLManga',
url: /https?:\/\/(www.)?klmanga.com\/.+chapter.+/,
homepage: 'https://klmanga.com/',
language: ['Raw'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('.chapter-content img')];
const chapter = document.querySelectorAll('.form-control')[0].querySelector('option:checked');
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.navbar-brand')?.getAttribute('href'),
pages: images.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
// == Leitor =======================================================================================
var leitor = {
name: 'Leitor',
url: /https?:\/\/(www.)?leitor.net\/manga\/.+\/.+\/.+/,
homepage: 'https://leitor.net/',
language: ['Portuguese'],
category: 'manga',
async run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const url = `https://leitor.net/leitor/pages/${W.READER_ID_RELEASE}.json?key=${W.READER_TOKEN}`;
const api = await fetch(url).then((res) => res.json());
const chapter = document.querySelector('.chapter-list .selected');
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.series-cover a')?.getAttribute('href'),
pages: api.images.length,
prev: chapter?.nextElementSibling?.querySelector('a')?.getAttribute('href'),
next: chapter?.previousElementSibling?.querySelector('a')?.getAttribute('href'),
listImages: api.images.map((img) => img.avif || img.legacy),
};
},
};
// == LHTranslation ================================================================================
var lhtranslation = {
name: 'LHTranslation',
url: /https?:\/\/(www.)?lhtranslation.net\/read.+/,
homepage: 'https://lhtranslation.net/',
language: ['English'],
category: 'manga',
run() {
const chapter = document.querySelector('.form-control option:checked');
const images = [...document.querySelectorAll('img.chapter-img')];
return {
title: document.querySelector('.chapter-img.tieude font')?.textContent?.trim(),
series: document.querySelector('.navbar-brand.manga-name')?.getAttribute('href'),
pages: images.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
// == Madara WordPress Plugin ======================================================================
// https://themeforest.net/item/madara-wordpress-theme-for-manga/20849828
var madarawp = {
name: [
'Madara WordPress Plugin',
'MangaHaus',
'Isekai Scan',
'Comic Kiba',
'Zinmanga',
'mangatx',
'Toonily',
'Mngazuki',
'ReaperScans',
'JaiminisBox',
'DisasterScans',
'ManhuaPlus',
],
url: /https?:\/\/.+\/(manga|series)\/.+\/.+/,
homepage: [
'#',
'https://manhuaus.com',
'https://isekaiscan.com/',
'https://comickiba.com/',
'https://zinmanga.com/',
'https://mangatx.com/',
'https://toonily.net/',
'https://mangazuki.me/',
'https://reaperscans.com/',
'https://jaiminisbox.net',
'https://disasterscans.com/',
'https://manhuaplus.com/',
],
language: ['English'],
obs: 'Any Site that uses Madara Wordpress Plugin',
category: 'manga',
run() {
const images = [
...document.querySelectorAll('.wp-manga-chapter-img, .blocks-gallery-item img, .reading-content img'),
];
return {
title: document.querySelector('#chapter-heading')?.textContent?.trim(),
series: (document.querySelector('.breadcrumb li:nth-child(3) a') ||
document.querySelector('.breadcrumb li:nth-child(2) a'))?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('.prev_page')?.getAttribute('href'),
next: document.querySelector('.next_page')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('src') ||
img.getAttribute('data-src') ||
img.getAttribute('data-full-url')),
};
},
};
// == MangaBuddy ===================================================================================
var mangabuddy = {
name: 'MangaBuddy',
url: /https?:\/\/(www.)?mangabuddy.com\/.+\/chapter.+/,
homepage: 'https://mangabuddy.com/',
language: ['English'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('#chapter-images img')];
return {
title: document.querySelector('.chapter-info')?.textContent?.trim(),
series: document
.querySelector('#breadcrumbs-container div:nth-child(2) a')
?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('a.prev')?.getAttribute('href'),
next: document.querySelector('a.next')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
// == MangaDex =====================================================================================
var mangadex = {
name: 'MangaDex',
url: /https?:\/\/(www.)?mangadex.org\/chapter\/.+(\/.+)?/,
homepage: 'https://mangadex.org/',
language: ['English'],
category: 'manga',
waitEle: "a[href^='/chapter/']",
async run() {
const chapterId = window.location.pathname.match(/\/chapter\/([^/]+)(\/\d+)?/)[1];
const home = `https://api.mangadex.org/at-home/server/${chapterId}`;
const server = await fetch(home).then((res) => res.json());
const images = server.chapter.data;
const chapters = document.querySelectorAll("a[href^='/chapter/']");
return {
title: document.querySelector('title')?.text.replace(' - MangaDex', ''),
series: document.querySelector("a.text-primary[href^='/title/']")?.getAttribute('href'),
pages: images.length,
prev: chapters[1].getAttribute('href'),
next: chapters[0].getAttribute('href'),
listImages: images.map((img) => `${server.baseUrl}/data/${server.chapter.hash}/${img}`),
};
},
};
// == MangaFox =====================================================================================
var mangafox = {
name: ['MangaFox', 'MangaHere'],
url: /https?:\/\/(www.)?(fanfox.net|mangahere.cc)\/manga\/.+\/.+\//,
homepage: ['https://fanfox.net/', 'https://www.mangahere.cc/'],
language: ['English'],
category: 'manga',
waitVar: 'chapterid',
async run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const key = document.querySelector('#dm5_key')?.getAttribute('value');
const options = {
method: 'GET',
headers: {
'Content-Type': 'text/plain',
},
};
const src = Array(W.imagecount)
.fill(0)
.map(async (_, i) => {
const url = `chapterfun.ashx?cid=${W.chapterid || W.chapter_id}&page=${i}&key=${key}`;
const api = await fetch(url, options).then((res) => res.text());
// eslint-disable-next-line no-eval
(0, eval)(api);
// @ts-ignore
return d;
});
const images = await Promise.all(src);
return {
title: document.querySelector('.reader-header-title div')?.textContent?.trim(),
series: document.querySelector('.reader-header-title a')?.getAttribute('href'),
pages: W.imagecount,
prev: W.prechapterurl,
next: W.nextchapterurl,
listImages: images.map((img, i) => img[i === 0 ? 0 : 1]),
};
},
};
// == MangaFreak ===================================================================================
var mangafreak = {
name: 'MangaFreak',
url: /https?:\/\/.{3,4}?(mangafreak).net\/Read.+/,
homepage: 'https://mangafreak.net/',
language: ['English'],
category: 'manga',
run() {
const chapter = document.querySelector('.chapter_list select option:checked');
const images = [...document.querySelectorAll('.mySlides img')];
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.title a')?.getAttribute('href'),
pages: images.length,
prev: chapter?.previousElementSibling?.getAttribute('value'),
next: chapter?.nextElementSibling?.getAttribute('value'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
var mangago = {
name: 'Mangago',
url: /https?:\/\/(www.)?mangago.me\/.*\/.*\/.*/,
homepage: 'https://www.mangago.me/',
language: ['English'],
category: 'manga',
waitVar: 'imgsrcs',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const key = CryptoJS.enc.Hex.parse('e11adc3949ba59abbe56e057f20f883e');
const iv = CryptoJS.enc.Hex.parse('1234567890abcdef1234567890abcdef');
const opinion = { iv, padding: CryptoJS.pad.ZeroPadding };
const images = CryptoJS.AES.decrypt(W.imgsrcs, key, opinion)
.toString(CryptoJS.enc.Utf8)
.split(',');
return {
title: `${W.manga_name} ${W.chapter_name}`,
series: W.mid,
pages: W.total_pages,
prev: document.querySelector('.recom p:nth-child(5) a')?.getAttribute('href'),
next: W.next_c_url,
listImages: images,
};
},
};
// == mangahosted ===================================================================================
var mangahosted = {
name: 'mangahosted',
url: /https?:\/\/(www.)?mangahosted.com\/manga\/.+\/.+/,
homepage: 'https://mangahosted.com/',
language: ['Portuguese'],
category: 'manga',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const images = [...document.querySelectorAll('picture img')];
return {
title: $('.breadcrumb li:eq(3)').text().trim(),
series: $('.breadcrumb li:eq(2) a').attr('href'),
pages: images.length,
prev: W.$read_prev,
next: W.$read_next,
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
// == MangaHub =====================================================================================
var mangahub = {
name: 'MangaHub',
url: /https?:\/\/(www.)?(mangahub).io\/chapter\/.+\/.+/,
homepage: 'https://mangahub.io/',
language: ['English'],
category: 'manga',
waitEle: '#select-chapter',
async run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const slug = W.CURRENT_MANGA_SLUG || window.location.pathname.split('/')[2];
const number = window.location.pathname.split('/')[3].replace('chapter-', '');
const data = { query: `{chapter(x:m01,slug:"${slug}",number:${number}){pages}}` };
const options = {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
},
};
const api = await fetch('https://api.mghubcdn.com/graphql', options).then((res) => res.json());
const images = Object.values(JSON.parse(api?.data.chapter.pages.toString()));
return {
title: document.querySelector('#mangareader h3')?.textContent?.trim(),
series: document.querySelector('#mangareader a')?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('.previous a')?.getAttribute('href'),
next: document.querySelector('.next a')?.getAttribute('href'),
listImages: images.map((i) => `https://img.mghubcdn.com/file/imghub/${i}`),
};
},
};
// == MangaKakalot =================================================================================
var mangakakalot = {
name: ['MangaKakalot', 'MangaNelo', 'MangaNato'],
url: /https?:\/\/(www.)?((manganelo|mangakakalot).com\/chapter\/.+\/.+|(manganato|readmanganato).com\/manga-\w\w\d+\/chapter-\d+)/,
homepage: [
'https://mangakakalot.com/page',
'https://www.manganelo.com/',
'https://www.manganato.com/',
],
language: ['English'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('#vungdoc img, .container-chapter-reader img')];
return {
title: document
.querySelector('.info-top-chapter h2, .imageOptions-chapter-info-top h1, .panel-chapter-info-top h1')
?.textContent?.trim(),
series: document.querySelectorAll('span a[title]').item(1).getAttribute('href'),
pages: images.length,
prev: document.querySelector('.navi-change-chapter-btn-prev, .next')?.getAttribute('href'),
next: document.querySelector('.navi-change-chapter-btn-next, .back')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
var mangapark = {
name: 'MangaPark',
url: /https?:\/\/(www.)?mangapark.(com|me|org|net)\/(manga|chapter|comic)\/.+\/.+/,
homepage: 'https://mangapark.net/',
language: ['English'],
category: 'manga',
waitVar: 'CryptoJS',
run() {
const pass = JSON.parse(CryptoJS.AES.decrypt(amWord, amPass).toString(CryptoJS.enc.Utf8));
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.nav-title a')?.getAttribute('href'),
pages: imgPathLis.length,
prev: document.querySelector('.nav-prev a')?.getAttribute('href'),
next: document.querySelector('.nav-next a')?.getAttribute('href'),
listImages: imgPathLis.map((i, index) => `${imgCdnHost + i}?${pass[index]}`),
};
},
};
// == MangaRaw =====================================================================================
var mangaraw = {
name: 'MangaRaw',
url: /https?:\/\/(www.)?mcreader.net\/reader\/.*/,
homepage: 'https://www.manga-raw.club/',
language: ['English'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('#chapter-reader img')];
return {
title: document.querySelector('.titles')?.textContent?.trim(),
series: document.querySelector('.titles a')?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('.chnav.prev')?.getAttribute('href'),
next: document.querySelector('.chnav.next')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
// == Mangareader ==================================================================================
var mangareader = {
name: 'Mangareader',
url: /https?:\/\/(www.)?mangareader.to\/read\/.+\/.+\/.+/,
homepage: 'https://mangareader.to',
language: ['English'],
category: 'manga',
obs: 'Some galleries will not be usable',
waitEle: '.ds-image, .iv-card',
run() {
const chapter = document.querySelector('.chapter-item.active');
const images = [
...document.querySelectorAll('.ds-image:not(.shuffled)[data-url], .iv-card:not(.shuffled)[data-url]'),
];
return {
title: document.querySelector('.hr-manga h2')?.textContent?.trim(),
series: document.querySelector('.hr-manga')?.getAttribute('href'),
pages: images.length,
prev: chapter?.nextElementSibling?.querySelector('a')?.getAttribute('href'),
next: chapter?.previousElementSibling?.querySelector('a')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('data-url')),
};
},
};
// == MangaSee =====================================================================================
var mangasee = {
name: ['MangaSee', 'Manga4life'],
url: /https?:\/\/(www.)?(mangasee123|manga4life).com\/read-online\/.+/,
homepage: ['https://mangasee123.com/', 'https://manga4life.com/'],
language: ['English'],
category: 'manga',
waitAttr: ['.img-fluid', 'src'],
run() {
const src = document.querySelector('.img-fluid')?.getAttribute('src') || '';
const script = [...document.querySelectorAll('body script:not([src])')].at(-1)?.textContent;
const textCurChapter = script?.match(/CurChapter = ({.+});/) || [];
const CurChapter = JSON.parse(textCurChapter[1]);
const textCHAPTERS = script?.match(/CHAPTERS = (\[\{.+}]);/) || [];
const CHAPTERS = JSON.parse(textCHAPTERS[1]);
const CurChapterIndex = CHAPTERS.findIndex((chap) => chap.Chapter === CurChapter.Chapter);
function ChapterURLEncode(reference) {
let ChapterString = CHAPTERS[CurChapterIndex + reference];
if (ChapterString === undefined) {
return '#';
}
ChapterString = ChapterString.Chapter;
let Index = '';
const IndexString = ChapterString.substring(0, 1);
if (IndexString !== '1') {
Index = `-index-${IndexString}`;
}
const Chapter = parseInt(ChapterString.slice(1, -1), 10);
let Odd = '';
const OddString = ChapterString[ChapterString.length - 1];
if (OddString !== '0') {
Odd = `.${OddString}`;
}
return window.location.href.replace(/-chapter-.+/, `-chapter-${Chapter}${Odd}${Index}.html`);
}
return {
title: document
.querySelector('title')
?.textContent?.replace(/ Page .+/, '')
.trim(),
series: document.querySelector('.MainContainer a')?.getAttribute('href'),
pages: parseInt(CurChapter.Page, 10),
prev: ChapterURLEncode(-1),
next: ChapterURLEncode(+1),
listImages: Array(parseInt(CurChapter.Page, 10))
.fill(0)
.map((_, i) => src.replace(/-\d\d\d.png/, `-${String(`000${i + 1}`).slice(-3)}.png`)),
};
},
};
// == MangaTigre ===================================================================================
var mangatigre = {
name: 'MangaTigre',
url: /https?:\/\/(www.)?mangatigre.net\/.+\/.+\/.+/,
homepage: 'https://www.mangatigre.net/',
language: ['Spanish'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('.chapter-content img')];
const chapter = document.querySelector('.form-control option:checked');
return {
title: document.querySelector('.page-title')?.textContent?.trim(),
series: document.querySelector('.breadcrumb li:nth-child(3) a')?.getAttribute('href'),
pages: images.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listImages: images.map((img) => img.getAttribute('data-src') || img.getAttribute('src')),
};
},
};
// == MangaTown ====================================================================================
var mangatown = {
name: 'MangaTown',
url: /https?:\/\/(www.|m.)?mangatown.com\/manga\/.+\/.+/,
homepage: 'https://www.mangatown.com/',
language: ['English'],
category: 'manga',
waitVar: 'chapter_id',
async run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const key = document.querySelector('#dm5_key')?.getAttribute('value');
const options = {
method: 'GET',
headers: {
'Content-Type': 'text/plain',
},
};
const src = Array(W.total_pages)
.fill(0)
.map(async (_, i) => {
const url = `chapterfun.ashx?cid=${W.chapter_id}&page=${i}&key=${key}`;
const api = await fetch(url, options).then((res) => res.text());
// eslint-disable-next-line no-eval
(0, eval)(api);
// @ts-ignore
return d;
});
const images = await Promise.all(src);
const chapter = document.querySelector('#top_chapter_list option:checked');
return {
title: document.querySelector('.title h1')?.textContent,
series: W.series_url,
pages: images.length,
prev: chapter?.previousElementSibling?.getAttribute('value'),
next: chapter?.nextElementSibling?.getAttribute('value'),
listImages: images.map((img, i) => img[i === 0 ? 0 : 1]),
};
},
};
var manhuascan = {
name: 'ManhuaScan',
url: /https?:\/\/(www.)?manhuascan.io\/.+chapter.+/,
homepage: 'https://manhuascan.io/',
language: ['English'],
category: 'manga',
waitVar: 'imgsrcs',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const key = CryptoJS.enc.Hex.parse('e11adc3949ba59abbe56e057f20f131e');
const iv = CryptoJS.enc.Hex.parse('1234567890abcdef1234567890abcdef');
const opinion = { iv, padding: CryptoJS.pad.ZeroPadding };
const images = CryptoJS.AES.decrypt(W.imgsrcs, key, opinion)
.toString(CryptoJS.enc.Utf8)
.split(',');
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.breadcrumb li:nth-child(3) a')?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('.chapter-select a:first-of-type')?.getAttribute('href'),
next: document.querySelector('.chapter-select a:last-of-type')?.getAttribute('href'),
listImages: images,
};
},
};
// == NineManga ====================================================================================
var ninemanga = {
name: 'NineManga',
url: /https?:\/\/(www.)?ninemanga.com\/chapter\/.+\/.+\.html/,
homepage: 'https://ninemanga.com/',
language: ['English'],
category: 'manga',
run() {
const chapter = document.querySelector('#chapter option:checked');
const pages = [...document.querySelector('#page').querySelectorAll('option')];
return {
title: document.querySelector('.tip a')?.textContent?.trim(),
series: document.querySelector('.subgiude > li:nth-child(2) > a')?.getAttribute('href'),
pages: pages.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listPages: pages.map((item) => $(item).val()),
img: '.manga_pic',
};
},
};
// == PandaManga ==================================================================================
var pandamanga = {
name: 'PandaManga',
url: /https?:\/\/(www.)?pandamanga.xyz\/.+\/.+\/.+/,
homepage: 'https://www.pandamanga.com/',
language: ['English'],
category: 'manga',
run() {
const chapter = document.querySelector('.select-chapter option:checked');
const data = JSON.parse(document.getElementById('__NEXT_DATA__').textContent);
const images = data.props.pageProps.mangaview.source
.split(',')
.filter((url) => url.length > 0);
return {
title: data.props.pageProps.mangaview.nameSeoChapter,
series: document.querySelector('.allc a')?.getAttribute('href'),
pages: images.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listImages: images,
};
},
};
// == RawDevart ===================================================================================
var rawdevart = {
name: 'RawDevart',
url: /https?:\/\/(www.)?rawdevart.com\/comic\/.+\/.+\//,
homepage: 'https://rawdevart.com',
language: ['Raw'],
category: 'manga',
waitVar: 'rconfig',
waitEle: '#chapter-list select',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const chapter = document.querySelector('#chapter-list option:checked');
const images = [...document.querySelectorAll('#img-container img')];
return {
title: W.rconfig.chapterTitle,
series: W.rconfig.prefix,
pages: images.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listImages: images.map((item) => $(item).attr('data-src') || $(item).attr('src')),
};
},
};
// == ReadComicsOnline =============================================================================
var readcomicsonline = {
name: 'ReadComicsOnline',
url: /https?:\/\/(www.)?readcomicsonline.ru\/comic\/.*\/\d*/,
homepage: 'https://readcomicsonline.ru/',
language: ['English'],
category: 'comic',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const images = [...document.querySelectorAll('#all img')];
return {
title: W.title.replace(/ - Page \d+/, ''),
series: document.querySelector('div.pager-cnt a')?.getAttribute('href'),
pages: W.pages.length,
prev: W.prev_chapter,
next: W.next_chapter,
listImages: images.map((img) => img.getAttribute('data-src')),
};
},
};
// == ReadManga.Today ==============================================================================
var readmangatoday = {
name: ['ReadManga Today', 'Funmanga', 'MangaDoom', 'MangaInn'],
url: /https?:\/\/(www.)?(funmanga|mngdoom|readmng|mangainn).(com|net)\/.+\/\d+/,
homepage: [
'https://www.readmng.com/',
'https://funmanga.com/',
'https://mngdoom.com/',
'https://www.mangainn.net/',
],
language: ['English'],
category: 'manga',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
return {
title: W.chapter_page_title,
series: W.manga_url,
pages: W.images.length,
prev: W.prev_chapter_url,
next: W.next_chapter_url,
listImages: W.images.map((item) => item.url),
};
},
};
// == SenManga =====================================================================================
var senmanga = {
name: 'SenManga(Raw)',
url: /https?:\/\/raw.senmanga.com\/.+\/.+\/?/,
homepage: 'https://raw.senmanga.com/',
language: ['Raw'],
category: 'manga',
run() {
const url = `/${window.location.pathname.split('/')[1]}/${window.location.pathname.split('/')[2]}`;
const num = parseInt(document.querySelector('.page-list select option:last-child')?.getAttribute('value') || '0', 10);
const chapter = [...document.querySelectorAll('.dropdown-chapter li')];
const origin = chapter.findIndex((item) => item.querySelector('a')?.getAttribute('href') === window.location.href);
return {
title: $('.title').text().trim(),
series: document.querySelector('.breadcrumb li:nth-child(2) a')?.getAttribute('href'),
pages: num,
prev: chapter
.at(origin + 1)
?.querySelector('a')
?.getAttribute('href'),
next: chapter
.at(origin - 1)
?.querySelector('a')
?.getAttribute('href'),
listPages: Array(num)
.fill(0)
.map((_, i) => `${url}/${i + 1}/`),
img: '.picture',
};
},
};
// == ShimadaScans =================================================================================
var shimadascans = {
name: 'ShimadaScans',
url: /https?:\/\/(www.)?shimadascans.com\/.+series.+/,
homepage: 'https://shimadascans.com/',
language: ['English'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('.reading-content img')];
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.breadcrumb li:nth-child(2) a')?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('a.prev_page')?.getAttribute('href'),
next: document.querySelector('a.next_page')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
// == TenManga =====================================================================================
var tenmanga = {
name: 'TenManga',
url: /https?:\/\/(www.)?(tenmanga|gardenmanage).com\/(chapter|statuses)\/.+/,
homepage: 'https://www.tenmanga.com/',
language: ['English'],
category: 'manga',
waitVar: '_pageCtrl',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const chapter = document.querySelector('.mangaread-pagenav select option:checked');
// eslint-disable-next-line no-underscore-dangle
const images = W._pageCtrl.options.all_imgs_url;
return {
title: document.querySelector('.title h1')?.textContent?.trim(),
series: document.querySelector('.title a:nth-child(2)')?.getAttribute('href'),
pages: images.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listImages: images,
};
},
};
// == TMOFans ==================================================================================
var tmofans = {
name: 'TuMangaOnline',
url: /https?:\/\/(www.)?(tmofans|lectortmo|followmanga).com\/.+\/.+\/(paginated|cascade)/,
homepage: 'https://lectortmo.com/',
language: ['Spanish'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('.img-container img')];
const pages = [
...document.querySelectorAll('div.container:nth-child(4) select#viewer-pages-select option'),
];
const num = images.length > 1 ? images.length : pages.length;
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('a[title="Volver"]')?.getAttribute('href'),
pages: num,
prev: document.querySelector('.chapter-prev a')?.getAttribute('href'),
next: document.querySelector('.chapter-next a')?.getAttribute('href'),
listPages: images.length > 1
? null
: Array(num)
.fill(0)
.map((_, i) => `${window.location.href.replace(/\/\d+$/, '')}/${i + 1}`),
listImages: images.length > 1 ? images.map((item) => $(item).attr('data-src')) : null,
img: '#viewer-container img, .viewer-page',
};
},
};
// == UnionMangas =================================================================================
var unionmangas = {
name: 'UnionMangas',
url: /https?:\/\/(www.)?unionleitor.top\/leitor\/.+\/.+/,
homepage: 'https://unionleitor.top/',
language: ['Portuguese'],
category: 'manga',
run() {
const chapter = document.querySelector('#capitulo_trocar option:checked');
const images = [...document.querySelectorAll('.img-manga')];
return {
title: document.querySelector('.titulo-leitura')?.textContent?.trim(),
series: document.querySelector('.breadcrumbs a:nth-child(3)')?.getAttribute('href'),
pages: images.length,
prev: chapter?.previousElementSibling?.getAttribute('value'),
next: chapter?.nextElementSibling?.getAttribute('value'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
// == WebToons =====================================================================================
var webtoons = {
name: 'WebToons',
url: /https?:\/\/(www.)?webtoons.com\/.+viewer.+/,
homepage: 'https://www.webtoons.com/',
language: ['English'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('#_imageList img')];
return {
title: document.querySelector('.subj_info')?.textContent?.trim(),
series: document.querySelector('.subj_info a')?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('._prevEpisode')?.getAttribute('href'),
next: document.querySelector('._nextEpisode')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('data-src') || img.getAttribute('src')),
};
},
};
// == WPManga ======================================================================================
var wpmanga = {
name: ['Manga33'],
url: /https?:\/\/(www.)?(manga33).com\/manga\/.+/,
homepage: ['https://manga33.com/'],
language: ['English'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('.chapter-content img')];
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.navbar-brand')?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('a.prev')?.getAttribute('href'),
next: document.querySelector('a.next')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('src')),
before() {
if (window.location.pathname.match(/all.html$/))
return;
if (window.location.pathname.match(/\d+-\d+.html$/))
window.location.pathname = window.location.pathname.replace(/-\d+.html$/, '-all.html');
},
};
},
};
// == ZeroScans ====================================================================================
var zeroscans = {
name: 'ZeroScans',
url: /https?:\/\/(www.)?zeroscans.com\/comics\/.+/,
homepage: 'https://zeroscans.com/',
language: ['English'],
category: 'manga',
waitVar: '__ZEROSCANS__',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
// eslint-disable-next-line no-underscore-dangle
const images = W.__ZEROSCANS__.data.at(0).current_chapter.high_quality;
const chapters = document.querySelectorAll('.v-btn--router');
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.v-breadcrumbs li:nth-child(2) + a')?.getAttribute('href'),
pages: images.length,
prev: chapters[0]?.getAttribute('href'),
next: chapters[1]?.getAttribute('href'),
listImages: images,
};
},
};
/* eslint-disable no-unused-vars,@typescript-eslint/no-unused-vars */
const sites = [
asurasflamecans,
batoto,
comicastle,
dysnatyscans,
inmanga,
klmanga,
leitor,
lhtranslation,
mangabuddy,
mangadex,
mangafox,
mangafreak,
mangago,
mangahosted,
mangahub,
mangakakalot,
mangapark,
mangaraw,
mangareader,
mangasee,
mangatigre,
mangatown,
manhuascan,
ninemanga,
pandamanga,
rawdevart,
readcomicsonline,
readmangatoday,
senmanga,
shimadascans,
tenmanga,
tmofans,
unionmangas,
webtoons,
wpmanga,
zeroscans,
foolslide,
madarawp, // Must be at the end because is a generic check
];
/* eslint-disable camelcase */
function logScript(...text) {
// eslint-disable-next-line no-console
console.log('MangaOnlineViewer: ', ...text);
return text;
}
// Replacement function for GM_listValues allowing for debugging in console
function getListGM() {
return typeof GM_listValues !== 'undefined' ? GM_listValues() : [];
}
// Replacement function for GM_listValues allowing for debugging in console
function removeValueGM(name) {
return typeof GM_deleteValue !== 'undefined'
? GM_deleteValue(name)
: logScript('Removing: ', name);
}
// Replacement function for GM_info allowing for debugging in console
const getInfoGM = typeof GM_info !== 'undefined'
? GM_info
: {
scriptHandler: 'Console',
script: {
name: 'Debug',
version: 'Testing',
},
};
// Replacement function for GM_getValue allowing for debugging in console
function getValueGM(name, defaultValue = null) {
if (typeof GM_getValue !== 'undefined') {
// logScript('Getting: ', name, ' = ', defaultValue);
return GM_getValue(name, defaultValue);
}
logScript('Fake Getting: ', name, ' = ', defaultValue);
return defaultValue;
}
// Replacement function for GM_setValue allowing for debugging in console
function setValueGM(name, value) {
try {
GM_setValue(name, value);
// logScript('Setting: ', name, ' = ', value as any);
return value.toString();
}
catch (e) {
logScript('Fake Setting: ', name, ' = ', value);
return String(value);
}
}
// See https://stackoverflow.com/a/2401861/331508 for optional browser sniffing code.
function getBrowser() {
const ua = navigator.userAgent;
let tem;
let M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
if (/trident/i.test(M[1])) {
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
return `IE ${tem[1] || ''}`;
}
if (M[1] === 'Chrome') {
tem = ua.match(/\b(OPR|Edge)\/(\d+)/);
if (tem !== null) {
return tem.slice(1).join(' ').replace('OPR', 'Opera');
}
}
M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];
tem = ua.match(/version\/(\d+)/i);
if (tem !== null) {
M.splice(1, 1, tem[1]);
}
return M.join(' ');
}
// See
// https://stackoverflow.com/questions/27487828/how-to-detect-if-a-userscript-is-installed-from-the-chrome-store
function getEngine() {
return getInfoGM.scriptHandler || 'Greasemonkey';
}
const isMobile = window.matchMedia('screen and (max-width: 768px)').matches;
/**
* Checks if a JavaScript value is empty
* @example
* isEmpty(null) // true
* isEmpty(undefined) // true
* isEmpty([]) // true
* isEmpty({}) // true
* isEmpty("") // true
* isEmpty(false) // false
* isEmpty(0) // false
* isEmpty([{},{"0":false},{"":0},{"0":0}]) // false
* isEmpty(42) // false
* isEmpty([{"":1},{"0":1}]) // false
* @param {any} value - item to test
* @returns {boolean} true if empty, otherwise false
*/
function isEmpty(value) {
return (value === null || // check for null
typeof value === 'undefined' ||
value === undefined || // check for undefined
(typeof value === 'string' && value === '') || // check for empty string
(Array.isArray(value) && value.length === 0) || // check for empty array
(typeof value === 'object' && Object.keys(value).length === 0));
}
/**
* Checks if value is nothing. Deep-checks arrays and objects
* @example
* isNothing(null) // true
* isNothing(undefined) // true
* isNothing([]) // true
* isNothing({}) // true
* isNothing("") // true
* isNothing(false) // true
* isNothing(0) // true
* isNothing([{},{"0":false},{"":0},{"0":0}]) // true
* isNothing(42) // false
* isNothing([{"":1},{"0":1}]) // false
* @param {any} value - item to test
* @returns {boolean} true if nothing, otherwise false
*/
function isNothing(value) {
const isEmptyObject = (a) => {
if (!Array.isArray(a)) {
// it's an Object, not an Array
const hasNonempty = Object.keys(a).some((element) => !isNothing(a[element]));
return hasNonempty ? false : isEmptyObject(Object.keys(a));
}
// check if array is really not empty as JS thinks at least one element should be non-empty
return !a.some((element) => !isNothing(element));
};
return (
// eslint-disable-next-line eqeqeq
value == false ||
value === 0 ||
isEmpty(value) ||
(typeof value === 'object' && isEmptyObject(value)));
}
const colors = {
dark: {
name: 'dark',
50: '#C1C2C5',
100: '#A6A7AB',
200: '#909296',
300: '#5c5f66',
400: '#373A40',
500: '#2C2E33',
600: '#25262b',
700: '#1A1B1E',
800: '#141517',
900: '#101113',
},
gray: {
name: 'gray',
50: '#f8f9fa',
100: '#f1f3f5',
200: '#e9ecef',
300: '#dee2e6',
400: '#ced4da',
500: '#adb5bd',
600: '#868e96',
700: '#495057',
800: '#343a40',
900: '#212529',
},
red: {
name: 'red',
50: '#fff5f5',
100: '#ffe3e3',
200: '#ffc9c9',
300: '#ffa8a8',
400: '#ff8787',
500: '#ff6b6b',
600: '#fa5252',
700: '#f03e3e',
800: '#e03131',
900: '#c92a2a',
},
pink: {
name: 'pink',
50: '#fff0f6',
100: '#ffdeeb',
200: '#fcc2d7',
300: '#faa2c1',
400: '#f783ac',
500: '#f06595',
600: '#e64980',
700: '#d6336c',
800: '#c2255c',
900: '#a61e4d',
},
grape: {
name: 'grape',
50: '#f8f0fc',
100: '#f3d9fa',
200: '#eebefa',
300: '#e599f7',
400: '#da77f2',
500: '#cc5de8',
600: '#be4bdb',
700: '#ae3ec9',
800: '#9c36b5',
900: '#862e9c',
},
violet: {
name: 'violet',
50: '#f3f0ff',
100: '#e5dbff',
200: '#d0bfff',
300: '#b197fc',
400: '#9775fa',
500: '#845ef7',
600: '#7950f2',
700: '#7048e8',
800: '#6741d9',
900: '#5f3dc4',
},
indigo: {
name: 'purple',
50: '#edf2ff',
100: '#dbe4ff',
200: '#bac8ff',
300: '#91a7ff',
400: '#748ffc',
500: '#5c7cfa',
600: '#4c6ef5',
700: '#4263eb',
800: '#3b5bdb',
900: '#364fc7',
},
blue: {
name: 'blue',
50: '#e7f5ff',
100: '#d0ebff',
200: '#a5d8ff',
300: '#74c0fc',
400: '#4dabf7',
500: '#339af0',
600: '#228be6',
700: '#1c7ed6',
800: '#1971c2',
900: '#1864ab',
},
cyan: {
name: 'cyan',
50: '#e3fafc',
100: '#c5f6fa',
200: '#99e9f2',
300: '#66d9e8',
400: '#3bc9db',
500: '#22b8cf',
600: '#15aabf',
700: '#1098ad',
800: '#0c8599',
900: '#0b7285',
},
teal: {
name: 'teal',
50: '#e6fcf5',
100: '#c3fae8',
200: '#96f2d7',
300: '#63e6be',
400: '#38d9a9',
500: '#20c997',
600: '#12b886',
700: '#0ca678',
800: '#099268',
900: '#087f5b',
},
green: {
name: 'green',
50: '#ebfbee',
100: '#d3f9d8',
200: '#b2f2bb',
300: '#8ce99a',
400: '#69db7c',
500: '#51cf66',
600: '#40c057',
700: '#37b24d',
800: '#2f9e44',
900: '#2b8a3e',
},
lime: {
name: 'lime',
50: '#f4fce3',
100: '#e9fac8',
200: '#d8f5a2',
300: '#c0eb75',
400: '#a9e34b',
500: '#94d82d',
600: '#82c91e',
700: '#74b816',
800: '#66a80f',
900: '#5c940d',
},
yellow: {
name: 'yellow',
50: '#fff9db',
100: '#fff3bf',
200: '#ffec99',
300: '#ffe066',
400: '#ffd43b',
500: '#fcc419',
600: '#fab005',
700: '#f59f00',
800: '#f08c00',
900: '#e67700',
},
orange: {
name: 'orange',
50: '#fff4e6',
100: '#ffe8cc',
200: '#ffd8a8',
300: '#ffc078',
400: '#ffa94d',
500: '#ff922b',
600: '#fd7e14',
700: '#f76707',
800: '#e8590c',
900: '#d9480f',
},
darkblue: {
name: 'darkblue',
50: '#E8F4F9',
100: '#D9DEE9',
200: '#B7C2DA',
300: '#6482C0',
400: '#4267B2',
500: '#385898',
600: '#314E89',
700: '#29487D',
800: '#223B67',
900: '#1E355B',
},
};
const darkest = 10;
const lightest = 95;
function setLightness(hsl, lightness) {
hsl.l = lightness;
return tinycolor(hsl).toHexString();
}
function getTextColor(hex) {
const color = tinycolor(hex);
const hsl = color.toHsl();
return setLightness(hsl, color.isDark() ? lightest : darkest);
}
function svgToUrl(str) {
const cleaned = str
.replace(/[\t\n\r]/gim, '') // Strip newlines and tabs
.replace(/\s\s+/g, ' ') // Condense multiple spaces
.replace(/'/gim, '\\i'); // Normalize quotes
const encoded = encodeURIComponent(cleaned)
.replace(/\(/g, '%28') // Encode brackets
.replace(/\)/g, '%29');
return `data:image/svg+xml;charset=UTF-8,${encoded}`;
}
Object.values(colors).map((i) => i['900']);
// Icons from https://tabler-icons.io/
// Icons for Navigation
const IconArrowBigRight = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<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" />
</svg>`;
const IconArrowBigLeft = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<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"></path>
</svg>`;
const IconFileDownload = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M14 3v4a1 1 0 0 0 1 1h4"></path>
<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"></path>
<path color="gold" d="M12 17v-6"></path>
<path color="gold" d="M9.5 14.5l2.5 2.5l2.5 -2.5"></path>
</svg>`;
const IconLoader2 = `
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-loader-2 inverse animate-spin" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 3a9 9 0 1 0 9 9"></path>
</svg>`;
const IconCategory = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M4 4h6v6h-6z"></path>
<path d="M14 4h6v6h-6z"></path>
<path d="M4 14h6v6h-6z"></path>
<circle cx="17" cy="17" r="3"></circle>
</svg>`;
// Icons for Global Controls
const IconX = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>`;
const IconMenu2 = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line x1="4" y1="6" x2="20" y2="6"></line>
<line x1="4" y1="12" x2="20" y2="12"></line>
<line x1="4" y1="18" x2="20" y2="18"></line>
</svg>`;
const IconArrowAutofitWidth = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M4 12v-6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v6"></path>
<path color="yellow" d="M10 18h-7"></path>
<path color="yellow" d="M21 18h-7"></path>
<path color="yellow" d="M6 15l-3 3l3 3"></path>
<path color="yellow" d="M18 15l3 3l-3 3"></path>
</svg>`;
const IconArrowAutofitHeight = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 20h-6a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h6"></path>
<path color="yellow" d="M18 14v7"></path>
<path color="yellow" d="M18 3v7"></path>
<path color="yellow" d="M15 18l3 3l3 -3"></path>
<path color="yellow" d="M15 6l3 -3l3 3"></path>
</svg>`;
const IconZoomInArea = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path color="lime" d="M15 13v4"></path>
<path color="lime" d="M13 15h4"></path>
<circle cx="15" cy="15" r="5"></circle>
<path d="M22 22l-3 -3"></path>
<path d="M6 18h-1a2 2 0 0 1 -2 -2v-1"></path>
<path d="M3 11v-1"></path>
<path d="M3 6v-1a2 2 0 0 1 2 -2h1"></path>
<path d="M10 3h1"></path>
<path d="M15 3h1a2 2 0 0 1 2 2v1"></path>
</svg>`;
const IconZoomOutArea = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path color="red" d="M13 15h4"></path>
<circle cx="15" cy="15" r="5"></circle>
<path d="M22 22l-3 -3"></path>
<path d="M6 18h-1a2 2 0 0 1 -2 -2v-1"></path>
<path d="M3 11v-1"></path>
<path d="M3 6v-1a2 2 0 0 1 2 -2h1"></path>
<path d="M10 3h1"></path>
<path d="M15 3h1a2 2 0 0 1 2 2v1"></path>
</svg>`;
const IconZoomPan = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<circle cx="12" cy="12" r="3"></circle>
<path d="M17 17l-2.5 -2.5"></path>
<path color="#9966FF" d="M10 5l2 -2l2 2"></path>
<path color="#9966FF" d="M19 10l2 2l-2 2"></path>
<path color="#9966FF" d="M5 10l-2 2l2 2"></path>
<path color="#9966FF" d="M10 19l2 2l2 -2"></path>
</svg>`;
const IconArrowAutofitDown = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 20h-6a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h8"></path>
<path color="#28FFBF" d="M18 4v17"></path>
<path color="#28FFBF" d="M15 18l3 3l3 -3"></path>
</svg>`;
const IconArrowAutofitLeft = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M4 12v-6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v8"></path>
<path color="#28FFBF" d="M20 18h-17"></path>
<path color="#28FFBF" d="M6 15l-3 3l3 3"></path>
</svg>`;
const IconArrowAutofitRight = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 12v-6a2 2 0 0 0 -2 -2h-12a2 2 0 0 0 -2 2v8"></path>
<path color="#28FFBF" d="M4 18h17"></path>
<path color="#28FFBF" d="M18 15l3 3l-3 3"></path>
</svg>`;
const IconSettings = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<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"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>`;
const IconKeyboard = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<rect x="2" y="6" width="20" height="12" rx="2"></rect>
<line x1="6" y1="10" x2="6" y2="10"></line>
<line x1="10" y1="10" x2="10" y2="10"></line>
<line x1="14" y1="10" x2="14" y2="10"></line>
<line x1="18" y1="10" x2="18" y2="10"></line>
<line x1="6" y1="14" x2="6" y2="14.01"></line>
<line x1="18" y1="14" x2="18" y2="14.01"></line>
<line x1="10" y1="14" x2="14" y2="14"></line>
</svg>`;
const IconListNumbers = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M11 6h9"></path>
<path d="M11 12h9"></path>
<path d="M12 18h8"></path>
<path color="#E48900" d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path>
<path color="#E48900" d="M6 10v-6l-2 2"></path>
</svg>`;
const IconBookmarks = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path color="orange" d="M13 7a2 2 0 0 1 2 2v12l-5 -3l-5 3v-12a2 2 0 0 1 2 -2h6z"></path>
<path color="orange" d="M9.265 4a2 2 0 0 1 1.735 -1h6a2 2 0 0 1 2 2v12l-1 -.6"></path>
</svg>`;
const IconExternalLink = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5"></path>
<line x1="10" y1="14" x2="20" y2="4"></line>
<polyline points="15 4 20 4 20 9"></polyline>
</svg>`;
const IconTrash = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line x1="4" y1="7" x2="20" y2="7"></line>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
<path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"></path>
<path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"></path>
</svg>`;
const IconSun = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<circle cx="12" cy="12" r="4"></circle>
<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"></path>
</svg>`;
const IconMoon = `
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-moon inverse" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<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"></path>
</svg>`;
const IconCheck = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M5 12l5 5l10 -10"></path>
</svg>`;
const IconPalette = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 21a9 9 0 1 1 0 -18a9 8 0 0 1 9 8a4.5 4 0 0 1 -4.5 4h-2.5a2 2 0 0 0 -1 3.75a1.3 1.3 0 0 1 -1 2.25"></path>
<circle cx="7.5" cy="10.5" r=".5" fill="currentColor"></circle>
<circle cx="12" cy="7.5" r=".5" fill="currentColor"></circle>
<circle cx="16.5" cy="10.5" r=".5" fill="currentColor"></circle>
</svg>`;
// Icons for Page Controls
const IconBookmark = `
<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">
<path color="orange" stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path color="orange" d="M9 4h6a2 2 0 0 1 2 2v14l-5 -3l-5 3v-14a2 2 0 0 1 2 -2"></path>
</svg>`;
const IconBookmarkOff = `
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-bookmark-off inverse" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path color="orange" stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line color="red" x1="3" y1="3" x2="21" y2="21"></line>
<path color="orange" d="M17 17v3l-5 -3l-5 3v-13m1.178 -2.818c.252 -.113 .53 -.176 .822 -.176h6a2 2 0 0 1 2 2v7"></path>
</svg>`;
const IconEye = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<circle cx="12" cy="12" r="2"></circle>
<path d="M22 12c-2.667 4.667 -6 7 -10 7s-7.333 -2.333 -10 -7c2.667 -4.667 6 -7 10 -7s7.333 2.333 10 7"></path>
</svg>`;
const IconEyeOff = `
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-eye-off inverse" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line color="red" x1="3" y1="3" x2="21" y2="21"></line>
<path d="M10.584 10.587a2 2 0 0 0 2.828 2.83"></path>
<path d="M9.363 5.365a9.466 9.466 0 0 1 2.637 -.365c4 0 7.333 2.333 10 7c-.778 1.361 -1.612 2.524 -2.503 3.488m-2.14 1.861c-1.631 1.1 -3.415 1.651 -5.357 1.651c-4 0 -7.333 -2.333 -10 -7c1.369 -2.395 2.913 -4.175 4.632 -5.341"></path>
</svg>`;
const IconZoomCancel = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<circle cx="10" cy="10" r="7"></circle>
<line color="#9966FF" x1="8" y1="8" x2="12" y2="12"></line>
<line color="#9966FF" x1="12" y1="8" x2="8" y2="12"></line>
<line x1="21" y1="21" x2="15" y2="15"></line>
</svg>`;
const IconZoomIn = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<circle cx="10" cy="10" r="7"></circle>
<line color="lime" x1="7" y1="10" x2="13" y2="10"></line>
<line color="lime" x1="10" y1="7" x2="10" y2="13"></line>
<line x1="21" y1="21" x2="15" y2="15"></line>
</svg>`;
const IconZoomOut = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<circle cx="10" cy="10" r="7"></circle>
<line color="red" x1="7" y1="10" x2="13" y2="10"></line>
<line x1="21" y1="21" x2="15" y2="15"></line>
</svg>`;
const IconRefresh = `
<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">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path color='cyan' d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path>
<path color='cyan' d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path>
</svg>`;
const IconPhoto = `
<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="1.25" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path color="silver" stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line color="silver" x1="15" y1="8" x2="15.01" y2="8"></line>
<rect color="silver" x="4" y="4" width="16" height="16" rx="3"></rect>
<path color="silver" d="M4 15l4 -4a3 5 0 0 1 3 0l5 5"></path>
<path color="silver" d="M14 14l1 -1a3 5 0 0 1 3 0l2 2"></path>
</svg>`;
const IconPhotoOff = `
<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="1.25" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path color="silver" stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line color="silver" x1="15" y1="8" x2="15.01" y2="8"></line>
<path color="silver" d="M19.121 19.122a3 3 0 0 1 -2.121 .878h-10a3 3 0 0 1 -3 -3v-10c0 -.833 .34 -1.587 .888 -2.131m3.112 -.869h9a3 3 0 0 1 3 3v9"></path>
<path color="silver" d="M4 15l4 -4c.928 -.893 2.072 -.893 3 0l5 5"></path>
<line color="orange" x1="3" y1="3" x2="21" y2="21"></line>
<path color="silver" d="M16.32 12.34c.577 -.059 1.162 .162 1.68 .66l2 2"></path>
</svg>`;
// language=CSS
var cssStyles = `
:root,
.dark,
.dark .default,
[data-theme='dark'] {
--theme-body-background: ${colors.dark['600']};
--theme-body-text-color: ${colors.dark['50']};
--theme-text-color: ${colors.dark['50']};
--theme-primary-color: ${colors.dark['700']};
--theme-primary-text-color: ${colors.dark['50']};
--theme-background-color: ${colors.dark['600']};
--theme-hightlight-color: ${colors.dark['500']};
--theme-border-color: ${colors.dark['400']};
}
.light,
.light .default,
[data-theme='light'] {
--theme-body-background: ${colors.gray['50']};
--theme-body-text-color: ${colors.gray['900']};
--theme-text-color: ${colors.gray['900']};
--theme-primary-color: ${colors.gray['300']};
--theme-primary-text-color: ${colors.gray['900']};
--theme-background-color: ${colors.gray['50']};
--theme-hightlight-color: ${colors.gray['500']};
--theme-border-color: ${colors.gray['100']};
}
/* Simple Normalizer */
html {
font-size: 100%;
}
body {
margin: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 20px;
color: #333;
background-color: #fff;
padding: 0;
}
a,
a:link,
a:visited,
a:active,
a:focus {
color: var(--theme-body-text-color);
text-decoration: none;
}
img {
height: auto;
vertical-align: middle;
border: 0 none;
}
.icon-tabler {
height: 1rem;
width: 1rem;
align-self: center;
vertical-align: sub;
}
#nprogress .bar {
background: #29d;
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 4px;
}
#MangaOnlineViewer {
padding-bottom: 40px;
min-height: 760px;
min-width: 360px;
width:100%;
height:100%;
text-decoration:none;
color: var(--theme-body-text-color);
background-color: var(--theme-body-background);
}
#MangaOnlineViewer #Chapter {
display: grid;
grid-template-columns: repeat(1, 1fr);
min-width: 225px;
}
#MangaOnlineViewer #Chapter.DoublePage {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
#MangaOnlineViewer #Chapter.DoublePage .PageImg {
min-width: unset;
}
#MangaOnlineViewer #Chapter.DoublePage .MangaPage.DoublePage {
grid-column: span 2;
}
#MangaOnlineViewer #Chapter.DoublePage .MangaPage:not(.DoublePage):nth-child(2n) {
justify-self: flex-start;
}
#MangaOnlineViewer #Chapter.DoublePage .MangaPage:not(.DoublePage):nth-child(2n-1) {
justify-self: flex-end;
}
#MangaOnlineViewer #Chapter.FluidLTR .MangaPage,
#MangaOnlineViewer #Chapter.FluidRTL .MangaPage {
width: auto;
}
#MangaOnlineViewer #Chapter.FluidLTR {
direction: ltr;
}
#MangaOnlineViewer #Chapter.FluidRTL {
direction: rtl;
}
#MangaOnlineViewer #Chapter.FluidLTR .ZoomWidth .icon-tabler,
#MangaOnlineViewer #Chapter.FluidRTL .ZoomWidth .icon-tabler {
color: red;
}
#MangaOnlineViewer #SettingsPanel {
color: var(--theme-text-color);
padding: 10px;
position: fixed;
top: 0;
left: 0;
z-index: 1000;
transition: transform 0.3s ease-in, background-color 0.3s linear;
transform: translateX(-100%);
display: flex;
flex-flow: column;
gap: 5px;
height: 100%;
max-width: 100vw;
width: 305px;
}
#MangaOnlineViewer #SettingsPanel.visible {
transform: translateX(0);
}
#MangaOnlineViewer #SettingsPanel .ControlLabel {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-items: center;
}
#MangaOnlineViewer #SettingsPanel .ControlLabelItem {
display: flex;
align-items: center;
justify-content: space-between;
}
#MangaOnlineViewer #SettingsPanel .ControlLabelItem:not(.show) {
display: none;
}
#MangaOnlineViewer #ThemeSection{
border: 1px solid var(--theme-body-text-color);
border-radius: 10px;
padding: 10px;
}
#MangaOnlineViewer .closeButton {
width: fit-content;
height: fit-content;
position: absolute;
right: 10px;
top: 10px;
}
#MangaOnlineViewer .overlay {
position: fixed;
display: none;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.5);
z-index: 950;
cursor: pointer;
}
#MangaOnlineViewer .overlay.visible {
display: block;
}
#MangaOnlineViewer .ThemeRadio {
border: 1px solid var(--theme-text-color);
color: var(--theme-primary-text-color);
background-color: var(--theme-primary-color);
height: 20px;
width: 20px;
border-radius: 50%;
padding: 1px;
margin: 2px 5px;
position: relative;
}
#MangaOnlineViewer .ThemeRadio svg{
position: absolute;
top: 15%;
right: 15%;
}
#MangaOnlineViewer .ThemeRadio.custom{
/*background-image: url("${svgToUrl(IconPalette)}");*/
/*background-position: center;*/
/*background-repeat: no-repeat;*/
/*background-size: 80%;*/
}
#MangaOnlineViewer .ThemeRadio.selected .icon-tabler-check {
display: inline;
}
#MangaOnlineViewer .ThemeRadio:not(.selected) .icon-tabler-check {
display: none;
}
#MangaOnlineViewer #KeybindingsPanel {
padding: 8px;
position: fixed;
top: 65px;
right: 0;
transition: transform 0.3s ease-in-out;;
transform: translateX(100%);
/*display: none;*/
line-height: 1.5em;
}
#MangaOnlineViewer #KeybindingsPanel.visible {
transform: translateX(0);
display: block;
}
#MangaOnlineViewer #BookmarksPanel {
position: fixed;
top: 10%;
width: 50%;
left: 25%;
right: 25%;
text-align: center;
max-height: 70%;
transition: transform 0.3s ease-in-out;;
transform: scaleY(0%);
z-index: 1000;
}
#MangaOnlineViewer #BookmarksPanel.visible {
transform: scaleY(100%);
display: block;
}
#MangaOnlineViewer #BookmarksList {
padding: 0 15px;
overflow: auto;
max-height: 60vh;
}
#MangaOnlineViewer #BookmarksList .BookmarkItem {
display: flex;
flex-flow: row;
justify-content: space-between;
align-items: center;
padding: 2px;
}
#MangaOnlineViewer #BookmarksList .bookmarkData {
flex-basis: 15%;
}
#MangaOnlineViewer #BookmarksList .bookmarkURl {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
flex-basis: 55%;
}
#MangaOnlineViewer select {
height: 20px;
padding: 0;
margin-bottom: 5px;
}
#MangaOnlineViewer .ControlButton {
cursor: pointer;
border-radius: 5px;
border-width: 1px;
padding: 2px;
min-height: 32px;
color: var(--theme-primary-text-color);
background-color: var(--theme-primary-color);
border-color: var(--theme-border-color);
}
#MangaOnlineViewer .ControlButton:hover {
opacity: 0.8;
}
#MangaOnlineViewer .panel {
padding: 5px;
position: inherit;
border-radius: 5px;
background-color: var(--theme-background-color);
}
#MangaOnlineViewer .MangaPage {
width: 100%;
display: inline-block;
text-align: center;
/*transform: translate3d(0, 0, 0);*/
/*backface-visibility: hidden;*/
/*perspective: 1000px;*/
line-height: 0;
min-height: 22px;
min-width: 100%;
}
#MangaOnlineViewer .PageContent {
text-align: center;
display: inline-block;
overflow-x: auto;
max-width: 100%;
transition: all 0.3s ease-in-out;
height: 100%;
overflow-y: hidden;
}
#MangaOnlineViewer .MangaPage.hide .PageContent {
/*display: none;*/
height: 0;
}
#MangaOnlineViewer .MangaPage.hide .PageFunctions {
/*position:relative;*/
}
#MangaOnlineViewer .PageContent .PageImg[src=""],
#MangaOnlineViewer .PageContent .PageImg:not([src]) {
width: 40vw;
height: 80vh;
display: inline-block;
background-image: url("${svgToUrl(IconPhoto)}");
background-position: center;
background-repeat: no-repeat;
background-size: 20%;
background-color: var(--theme-hightlight-color);
}
#MangaOnlineViewer .PageContent .PageImg.imgBroken {
width: 40vw;
height: 80vh;
display: inline-block;
background-image: url("${svgToUrl(IconPhotoOff)}");
background-position: center;
background-repeat: no-repeat;
background-size: 20%;
background-color: var(--theme-hightlight-color);
}
#MangaOnlineViewer .Thumbnail .ThumbnailImg[src=""],
#MangaOnlineViewer .Thumbnail .ThumbnailImg:not([src]) {
width: 100px;
height: 150px;
display: inline-block;
background-image: url("${svgToUrl(IconPhoto)}");
background-position: center;
background-repeat: no-repeat;
background-size: 20%;
}
#MangaOnlineViewer .fitWidthIfOversize .PageContent .PageImg {
max-width: 100%;
}
#MangaOnlineViewer #gotoPage {
min-width: 35px;
}
#MangaOnlineViewer #ThemeSelector {
width: 110px;
}
#MangaOnlineViewer #Header {
display: flex;
justify-content: space-between;
align-items: center;
flex-flow: row nowrap;
transition: transform 0.3s ease-in;
position: sticky;
top: 0;
left: 0;
right: 0;
background-color: inherit;
z-index: 900;
}
#MangaOnlineViewer #Header.scroll-hide {
transform: translateY(-100%);
}
#MangaOnlineViewer #Header.scroll-show {
transform: translateY(-1%);
}
#MangaOnlineViewer #Header.mouseOverMenu {
position: static;
transform: none;
}
#MangaOnlineViewer #Header.scroll-end,
#MangaOnlineViewer #Header.visible {
transform: translateY(-1%);
position: sticky;
}
#MangaOnlineViewer #Header.mouseOverMenu:hover {
position: sticky;
}
#MangaOnlineViewer #Header:not(.mouseOverMenu) #menu,
#MangaOnlineViewer #Header.mouseOverMenu:hover #menu {
display: none;
}
#MangaOnlineViewer #menu {
position: fixed;
min-height: 70px;
width: 100%;
top: 0;
z-index: 1;
color: var(--theme-body-text-color);
}
#MangaOnlineViewer #MangaTitle {
padding: 2px;
margin: 0;
font-size: 1.2rem;
font-weight: normal;
}
#MangaOnlineViewer #GlobalFunctions{
display: flex;
gap: 3px;
padding-left:10px;
flex-wrap: wrap;
width: 300px;
z-index: 100;
}
#MangaOnlineViewer #GlobalFunctions .icon-tabler{
width: 25px;
height: 25px;
}
#MangaOnlineViewer #ChapterNavigation {
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: end;
padding-right: 10px;
width: 300px;
}
#MangaOnlineViewer .ChapterControl {
display: flex;
flex-wrap: nowrap;
}
#MangaOnlineViewer .ChapterControl .NavigationControlButton {
display: inline-flex;
margin-left: 3px;
justify-content: center;
align-items: center;
padding: 5px 10px;
gap: 0.5em;
}
#MangaOnlineViewer .ChapterControl .NavigationControlButton .icon-tabler {
flex-shrink: 0;
align-self: center;
width: 1rem;
height: 1rem;
}
#MangaOnlineViewer .ChapterControl .NavigationControlButton[onclick="window.location.href='#';"],
#MangaOnlineViewer .ChapterControl .NavigationControlButton[onclick="window.location.href='';"],
#MangaOnlineViewer .ChapterControl .NavigationControlButton[onclick="window.location.href='undefined';"] {
visibility: hidden;
}
#MangaOnlineViewer .ChapterControl .download.loading{
cursor: not-allowed;
}
#MangaOnlineViewer .ViewerTitle {
text-align: center;
min-height: 60px;
/*max-width: 500px;*/
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 5px;
flex-basis: 60%;
}
#MangaOnlineViewer #Counters {
}
#MangaOnlineViewer .PageFunctions {
font-family: monospace;
display: flex;
justify-content: flex-end;
align-items: center;
margin: 0;
padding: 0;
gap: 3px;
position: absolute;
right: 0;
direction: ltr;
}
#MangaOnlineViewer .PageFunctions > .PageIndex {
background-color: var(--theme-primary-color);
color: var(--theme-primary-text-color);
min-width: 20px;
text-align: center;
display: inline-block;
padding: 3px 5px;
line-height: 1rem;
border-radius: 5px;
border-top-right-radius: 5px;
border-top-right-radius: 0;
}
#MangaOnlineViewer .PageFunctions .ControlButton {
padding: 3px;
display: flex;
justify-content: center;
align-items: center;
margin: 0;
border-width: 0;
min-height: auto;
opacity: 0.5;
}
#MangaOnlineViewer .PageFunctions:hover .ControlButton {
opacity: 1;
}
#MangaOnlineViewer .PageFunctions .ControlButton:hover {
opacity: 0.9;
}
#MangaOnlineViewer.light #ColorScheme > :not(.inverse),
#MangaOnlineViewer:not(.light) #ColorScheme > .inverse,
#MangaOnlineViewer .ChapterControl .download.loading > :not(.inverse),
#MangaOnlineViewer .ChapterControl .download:not(.loading) > .inverse,
#MangaOnlineViewer .MangaPage.hide .ControlButton.Hide > .inverse,
#MangaOnlineViewer .MangaPage:not(.hide) .ControlButton.Hide > :not(.inverse),
#MangaOnlineViewer.bookmarked .ControlButton.Bookmark > :not(.inverse),
#MangaOnlineViewer:not(.bookmarked) .ControlButton.Bookmark > .inverse {
display: none;
}
#MangaOnlineViewer.hideControls .PageFunctions {
visibility: hidden;
}
#MangaOnlineViewer #NavigationCounters {
margin: 5px;
width: 100%;
line-height: 1rem;
}
#MangaOnlineViewer #Navigation {
color: var(--theme-text-color);
background-color: var(--theme-hightlight-color);
bottom: -180px;
height: 185px;
overflow-x: hidden;
overflow-y: hidden;
padding-bottom: 20px;
position: fixed;
white-space: nowrap;
width: 100%;
text-align: center;
transition: transform 0.3s ease-in, background-color 0.3s linear;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
line-height: 0rem;
}
#MangaOnlineViewer #Navigation #Thumbnails {
overflow-x: auto;
overflow-y: hidden;
margin-right: 10px;
}
#MangaOnlineViewer #Navigation:hover {
transform: translateY(-180px);
}
#MangaOnlineViewer #Navigation.disabled {
display: none;
}
#MangaOnlineViewer #Navigation.visible {
transform: translateY(-180px);
}
#MangaOnlineViewer #Navigation .Thumbnail {
display: inline-block;
height: 150px;
margin: 0 5px;
border: 1px solid var(--theme-primary-color);
}
#MangaOnlineViewer #Navigation .Thumbnail .ThumbnailIndex {
color: var(--theme-text-color);
background-color: var(--theme-hightlight-color);
display: block;
opacity: 0.8;
position: relative;
bottom: 25%;
width: 100%;
line-height: 1rem;
}
#MangaOnlineViewer #Navigation .Thumbnail .ThumbnailImg {
cursor: pointer;
display: inline-block;
max-height: 150px;
min-height: 150px;
min-width: 80px;
max-width: 160px;
}
#MangaOnlineViewer #menu .icon-tabler {
position: absolute;
top: 5px;
left: 10px;
height: 32px;
width: 32px;
}
@media (max-width: 992px) {
#MangaOnlineViewer #Header {
flex-direction: column;
}
#MangaOnlineViewer #Header.mouseOverMenu {
position: sticky;
transition: transform 0.3s ease-in;
}
#MangaOnlineViewer #Header.scroll-show {
transform: translateY(-1%);
}
#MangaOnlineViewer #Header.scroll-hide {
transform: translateY(-100%);
}
#MangaOnlineViewer .PageContent .PageImg {
max-width: 100%;
}
#MangaOnlineViewer .ViewerTitle {
order: 1;
min-height: auto;
padding: 0px;
margin: 0px;
}
#MangaOnlineViewer #GlobalFunctions {
flex-wrap: nowrap;
padding: 0;
width: auto;
order: 3;
padding: 5px;
}
#MangaOnlineViewer #ChapterNavigation {
order: 2;
}
#MangaOnlineViewer #menu {
display: none;
}
}
/* Small devices (landscape phones) */
@media (max-width: 600px) {
#MangaOnlineViewer #Header {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
#MangaOnlineViewer #Header.mouseOverMenu {
position: sticky;
transition: transform 0.3s ease-in;
}
#MangaOnlineViewer #Header.scroll-show {
transform: translateY(-1%);
}
#MangaOnlineViewer #Header.scroll-hide {
transform: translateY(-100%);
}
#MangaOnlineViewer .ViewerTitle {
order: 1;
flex-basis: 100%;
margin-top: 0px;
}
#MangaOnlineViewer #GlobalFunctions {
order: 2;
padding: 0;
}
#MangaOnlineViewer #ChapterNavigation {
order: 3;
width: auto;
}
#MangaOnlineViewer #Navigation {
display: none;
}
#MangaOnlineViewer .PageFunctions {
padding: 0;
}
#MangaOnlineViewer .PageFunctions .ControlButton:not(.Bookmark) {
display: none;
}
#MangaOnlineViewer .PageFunctions .ControlButton.Bookmark {
opacity: 1;
}
#MangaOnlineViewer .PageContent {
margin: 0;
width: 100%;
}
#MangaOnlineViewer .PageContent .PageImg {
max-width: 100%;
}
#MangaOnlineViewer #GlobalFunctions .ControlButton:not(#settings) {
display: none;
}
#MangaOnlineViewer #GlobalFunctions {
min-width: auto;
}
#MangaOnlineViewer #SettingsPanel .DefaultZoom,
#MangaOnlineViewer #SettingsPanel .viewMode,
#MangaOnlineViewer #SettingsPanel .fitIfOversize,
#MangaOnlineViewer #SettingsPanel .showThumbnails,
#MangaOnlineViewer #SettingsPanel .lazyLoadImages,
#MangaOnlineViewer #SettingsPanel .downloadZip,
#MangaOnlineViewer #SettingsPanel .minZoom,
#MangaOnlineViewer #SettingsPanel .zoomStep,
#MangaOnlineViewer #SettingsPanel .mouseOverMenu {
display: none;
}
#MangaOnlineViewer #KeybindingsPanel {
display: none;
}
#MangaOnlineViewer #menu {
display: none;
}
#MangaOnlineViewer .ViewerTitle {
height: auto;
padding: 0;
}
#MangaOnlineViewer .ChapterControl {
}
#MangaOnlineViewer .ChapterControl .download {
display: none;
}
#MangaOnlineViewer #Counters {
display: none;
}
}
@-webkit-keyframes spin {
to {
transform:rotate(360deg)
}
}
@keyframes spin {
to {
transform:rotate(360deg)
}
}
.animate-spin {
-webkit-animation:spin 1s linear infinite;
animation:spin 1s linear infinite
}
@-webkit-keyframes spin-reverse {
0% {
transform:rotate(360deg)
}
to {
transform:rotate(0)
}
}
@keyframes spin-reverse {
0% {
transform:rotate(360deg)
}
to {
transform:rotate(0)
}
}
.animate-spin-reverse {
-webkit-animation:spin-reverse 1s linear infinite;
animation:spin-reverse 1s linear infinite
}
`;
// Configuration
const settings$1 = {
theme: getValueGM('Theme', 'darkblue'),
customTheme: getValueGM('CustomTheme', '#263e3a'),
themeShade: getValueGM('ThemeShade', 600),
colorScheme: getValueGM('ColorScheme', 'dark'),
fitWidthIfOversize: getValueGM('FitWidthIfOversize', true),
showThumbnails: getValueGM('ShowThumbnails', true),
downloadZip: getValueGM('DownloadZip', false),
throttlePageLoad: getValueGM('Timer', 1000),
zoom: getValueGM('Zoom', 100),
zoomStep: getValueGM('ZoomStep', 25),
minZoom: getValueGM('MinZoom', 50),
loadMode: getValueGM('LoadMode', 'wait'),
viewMode: getValueGM('ViewMode', ''),
bookmarks: JSON.parse(getValueGM('Bookmarks', '[]')),
lazyLoadImages: getValueGM('LazyLoadImages', false),
lazyStart: getValueGM('LazyStart', 50),
hidePageControls: getValueGM('HidePageControls', false),
mouseOverMenu: getValueGM('MouseOverMenu', true),
};
// Force Settings for mobile
if (isMobile) {
settings$1.lazyLoadImages = true;
settings$1.lazyStart = 5;
settings$1.fitWidthIfOversize = true;
settings$1.showThumbnails = false;
settings$1.viewMode = '';
}
// Clear old Bookmarks
const bookmarkTimeLimit = 1000 * 60 * 60 * 24 * 30 * 12; // year
settings$1.bookmarks = settings$1.bookmarks.filter((el) => Date.now() - el.date < bookmarkTimeLimit);
setValueGM('Bookmarks', JSON.stringify(settings$1.bookmarks));
// Creates the style element
function createStyleElement(id, content) {
const style = document.createElement('style');
style.id = id;
style.appendChild(document.createTextNode(content));
return style;
}
// Appends CSS content to the head of the site
function appendStyleSheet(id, content) {
if (!document.querySelector(`#${id}`)) {
const head = document.head || document.querySelector('head');
head.appendChild(createStyleElement(id, content));
}
}
function removeStyleSheet(id) {
document.querySelectorAll(`style[id="${id}"]`).forEach((elem) => elem.remove());
}
function replaceStyleSheet(id, content) {
removeStyleSheet(id);
appendStyleSheet(id, content);
}
function wrapStyle(id, css) {
return `<style type='text/css' id='${id}'>${css}</style>`;
}
function generateThemeCSS(name, primary, text) {
// language=CSS
return `
.${name},
[data-theme='${name}'] {
--theme-primary-color: ${primary};
--theme-primary-text-color: ${text};
}
`;
}
function getNormalThemeCSS(theme) {
return generateThemeCSS(theme.name, theme[settings$1.themeShade], settings$1.themeShade < 500 ? theme['900'] : theme['50']);
}
function getCustomThemeCSS(hex) {
return generateThemeCSS('custom', hex, getTextColor(hex));
}
// Add custom Themes to the page
function addTheme(theme) {
return wrapStyle(theme.name, getNormalThemeCSS(theme));
}
function addCustomTheme(hex) {
replaceStyleSheet('custom', getCustomThemeCSS(hex));
}
const themes = () => Object.values(colors);
// Object.values({ ...colors, custom: customColor(settings.customTheme) });
const themesSelector = [...Object.keys(colors).map((color) => colors[color].name)].map((theme) => `
<span class='${theme} ThemeRadio ${settings$1.theme === theme ? 'selected' : ''}'
title='${theme}'
>
${IconCheck}
</span>
`);
function refreshThemes() {
themes().forEach((theme) => replaceStyleSheet(theme.name, getNormalThemeCSS(theme)));
replaceStyleSheet('custom', getCustomThemeCSS(settings$1.customTheme));
}
const themesCSS = themes().map(addTheme).join('') + wrapStyle('custom', getCustomThemeCSS(settings$1.customTheme));
// https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/11.4.20/sweetalert2.css
// .swal2-title -> #swal2-title
// language=CSS
const sweetalert = `.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 hsla(0deg,0%,0%,.075),0 1px 2px hsla(0deg,0%,0%,.075),1px 2px 4px hsla(0deg,0%,0%,.075),1px 3px 8px hsla(0deg,0%,0%,.075),2px 4px 16px hsla(0deg,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:0 0;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:0 0;font-size:1.125em}.swal2-textarea{height:6.75em;padding:.75em}.swal2-select{min-width:50%;max-width:100%;padding:.375em .625em;background:0 0;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:0 0;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}.leave-russia-now-and-apply-your-skills-to-the-world{display:flex;position:fixed;z-index:1939;top:0;right:0;bottom:0;left:0;flex-direction:column;align-items:center;justify-content:center;padding:25px 0 20px;background:#20232a;color:#fff;text-align:center}.leave-russia-now-and-apply-your-skills-to-the-world div{max-width:560px;margin:10px;line-height:146%}.leave-russia-now-and-apply-your-skills-to-the-world iframe{max-width:100%;max-height:55.5555555556vmin;margin:16px auto}.leave-russia-now-and-apply-your-skills-to-the-world strong{border-bottom:2px dashed #fff}.leave-russia-now-and-apply-your-skills-to-the-world button{display:flex;position:fixed;z-index:1940;top:0;right:0;align-items:center;justify-content:center;width:48px;height:48px;margin-right:10px;margin-bottom:-10px;border:none;background:0 0;color:#aaa;font-size:48px;font-weight:700;cursor:pointer}.leave-russia-now-and-apply-your-skills-to-the-world button:hover{color:#fff}@-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}`;
// https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css
// language=CSS
const normalize = `/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}`;
// https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.css
// language=CSS
const nprogress = `#nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;-webkit-transform:rotate(3deg) translate(0,-4px);-ms-transform:rotate(3deg) translate(0,-4px);transform:rotate(3deg) translate(0,-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:2px solid transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;-webkit-animation:nprogress-spinner 400ms linear infinite;animation:nprogress-spinner 400ms linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}`;
// https://cdn.jsdelivr.net/npm/@gerhobbelt/[email protected]/keys.css
// language=CSS
const keycss = `.key,kbd{display:inline;display:inline-block;white-space:nowrap;min-width:1em;padding:0.3em 0.4em 0.2em 0.3em;font-style:normal;font-family:"Lucida Grande", Lucida, Arial, sans-serif;text-align:center;text-decoration:none;border-radius:0.3em;border:none;background-color:#505050;background-color:gradient(linear, left top, left bottom, from(#3c3c3c), to(#505050));color:#fafafa;text-shadow:-1px -1px 0 #464646;-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);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);font-size:0.85em;line-height:1;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.key[title],kbd[title]{cursor:help}.dark-keys .key,.dark-keys kbd,.key.dark,kbd.dark{display:inline;display:inline-block;white-space:nowrap;min-width:1em;padding:0.3em 0.4em 0.2em 0.3em;font-style:normal;font-family:"Lucida Grande", Lucida, Arial, sans-serif;text-align:center;text-decoration:none;border-radius:0.3em;border:none;background-color:#505050;background-color:gradient(linear, left top, left bottom, from(#3c3c3c), to(#505050));color:#fafafa;text-shadow:-1px -1px 0 #464646;-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);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)}.key.light,.light-keys .key,.light-keys kbd,kbd.light{display:inline;display:inline-block;white-space:nowrap;min-width:1em;padding:0.3em 0.4em 0.2em 0.3em;font-style:normal;font-family:"Lucida Grande", Lucida, Arial, sans-serif;text-align:center;text-decoration:none;border-radius:0.3em;border:none;background-color:#fafafa;background-color:gradient(linear, left top, left bottom, from(#d2d2d2), to(#ffffff));color:#323232;text-shadow:0 0 2px #ffffff;-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);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)}.key.so,.so-keys .key,.so-keys kbd,kbd.so{display:inline;display:inline-block;white-space:nowrap;min-width:1em;padding:0.3em 0.4em 0.2em 0.3em;font-style:normal;font-family:"Lucida Grande", Lucida, Arial, sans-serif;text-align:center;text-decoration:none;border-radius:0.3em;border:none;margin:0 0.1em;padding:0.1em 0.6em;font-family:Arial, "Helvetica Neue", Helvetica, sans-serif;line-height:1.4;color:#242729;text-shadow:0 1px 0 #FFF;background-color:#e1e3e5;border:1px solid #adb3b9;border-radius:0.27272727em;-webkit-box-shadow:0 1px 0 rgba(12, 13, 14, 0.2), 0 0 0 2px #FFF inset;box-shadow:0 1px 0 rgba(12, 13, 14, 0.2), 0 0 0 2px #FFF inset}.github-keys .key,.github-keys kbd,.key.github,kbd.github{display:inline;display:inline-block;white-space:nowrap;min-width:1em;padding:0.3em 0.4em 0.2em 0.3em;font-style:normal;font-family:"Lucida Grande", Lucida, Arial, sans-serif;text-align:center;text-decoration:none;border-radius:0.3em;border:none;padding:0.27272727em 0.45454545em;font-size:68.75%;line-height:0.90909091;color:#444d56;vertical-align:middle;background-color:#fafbfc;border:solid 1px #c6cbd1;border-bottom-color:#959da5;border-radius:0.27272727em;-webkit-box-shadow:inset 0 -1px 0 #959da5;box-shadow:inset 0 -1px 0 #959da5;font-family:"SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;-webkit-box-sizing:border-box;box-sizing:border-box;text-shadow:none}`;
var sweetalertStyle = [normalize, sweetalert, nprogress, keycss].join('\n');
function head(manga) {
return `
<head>
<title>${manga.title}</title>
<meta charset='UTF-8'>
${wrapStyle('externals', sweetalertStyle)}
${wrapStyle('reader', cssStyles)}
${themesCSS}
${wrapStyle('MinZoom', `#MangaOnlineViewer .PageContent .PageImg {min-width: ${settings$1.minZoom}vw;}`)}
</head>
`;
}
function indexList(repeat, begin = 1) {
return Array(repeat)
.fill(0)
.map((_, i) => i + 1)
.filter((i) => i >= begin);
}
const listPages = (times, begin) => indexList(times, begin).map((index) => `
<div id='Page${index}' class='MangaPage'>
<div class='PageFunctions'>
<button class='Bookmark ControlButton' title='Bookmark'>
${IconBookmark}
${IconBookmarkOff}
</button>
<button class='ZoomIn ControlButton' title='Zoom In'>
${IconZoomIn}
</button>
<button class='ZoomRestore ControlButton' title='Zoom Restore'>
${IconZoomCancel}
</button>
<button class='ZoomOut ControlButton' title='Zoom Out'>
${IconZoomOut}
</button>
<button class='ZoomWidth ControlButton' title='Zoom to Width'>
${IconArrowAutofitWidth}
</button>
<button class='ZoomHeight ControlButton' title='Zoom to Height'>
${IconArrowAutofitHeight}
</button>
<button class='Hide ControlButton' title='Hide'>
${IconEye}
${IconEyeOff}
</button>
<button class='Reload ControlButton' title='Reload'>
${IconRefresh}
</button>
<span class='PageIndex'>${index}</span>
</div>
<div class='PageContent'>
<img id='PageImg${index}' alt='' class='PageImg' />
</div>
</div>`);
// language=html
const SettingsPanel = `
<div id='SettingsOverlay' class='overlay'></div>
<div id='SettingsPanel' class='panel'>
<h2>Settings</h2>
<button id='CloseSettings' class='closeButton'>${IconX}</button>
<button id='ResetSettings' class='simpleButton'>Reset Settings</button>
<div id='ThemeSection'>
<div class='ControlLabel ColorSchemeSelector'>Color Scheme:
<button id='ColorScheme' class='simpleButton'>
${IconSun}
${IconMoon}
</button>
</div>
<!-- =========================================================================================== -->
<div class='ControlLabel ThemeSelector'>Theme:
<span class='custom ThemeRadio
${settings$1.theme === 'custom' ? 'selected' : ''}'
title='custom'>
${IconPalette}
${IconCheck}
</span>
${themesSelector.join('')}
</div>
<!-- =========================================================================================== -->
<div id='Hue' class='ControlLabel CustomTheme ControlLabelItem
${settings$1.theme.startsWith('custom') ? 'show' : ''}'>
Theme Primary Color Hue:<input id='CustomThemeHue' type='color' value='${settings$1.customTheme}' class='colorpicker CustomTheme'/>
</div>
<!-- =========================================================================================== -->
<div id='Shade' class='ControlLabel CustomTheme ControlLabelItem
${settings$1.theme.startsWith('custom') ? '' : 'show'}'>
<span>
Theme Primary Color Shade:
<output id='themeShadeVal' for='themeShade'>${settings$1.themeShade}</output>
</span>
<input type='range'
value='${settings$1.themeShade}'
name='ThemeShade'
id='ThemeShade'
min='100'
max='900'
step='100'
oninput='themeShadeVal.value = this.value'
/>
</div>
</div>
<!-- =========================================================================================== -->
<div class='ControlLabel loadMode'>Default Load Mode:
<select id='loadMode'>
<option value='wait' ${settings$1.loadMode === 'wait' ? 'selected' : ''}>Normal(Wait 3 sec)</option>
<option value='always' ${settings$1.loadMode === 'always' ? 'selected' : ''}>Always(Immediately)</option>
<option value='never' ${settings$1.loadMode === 'never' ? 'selected' : ''}>Never(Manually)</option>
</select>
</div>
<!-- =========================================================================================== -->
<div class='ControlLabel PagesPerSecond'>Pages/Second:
<select id='PagesPerSecond'>
<option value='3000' ${settings$1.throttlePageLoad === 3000 ? 'selected' : ''}>0.3(Slow)</option>
<option value='2000' ${settings$1.throttlePageLoad === 2000 ? 'selected' : ''}>0.5</option>
<option value='1000' ${settings$1.throttlePageLoad === 1000 ? 'selected' : ''}>01(Normal)</option>
<option value='500' ${settings$1.throttlePageLoad === 500 ? 'selected' : ''}>02</option>
<option value='250' ${settings$1.throttlePageLoad === 250 ? 'selected' : ''}>04(Fast)</option>
<option value='125' ${settings$1.throttlePageLoad === 125 ? 'selected' : ''}>08</option>
<option value='100' ${settings$1.throttlePageLoad === 100 ? 'selected' : ''}>10(Extreme)</option>
</select>
</div>
<!-- =========================================================================================== -->
<div class='ControlLabel DefaultZoom'>Default Zoom:
<select id='DefaultZoom'>
<option value='50' ${settings$1.zoom === 50 ? 'selected' : ''}>50%</option>
<option value='75' ${settings$1.zoom === 75 ? 'selected' : ''}>75%</option>
<option value='100' ${settings$1.zoom === 100 ? 'selected' : ''}>100%</option>
<option value='125' ${settings$1.zoom === 125 ? 'selected' : ''}>125%</option>
<option value='150' ${settings$1.zoom === 150 ? 'selected' : ''}>150%</option>
<option value='175' ${settings$1.zoom === 175 ? 'selected' : ''}>175%</option>
<option value='200' ${settings$1.zoom === 200 ? 'selected' : ''}>200%</option>
<option value='1000' ${settings$1.zoom === 1000 ? 'selected' : ''}>Fit Width</option>
<option value='-1000' ${settings$1.zoom === -1000 ? 'selected' : ''}>Fit Height</option>
</select>
</div>
<!-- =========================================================================================== -->
<div class='ControlLabel minZoom'>
<span>
Minimun Zoom relative to the width of screen (between 30 and 100):
<output id='minZoomVal' for='minZoom'>${settings$1.minZoom}</output>
</span>
<input type='range'
value='${settings$1.minZoom}'
name='minZoom'
id='minZoom'
min='30'
max='100'
step='10'
oninput='minZoomVal.value = this.value'
/>
</div>
<!-- =========================================================================================== -->
<div class='ControlLabel zoomStep'>
<span>
Zoom Change Step (between 5 and 50):
<output id='zoomStepVal' for='zoomStep'>${settings$1.zoomStep}</output>
</span>
<input type='range'
value='${settings$1.zoomStep}'
name='zoomStep'
id='zoomStep'
min='5'
max='50'
step='5'
oninput='zoomStepVal.value = this.value'
/>
</div>
<!-- =========================================================================================== -->
<div class='ControlLabel viewMode'>Default View Mode:
<select id='viewMode'>
<option value='' ${settings$1.viewMode !== 'FluidLTR' && settings$1.viewMode !== 'FluidRTL' ? 'selected' : ''}>
Vertical
</option>
<option value='FluidLTR' ${settings$1.viewMode === 'FluidLTR' ? 'selected' : ''}>
Left to Right
</option>
<option value='FluidRTL' ${settings$1.viewMode === 'FluidRTL' ? 'selected' : ''}>
Right to Left
</option>
</select>
</div>
<!-- =========================================================================================== -->
<div class='ControlLabel fitIfOversize'>Fit Width if Oversize:
<input type='checkbox' value='true' name='fitIfOversize' id='fitIfOversize' ${settings$1.fitWidthIfOversize ? 'checked' : ''}/>
</div>
<!-- =========================================================================================== -->
<div class='ControlLabel showThumbnails'>Show Thumbnails:
<input type='checkbox' value='true' name='showThumbnails' id='showThumbnails' ${settings$1.showThumbnails ? 'checked' : ''}/>
</div>
<!-- =========================================================================================== -->
<div class='ControlLabel lazyLoadImages'>Lazy Load Images:
<input type='checkbox' value='true' name='lazyLoadImages' id='lazyLoadImages' ${settings$1.lazyLoadImages ? 'checked' : ''}/>
</div>
<!-- =========================================================================================== -->
<div class='ControlLabel lazyStart'>
<span>
Lazy Start From Page (between 5 and 100):
<output id='lazyStartVal' for='lazyStart'>${settings$1.lazyStart}</output>
</span>
<input type='range' value='${settings$1.lazyStart}' name='lazyStart' id='lazyStart' min='5' max='100' step='5' oninput='lazyStartVal.value = this.value'/>
</div>
<!-- =========================================================================================== -->
<div class='ControlLabel downloadZip'>Download Images as Zip Automatically:
<input type='checkbox' value='false' name='downloadZip' id='downloadZip' ${settings$1.downloadZip ? 'checked' : ''}/>
</div>
<!-- =========================================================================================== -->
<div class='ControlLabel hidePageControls'>Always Hide Page Controls:
<input type='checkbox' value='false' name='hidePageControls' id='hidePageControls' ${settings$1.hidePageControls ? 'checked' : ''}/>
</div>
<!-- =========================================================================================== -->
<div class='ControlLabel mouseOverMenu'>Toggle Sticky Header / MouseOverMenu:
<input type='checkbox' value='false' name='mouseOverMenu' id='mouseOverMenu' ${settings$1.mouseOverMenu ? 'checked' : ''}/>
</div>
</div>
`;
const KeybindingsPanel = `
<div id='KeybindingsPanel' class='panel'>
<h2>Keybindings</h2>
<button id='CloseKeybindings' class='closeButton'>${IconX}</button>
<kbd class='dark'>Numpad 5</kbd>/<kbd class='dark'>/</kbd>: Open Settings<br/>
<kbd class='dark'>Numpad +</kbd>/<kbd class='dark'>=</kbd>: Global Zoom in pages (enlarge)<br/>
<kbd class='dark'>Numpad -</kbd>/<kbd class='dark'>-</kbd>: Global Zoom out pages (reduce)<br/>
<kbd class='dark'>Numpad /</kbd>/<kbd class='dark'>9</kbd>: Global Restore pages to original<br/>
<kbd class='dark'>Numpad *</kbd>/<kbd class='dark'>0</kbd>: Global Fit window width<br/>
<kbd class='dark'>V</kbd>: Vertical Mode<br/>
<kbd class='dark'>C</kbd>: WebComic Mode<br/>
<kbd class='dark'>N</kbd>: Right to Left Mode<br/>
<kbd class='dark'>B</kbd>: Left to Right Mode<br/>
<kbd class='dark'>→</kbd>/<kbd class='dark'>D</kbd>/<kbd class='dark'>Numpad 6</kbd>/<kbd class='dark'>.</kbd> : Next Chapter<br/>
<kbd class='dark'>←</kbd>/<kbd class='dark'>A</kbd>/<kbd class='dark'>Numpad 4</kbd>/<kbd class='dark'>,</kbd> : Previous Chapter<br/>
<kbd class='dark'>↑</kbd>/<kbd class='dark'>W</kbd>/<kbd class='dark'>Numpad 8</kbd>: Scroll Up<br/>
<kbd class='dark'>↓</kbd>/<kbd class='dark'>S</kbd>/<kbd class='dark'>Numpad 2</kbd>: Scroll Down<br/>
</div>`;
const ThumbnailsPanel = (times, begin) => indexList(times, begin).map((index) => `
<div id='Thumbnail${index}' class='Thumbnail'>
<img id='ThumbnailImg${index}' alt='' class='ThumbnailImg' src=''/>
<span class='ThumbnailIndex'>${index}</span>
</div>`);
const listBookmarks = () => {
if (isEmpty(settings$1.bookmarks))
return ['List Empty'];
return settings$1.bookmarks.map((mark, index) => `
<div id='Bookmark${index + 1}' class='BookmarkItem'>
<span class='bookmarkData bookmarkDate'>
${new Date(mark.date).toLocaleDateString()}
</span>
<span class='bookmarkData bookmarkURl'
title='${mark.url}'>
${mark.url}
</span>
<span class='bookmarkData bookmarkPage'>Page: ${mark.page}</span>
<span class='bookmarkData bookmarkFunctions'>
<button class='ControlButton open' title='Open Bookmark' type='button'
onclick="window.open('${mark.url}','_blank')">
${IconExternalLink}
</button>
<button class='ControlButton erase' title='Delete Bookmark' type='button'
value='${mark.url}'>
${IconTrash}
</button>
</pan>
</div>`);
};
const BookmarkPanel = `
<div id='BookmarksOverlay' class='overlay'></div>
<div id='BookmarksPanel' class='panel'>
<button id='CloseBookmarks' class='closeButton'>${IconX}</button>
<h2>Bookmarks</h2>
<div id='BookmarksList'>
${listBookmarks().join('')}
</div>
</div>
`;
function reloadBookmarks() {
const list = document.getElementById('BookmarksList');
if (list) {
list.innerHTML = listBookmarks().join('');
}
}
const listOptions = (times, begin) => indexList(times, begin).map((index) => `<option value='${index}'>${index}</option>`);
const body = (manga, begin = 1) => `
<div id='MangaOnlineViewer'
class="${settings$1.colorScheme} ${settings$1.hidePageControls ? 'hideControls' : ''}"
data-theme='${settings$1.theme}'>
<header id="Header" class="${settings$1.mouseOverMenu ? 'mouseOverMenu' : ''}">
<div id='menu'>
${IconMenu2}
</div>
<aside id='GlobalFunctions'>
<span>
<button id='enlarge' title='Enlarge' class='ControlButton'>${IconZoomInArea}</button>
<button id='restore' title='Restore' class='ControlButton'>${IconZoomPan}</button>
<button id='reduce' title='Reduce' class='ControlButton'>${IconZoomOutArea}</button>
<button id='fitWidth' title='Fit Width' class='ControlButton'>${IconArrowAutofitWidth}</button>
<button id='fitHeight' title='Fit Height' class='ControlButton'>${IconArrowAutofitHeight}</button>
<button id='keybindings' title='Keybindings' class='ControlButton'>${IconKeyboard}</button>
</span>
<span>
<button id='ltrMode' title='Left to Right Mode' class='ControlButton'>${IconArrowAutofitRight}</button>
<button id='verticalMode' title='Vertical Mode' class='ControlButton'>${IconArrowAutofitDown}</button>
<button id='rtlMode' title='Right to Left Mode' class='ControlButton'>${IconArrowAutofitLeft}</button>
<button id='pageControls' title='Toggle Page Controls' class='ControlButton'>${IconListNumbers}</button>
<button id='bookmarks' title='List Bookmarks' class='ControlButton'>${IconBookmarks}</button>
<button id='settings' title='Settings' class='ControlButton'>${IconSettings}</button>
</span>
</aside>
<div class='ViewerTitle'>
<h1 id='MangaTitle'>${manga.title}</h1>
<a id='series' href='${manga.series}'>(Return to Chapter List)</a>
</div>
<nav id='ChapterNavigation'>
<div id='Counters' class='ControlLabel'>
<i>0</i> of <b>${manga.pages - (begin - 1)}</b> Pages Loaded,
<span class='ControlLabel'>Go to Page:</span>
<select id='gotoPage'>
<option selected>#</option>
${listOptions(manga.pages, begin).join('')}
</select>
</div>
<div id='ChapterControl' class='ChapterControl'>
<button class='download NavigationControlButton ControlButton' title='Donwload Images Zip' type='button'>
${IconFileDownload}
${IconLoader2}
Download
</button>
<button id='prev' class='prev NavigationControlButton ControlButton' title='Previos Chapter' type='button'
onclick="window.location.href='${manga.prev || ''}';">
${IconArrowBigLeft}
Previous
</button>
<button id='next' class='next NavigationControlButton ControlButton' title='Next Chapter' type='button'
onclick="window.location.href='${manga.next || ''}';">
Next
${IconArrowBigRight}
</button>
</div>
</nav>
</header>
<main id='Chapter' class='${settings$1.fitWidthIfOversize === true ? 'fitWidthIfOversize' : ''} ${settings$1.viewMode}'>
${listPages(manga.pages, begin).join('')}
</main>
<nav id='Navigation' class='panel ${settings$1.showThumbnails ? '' : 'disabled'}'>
<div id='NavigationCounters' class='ControlLabel'>
${IconCategory}
<i>0</i> of <b>${manga.pages - (begin - 1)}</b> Pages Loaded
</div>
<div id='Thumbnails'>
${ThumbnailsPanel(manga.pages, begin).join('')}
</div>
</nav>
${SettingsPanel}
${KeybindingsPanel}
${BookmarkPanel}
</div>`;
const cache = {
zip: new JSZip(),
downloadFiles: 0,
Data: {},
};
const getExtension = (mimeType) => ((/image\/(?<ext>jpe?g|png|webp)/.exec(mimeType) || {}).groups || {}).ext || '' || 'png';
const getFilename = (name, index, total, ext) => `${name}${(index + 1).toString().padStart(Math.floor(Math.log10(total)) + 1, '0')}.${ext.replace('jpeg', 'jpg')}`;
// Generate Zip File for download
function generateZip() {
// Source:
// http://stackoverflow.com/questions/8778863/downloading-an-image-using-xmlhttprequest-in-a-userscript/8781262#8781262
if (cache.downloadFiles === 0) {
const filenameRegex = /^(?<name>.*?)(?<index>\d+)\.(?<ext>\w+)$/;
const images = [...document.querySelectorAll('.PageImg')];
const filenames = (() => {
const result = [];
for (let i = 0; i < images.length; i += 1) {
const $img = images[i];
const filename = $img.getAttribute('src')?.split(/[?#]/)[0].split('/').pop() ?? '';
const match = filenameRegex.exec(filename);
if (!match || !match.groups)
break;
const fixedFilename = getFilename(match.groups.name, parseInt(match.groups.index, 10), images.length, match.groups?.ext);
if (result.length > 0 && fixedFilename <= result[result.length - 1])
break;
result.push(fixedFilename);
}
if (result.length < images.length)
return [];
return result;
})();
images.forEach((img, index) => {
const src = img.getAttribute('src') ?? '';
const base64 = /^data:(?<mimeType>image\/\w+);base64,+(?<data>.+)/.exec(src);
if (base64 && base64.groups) {
const filename = getFilename('Page ', index, images.length, getExtension(base64.groups?.mimeType));
cache.zip.file(filename, base64.groups.data, {
base64: true,
createFolders: true,
});
logScript(`${filename} Added to Zip from Base64 Image, From: ${src}`);
cache.downloadFiles += 1;
}
else {
try {
GM_xmlhttpRequest({
method: 'GET',
url: src,
headers: { referer: src, origin: src },
responseType: 'blob',
onload(request) {
const filename = filenames[index] ||
getFilename('Page ', index, images.length, getExtension(request.response.type));
cache.zip.file(filename, request.response, {
base64: true,
createFolders: true,
compression: 'DEFLATE',
});
logScript(`${filename} Added to Zip as Base64 Image, From: ${src}, Data:`, request.response);
cache.downloadFiles += 1;
},
});
}
catch (e) {
logScript(e);
}
}
});
}
const total = document.querySelectorAll('.PageImg').length;
if (cache.downloadFiles < total) {
logScript(`Waiting for Files to Download ${cache.downloadFiles} of ${total}`);
setTimeout(generateZip, 2000);
}
else {
try {
logScript('Generating Zip');
cache.zip
.generateAsync({
type: 'blob',
})
.then((content) => {
logScript('Download Ready');
const zipName = `${document.querySelector('#MangaTitle')?.textContent?.trim()}.zip`;
saveAs(content, zipName);
const button = document.querySelector('.download');
button.disabled = false;
button.classList.remove('loading');
})
.catch(logScript);
}
catch (e) {
logScript(e);
}
}
}
function isImagesManga(manga) {
return 'listImages' in manga && !isNothing(manga.listImages);
}
function isPagesManga(manga) {
return 'listPages' in manga && !isNothing(manga.listPages);
}
function isBruteforceManga(manga) {
return 'bruteForce' in manga && !isNothing(manga.bruteForce);
}
var Language;
(function (Language) {
Language["ENGLISH"] = "English";
Language["SPANISH"] = "Spanish";
Language["PORTUGUESE"] = "Portuguese";
Language["RAW"] = "Raw";
})(Language || (Language = {}));
var Category;
(function (Category) {
Category["MANGA"] = "manga";
Category["COMIC"] = "comic";
Category["HENTAI"] = "hentai";
})(Category || (Category = {}));
function fetchText(url, format) {
return new Promise((resolve) => {
logScript('Fetching page: ', url);
fetch(url)
.then((response) =>
// When the page is loaded convert it to text
response.text())
.then((html) => {
// Initialize the DOM parser
const parser = new DOMParser();
// Parse the text
const doc = parser.parseFromString(html, format);
// You can now even select part of that html as you would in the regular DOM
// Example:
// var docArticle = doc.querySelector('article').innerHTML;
// console.log(doc);
resolve(doc);
})
.catch((err) => {
logScript('Failed to fetch page: ', err);
});
});
}
function fetchHtml(url) {
return fetchText(url, 'text/html');
}
function getElementAttribute(url, selector, attribute) {
return fetchHtml(url).then((doc) => doc.querySelector(selector)?.getAttribute(attribute));
}
/**
* Settings the lazy load will obey
*/
const settings = {
threshold: 2000,
throttle: 500,
lazyAttribute: 'data-src',
targetAttribute: 'src',
};
/**
* List of elements that will be lazy loaded
*/
let listElements = [];
/**
* Check if the image ins nearing the viewport, so it needs to load.
* @param value
*/
function filterInView(value) {
const { element } = value;
const rect = element.getBoundingClientRect();
const viewport = {
top: 0 - settings.threshold,
bottom: window.scrollY + window.innerHeight + settings.threshold,
};
return rect.bottom >= viewport.top && rect.top <= viewport.bottom;
}
/**
* Execute the loading of the image
* @param item
*/
function showElement(item) {
const value = item.element.getAttribute(settings.lazyAttribute);
if (value)
item.element.setAttribute(settings.targetAttribute, value);
item.callback(item.element);
}
/**
* Lookup images that should be loaded, and update the current list
*/
function executeCheck() {
const inView = listElements.filter(filterInView);
listElements = listElements.filter((item) => !filterInView(item));
inView.forEach(showElement);
}
/**
* Throttle controller
*/
let wait = false;
/**
* Function responsible for observing the screen move/change
*/
function observerEvent() {
if (listElements.length === 0) {
window.removeEventListener('scroll', observerEvent);
window.removeEventListener('touchmove', observerEvent);
window.removeEventListener('resize', observerEvent);
// console.info('All items lazy loaded');
return;
}
if (wait) {
return;
}
executeCheck();
wait = true;
setTimeout(() => {
wait = false;
}, settings.throttle);
}
/**
* Simple lazy loading for images.
* Add an image element to a list, wait for it to be close to appearing on screen then load its 'src' from 'data-src'
* then call a callback function.
* @param element
* @param callback
*/
function lazyLoad(element, callback) {
if (listElements.length === 0) {
// console.info('Initializing lazy load');
window.addEventListener('scroll', observerEvent, {
passive: true,
});
window.addEventListener('touchmove', observerEvent, {
passive: true,
});
window.addEventListener('resize', observerEvent, {
passive: true,
});
}
listElements.push({ element, callback });
observerEvent();
}
// After pages load apply default Zoom
function applyZoom(pages = '.PageContent img', zoom = settings$1.zoom) {
const pg = [...document.querySelectorAll(pages)];
pg.forEach((value) => {
const img = value;
img.removeAttribute('width');
img.removeAttribute('height');
img.removeAttribute('style');
if (zoom === 1000) {
img.style.width = `${window.innerWidth}px`;
}
else if (zoom === -1000) {
const nav = document.querySelector('#Navigation')?.classList.contains('disabled');
const chap = document.querySelector('#Chapter')?.classList.contains('WebComic');
const nextHeight = window.innerHeight + (nav ? 0 : -34) + (chap ? 0 : -35);
img.style.height = `${nextHeight}px`;
}
else {
img.style.width = `${img.naturalWidth * (zoom / 100)}px`;
}
});
}
// Force reload the image
function reloadImage(img) {
const src = img.getAttribute('src');
if (src) {
img.removeAttribute('src');
setTimeout(() => {
img.setAttribute('src', src);
}, 500);
}
}
function onImagesDone() {
logScript('Images Loading Complete');
if (!settings$1.lazyLoadImages) {
document.querySelector('.download')?.setAttribute('href', '#download');
logScript('Download Available');
if (settings$1.downloadZip) {
document.querySelector('#blob')?.dispatchEvent(new Event('click'));
}
}
}
function updateProgress() {
const total = document.querySelectorAll('.PageContent .PageImg').length;
const loaded = document.querySelectorAll('.PageContent .PageImg.imgLoaded').length;
const percentage = Math.floor((loaded / total) * 100);
const title = document.querySelector('title');
if (title) {
title.innerHTML = `(${percentage}%) ${document.querySelector('#MangaTitle')?.textContent}`;
}
document.querySelectorAll('#Counters i, #NavigationCounters i').forEach((ele) => {
ele.textContent = loaded.toString();
});
NProgress.configure({
showSpinner: false,
}).set(loaded / total);
logScript(`Progress: ${percentage}%`);
if (loaded === total)
onImagesDone();
}
// change class if the image is loaded or broken
function onImagesProgress(instance, image) {
if (image) {
if (image.isLoaded) {
image.img.classList.add('imgLoaded');
image.img.classList.remove('imgBroken');
image.img.getAttribute('id');
const thumbId = image.img.getAttribute('id').replace('PageImg', 'ThumbnailImg');
const thumb = document.getElementById(thumbId);
if (thumb) {
thumb.onload = () => applyZoom(`#${image.img.getAttribute('id')}`);
thumb.setAttribute('src', image.img.getAttribute('src'));
}
}
else {
image.img.classList.add('imgBroken');
reloadImage(image.img);
const imgLoad = imagesLoaded(image.img.parentElement);
imgLoad.on('progress', onImagesProgress);
}
}
updateProgress();
}
// Corrects urls
function normalizeUrl(url = '') {
let uri = url.trim();
if (uri.startsWith('//')) {
uri = `https:${uri}`;
}
return uri;
}
// Adds an image to the place-holder div
function addImg(manga, index, imageSrc, position) {
const src = normalizeUrl(imageSrc);
const img = document.querySelector(`#PageImg${index}`);
if (img) {
if (!settings$1.lazyLoadImages || position <= settings$1.lazyStart) {
setTimeout(() => {
img.setAttribute('src', src);
const imgLoad = imagesLoaded(img.parentElement);
imgLoad.on('progress', onImagesProgress);
logScript('Loaded Image:', index, 'Source:', src);
}, (manga.timer || settings$1.throttlePageLoad) * position);
}
else {
img.setAttribute('data-src', src);
lazyLoad(img, () => {
const imgLoad = imagesLoaded(img.parentElement);
imgLoad.on('progress', onImagesProgress);
logScript('Lazy Image: ', index, ' Source: ', img.getAttribute('src'));
});
}
}
}
function findPage(manga, index, pageUrl, lazy) {
return async () => {
const src = await getElementAttribute(pageUrl, manga.img, manga.lazyAttr ?? 'src');
const img = document.querySelector(`#PageImg${index}`);
if (src && img) {
img.setAttribute('src', src);
img.style.width = 'auto';
const imgLoad = imagesLoaded(img.parentElement);
imgLoad.on('progress', onImagesProgress);
logScript(`${lazy && 'Lazy '}Page: `, index, ' Source: ', img.getAttribute('src'));
}
};
}
// Adds a page to the place-holder div
async function addPage(manga, index, pageUrl, position) {
const img = document.querySelector(`#PageImg${index}`);
if (img) {
if (!settings$1.lazyLoadImages || position <= settings$1.lazyStart) {
setTimeout(() => {
findPage(manga, index, pageUrl, false)();
}, (manga.timer || settings$1.throttlePageLoad) * position);
}
else {
img.setAttribute('data-src', 'data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
lazyLoad(img, findPage(manga, index, pageUrl, true));
}
}
}
// use a list of pages to fill the viewer
function loadMangaPages(begin, manga) {
indexList(manga.pages, begin).forEach((index, position) => addPage(manga, index, manga.listPages[index - 1], position));
}
// use a list of images to fill the viewer
function loadMangaImages(begin, manga) {
indexList(manga.pages, begin).forEach((index, position) => addImg(manga, index, manga.listImages[index - 1], position));
}
// Entry point for loading hte Manga pages
function loadManga(manga, begin = 1) {
settings$1.lazyLoadImages = manga.lazy || settings$1.lazyLoadImages;
logScript('Loading Images');
logScript(`Intervals: ${manga.timer || settings$1.throttlePageLoad || 'Default(1000)'}`);
logScript(`Lazy: ${settings$1.lazyLoadImages}, Starting from: ${settings$1.lazyStart}`);
if (settings$1.lazyLoadImages) {
logScript('Download may not work with Lazy Loading Images');
}
if (isImagesManga(manga)) {
logScript('Method: Images:', manga.listImages);
loadMangaImages(begin, manga);
}
else if (isPagesManga(manga)) {
logScript('Method: Pages:', manga.listPages);
loadMangaPages(begin, manga);
}
else if (isBruteforceManga(manga)) {
logScript('Method: Brute Force');
manga.bruteForce({
begin,
addImg,
loadImages: (list) => loadMangaImages(begin, { ...manga, listImages: list }),
loadPages: (list, img, lazyAttr) => loadMangaPages(begin, {
...manga,
listPages: list,
img,
lazyAttr,
}),
wait: settings$1.throttlePageLoad,
});
}
}
// Goto Page and Thumbnails
function scrollToElement(ele) {
window.scroll(0, ele?.offsetTop || 0);
}
// Clean key press configurations and set some when specified
function setKeyDownEvents() {
try {
document.onkeydown = null;
document.onkeypress = null;
window.onkeydown = null;
window.onkeypress = null;
window.onload = null;
document.body.onload = null;
}
catch (e) {
logScript(`Keybinds error: ${e}`);
}
function processKey(e) {
const a = e.code;
const usedKeys = [
'KeyW',
'Numpad8',
'KeyS',
'Numpad2',
'ArrowRight',
'Period',
'KeyD',
'Numpad6',
'ArrowLeft',
'Comma',
'KeyA',
'Numpad4',
'Equal',
'NumpadAdd',
'KeyE',
'Minus',
'NumpadSubtract',
'KeyQ',
'Digit9',
'NumpadDivide',
'KeyR',
'Digit0',
'NumpadMultiply',
'KeyF',
'Slash',
'Numpad5',
'KeyX',
'KeyC',
'KeyV',
'KeyB',
'KeyN',
];
if (!e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey && usedKeys.some((i) => i === a)) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
logScript('Keyboard:', a, ' Event:', e);
switch (a) {
case 'ArrowUp':
case 'KeyW':
case 'Numpad8':
if (settings$1.zoom === -1000) {
const next = [...document.querySelectorAll('.MangaPage')].find((element) => element.offsetTop - window.scrollY > 10);
scrollToElement(next?.previousElementSibling);
}
else {
window.scrollBy({
top: -window.innerHeight / 2,
behavior: 'smooth',
});
}
break;
case 'ArrowDown':
case 'KeyS':
case 'Numpad2':
if (settings$1.zoom === -1000) {
const next = [...document.querySelectorAll('.MangaPage')].find((element) => element.offsetTop - window.scrollY > 10);
scrollToElement(next);
}
else {
window.scrollBy({
top: window.innerHeight / 2,
behavior: 'smooth',
});
}
break;
case 'ArrowRight':
case 'Period':
case 'KeyD':
case 'Numpad6':
logScript('Click next');
document.querySelector('#next')?.click();
break;
case 'ArrowLeft':
case 'Comma':
case 'KeyA':
case 'Numpad4':
document.querySelector('#prev')?.click();
break;
case 'Equal':
case 'NumpadAdd':
case 'KeyE':
document.querySelector('#enlarge')?.dispatchEvent(new Event('click'));
break;
case 'Minus':
case 'NumpadSubtract':
case 'KeyQ':
document.querySelector('#reduce')?.dispatchEvent(new Event('click'));
break;
case 'Digit9':
case 'NumpadDivide':
case 'KeyR':
document.querySelector('#restore')?.dispatchEvent(new Event('click'));
break;
case 'Digit0':
case 'NumpadMultiply':
case 'KeyF':
document.querySelector('#fitWidth')?.dispatchEvent(new Event('click'));
break;
case 'Slash':
case 'Numpad5':
case 'KeyX':
document.querySelector('#settings')?.dispatchEvent(new Event('click'));
break;
case 'KeyV':
document.querySelector('#verticalMode')?.dispatchEvent(new Event('click'));
break;
case 'KeyN':
document.querySelector('#rtlMode')?.dispatchEvent(new Event('click'));
break;
case 'KeyB':
document.querySelector('#ltrMode')?.dispatchEvent(new Event('click'));
break;
}
return false;
}
return true;
}
document.addEventListener('keydown', processKey);
}
function updateZoomPercent(percent = settings$1.zoom) {
const zoom = document.querySelector('#ZoomPercent');
if (zoom) {
zoom.textContent = percent.toString();
}
}
// Controls for the extra features added to the sites
function controls() {
// Settings Control
function buttonSettings() {
document.querySelector('#SettingsPanel')?.classList.toggle('visible');
document.querySelector('#Navigation')?.classList.toggle('visible');
document.querySelector('#Header')?.classList.toggle('visible');
document.querySelector('#SettingsOverlay')?.classList.toggle('visible');
}
document.querySelector('#settings')?.addEventListener('click', buttonSettings);
document.querySelector('#CloseSettings')?.addEventListener('click', buttonSettings);
document.querySelector('#SettingsOverlay')?.addEventListener('click', buttonSettings);
// Keybindings list
function buttonKeybindings() {
document.querySelector('#KeybindingsPanel')?.classList.toggle('visible');
}
document.querySelector('#keybindings')?.addEventListener('click', buttonKeybindings);
document.querySelector('#CloseKeybindings')?.addEventListener('click', buttonKeybindings);
// List of Bookmarks
function buttonBookmarks() {
document.querySelector('#BookmarksPanel')?.classList.toggle('visible');
document.querySelector('#BookmarksOverlay')?.classList.toggle('visible');
}
document.querySelector('#bookmarks')?.addEventListener('click', buttonBookmarks);
document.querySelector('#CloseBookmarks')?.addEventListener('click', buttonBookmarks);
document.querySelector('#BookmarksOverlay')?.addEventListener('click', buttonBookmarks);
function eraseBookmarks(elem) {
elem.addEventListener('click', (event) => {
const target = event.currentTarget.value;
settings$1.bookmarks = settings$1.bookmarks.filter((el) => el.url !== target);
if (target === window.location.href) {
document.querySelector('#MangaOnlineViewer')?.classList.toggle('bookmarked');
}
logScript(`Bookmark Removed ${target}`);
Swal.fire({
title: 'Bookmark Removed',
timer: 10000,
icon: 'error',
});
setValueGM('Bookmarks', JSON.stringify(settings$1.bookmarks));
logScript(`MangaBookmarks: ${getValueGM('Bookmarks')}`);
reloadBookmarks();
document.querySelectorAll('.BookmarkItem .erase')?.forEach(eraseBookmarks);
});
}
document.querySelectorAll('.BookmarkItem .erase')?.forEach(eraseBookmarks);
// Size Controls
// Global Zoom In Button
function buttonGlobalZoomIn() {
settings$1.zoom += settings$1.zoomStep;
updateZoomPercent();
applyZoom();
}
document.querySelector('#enlarge')?.addEventListener('click', buttonGlobalZoomIn);
// Global Zoom Out Button
function buttonGlobalZoomOut() {
settings$1.zoom -= settings$1.zoomStep;
updateZoomPercent();
applyZoom();
}
document.querySelector('#reduce')?.addEventListener('click', buttonGlobalZoomOut);
// Global Zoom Restore Button
function buttonGlobalRestoreZoom() {
settings$1.zoom = 100;
updateZoomPercent();
applyZoom();
}
document.querySelector('#restore')?.addEventListener('click', buttonGlobalRestoreZoom);
// Global Fit Width Button
function buttonGlobalFitWidth() {
settings$1.zoom = 1000;
updateZoomPercent();
applyZoom();
}
document.querySelector('#fitWidth')?.addEventListener('click', buttonGlobalFitWidth);
// Global Fit height Button
function buttonGlobalFitHeight() {
settings$1.zoom = -1000;
updateZoomPercent();
applyZoom();
}
document.querySelector('#fitHeight')?.addEventListener('click', buttonGlobalFitHeight);
// Fluid LTR View Mode Button
function buttonLtrMode() {
document.querySelector('#Chapter')?.classList.add('FluidLTR');
document.querySelector('#Chapter')?.classList.remove('FluidRTL');
document.querySelector('#Chapter')?.classList.add('DoublePage');
applyZoom();
}
document.querySelector('#ltrMode')?.addEventListener('click', buttonLtrMode);
// Fluid RTL View Mode Button
function buttonRtlMode() {
document.querySelector('#Chapter')?.classList.remove('FluidLTR');
document.querySelector('#Chapter')?.classList.add('FluidRTL');
document.querySelector('#Chapter')?.classList.add('DoublePage');
applyZoom();
}
document.querySelector('#rtlMode')?.addEventListener('click', buttonRtlMode);
// Vertical View Mode Button
function buttonVerticalMode() {
document.querySelector('#Chapter')?.classList.remove('FluidLTR');
document.querySelector('#Chapter')?.classList.remove('FluidRTL');
document.querySelector('#Chapter')?.classList.remove('DoublePage');
applyZoom();
}
document.querySelector('#verticalMode')?.addEventListener('click', buttonVerticalMode);
// Image Fit width if Oversize Toggle
function checkFitWidthOversize(event) {
document.querySelector('#Chapter')?.classList.toggle('fitWidthIfOversize');
if (event.currentTarget.checked) {
setValueGM('FitWidthIfOversize', true);
}
else {
setValueGM('FitWidthIfOversize', false);
}
logScript(`fitIfOversize: ${getValueGM('FitWidthIfOversize')}`);
}
document.querySelector('#fitIfOversize')?.addEventListener('change', checkFitWidthOversize);
// Default View mode Selector
function changeViewMode(event) {
const mode = event.currentTarget.value;
document.querySelector('#Chapter')?.classList.remove('FluidLTR');
document.querySelector('#Chapter')?.classList.remove('FluidRTL');
document.querySelector('#Chapter')?.classList.add(mode);
setValueGM('ViewMode', mode);
logScript(`ViewMode: ${getValueGM('ViewMode')}`);
applyZoom();
}
document.querySelector('#viewMode')?.addEventListener('change', changeViewMode);
// Start/Load mode Selector
function changeLoadMode(event) {
const mode = event.currentTarget.value;
setValueGM('LoadMode', mode);
logScript(`MangaLoadMode: ${getValueGM('LoadMode')}`);
}
document.querySelector('#loadMode')?.addEventListener('change', changeLoadMode);
// Show Thumbnail Toggle
function checkShowThumbnails(event) {
document.querySelector('#Navigation')?.classList.toggle('disabled');
if (event.currentTarget.checked) {
setValueGM('ShowThumbnails', true);
}
else {
setValueGM('ShowThumbnails', false);
}
logScript(`MangaShowThumbnails: ${getValueGM('ShowThumbnails')}`);
applyZoom();
}
document.querySelector('#showThumbnails')?.addEventListener('change', checkShowThumbnails);
// Download auto start toggle
function changeAutoDownload(event) {
if (event.currentTarget.checked) {
setValueGM('DownloadZip', true);
Swal.fire({
title: 'Attention',
text: 'Next time a chapter finish loading you will be prompted to save automatically',
timer: 10000,
icon: 'info',
});
}
else {
setValueGM('DownloadZip', false);
}
logScript(`MangaDownloadZip: ${getValueGM('DownloadZip')}`);
}
document.querySelector('#downloadZip')?.addEventListener('change', changeAutoDownload);
// Download starter
document.querySelector('.download')?.addEventListener('click', (event) => {
const button = event.currentTarget;
logScript('Downloading Chapter');
button.disabled = true;
button.classList.add('loading');
generateZip();
});
// Lazy load Toggle
function checkLazyLoad(event) {
if (event.currentTarget.checked) {
setValueGM('LazyLoadImages', true);
Swal.fire({
title: 'Warning',
html: `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.`,
icon: 'warning',
});
}
else {
setValueGM('LazyLoadImages', false);
}
logScript(`MangaLazyLoadImages: ${getValueGM('LazyLoadImages')}`);
}
document.querySelector('#lazyLoadImages')?.addEventListener('change', checkLazyLoad);
// Lazy load starting point Slider
function changeLazyStart(event) {
const start = event.currentTarget.value;
setValueGM('LazyStart', start);
logScript(`lazyStart: ${getValueGM('LazyStart')}`);
}
document.querySelector('#lazyStart')?.addEventListener('change', changeLazyStart);
// Images load speed Selector
function changePagesPerSecond(event) {
setValueGM('Timer', parseInt(event.currentTarget.value, 10));
logScript(`MangaTimer: ${getValueGM('Timer')}`);
}
document.querySelector('#PagesPerSecond')?.addEventListener('change', changePagesPerSecond);
// Global Default Zoom Selector
function changeDefaultZoom(event) {
settings$1.zoom = parseInt(event.currentTarget.value, 10);
updateZoomPercent();
setValueGM('Zoom', parseInt(settings$1.zoom.toString(), 10));
logScript(`MangaZoom: ${getValueGM('Zoom')}`);
applyZoom();
}
document.querySelector('#DefaultZoom')?.addEventListener('change', changeDefaultZoom);
// Zoom Step Slider
function changeZoomStep(event) {
const step = event.currentTarget.value;
setValueGM('ZoomStep', parseInt(step, 10));
logScript(`ZoomStep: ${getValueGM('ZoomStep')}`);
}
document.querySelector('#zoomStep')?.addEventListener('change', changeZoomStep);
// Min Zoom Slider
function changeMinZoom(event) {
const min = event.currentTarget.value;
replaceStyleSheet('MinZoom', `#MangaOnlineViewer .PageContent .PageImg {min-width: ${min}vw;}`);
setValueGM('MinZoom', parseInt(min, 10));
logScript(`MinZoom: ${getValueGM('MinZoom')}`);
}
document.querySelector('#minZoom')?.addEventListener('change', changeMinZoom);
// Show/hide Image Controls Button
function globalHideImageControls() {
document.querySelector('#MangaOnlineViewer')?.classList.toggle('hideControls');
}
document.querySelector('#pageControls')?.addEventListener('click', globalHideImageControls);
// Show/hide Image Controls Toggle
function checkHideImageControls(event) {
document.querySelector('#MangaOnlineViewer')?.classList.toggle('hideControls');
if (event.currentTarget.checked) {
setValueGM('HidePageControls', true);
}
else {
setValueGM('HidePageControls', false);
}
logScript(`MangaHidePageControls: ${getValueGM('HidePageControls')}`);
}
document.querySelector('#hidePageControls')?.addEventListener('change', checkHideImageControls);
// Sticky Header or MouseOverMenu Toggle
function checkMouseOverMenu(event) {
document.querySelector('#Header')?.classList.toggle('mouseOverMenu');
if (event.currentTarget.checked) {
setValueGM('MouseOverMenu', true);
}
else {
setValueGM('MouseOverMenu', false);
}
logScript(`MangaHidePageControls: ${getValueGM('MouseOverMenu')}`);
}
document.querySelector('#mouseOverMenu')?.addEventListener('change', checkMouseOverMenu);
// ColorScheme Selector
function changeColorScheme() {
const isDark = settings$1.colorScheme === 'dark';
settings$1.colorScheme = isDark ? 'light' : 'dark';
setValueGM('ColorScheme', settings$1.colorScheme);
[...document.querySelectorAll('#MangaOnlineViewer , body')].forEach((elem) => {
elem.classList.remove(isDark ? 'dark' : 'light');
elem.classList.add(settings$1.colorScheme);
});
logScript('ColorScheme', getValueGM('ColorScheme'));
}
document.querySelector('#ColorScheme')?.addEventListener('click', changeColorScheme);
// Theme Control Selector
function changeTheme(event) {
const target = event.currentTarget;
[...document.querySelectorAll('.ThemeRadio')].forEach((elem) => elem.classList.remove('selected'));
target.classList.add('selected');
[...document.querySelectorAll('#MangaOnlineViewer , body')].forEach((elem) => {
elem.setAttribute('data-theme', target.title);
});
setValueGM('Theme', target.title);
logScript('Theme', getValueGM('Theme'));
const hue = document.querySelector('#Hue');
const shade = document.querySelector('#Shade');
if (target.title.startsWith('custom')) {
hue?.classList.add('show');
shade?.classList.remove('show');
}
else {
hue?.classList.remove('show');
shade?.classList.add('show');
}
}
[...document.querySelectorAll('.ThemeRadio')].forEach((elem) => elem.addEventListener('click', changeTheme));
// Custom theme Color Input
function changeCustomTheme(event) {
const target = event.currentTarget.value;
logScript(`CustomTheme: ${target}`);
settings$1.customTheme = target;
addCustomTheme(target);
setValueGM('CustomTheme', target);
logScript(`CustomTheme: ${getValueGM('CustomTheme')}`);
}
document.querySelector('#CustomThemeHue')?.addEventListener('change', changeCustomTheme);
// Theme Shade Input
function changeThemeShade(event) {
const target = parseInt(event.currentTarget.value, 10);
logScript(`ThemeShade: ${target}`);
settings$1.themeShade = target;
refreshThemes();
setValueGM('ThemeShade', target);
logScript(`ThemeShade: ${getValueGM('ThemeShade')}`);
}
document.querySelector('#ThemeShade')?.addEventListener('change', changeThemeShade);
// Goto Navigation Selector
function selectGoToPage(event) {
const target = event.currentTarget.value;
applyZoom();
scrollToElement(document.querySelector(`#Page${target}`));
}
document.querySelector('#gotoPage')?.addEventListener('change', selectGoToPage);
// Thumbnail Navigation
function clickThumbnail(elem) {
return elem.addEventListener('click', (event) => {
applyZoom();
scrollToElement(document.querySelector(`#Page${event.currentTarget.querySelector('.ThumbnailIndex')?.textContent}`));
});
}
document.querySelectorAll('.Thumbnail')?.forEach(clickThumbnail);
// Individual Page functions
// Bookmark Page to resume reading
function buttonBookmark(elem) {
elem.addEventListener('click', (event) => {
document.querySelector('#MangaOnlineViewer')?.classList.toggle('bookmarked');
const num = parseInt(event.currentTarget.parentElement?.querySelector('.PageIndex')
?.textContent || '0', 10);
const mark = {
url: window.location.href,
page: num,
date: Date.now(),
};
const found = settings$1.bookmarks.filter((el) => el.url === mark.url).length > 0;
if (found) {
settings$1.bookmarks = settings$1.bookmarks.filter((el) => el.url !== mark.url);
Swal.fire({
title: 'Bookmark Removed',
timer: 10000,
icon: 'error',
});
}
else {
settings$1.bookmarks.push(mark);
Swal.fire({
title: 'Saved Bookmark',
html: `Next time you open this chapter it will resume from:<h4>Page ${num}</h4>(Only <i>ONCE</i> per Bookmark, will be removed after a year unused)`,
icon: 'success',
});
}
setValueGM('Bookmarks', JSON.stringify(settings$1.bookmarks));
logScript(`MangaBookmarks: ${getValueGM('Bookmarks')}`);
reloadBookmarks();
document.querySelectorAll('.BookmarkItem .erase')?.forEach(eraseBookmarks);
});
}
document.querySelectorAll('.Bookmark')?.forEach(buttonBookmark);
// Reload Page
function buttonReloadPage(elem) {
return elem.addEventListener('click', (event) => {
const img = event.currentTarget.parentElement?.parentElement?.querySelector('.PageImg');
reloadImage(img);
});
}
document.querySelectorAll('.Reload')?.forEach(buttonReloadPage);
// ZoomIn
function buttonZoomIn(elem) {
return elem.addEventListener('click', (event) => {
const img = event.currentTarget.parentElement?.parentElement?.querySelector('.PageImg');
const ratio = (img.width / img.naturalWidth) * (100 + settings$1.zoomStep);
applyZoom(`#${img.getAttribute('id')}`, ratio);
});
}
document.querySelectorAll('.ZoomIn')?.forEach(buttonZoomIn);
// ZoomOut
function buttonZoomOut(elem) {
return elem.addEventListener('click', (event) => {
const img = event.currentTarget.parentElement?.parentElement?.querySelector('.PageImg');
const ratio = (img.width / img.naturalWidth) * (100 - settings$1.zoomStep);
applyZoom(`#${img.getAttribute('id')}`, ratio);
});
}
document.querySelectorAll('.ZoomOut')?.forEach(buttonZoomOut);
// ZoomRestore
function buttonRestoreZoom(elem) {
return elem.addEventListener('click', () => {
document.querySelector('.PageContent .PageImg')?.removeAttribute('width');
});
}
document.querySelectorAll('.ZoomRestore')?.forEach(buttonRestoreZoom);
// ZoomWidth
function buttonZoomWidth(elem) {
return elem.addEventListener('click', (event) => {
const page = event.currentTarget.parentElement?.parentElement;
const img = page?.querySelector('.PageImg');
applyZoom(`#${img.getAttribute('id')}`, 1000);
page?.classList.toggle('DoublePage');
});
}
document.querySelectorAll('.ZoomWidth')?.forEach(buttonZoomWidth);
// ZoomHeight
function buttonZoomHeight(elem) {
elem.addEventListener('click', (event) => {
const img = event.currentTarget.parentElement?.parentElement?.querySelector('.PageImg');
applyZoom(`#${img.getAttribute('id')}`, -1000);
});
}
document.querySelectorAll('.ZoomHeight')?.forEach(buttonZoomHeight);
// Hide
function buttonHidePage(elem) {
elem.addEventListener('click', (event) => {
const img = event.currentTarget.parentElement?.parentElement;
img.classList.toggle('hide');
});
}
document.querySelectorAll('.Hide')?.forEach(buttonHidePage);
// Reset Reader Settings
function buttonResetSettings() {
getListGM().forEach((setting) => removeValueGM(setting));
Swal.fire({
title: 'Attention',
text: 'Settings have been reset, reload the page to take effect',
timer: 10000,
icon: 'info',
});
}
document.querySelector('#ResetSettings')?.addEventListener('click', buttonResetSettings);
/**
* Changes header class when scrolling up or down to show/hide it
* @param showEnd [default 0]px from end of the screen to show header
*/
function useScrollDirection(showEnd = 0) {
let prevOffset = 0;
const header = document.querySelector('#Header');
const setScrollDirection = (classSuffix) => {
header.classList.remove('scroll-end');
header.classList.remove('scroll-hide');
header.classList.remove('scroll-show');
if (classSuffix)
header.classList.add(`scroll-${classSuffix}`);
};
function toggleScrollDirection() {
const { scrollY } = window;
if (showEnd && scrollY + window.innerHeight + showEnd > document.body.offsetHeight) {
setScrollDirection('end');
}
else if (scrollY > prevOffset && scrollY > 50) {
setScrollDirection('hide');
}
else if (scrollY < prevOffset && scrollY > 50) {
setScrollDirection('show');
}
else {
setScrollDirection('');
}
prevOffset = scrollY;
}
window.addEventListener('scroll', toggleScrollDirection);
}
useScrollDirection(100);
}
function display(manga, begin) {
window.stop();
if (manga.before !== undefined) {
manga.before();
}
document.head.innerHTML = head(manga);
document.body.innerHTML = body(manga, begin);
document.body.className = '';
document.body.removeAttribute('style');
// document.documentElement.innerHTML = reader(manga, begin);
logScript('Rebuilding Site');
setTimeout(() => {
try {
controls();
setKeyDownEvents();
setTimeout(() => {
window.scrollTo(0, 0);
loadManga(manga, begin);
}, 50);
// Clear used Bookmarks
if (!isNothing(settings$1.bookmarks.filter((el) => el.url === window.location.href))) {
logScript(`Bookmark Removed ${window.location.href}`);
settings$1.bookmarks = settings$1.bookmarks.filter((el) => el.url !== window.location.href);
setValueGM('Bookmarks', JSON.stringify(settings$1.bookmarks));
}
}
catch (e) {
logScript(e);
}
}, 50);
if (manga.after !== undefined) {
manga.after();
}
}
async function formatPage(manga, begin = 0) {
display(manga, begin);
}
async function lateStart(site, begin = 1) {
const manga = await site.run();
logScript('LateStart');
const options = {
title: 'Starting<br>MangaOnlineViewer',
input: 'range',
inputAttributes: {
min: '1',
max: manga.pages.toString(),
step: '1',
},
inputValue: begin || 1,
text: 'Choose the Page to start from:',
showCancelButton: true,
cancelButtonColor: '#d33',
reverseButtons: true,
icon: 'question',
};
Swal.fire(options).then((result) => {
if (result.value) {
logScript(`Choice: ${result.value}`);
formatPage(manga, result.value);
}
else {
logScript(result.dismiss);
}
});
}
function createLateStartButton(site, beginning) {
const button = document.createElement('button');
button.innerText = 'Start MangaOnlineViewer';
button.id = 'StartMOV';
button.onclick = () => {
lateStart(site, beginning);
};
document.body.appendChild(button);
// language=CSS
const css = `
#StartMOV {
font-size: 20px;
font-weight: bold;
color: #fff;
cursor: pointer;
margin: 20px;
padding: 10px 20px;
text-align: center;
border: none;
background-size: 300% 100%;
border-radius: 50px;
transition: all 0.4s ease-in-out;
background-image: linear-gradient(to right, #667eea, #764ba2, #6b8dd6, #8e37d7);
box-shadow: 0 4px 15px 0 rgba(116, 79, 168, 0.75);
position: fixed;
top: 10px;
right: 10px;
z-index: 10000;
}
#StartMOV:hover {
background-position: 100% 0;
transition: all 0.4s ease-in-out;
}
#StartMOV:focus {
outline: none;
}
`;
const style = document.createElement('style');
style.appendChild(document.createTextNode(css));
document.head.appendChild(style);
logScript('Start Button added to page', button);
}
// Organize the site adding place-holders for the manga pages
function preparePage(site, manga, begin = 0) {
logScript(`Found Pages: ${manga.pages}`);
if (manga.pages > 0) {
let beginning = begin;
if (beginning <= 1) {
beginning = settings$1?.bookmarks?.find((b) => b.url === window.location.href)?.page || 1;
}
const style = document.createElement('style');
style.appendChild(document.createTextNode(sweetalertStyle));
document.body.appendChild(style);
// window.mov = (b: number) => lateStart(site, b || beginning);
switch (site.start || settings$1?.loadMode) {
case 'never':
createLateStartButton(site, beginning);
break;
case 'always':
formatPage(manga, 0);
break;
case 'wait':
default:
Swal.fire({
title: 'Starting<br>MangaOnlineViewer',
html: `${beginning > 1 ? `Resuming reading from Page ${beginning}.<br/>` : ''}Please wait, 3 seconds...`,
showCancelButton: true,
cancelButtonColor: '#d33',
reverseButtons: true,
timer: 3000,
}).then((result) => {
if (result.value || result.dismiss === Swal.DismissReason.timer) {
formatPage(manga, beginning);
}
else {
createLateStartButton(site, beginning);
logScript(result.dismiss);
}
});
break;
}
}
}
// Wait for something on the site to be ready before executing the script
async function waitExec(site, waitElapsed = 0) {
if (waitElapsed >= (site.waitMax || 5000)) {
preparePage(site, await site.run());
return;
}
if (site.waitAttr !== undefined) {
const wait = document.querySelector(site.waitAttr[0])?.getAttribute(site.waitAttr[1]);
if (isNothing(wait)) {
logScript(`Waiting for Attribute ${site.waitAttr[1]} of ${site.waitAttr[0]} = ${wait}`);
setTimeout(() => {
waitExec(site, waitElapsed + (site.waitStep || 1000));
}, site.waitStep || 1000);
return;
}
logScript(`Found Attribute ${site.waitAttr[1]} of ${site.waitAttr[0]} = ${wait}`);
}
if (site.waitEle !== undefined) {
const wait = document.querySelector(site.waitEle);
if (isNothing(wait?.tagName)) {
logScript(`Waiting for Element ${site.waitEle} = `, wait);
setTimeout(() => {
waitExec(site, waitElapsed + (site.waitStep || 1000));
}, site.waitStep || 1000);
return;
}
logScript(`Found Element ${site.waitEle} = `, wait);
}
if (site.waitVar !== undefined) {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const wait = W[site.waitVar];
if (isNothing(wait)) {
logScript(`Waiting for Variable ${site.waitVar} = ${wait}`);
setTimeout(() => {
waitExec(site, waitElapsed + (site.waitStep || 1000));
}, site.waitStep || 1000);
return;
}
logScript(`Found Variable ${site.waitVar} = ${wait}`);
}
preparePage(site, await site.run());
}
// Script Entry point
function start(sites) {
logScript(`Starting ${getInfoGM.script.name} ${getInfoGM.script.version} on ${getBrowser()} with ${getEngine()}`);
// window.InfoGM = getInfoGM;
logScript(`${sites.length} Known Manga Sites, Looking for a match...`);
const site = sites.find((s) => s.url.test(window.location.href));
if (site) {
logScript(`Found site: ${site.name}`);
waitExec(site);
}
else {
logScript(`Sorry, didnt find any valid site`);
}
}
start(sites);
})();