// ==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, Alpha-scans, Voids-Scans, Batoto, ComiCastle, Dynasty-Scans, InManga, KLManga, Leitor, LHTranslation, MangaBuddy, MangaDex, MangaFox, MangaHere, MangaFreak, Mangago, mangahosted, MangaHub, MangaKakalot, MangaNelo, MangaNato, MangaPark, MReader, Mangareader, MangaSee, Manga4life, MangaTigre, MangaTown, ManhuaScan, NineManga, PandaManga, RawDevart, ReadComicsOnline, ReadManga Today, Funmanga, MangaDoom, MangaInn, ReaperScans, SenManga(Raw), ShimadaScans, KLManga, TenManga, TuMangaOnline, UnionMangas, WebToons, Manga33, ZeroScans, FoOlSlide, Kireicake, Madara WordPress Plugin, MangaHaus, Isekai Scan, Comic Kiba, Zinmanga, mangatx, Toonily, Mngazuki, JaiminisBox, DisasterScans, ManhuaPlus // @version 2022.10.25 // @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.35/sweetalert2.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js // @include /https?:\/\/(www.)?(asura|flamescans|realmscans|alpha-scans|void-scans).(com|org|gg)\/.+/ // @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|chapmanganato).com\/manga-\w\w\d+\/chapter-\d+)/ // @include /https?:\/\/(www.)?mangapark.(com|me|org|net)\/(manga|chapter|comic)\/.+\/.+/ // @include /https?:\/\/(www.)?mreader.co\/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?:\/\/(www.)?reaperscans.com\/comics\/.+\/chapters\/.+/ // @include /https?:\/\/raw.senmanga.com\/.+\/.+\/?/ // @include /https?:\/\/(www.)?shimadascans.com\/.+series.+/ // @include /https?:\/\/(www.)?tapas.io\/episode\/.+/ // @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\/.+/ // @downloadURL none // ==/UserScript== (function () { 'use strict'; // == AsuraScans and FlameScans ==================================================================== var asurasflamecans = { name: ['Asura Scans', 'Flame Scans', 'Realm Scans', 'Alpha-scans', 'Voids-Scans'], url: /https?:\/\/(www.)?(asura|flamescans|realmscans|alpha-scans|void-scans).(com|org|gg)\/.+/, homepage: [ 'https://www.asura.gg/', 'https://flamescans.org/', 'https://realmscans.com/', 'https://alpha-scans.org/', 'https://void-scans.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'], url: /^(?!.*jaiminisbox).*\/read\/.+/, homepage: ['#', 'https://reader.kireicake.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() ?? document.querySelector('title')?.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', '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://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() { function getCookie(name) { const re = new RegExp(`${name}=([^;]+)`); const value = re.exec(document.cookie); return value != null ? decodeURIComponent(value[1]) : null; } 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', 'x-mhub-access': getCookie('mhub_access'), }, }; 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|chapmanganato).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]}`), }; }, }; // == 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, }; }, }; // == MReader ====================================================================================== var mreader = { name: 'MReader', url: /https?:\/\/(www.)?mreader.co\/reader\/.*/, homepage: 'https://www.mreader.co/', 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')), }; }, }; // == 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), }; }, }; // == ReaperScans ================================================================================== var reaperscans = { name: 'ReaperScans', url: /https?:\/\/(www.)?reaperscans.com\/comics\/.+\/chapters\/.+/, homepage: 'https://reaperscans.com/', language: ['English'], category: 'manga', run() { const images = [...document.querySelectorAll('main img')]; return { title: document.querySelector('title')?.textContent?.trim(), series: document.querySelector('.fa-list')?.parentElement?.getAttribute('href'), pages: images.length, prev: document.querySelector('.fa-arrow-left')?.parentElement?.getAttribute('href'), next: document.querySelector('.fa-arrow-right')?.parentElement?.getAttribute('href'), listImages: images.map((img) => img.getAttribute('data-src') || img.getAttribute('src')), }; }, }; // == 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')), }; }, }; // == Tapas ====================================================================================== var tapas = { name: 'KLManga', url: /https?:\/\/(www.)?tapas.io\/episode\/.+/, homepage: 'https://tapas.io/', language: ['English'], category: 'manga', run() { const images = [...document.querySelectorAll('.viewer__body img.content__img')]; const chapter = document.querySelector('.js-episodes .body__item--selected'); return { title: document.querySelector('.viewer__header .title')?.textContent?.trim(), series: document.querySelector('.vw-nav__left a')?.getAttribute('href'), pages: images.length, prev: chapter?.previousElementSibling?.getAttribute('data-href'), next: chapter?.nextElementSibling?.getAttribute('data-href'), listImages: images.map((img) => img.getAttribute('data-src') || 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, mreader, mangareader, mangasee, mangatigre, mangatown, manhuascan, ninemanga, pandamanga, rawdevart, readcomicsonline, readmangatoday, reaperscans, senmanga, shimadascans, tapas, 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') { return GM_getValue(name, defaultValue); } logScript('Fake Getting: ', name, ' = ', defaultValue); return defaultValue; } function getJsonGM(name, defaultValue = null) { const result = getValueGM(name, defaultValue); if (typeof result === 'string') return JSON.parse(result); return result; } function getSettings(defaultSettings) { return getJsonGM('settings', defaultSettings); } // 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); } } function setJsonGM(name, value) { return setValueGM(name, JSON.stringify(value)); } function setSettings(value) { return setJsonGM('settings', 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 = ` `; const IconArrowBigLeft = ` `; const IconFileDownload = ` `; const IconLoader2 = ` `; const IconCategory = ` `; // Icons for Global Controls const IconX = ` `; const IconMenu2 = ` `; const IconArrowAutofitWidth = ` `; const IconArrowAutofitHeight = ` `; const IconZoomInArea = ` `; const IconZoomOutArea = ` `; const IconZoomPan = ` `; const IconArrowAutofitDown = ` `; const IconArrowAutofitLeft = ` `; const IconArrowAutofitRight = ` `; const IconSpacingVertical = ` `; const IconSettings = ` `; const IconKeyboard = ` `; const IconListNumbers = ` `; const IconBookmarks = ` `; const IconExternalLink = ` `; const IconTrash = ` `; const IconSun = ` `; const IconMoon = ` `; const IconCheck = ` `; const IconPalette = ` `; // Icons for Page Controls const IconBookmark = ` `; const IconBookmarkOff = ` `; const IconEye = ` `; const IconEyeOff = ` `; const IconZoomCancel = ` `; const IconZoomIn = ` `; const IconZoomOut = ` `; const IconRefresh = ` `; const IconPhoto = ` `; const IconPhotoOff = ` `; // 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.FluidLTR { direction: ltr; } #MangaOnlineViewer #Chapter.FluidRTL { direction: rtl; } #MangaOnlineViewer #Chapter.FluidLTR, #MangaOnlineViewer #Chapter.FluidRTL { display: grid; grid-template-columns: repeat(2, 1fr); } #MangaOnlineViewer #Chapter.FluidLTR .PageImg, #MangaOnlineViewer #Chapter.FluidRTL .PageImg { min-width: unset; } #MangaOnlineViewer #Chapter.FluidLTR .MangaPage.DoublePage, #MangaOnlineViewer #Chapter.FluidRTL .MangaPage.DoublePage { grid-column: span 2; } #MangaOnlineViewer #Chapter.FluidLTR .MangaPage:not(.DoublePage):nth-child(2n), #MangaOnlineViewer #Chapter.FluidRTL .MangaPage:not(.DoublePage):nth-child(2n) { justify-self: flex-start; } #MangaOnlineViewer #Chapter.FluidLTR .MangaPage:not(.DoublePage):nth-child(2n-1), #MangaOnlineViewer #Chapter.FluidRTL .MangaPage:not(.DoublePage):nth-child(2n-1) { justify-self: flex-end; } #MangaOnlineViewer #Chapter.Vertical .PageContent { margin-bottom: 15px; } #MangaOnlineViewer #Chapter.FluidLTR .MangaPage, #MangaOnlineViewer #Chapter.FluidRTL .MangaPage { width: auto; } #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; bottom: 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; overflow-y: auto; 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%); line-height: 1.5em; z-index: 1000; } #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.headroom-hide { transform: translateY(-100%); } #MangaOnlineViewer #Header.scroll.headroom-show { transform: translateY(-1%); } #MangaOnlineViewer #Header.hover, #MangaOnlineViewer #Header.fixed, #MangaOnlineViewer #Header.click { position: static; transform: none; } #MangaOnlineViewer #Header.headroom-end, #MangaOnlineViewer #Header.visible, #MangaOnlineViewer #Header.fixed { transform: translateY(-1%); position: sticky; } #MangaOnlineViewer #Header.hover:hover, #MangaOnlineViewer #Header.fixed { position: sticky; } #MangaOnlineViewer #Header.scroll #menu, #MangaOnlineViewer #Header.fixed #menu, #MangaOnlineViewer #Header.hover:hover #menu, #MangaOnlineViewer #Header:not(.click).visible #menu { display: none; } #MangaOnlineViewer #menu { position: fixed; min-height: 70px; width: 100%; top: 0; z-index: 1; color: var(--theme-body-text-color); } #MangaOnlineViewer #Header.click #menu { cursor: pointer; } #MangaOnlineViewer #Header.click.visible #menu { position: static; width: 50px; min-height: unset; } #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[value='#'], #MangaOnlineViewer .ChapterControl .NavigationControlButton[value=''], #MangaOnlineViewer .ChapterControl .NavigationControlButton[value='undefined'] { visibility: hidden; } #MangaOnlineViewer .ChapterControl #download.loading { cursor: not-allowed; pointer-events: none; opacity: 0.6; } #MangaOnlineViewer .ChapterControl #download.disabled { visibility: hidden; } #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; } #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 .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 #GlobalFunctions #keybindings { 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 .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 .headerType { display: none; } #MangaOnlineViewer #KeybindingsPanel { 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 } `; // language=CSS const startButton = ` #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; } `; /** * Deep diff between two object, using lodash * * We are comparing two objects: a and b. * Object b is newer and similar to a. * We are looking for changes from a to b. * We assume that data types haven't changed (String to Number). * We assume that parent is either an Array or an Object. * * This is the "easier on the programmer, harder on the computer" method. * It's very inefficient. Worst case is something like O(n^n). * But this works, is easier to understand that the "correct" * implementation, and entertained me for a bit. * * * http://stackoverflow.com/questions/31295545/how-to-get-only-the-changed-values-from-two-json-objects * https://gist.github.com/Yimiprod/7ee176597fef230d1451 * https://stackoverflow.com/questions/8572826/generic-deep-diff-between-two-objects * https://qastack.com.br/programming/31683075/how-to-do-a-deep-comparison-between-2-objects-with-lodash * @param {Object} changed Object compared * @param {Object} original Object to compare with * @return {Object} Return a new object who represent the diff */ const diffObj = (changed, original) => { const changes = (object, base) => _.transform(object, (result, value, key) => { if (!_.isEqual(value, base[key])) { if (_.isArray(value)) { result[key] = _.difference(value, base[key]); } else if (_.isObject(value) && _.isObject(base[key])) { result[key] = changes(value, base[key]); } else { result[key] = value; } } }); return changes(changed, original); }; var en_US = { ID: 'en_US', NAME: 'English (US)', STARTING: 'Starting
MangaOnlineViewer', RESUME: 'Resuming reading from Page ', WAITING: 'Please wait, 3 seconds...', CHOOSE_BEGINNING: 'Choose the Page to start from:', BUTTON_START: 'Start MangaOnlineViewer', SETTINGS: 'Settings', LANGUAGE: 'Language', COLOR_SCHEME: 'Color Scheme', THEME: 'Theme', THEME_HUE: 'Theme Primary Color Hue', THEME_SHADE: 'Theme Primary Color Shade', DEFAULT_LOAD_MODE: 'Default Load Mode', LOAD_MODE_NORMAL: 'Normal(Wait 3 sec)', LOAD_MODE_ALWAYS: 'Always(Immediately)', LOAD_MODE_NEVER: 'Never(Manually)', LOAD_SPEED: 'Load Speed Pages/Second', DEFAULT_ZOOM: 'Default Zoom', MINIMUM_ZOOM: 'Minimum Zoom relative to the width of screen (between 30 and 100)', ZOOM_STEP: 'Zoom Change Step (between 5 and 50)', DEFAULT_VIEW_MODE: 'Default View Mode', VIEW_MODE_VERTICAL: 'Vertical', VIEW_MODE_LEFT: 'Left to Right', VIEW_MODE_RIGHT: 'Right to Left', VIEW_MODE_WEBCOMIC: 'WebComic', FIT_WIDTH_OVERSIZED: 'Fit Width if Oversized', SHOW_THUMBNAILS: 'Show Thumbnails', HIDE_CONTROLS: 'Always Hide Page Controls', HEADER_TYPE: 'Change Header Type', HEADER_HOVER: 'Hover', HEADER_SCROLL: 'Scroll', HEADER_CLICK: 'Click', HEADER_FIXED: 'Fixed', BUTTON_DOWNLOAD: 'Download', DOWNLOAD_ZIP: 'Download Zip file', DOWNLOAD_IMAGES: 'Download Images as Zip Automatically', BUTTON_NEXT: 'Next', NEXT_CHAPTER: 'Next Chapter', BUTTON_PREVIOUS: 'Previous', PREVIOUS_CHAPTER: 'Previous Chapter', BOOKMARKS: 'Bookmarks', BOOKMARK: 'Bookmark', BOOKMARK_REMOVED: 'Bookmark Removed', BOOKMARK_SAVED: 'Bookmark Saved', BOOKMARK_MESSAGE: 'Next time you open this chapter it will resume from:

Page ##num##

(Only ONCE per Bookmark)', KEYBINDINGS: 'Keybindings', ATTENTION: 'Attention', WARNING: 'Warning', BUTTON_RESET_SETTINGS: 'Reset Settings', SETTINGS_RESET: 'Settings have been reset, reload the page to take effect', LANGUAGE_CHANGED: 'Language has been changed, reload the page to take effect', AUTO_DOWNLOAD: 'Next time a chapter finish loading you will be prompted to save automatically', LAZY_LOAD: 'Lazy load is incompatible with zip download, you will not be able to download with this setting ON.
Suggestion: Disable Thumbnails to save Bandwidth/Memory.', LAZY_LOAD_IMAGES: 'Lazy Start From Page (between 5 and 100)', RETURN_CHAPTER_LIST: 'Return to Chapter List', PAGES_LOADED: 'Pages Loaded', GO_TO_PAGE: 'Go to Page', ENLARGE: 'Enlarge', RESTORE: 'Restore', REDUCE: 'Restore', FIT_WIDTH: 'Fit Width', FIT_HEIGHT: 'Fit Height', TOGGLE_CONTROLS: 'Toggle page controls', ZOOM_IN: 'Zoom In', ZOOM_OUT: 'Zoom Out', ZOOM_RESET: 'Zoom Reset', ZOOM_WIDTH: 'Zoom to Width', ZOOM_HEIGHT: 'Zoom to Height', HIDE: 'Hide', RELOAD: 'Reload', SLOWLY: 'Slowly', NORMAL: 'Normal', FAST: 'Fast', EXTREME: 'Extreme', SCROLL_UP: 'Scroll Up', SCROLL_DOWN: 'Scroll Down', CLOSE: 'Close', LIST_EMPTY: 'List Empty', }; var pt_BR = { ID: 'pt_BR', NAME: 'Portugues (Brasil)', STARTING: 'Iniciando
MangaOnlineViewer', RESUME: 'Continuando leitura na Pagina ', WAITING: 'Por Favor espere, 3 segundos...', CHOOSE_BEGINNING: 'Escolha a pagina de onde começar:', BUTTON_START: 'Iniciar MangaOnlineViewer', SETTINGS: 'Configurações', LANGUAGE: 'Idioma', COLOR_SCHEME: 'Esquema de Color', THEME: 'Tema', THEME_HUE: 'Coloração primaria', THEME_SHADE: 'Saturação de Cor', DEFAULT_LOAD_MODE: 'Forma de Carregamento Padrão', LOAD_MODE_NORMAL: 'Normal(Esperando 3 sec)', LOAD_MODE_ALWAYS: 'Sempre(Imediatamente)', LOAD_MODE_NEVER: 'Nunca(Manualmente)', LOAD_SPEED: 'Velocidade de Carregamento Paginas/Segundo', DEFAULT_ZOOM: 'Zoom padrão', MINIMUM_ZOOM: 'Zoom minimo, relativo ao tamanho da tela (entre 30 e 100)', ZOOM_STEP: 'Precisão da Mudança do Zoom (entre 5 e 50)', DEFAULT_VIEW_MODE: 'Modo de Visualização Padrão', VIEW_MODE_VERTICAL: 'Vertical', VIEW_MODE_LEFT: 'Esquerda para Direita', VIEW_MODE_RIGHT: 'Direita para Esquerda', VIEW_MODE_WEBCOMIC: 'WebComic', FIT_WIDTH_OVERSIZED: 'Encher a tela se grande demais', SHOW_THUMBNAILS: 'Mostra Miniaturas', HIDE_CONTROLS: 'Sempre esconder controles das paginas', HEADER_TYPE: 'Mudar Tipo de Cabeçalho', HEADER_HOVER: 'Passar por perto', HEADER_SCROLL: 'Rolagem do Mouse', HEADER_CLICK: 'Click', HEADER_FIXED: 'Fixo', BUTTON_DOWNLOAD: 'Download', DOWNLOAD_ZIP: 'Baixar arquivo Zip', DOWNLOAD_IMAGES: 'Download das Imagens como Zip Automaticamente', BUTTON_NEXT: 'Proximo', NEXT_CHAPTER: 'Proximo Capitulo', BUTTON_PREVIOUS: 'Anterior', PREVIOUS_CHAPTER: 'Capitulo Anterior', BOOKMARKS: 'Marca paginas', BOOKMARK: 'Marcar pagina', BOOKMARK_REMOVED: 'Marca pagina Removido', BOOKMARK_SAVED: 'Marca pagina Salvo', BOOKMARK_MESSAGE: 'Proxima vez que abrir este capitulo continuará a partir da

Pagina ##num##

(Apenas UMA VEZ por marca pagina)', KEYBINDINGS: 'Atalhos', ATTENTION: 'Atenção', WARNING: 'Alerta', BUTTON_RESET_SETTINGS: 'Limpar Configurações', SETTINGS_RESET: 'Configurações foram limpas, recarregue o site para efetivar a alteração', LANGUAGE_CHANGED: 'Idioma foi alterado, recarregue o site para efetivar a alteração', AUTO_DOWNLOAD: 'Proxima vez que abrir um capitulo download iniciara automaticamente', LAZY_LOAD: 'Lazy load is incompatible with zip download, you will not be able to download with this setting ON.
Suggestion: Disable Thumbnails to save Bandwidth/Memory.', LAZY_LOAD_IMAGES: 'Carregamento de paginas preguiçoso começa a partir de (entre 5 e 100)', RETURN_CHAPTER_LIST: 'Voltar a lista de Capitulos', PAGES_LOADED: 'Paginas Carregadas', GO_TO_PAGE: 'Pular para', ENLARGE: 'Aumentar', RESTORE: 'Restaurar', REDUCE: 'Diminuir', FIT_WIDTH: 'Preencher Largura', FIT_HEIGHT: 'Preencher Altura ', TOGGLE_CONTROLS: 'Mostar controles de pagina', ZOOM_IN: 'Mais Zoom', ZOOM_OUT: 'Menos Zoom', ZOOM_RESET: 'Resetar Zoom', ZOOM_WIDTH: 'Zoom para Largura', ZOOM_HEIGHT: 'Zoom para Altura', HIDE: 'Esconder', RELOAD: 'Recarregar', SLOWLY: 'Devagar', NORMAL: 'Normal', FAST: 'Rapido', EXTREME: 'Extremo', SCROLL_UP: 'Subir Pagina', SCROLL_DOWN: 'Descer Pagina', CLOSE: 'Fechar', LIST_EMPTY: 'Lista Vazia', }; // Translation by lhj5426 var zh_CN = { ID: 'zh_cn', NAME: '中文 (简体)', STARTING: '正在启动
MangaOnlineViewer', RESUME: '从页面继续阅读 ', WAITING: '请等待3秒钟...', CHOOSE_BEGINNING: '选择要开始的页数:', BUTTON_START: '启动MangaOnlineViewer', SETTINGS: '设置', LANGUAGE: '语言', COLOR_SCHEME: '配色方案', THEME: '主题', THEME_HUE: '主题色调', THEME_SHADE: '主题阴影', DEFAULT_LOAD_MODE: '默认加载模式', LOAD_MODE_NORMAL: '等待模式(等待3秒自动加载 )', LOAD_MODE_ALWAYS: '自动模式(无需等待)', LOAD_MODE_NEVER: '手动模式(点击启动)', LOAD_SPEED: '加载速度页数/秒', DEFAULT_ZOOM: '默认缩放', MINIMUM_ZOOM: '相对于屏幕宽度的最小缩放 (最小 30 最大 100)', ZOOM_STEP: '缩放级别 (最小 5 最大 50)', DEFAULT_VIEW_MODE: '默认视图模式', VIEW_MODE_VERTICAL: '垂直拼接', VIEW_MODE_LEFT: '从左到右', VIEW_MODE_RIGHT: '从右到左', VIEW_MODE_WEBCOMIC: '网络漫画', FIT_WIDTH_OVERSIZED: '如果尺寸过大、则适合宽度', SHOW_THUMBNAILS: '显示缩略图', HIDE_CONTROLS: '始终隐藏页面控件', HEADER_TYPE: '更改标题显示方式', HEADER_HOVER: '悬停', HEADER_SCROLL: '滚动', HEADER_CLICK: '点击', HEADER_FIXED: '固定', BUTTON_DOWNLOAD: '下载', DOWNLOAD_ZIP: '下载压缩文件', DOWNLOAD_IMAGES: '自动将图片下载成ZIP', BUTTON_NEXT: '下一页', NEXT_CHAPTER: '下一章', BUTTON_PREVIOUS: '上一页', PREVIOUS_CHAPTER: '上一章', BOOKMARKS: '书签', BOOKMARK: 'Bookmark', BOOKMARK_REMOVED: '删除书签', BOOKMARK_SAVED: '保存书签', BOOKMARK_MESSAGE: '下次打开本章时,将从:

页码 ##num##

(仅一次 每个书签)', KEYBINDINGS: '快捷键', ATTENTION: '注意', WARNING: '警告', BUTTON_RESET_SETTINGS: '重置设置', SETTINGS_RESET: '设置已重置、重新加载页面才能生效', LANGUAGE_CHANGED: '语言已更改、重新加载页面才能生效', AUTO_DOWNLOAD: '下次章节加载完成时、系统将提示您自动保存', LAZY_LOAD: '延迟加载与zip下载不兼容、您将无法使用此设置下载.
建议: 禁用缩略图 以节省流量和内存.', LAZY_LOAD_IMAGES: '惰性加载从页面 (最小 5 最大 100)', RETURN_CHAPTER_LIST: '返回章节列表', PAGES_LOADED: '已加载的页数', GO_TO_PAGE: '转到页数', ENLARGE: '放大', RESTORE: '还原', REDUCE: '缩小', FIT_WIDTH: '适合宽度', FIT_HEIGHT: '适合高度', TOGGLE_CONTROLS: '显示隐藏页面控件', ZOOM_IN: '放大', ZOOM_OUT: '缩小', ZOOM_RESET: '还原', ZOOM_WIDTH: '适合宽度', ZOOM_HEIGHT: '适合高度', HIDE: '显示隐藏页面控件', RELOAD: '重新加载', SLOWLY: '慢速', NORMAL: '正常', FAST: '快速', EXTREME: '极端', SCROLL_UP: '向上滚动', SCROLL_DOWN: '向下滚动', CLOSE: '关闭', LIST_EMPTY: '没有收藏书签', }; const locales = [en_US, pt_BR, zh_CN]; const defaultSettings = { locale: 'en_US', theme: 'darkblue', customTheme: '#263e3a', themeShade: 600, colorScheme: 'dark', fitWidthIfOversize: true, showThumbnails: true, downloadZip: false, throttlePageLoad: 1000, zoom: 100, zoomStep: 25, minZoom: 50, loadMode: 'wait', viewMode: 'WebComic', bookmarks: [], lazyLoadImages: false, lazyStart: 50, hidePageControls: false, header: 'hover', maxReload: 5, }; // Configuration let settings$1 = _.defaultsDeep(getSettings(defaultSettings), defaultSettings); // Force Settings for mobile if (isMobile) { settings$1.lazyLoadImages = true; settings$1.fitWidthIfOversize = true; settings$1.showThumbnails = false; settings$1.viewMode = 'WebComic'; settings$1.header = 'click'; } function useSettings() { return settings$1; } function getLocaleString(name) { const locale = locales.find((l) => l.ID === settings$1.locale); if (locale) { return locale[name]; } return '##MISSING_STRING##'; } function updateSettings(newValue) { logScript(JSON.stringify(newValue)); settings$1 = { ...settings$1, ...newValue }; setSettings(diffObj(settings$1, defaultSettings)); } function resetSettings() { getListGM().forEach((setting) => removeValueGM(setting)); updateSettings(defaultSettings); } // Clear old Bookmarks const bookmarkTimeLimit = 1000 * 60 * 60 * 24 * 30 * 12; // year const refreshedBookmark = settings$1.bookmarks.filter((el) => Date.now() - el.date < bookmarkTimeLimit); if (settings$1.bookmarks.length !== refreshedBookmark.length) { updateSettings({ bookmarks: refreshedBookmark }); } function isBookmarked(url = window.location.href) { return useSettings().bookmarks.some((el) => el.url === url); } // 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 ``; } 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[useSettings().themeShade], useSettings().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); const themesSelector = [...Object.keys(colors).map((color) => colors[color].name)].map((theme) => ` ${IconCheck} `); function refreshThemes() { themes().forEach((theme) => replaceStyleSheet(theme.name, getNormalThemeCSS(theme))); replaceStyleSheet('custom', getCustomThemeCSS(useSettings().customTheme)); } const themesCSS = themes().map(addTheme).join('') + wrapStyle('custom', getCustomThemeCSS(useSettings().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/keyscss@1.1.3-6/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 ` ${manga.title} ${wrapStyle('externals', sweetalertStyle)} ${wrapStyle('reader', cssStyles)} ${themesCSS} ${wrapStyle('MinZoom', `#MangaOnlineViewer .PageContent .PageImg {min-width: ${useSettings().minZoom}vw;}`)} `; } 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) => `
${index}
`); const localeSelector = locales.map((locale) => ``); // language=html const SettingsPanel = `

${getLocaleString('SETTINGS')}

${getLocaleString('LANGUAGE')}:
${getLocaleString('COLOR_SCHEME')}:
${getLocaleString('THEME')}: ${IconPalette} ${IconCheck} ${themesSelector.join('')}
${getLocaleString('THEME_HUE')}:
${getLocaleString('THEME_SHADE')}: ${useSettings().themeShade}
${getLocaleString('DEFAULT_LOAD_MODE')}:
${getLocaleString('LOAD_SPEED')}:
${getLocaleString('DEFAULT_ZOOM')}:
${getLocaleString('MINIMUM_ZOOM')}: ${useSettings().minZoom}
${getLocaleString('ZOOM_STEP')}: ${useSettings().zoomStep}
${getLocaleString('DEFAULT_VIEW_MODE')}:
${getLocaleString('FIT_WIDTH_OVERSIZED')}:
${getLocaleString('SHOW_THUMBNAILS')}:
${getLocaleString('LAZY_LOAD_IMAGES')}:
${getLocaleString('LAZY_LOAD_IMAGES')}: ${useSettings().lazyStart}
${getLocaleString('DOWNLOAD_IMAGES')}:
${getLocaleString('HIDE_CONTROLS')}:
${getLocaleString('HEADER_TYPE')}:
`; function scrollToElement(ele) { window.scroll(0, ele?.offsetTop || 0); } const doClick = (selector) => document.querySelector(selector)?.dispatchEvent(new Event('click')); function doScrolling(sign) { if (useSettings().zoom === -1000) { // Fit height const pages = [...document.querySelectorAll('.MangaPage')]; const distance = pages.map((element) => Math.abs(element.offsetTop - window.scrollY)); const currentPage = distance.findIndex((d) => d <= 5); const target = currentPage + sign; const header = document.querySelector('#Header'); if (target < 0) { scrollToElement(header); } else if (target >= pages.length) { header.classList.add('headroom-end'); } else { logScript(`Current array page ${currentPage},`, `Scrolling to page ${target}`); scrollToElement(pages.at(target)); } } else { window.scrollBy({ top: (sign * window.innerHeight) / 2, behavior: 'smooth', }); } } const keybinds = [ { name: 'SCROLL_UP', keys: ['ArrowUp', 'KeyW', 'Numpad8'], action() { doScrolling(-1); }, }, { name: 'SCROLL_DOWN', keys: ['ArrowDown', 'KeyS', 'Numpad2'], action() { doScrolling(1); }, }, { name: 'NEXT_CHAPTER', keys: ['ArrowRight', 'Period', 'KeyD', 'Numpad6'], action() { doClick('#next'); }, }, { name: 'PREVIOUS_CHAPTER', keys: ['ArrowLeft', 'Comma', 'KeyA', 'Numpad4'], action() { doClick('#prev'); }, }, { name: 'ENLARGE', keys: ['Equal', 'NumpadAdd', 'KeyE'], action() { doClick('#enlarge'); }, }, { name: 'REDUCE', keys: ['Minus', 'NumpadSubtract', 'KeyQ'], action() { doClick('#reduce'); }, }, { name: 'RESTORE', keys: ['Digit9', 'NumpadDivide', 'KeyR'], action() { doClick('#restore'); }, }, { name: 'FIT_WIDTH', keys: ['Digit0', 'NumpadMultiply', 'KeyF'], action() { doClick('#fitWidth'); }, }, { name: 'FIT_HEIGHT', keys: ['KeyH'], action() { doClick('#fitHeight'); }, }, { name: 'SETTINGS', keys: ['Slash', 'Numpad5', 'KeyX'], action() { doClick('#settings'); }, }, { name: 'VIEW_MODE_WEBCOMIC', keys: ['KeyC'], action() { doClick('#webComic'); }, }, { name: 'VIEW_MODE_VERTICAL', keys: ['KeyV'], action() { doClick('#verticalMode'); }, }, { name: 'VIEW_MODE_LEFT', keys: ['KeyN'], action() { doClick('#rtlMode'); }, }, { name: 'VIEW_MODE_RIGHT', keys: ['KeyB'], action() { doClick('#ltrMode'); }, }, ]; const usedKeys = keybinds.flatMap((kb) => kb.keys); function processKey(e) { if (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey || !usedKeys.some((i) => i === e.code)) return true; e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); const keyBindings = keybinds.find((kb) => kb.keys.some((key) => key === e.code)); logScript('Keyboard:', e.code, /* ' Event:', e, */ 'Entry', keyBindings); keyBindings?.action(); return false; } // Clean key press configurations and set some when specified function hotkeys() { document.onkeydown = null; document.onkeyup = null; document.onkeypress = null; window.onkeydown = null; window.onkeyup = null; window.onkeypress = null; window.onload = null; document.body.onload = null; document.addEventListener('keydown', processKey); } function formatKeyName(key) { let formatted = key; formatted = formatted.replace('Key', ''); formatted = formatted.replace('Digit', ''); formatted = formatted.replace('Numpad', 'Numpad '); formatted = formatted.replace('Subtract', '-'); formatted = formatted.replace('Add', '+'); formatted = formatted.replace('Minus', '-'); formatted = formatted.replace('Equal', '='); formatted = formatted.replace('Divide', '/'); formatted = formatted.replace('Multiply', '*'); formatted = formatted.replace('Comma', ','); formatted = formatted.replace('Period', '.'); formatted = formatted.replace('Slash', '/'); formatted = formatted.replace('ArrowUp', '↑'); formatted = formatted.replace('ArrowDown', '↓'); formatted = formatted.replace('ArrowRight', '→'); formatted = formatted.replace('ArrowLeft', '←'); return formatted; } const keybindings = keybinds .map((kb) => { const keys = kb.keys.map((key) => `${formatKeyName(key)}`).join(' / '); return `${keys}: ${getLocaleString(kb.name)}
`; }) .join('\n'); const KeybindingsPanel = `

${getLocaleString('KEYBINDINGS')}

${keybindings}
`; const ThumbnailsPanel = (times, begin) => indexList(times, begin).map((index) => `
${index}
`); const listBookmarks = () => { if (isEmpty(useSettings().bookmarks)) return [getLocaleString('LIST_EMPTY')]; return useSettings().bookmarks.map((mark, index) => `
${new Date(mark.date).toLocaleDateString()} ${mark.url} Page: ${mark.page}
`); }; const BookmarkPanel = `

${getLocaleString('BOOKMARKS')}

${listBookmarks().join('')}
`; function reloadBookmarks() { const list = document.getElementById('BookmarksList'); if (list) { list.innerHTML = listBookmarks().join(''); } } const listOptions = (times, begin) => indexList(times, begin).map((index) => ``); const app = (manga, begin = 1) => `
${listPages(manga.pages, begin).join('')}
${SettingsPanel} ${KeybindingsPanel} ${BookmarkPanel}
`; function buttonBookmarks() { document.querySelector('#BookmarksPanel')?.classList.toggle('visible'); document.querySelector('#BookmarksOverlay')?.classList.toggle('visible'); } function eraseBookmarks(elem) { elem.addEventListener('click', (event) => { const target = event.currentTarget.value; const marks = useSettings().bookmarks.filter((el) => el.url !== target); if (target === window.location.href) { document.querySelector('#MangaOnlineViewer')?.classList.toggle('bookmarked'); } logScript(`Bookmark Removed ${target}`); Swal.fire({ title: getLocaleString('BOOKMARK_REMOVED'), timer: 10000, icon: 'error', }); updateSettings({ bookmarks: marks }); reloadBookmarks(); document.querySelectorAll('.BookmarkItem .erase')?.forEach(eraseBookmarks); }); } 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(), }; if (isBookmarked(mark.url)) { updateSettings({ bookmarks: useSettings().bookmarks.filter((el) => el.url !== mark.url) }); Swal.fire({ title: getLocaleString('BOOKMARK_REMOVED'), timer: 10000, icon: 'error', }); } else { updateSettings({ bookmarks: [...useSettings().bookmarks, mark] }); Swal.fire({ title: getLocaleString('BOOKMARK_SAVED'), html: getLocaleString('BOOKMARK_SAVED').replace('##NUM##', num.toString()), icon: 'success', }); } reloadBookmarks(); document.querySelectorAll('.BookmarkItem .erase')?.forEach(eraseBookmarks); }); } function bookmarks() { // List of Bookmarks document.querySelector('#bookmarks')?.addEventListener('click', buttonBookmarks); document.querySelector('#CloseBookmarks')?.addEventListener('click', buttonBookmarks); document.querySelector('#BookmarksOverlay')?.addEventListener('click', buttonBookmarks); // Erase Bookmark document.querySelectorAll('.BookmarkItem .erase')?.forEach(eraseBookmarks); // Bookmark Page to resume reading document.querySelectorAll('.Bookmark')?.forEach(buttonBookmark); } let zip; // const filenameRegex = /^(?.*?)(?\d+)\.(?\w+)$/; const base64Regex = /^data:(?image\/\w+);base64,+(?.+)/; const getExtension = (mimeType) => ((/image\/(?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')}`; function getImage(src) { return new Promise((resolve) => { logScript(`Getting Image data: ${src}`); GM_xmlhttpRequest({ method: 'GET', url: src, headers: { referer: src, origin: src }, responseType: 'blob', onload(response) { // logScript(`Received image: ${src}`); resolve(response); }, }); }); } function getImageData(img, index, array) { const src = img.getAttribute('src') ?? img.getAttribute('data-src'); if (src == null) return Promise.reject(new Error('Image source not specified')); const base64 = base64Regex.exec(src); if (base64 && base64.groups) { return Promise.resolve({ name: getFilename('Page-', index, array.length, getExtension(base64.groups?.mimeType)), data: base64.groups.data, }); } return new Promise((resolve) => { // setTimeout( // () => getImage(src).then((res) => resolve({ name: getFilename('Page-', index, array.length, getExtension(res.response.type)), data: res.response, })); // useSettings().throttlePageLoad * index, // ); }); } function addZip(img) { logScript(`${img.name} Added to Zip from Base64 Image`); zip.file(img.name, img.data, { base64: true, createFolders: true, compression: 'DEFLATE', }); } async function generateZip() { zip = new JSZip(); const images = [...document.querySelectorAll('.PageImg')]; const data = await Promise.all(images.map(getImageData)); data.forEach(addZip); logScript('Generating Zip'); zip .generateAsync({ type: 'blob', }) .then((content) => { logScript('Download Ready'); const zipName = `${document.querySelector('#MangaTitle')?.textContent?.trim()}.zip`; saveAs(content, zipName); document.getElementById('download')?.classList.remove('loading'); }) .catch(logScript); } function startDownload(event) { const button = event.currentTarget; if (button.classList.contains('loading')) return; logScript('Downloading Chapter'); button.classList.add('loading'); generateZip(); } function globalHideImageControls() { document.querySelector('#MangaOnlineViewer')?.classList.toggle('hideControls'); } function redirect(event) { const url = event.target.getAttribute('value'); if (url) window.location.href = url; } function globals() { document.querySelector('#download')?.addEventListener('click', startDownload); document.querySelector('#pageControls')?.addEventListener('click', globalHideImageControls); document.querySelector('#next')?.addEventListener('click', redirect); document.querySelector('#prev')?.addEventListener('click', redirect); } /** * 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 headroom(showEnd = 0) { let prevOffset = 0; const setScrollDirection = (classSuffix) => { const header = document.querySelector('#Header'); header.classList.remove('headroom-end'); header.classList.remove('headroom-hide'); header.classList.remove('headroom-show'); if (classSuffix) header.classList.add(`headroom-${classSuffix}`); }; function toggleScrollDirection() { const { scrollY } = window; if (showEnd && useSettings().zoom !== -1000 && 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', _.debounce(toggleScrollDirection, 50)); } 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["CHINESE"] = "Chinese"; 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)); } /** * Interface for the settings */ /** * 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 = []; let setup = false; /** * 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); } /** * Function responsible for observing the screen move/change */ const observerEvent = _.throttle(executeCheck, 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 (!setup) { window.addEventListener('scroll', observerEvent, { passive: true, }); window.addEventListener('touchmove', observerEvent, { passive: true, }); window.addEventListener('resize', observerEvent, { passive: true, }); setup = true; } listElements.push({ element, callback }); observerEvent(); } // After pages load apply default Zoom function applyZoom(pages = '.PageContent img', zoom = useSettings().zoom) { const pg = [...document.querySelectorAll(pages)]; pg.forEach((img) => { img.removeAttribute('width'); img.removeAttribute('height'); img.removeAttribute('style'); img.classList.remove('FreeWidth'); if (zoom === 1000) { // Fit width img.style.width = `${window.innerWidth}px`; } else if (zoom === -1000) { // Fit height const nav = document.querySelector('#Navigation')?.classList.contains('disabled'); const chap = document.querySelector('#Chapter')?.classList.contains('WebComic'); const nextHeight = window.innerHeight + (nav ? 0 : -30) + (chap ? 0 : -35); img.style.height = `${nextHeight}px`; img.style.minWidth = 'unset'; } else { img.style.width = `${img.naturalWidth * (zoom / 100)}px`; } }); } function invalidateImageCache(src, repeat) { const url = src.replace(/[?&]cache=\d+$/, ''); const symbol = url.indexOf('?') === -1 ? '?' : '&'; return `${url + symbol}cache=${repeat}`; } function getRepeatValue(src) { let repeat = 1; const cache = src?.match(/cache=(\d+)$/); if (cache && cache[1]) repeat = parseInt(cache[1], 10) + 1; return repeat; } // Force reload the image function reloadImage(img) { const src = img.getAttribute('src'); if (!src) return; img.removeAttribute('src'); img.setAttribute('src', invalidateImageCache(src, getRepeatValue(src))); } function onImagesDone() { logScript('Images Loading Complete'); if (useSettings().downloadZip) { document.getElementById('download')?.dispatchEvent(new Event('click')); } document.getElementById('download')?.classList.remove('disabled'); } 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(); } function onImagesSuccess(instance) { instance.images.forEach((image) => { image.img.classList.add('imgLoaded'); image.img.classList.remove('imgBroken'); const thumbId = image.img.id.replace('PageImg', 'ThumbnailImg'); const thumb = document.getElementById(thumbId); if (thumb) thumb.setAttribute('src', image.img.getAttribute('src')); applyZoom(`#${image.img.id}`); updateProgress(); }); } function onImagesFail(instance) { instance.images.forEach((image) => { image.img.classList.add('imgBroken'); const src = image.img.getAttribute('src'); if (src && getRepeatValue(src) <= useSettings().maxReload) { setTimeout(() => { reloadImage(image.img); const imgLoad = imagesLoaded(image.img.parentElement); imgLoad.on('done', onImagesSuccess); imgLoad.on('fail', onImagesFail); }, 2000); } }); } // 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 (!useSettings().lazyLoadImages || position <= useSettings().lazyStart) { setTimeout(() => { const imgLoad = imagesLoaded(img.parentElement); imgLoad.on('done', onImagesSuccess); imgLoad.on('fail', onImagesFail); img.setAttribute('src', src); logScript('Loaded Image:', index, 'Source:', src); }, (manga.timer || useSettings().throttlePageLoad) * position); } else { img.setAttribute('data-src', src); lazyLoad(img, () => { const imgLoad = imagesLoaded(img.parentElement); imgLoad.on('done', onImagesSuccess); imgLoad.on('fail', onImagesFail); 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.style.width = 'auto'; const imgLoad = imagesLoaded(img.parentElement); imgLoad.on('done', onImagesSuccess); imgLoad.on('fail', onImagesFail); img.setAttribute('src', src); 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 (!useSettings().lazyLoadImages || position <= useSettings().lazyStart) { setTimeout(() => { findPage(manga, index, pageUrl, false)(); }, (manga.timer || useSettings().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) { useSettings().lazyLoadImages = manga.lazy || useSettings().lazyLoadImages; logScript('Loading Images'); logScript(`Intervals: ${manga.timer || useSettings().throttlePageLoad || 'Default(1000)'}`); logScript(`Lazy: ${useSettings().lazyLoadImages}, Starting from: ${useSettings().lazyStart}`); 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: useSettings().throttlePageLoad, }); } else { logScript('No Loading Method Found'); } } function individual() { // 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); // Hide function buttonHidePage(elem) { elem.addEventListener('click', (event) => { const img = event.currentTarget.parentElement?.parentElement; img.classList.toggle('hide'); }); } document.querySelectorAll('.Hide')?.forEach(buttonHidePage); } function navigation() { // 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); } function options() { // Reset Reader Settings function buttonResetSettings() { resetSettings(); Swal.fire({ title: getLocaleString('ATTENTION'), text: getLocaleString('SETTINGS_RESET'), timer: 10000, icon: 'info', }); } document.querySelector('#ResetSettings')?.addEventListener('click', buttonResetSettings); // Change Locale function changeLocale(event) { const locale = event.currentTarget.value; updateSettings({ locale }); Swal.fire({ title: getLocaleString('ATTENTION'), text: getLocaleString('LANGUAGE_CHANGED'), timer: 10000, icon: 'info', }); } document.querySelector('#locale')?.addEventListener('change', changeLocale); // Image Fit width if Oversize Toggle function checkFitWidthOversize(event) { document.querySelector('#Chapter')?.classList.toggle('fitWidthIfOversize'); updateSettings({ fitWidthIfOversize: event.currentTarget.checked }); } document.querySelector('#fitIfOversize')?.addEventListener('change', checkFitWidthOversize); // Start/Load mode Selector function changeLoadMode(event) { const mode = event.currentTarget.value; updateSettings({ loadMode: mode }); } document.querySelector('#loadMode')?.addEventListener('change', changeLoadMode); // Show Thumbnail Toggle function checkShowThumbnails(event) { document.querySelector('#Navigation')?.classList.toggle('disabled'); updateSettings({ showThumbnails: event.currentTarget.checked }); applyZoom(); } document.querySelector('#showThumbnails')?.addEventListener('change', checkShowThumbnails); // Download auto start toggle function changeAutoDownload(event) { updateSettings({ downloadZip: event.currentTarget.checked }); if (event.currentTarget.checked) { Swal.fire({ title: getLocaleString('ATTENTION'), text: getLocaleString('AUTO_DOWNLOAD'), timer: 10000, icon: 'info', }); } } document.querySelector('#downloadZip')?.addEventListener('change', changeAutoDownload); // Lazy load Toggle function checkLazyLoad(event) { updateSettings({ lazyLoadImages: event.currentTarget.checked }); if (event.currentTarget.checked) { Swal.fire({ title: getLocaleString('WARNING'), html: getLocaleString('LAZY_LOAD'), icon: 'warning', }); } } document.querySelector('#lazyLoadImages')?.addEventListener('change', checkLazyLoad); // Lazy load starting point Slider function changeLazyStart(event) { const start = event.currentTarget.value; updateSettings({ lazyStart: parseInt(start, 10) }); } document.querySelector('#lazyStart')?.addEventListener('change', changeLazyStart); // Images load speed Selector function changePagesPerSecond(event) { const timer = parseInt(event.currentTarget.value, 10); updateSettings({ throttlePageLoad: timer }); } document.querySelector('#PagesPerSecond')?.addEventListener('change', changePagesPerSecond); // Zoom Step Slider function changeZoomStep(event) { const step = event.currentTarget.value; updateSettings({ zoomStep: parseInt(step, 10) }); } 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;}`); updateSettings({ minZoom: parseInt(min, 10) }); } document.querySelector('#minZoom')?.addEventListener('change', changeMinZoom); // Show/hide Image Controls Toggle function checkHideImageControls(event) { document.querySelector('#MangaOnlineViewer')?.classList.toggle('hideControls'); updateSettings({ hidePageControls: event.currentTarget.checked }); } document.querySelector('#hidePageControls')?.addEventListener('change', checkHideImageControls); // Change Header Type function changeHeaderType(event) { document.querySelector('#Header').className = ''; const headerType = event.currentTarget.value; document.querySelector('#Header')?.classList.add(headerType); updateSettings({ header: headerType }); } document.querySelector('#headerType')?.addEventListener('change', changeHeaderType); } function panels() { // Show Header list function buttonHeader() { const header = document.querySelector('#Header'); if (header?.classList.contains('click')) header?.classList.toggle('visible'); } document.querySelector('#menu')?.addEventListener('click', buttonHeader); // Settings Control function buttonSettingsOpen() { document.querySelector('#SettingsPanel')?.classList.add('visible'); document.querySelector('#Navigation')?.classList.add('visible'); document.querySelector('#Header')?.classList.add('visible'); document.querySelector('#SettingsOverlay')?.classList.add('visible'); } function buttonSettingsClose() { document.querySelector('#SettingsPanel')?.classList.remove('visible'); document.querySelector('#Navigation')?.classList.remove('visible'); document.querySelector('#Header')?.classList.remove('visible'); document.querySelector('#SettingsOverlay')?.classList.remove('visible'); } document.querySelector('#settings')?.addEventListener('click', buttonSettingsOpen); document.querySelector('#CloseSettings')?.addEventListener('click', buttonSettingsClose); document.querySelector('#SettingsOverlay')?.addEventListener('click', buttonSettingsClose); // Keybindings list function buttonKeybindings() { document.querySelector('#KeybindingsPanel')?.classList.toggle('visible'); } document.querySelector('#keybindings')?.addEventListener('click', buttonKeybindings); document.querySelector('#CloseKeybindings')?.addEventListener('click', buttonKeybindings); } function size() { // ZoomIn function buttonZoomIn(elem) { return elem.addEventListener('click', (event) => { const img = event.currentTarget.parentElement?.parentElement?.querySelector('.PageImg'); const ratio = (img.width / img.naturalWidth) * (100 + useSettings().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 - useSettings().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); } function theming() { // ColorScheme Selector function changeColorScheme() { const isDark = useSettings().colorScheme === 'dark'; updateSettings({ colorScheme: isDark ? 'light' : 'dark' }); [...document.querySelectorAll('#MangaOnlineViewer , body')].forEach((elem) => { elem.classList.remove(isDark ? 'dark' : 'light'); elem.classList.add(useSettings().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); }); updateSettings({ theme: target.title }); 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; updateSettings({ customTheme: target }); addCustomTheme(target); } document.querySelector('#CustomThemeHue')?.addEventListener('change', changeCustomTheme); // Theme Shade Input function changeThemeShade(event) { const target = parseInt(event.currentTarget.value, 10); updateSettings({ themeShade: target }); refreshThemes(); } document.querySelector('#ThemeShade')?.addEventListener('change', changeThemeShade); } function updateViewMode(mode) { return () => { document.querySelector('#Chapter')?.classList.remove('Vertical'); document.querySelector('#Chapter')?.classList.remove('WebComic'); document.querySelector('#Chapter')?.classList.remove('FluidLTR'); document.querySelector('#Chapter')?.classList.remove('FluidRTL'); document.querySelector('#Chapter')?.classList.add(mode); applyZoom(); }; } function changeViewMode(event) { const mode = event.currentTarget.value; updateViewMode(mode)(); updateSettings({ viewMode: mode }); } function viewMode() { // Default View mode Selector document.querySelector('#viewMode')?.addEventListener('change', changeViewMode); // WebComic View Mode Button document.querySelector('#webComic')?.addEventListener('click', updateViewMode('WebComic')); // Fluid LTR View Mode Button document.querySelector('#ltrMode')?.addEventListener('click', updateViewMode('FluidLTR')); // Fluid RTL View Mode Button document.querySelector('#rtlMode')?.addEventListener('click', updateViewMode('FluidRTL')); // Vertical View Mode Button document.querySelector('#verticalMode')?.addEventListener('click', updateViewMode('Vertical')); } function updateZoomPercent(percent = useSettings().zoom) { const target = document.querySelector('#ZoomPercent'); if (target) { target.textContent = percent.toString(); } } function changeGlobalZoom(value) { return () => { useSettings().zoom = value; updateZoomPercent(); applyZoom(); }; } function changeZoomByStep(sign = 1) { return () => { useSettings().zoom = useSettings().zoom + useSettings().zoomStep * sign; updateZoomPercent(); applyZoom(); }; } function zoom() { // Global Default Zoom Selector function changeDefaultZoom(event) { const target = parseInt(event.currentTarget.value, 10); updateSettings({ zoom: target }); changeGlobalZoom(target)(); } document.querySelector('#DefaultZoom')?.addEventListener('change', changeDefaultZoom); // Global Zoom In Button document.querySelector('#enlarge')?.addEventListener('click', changeZoomByStep()); // Global Zoom Out Button document.querySelector('#reduce')?.addEventListener('click', changeZoomByStep(-1)); // Global Zoom Restore Button document.querySelector('#restore')?.addEventListener('click', changeGlobalZoom(100)); // Global Fit Width Button document.querySelector('#fitWidth')?.addEventListener('click', changeGlobalZoom(1000)); // Global Fit height Button document.querySelector('#fitHeight')?.addEventListener('click', changeGlobalZoom(-1000)); } // Controls for the extra features added to the sites function events() { bookmarks(); globals(); headroom(100); hotkeys(); individual(); navigation(); options(); panels(); size(); theming(); viewMode(); zoom(); } function display(manga, begin) { window.stop(); if (manga.before !== undefined) { manga.before(); } document.head.innerHTML = head(manga); document.body.innerHTML = app(manga, begin); document.body.className = ''; document.body.removeAttribute('style'); logScript('Rebuilding Site'); setTimeout(() => { try { events(); setTimeout(() => { window.scrollTo(0, 0); loadManga(manga, begin); }, 50); // Clear used Bookmarks if (!isNothing(useSettings().bookmarks.filter((el) => el.url === window.location.href))) { logScript(`Bookmark Removed ${window.location.href}`); useSettings().bookmarks = useSettings().bookmarks.filter((el) => el.url !== window.location.href); setValueGM('Bookmarks', JSON.stringify(useSettings().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: getLocaleString('STARTING'), input: 'range', inputAttributes: { min: '1', max: manga.pages.toString(), step: '1', }, inputValue: begin || 1, text: getLocaleString('CHOOSE_BEGINNING'), 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 = getLocaleString('BUTTON_START'); button.id = 'StartMOV'; button.onclick = () => { lateStart(site, beginning); }; document.body.appendChild(button); const style = document.createElement('style'); style.appendChild(document.createTextNode(startButton)); 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 = useSettings()?.bookmarks?.find((b) => b.url === window.location.href)?.page || 1; } const style = document.createElement('style'); style.appendChild(document.createTextNode(sweetalertStyle)); document.body.appendChild(style); switch (site.start || useSettings()?.loadMode) { case 'never': createLateStartButton(site, beginning); break; case 'always': formatPage(manga, 0); break; case 'wait': default: Swal.fire({ title: getLocaleString('STARTING'), html: `${beginning > 1 ? `${getLocaleString('RESUME')}${beginning}.
` : ''}${getLocaleString('WAITING')}`, 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()}`); 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); })();