Clip Studio Reader Downloader

Download books from the browser version of Clip Studio Reader

// ==UserScript==
// @name         Clip Studio Reader Downloader
// @namespace    http://tampermonkey.net/
// @version      1.10
// @description  Download books from the browser version of Clip Studio Reader
// @author       mrcoconuat
// @supportURL   https://github.com/MrCocoNuat/clip-studio-reader-downloader/issues
// @match        *://*/*
// @require      https://unpkg.com/[email protected]/dist/jszip.js
// @require      https://unpkg.com/[email protected]/dist/FileSaver.js
// @icon         https://www.google.com/s2/favicons?sz=64&domain=mobilebook.jp
// @license      MIT
// @grant        none
// ==/UserScript==

'use strict';

// Site-blind Clip Studio Reader integration support:
//------------------------------

const downloadButtonId = "download-button";
const errorMessageId = "error-message";
const warningMessagesId = "warning-messages";

const ELEMENT = {
    SCREEN_CONTROLLER: 0, // used to flip pages
    CURRENT_PAGE_COUNTER: 1, // duh
    TOTAL_PAGE_COUNTER: 2, // duh
    LOADER_SPINNER: 3, // used to detect if the reader is loading a page
    MENU: 4, // used to detect if the menu must be raised since it contains the page scroller
    PAGE_SPREAD: 5, // contains the actual pages, and is checked to see if the reader as a whole has loaded
    PAGE_SLIDER: 7, // duh
    COLOPHON: 8 // used to detect if the colophon page is being shown, usually because the reader has reached the end of the book
}

const DOWNLOAD_MODE = {
    PAGE_BY_PAGE: 0, // each page must be descrambled and rendered individually. Usually these are paid books
    DIRECT: 1 // links to each page are accessible right from the start. Usually these are free samples or outright free books
}

// Data distribution for site-specific integrations:
//------------------------------

const siteSupport = {
    "mbj-bs.pf.mobilebook.jp": {
        mode: DOWNLOAD_MODE.PAGE_BY_PAGE,
        ids: {
            [ELEMENT.SCREEN_CONTROLLER]: "screen_surface",
            [ELEMENT.CURRENT_PAGE_COUNTER]: "paging_slider_nombre_current",
            [ELEMENT.TOTAL_PAGE_COUNTER]: "paging_slider_nombre_total",
            [ELEMENT.LOADER_SPINNER]: "loading_spinner_layer",
            [ELEMENT.MENU]: "menu_layer",
            [ELEMENT.PAGE_SPREAD]: "spread_a",
            [ELEMENT.PAGE_SLIDER]: "paging_slider"
        },
        // sometimes the necessary element does not have an id, which sucks
        classes: {}
    },
    "mbj-bs2.pf.mobilebook.jp": {
        mode: DOWNLOAD_MODE.PAGE_BY_PAGE,
        ids: {
            [ELEMENT.CURRENT_PAGE_COUNTER]: "menu_footer_nombre_progress",
            [ELEMENT.MENU]: "menu_container",
            [ELEMENT.PAGE_SPREAD]: { // more complicated structure is needed
                id: "ct-main-viewer",
                path: "children.1.children.0" // the page spread does not have an ID, so we have to go through the react structure >:(
            },
            // [ELEMENT.PAGE_SLIDER]: "menu_pagination_slider", // not working right, make the user drag it manually
            [ELEMENT.COLOPHON]: "reading_panel_colophon",
        },
        classes: {
            [ELEMENT.SCREEN_CONTROLLER]: "viewer-overlay-container", 
        },
        options:{
            poorSupport: true, // this site has some issues
            pageCounterPercentage: true // this site uses a percentage instead of page numbers, which complicates progress detection
        }
    },
    "api.distribution.mediadotech.com": {
        mode: DOWNLOAD_MODE.PAGE_BY_PAGE,
        ids: {
            [ELEMENT.SCREEN_CONTROLLER]: "screen_control_pad",
            [ELEMENT.CURRENT_PAGE_COUNTER]: "menu_nombre_current",
            [ELEMENT.TOTAL_PAGE_COUNTER]: "menu_nombre_total",
            [ELEMENT.LOADER_SPINNER]: "screen_loading_spinner_layer",
            [ELEMENT.MENU]: "menu_container",
            [ELEMENT.PAGE_SPREAD]: "screen_layer"
        },
    },
    "comic-viewer.iowl.jp": {
        mode: DOWNLOAD_MODE.PAGE_BY_PAGE,
        ids: {
            [ELEMENT.SCREEN_CONTROLLER]: "screen_control_pad",
            [ELEMENT.CURRENT_PAGE_COUNTER]: "menu_nombre_current",
            [ELEMENT.TOTAL_PAGE_COUNTER]: "menu_nombre_total",
            [ELEMENT.LOADER_SPINNER]: "screen_loading_spinner_layer",
            [ELEMENT.MENU]: "menu_footer",
            [ELEMENT.PAGE_SPREAD]: "screen_layer",
        },
    },
    "comic.pixiv.net": {
        // readers can have extra conditions tacked on
        condition: () => document.location.pathname.substring(0, 7) === "/viewer",
        mode: DOWNLOAD_MODE.DIRECT,
        href: (pageNumber) => document.getElementById(`page-${pageNumber}`)?.style["background-image"].match(/url\("(.*)"\)/)?.[1], // TODO: these urls are not all loaded at start. So clicking it too fast will lose some ending pages
        classes: {
            [ELEMENT.PAGE_SPREAD]: "h-screen",
        },
    },
    "bs.comicdc.jp": {
        mode: DOWNLOAD_MODE.PAGE_BY_PAGE,
        ids: {
            [ELEMENT.SCREEN_CONTROLLER]: "screen_control_pad",
            [ELEMENT.CURRENT_PAGE_COUNTER]: "menu_nombre_current",
            [ELEMENT.TOTAL_PAGE_COUNTER]: "menu_nombre_total",
            [ELEMENT.LOADER_SPINNER]: "screen_loading_spinner_layer",
            [ELEMENT.MENU]: "menu_container",
            [ELEMENT.PAGE_SPREAD]: "screen_layer"
        },
    }
}

// try by ID if given, then by class
const getCSRElement = (elementEnum) => {
    if (siteSupport[window.location.hostname]) {
        // id search?
        if (typeof siteSupport[window.location.hostname].ids?.[elementEnum] === "string") {
            console.debug("Searching by id:", siteSupport[window.location.hostname].ids?.[elementEnum]);
            return document.getElementById(siteSupport[window.location.hostname].ids?.[elementEnum]);
        }  
        // id + path search?
        else if (typeof siteSupport[window.location.hostname].ids?.[elementEnum] === "object") {
            console.debug("Searching by id + path:", siteSupport[window.location.hostname].ids?.[elementEnum]);
            const idElement = document.getElementById(siteSupport[window.location.hostname].ids?.[elementEnum].id);
            if (idElement) {
                const pathParts = siteSupport[window.location.hostname].ids?.[elementEnum].path.split(".");
                let currentElement = idElement;
                for (const part of pathParts) {
                    if (part === "children") {
                        currentElement = currentElement.children;
                    } else {
                        currentElement = currentElement[part];
                    }
                    if (!currentElement) {
                        break;
                    }
                }
                return currentElement;
            }
        } 
        // class search?
        else if (typeof siteSupport[window.location.hostname].classes?.[elementEnum] === "string") {
            console.debug("Searching by class:", siteSupport[window.location.hostname].classes?.[elementEnum]);
            return document.getElementsByClassName(siteSupport[window.location.hostname].classes?.[elementEnum])[0];
        // class search with multiple elements of the same class?
        } else if (typeof siteSupport[window.location.hostname].classes?.[elementEnum] === "object") {
            console.debug("Searching by class + index:", siteSupport[window.location.hostname].classes?.[elementEnum]);
            const elements = document.getElementsByClassName(siteSupport[window.location.hostname].classes?.[elementEnum].class);
            return elements[siteSupport[window.location.hostname].classes?.[elementEnum].index];
        }
    }
}

// returns a list of direct image links
function getCSRPageHrefs() {
    // start from page 0, and go until null
    const result = [];
    for (let i = 0; /*blank*/; i++) {
        const href = siteSupport[window.location.hostname].href(i);
        if (href === undefined) {
            break;
        }
        result.push(href);
    }
    return result;
}

function siteIsSupported() {
    return !!siteSupport[document.location.hostname] && (siteSupport[document.location.hostname].condition?.() ?? true);
}

function downloadMode() {
    return siteSupport[document.location.hostname].mode;
}

function options() {
    return siteSupport[document.location.hostname].options ?? {};
}


init();


// SVG Handling
// Convert SVG to image (JPEG, PNG, etc.) in the browser
// Thanks, Thom Kiesewetter
// https://stackoverflow.com/a/58142441
//------------------------------

const downloadSvg = `<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <path id="download-a" d="M4.29289322,1.70710678 C3.90236893,1.31658249 3.90236893,0.683417511 4.29289322,0.292893219 C4.68341751,-0.0976310729 5.31658249,-0.0976310729 5.70710678,0.292893219 L7.70710678,2.29289322 C8.09763107,2.68341751 8.09763107,3.31658249 7.70710678,3.70710678 C7.31658249,4.09763107 6.68341751,4.09763107 6.29289322,3.70710678 L4.29289322,1.70710678 Z M0,8 L16,8 L16,10 L0,10 L0,8 Z"/>
    <path id="download-c" d="M11,9.58578644 L13.2928932,7.29289322 C13.6834175,6.90236893 14.3165825,6.90236893 14.7071068,7.29289322 C15.0976311,7.68341751 15.0976311,8.31658249 14.7071068,8.70710678 L10.7071068,12.7071068 C10.3165825,13.0976311 9.68341751,13.0976311 9.29289322,12.7071068 L5.29289322,8.70710678 C4.90236893,8.31658249 4.90236893,7.68341751 5.29289322,7.29289322 C5.68341751,6.90236893 6.31658249,6.90236893 6.70710678,7.29289322 L9,9.58578644 L9,0.998529185 C9,0.447056744 9.44771525,-7.95978809e-15 10,-7.99360578e-15 C10.5522847,-8.02742346e-15 11,0.447056744 11,0.998529185 L11,9.58578644 Z M18,16 L18,10 C18,9.44771525 18.4477153,9 19,9 C19.5522847,9 20,9.44771525 20,10 L20,17 C20,17.5522847 19.5522847,18 19,18 L1,18 C0.44771525,18 0,17.5522847 0,17 L0,10 C0,9.44771525 0.44771525,9 1,9 C1.55228475,9 2,9.44771525 2,10 L2,16 L18,16 Z"/>
  </defs>
  <g fill="none" fill-rule="evenodd" transform="translate(2 3)">
    <g transform="translate(2 6)">
      <mask id="download-b" fill="#ffffff">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAMgCAYAAADbcAZoAAAgAElEQVR4XuzdCbQdVZkv8C8BQggQCFOHhBkEEQEVxLZRZEyQyW5QmWx9igiIgowtSveLovJkFhDDZKutgOKE2BKQEFAGlUEJLSozAcI8hoQQSPJOGPoiJDf7nqGqdtXPtVyre7mr9q7f/mpz/nz33Dso/IcAAQIECBAgQIAAAQIFCQwqaB7TECBAgAABAgQIECBAIAQQRUCAAAECBAgQIECAQGECAkhh1CYiQIAAAQIECBAgQEAAUQMECBAgQIAAAQIECBQmIIAURm0iAgQIECBAgAABAgQEEDVAgAABAgQIECBAgEBhAgJIYdQmIkCAAAECBAgQIEBAAFEDBAgQIECAAAECBAgUJiCAFEZtIgIECBAgQIAAAQIEBBA1QIAAAQIECBAgQIBAYQICSGHUJiJAgAABAgQIECBAQABRAwQIECBAgAABAgQIFCYggBRGbSICBAgQIECAAAECBAQQNUCAAAECBAgQIECAQGECAkhh1CYiQIAAAQIECBAgQEAAUQMECBAgQIAAAQIECBQmIIAURm0iAgQIECBAgAABAgQEEDVAgAABAgQIECBAgEBhAgJIYdQmIkCAAAECBAgQIEBAAFEDBAgQIECAAAECBAgUJiCAFEZtIgIECBAgQIAAAQIEBBA1QIAAAQIECBAgQIBAYQICSGHUJiJAgAABAgQIECBAQABRAwQIECBAgAABAgQIFCYggBRGbSICBAgQIECAAAECBAQQNUCAAAECBAgQIECAQGECAkhh1CYiQIAAAQIECBAgQEAAUQMECBAgQIAAAQIECBQmIIAURm0iAgQIECBAgAABAgQEEDVAgAABAgQIECBAgEBhAgJIYdQmIkCAAAECBAgQIEBAAFEDBAgQIECAAAECBAgUJiCAFEZtIgIECBAgQIAAAQIEBBA1QIAAAQIECBAgQIBAYQICSGHUJiJAgAABAgQIECBAQABRAwQIECBAgAABAgQIFCYggBRGbSICBAgQIECAAAECBAQQNUCAAAECBAgQIECAQGECAkhh1CYiQIAAAQIECBAgQEAAUQMECBAgQIAAAQIECBQmIIAURm0iAgQIECBAgAABAgQEEDVAgAABAgQIECBAgEBhAgJIYdQmIkCAAAECBAgQIEBAAFEDBAgQIECAAAECBAgUJiCAFEZtIgIECBAgQIAAAQIEBBA1QIAAAQIECBAgQIBAYQICSGHUJiJAgAABAgQIECBAQABRAwQIECBAgAABAgQIFCYggBRGbSICBAgQIECAAAECBAQQNUCAAAECBAgQIECAQGECAkhh1CYiQIAAAQIECBAgQEAAUQMECBAgQIAAAQIECBQmIIAURm0iAgQIECBAgAABAgQEEDVAgAABAgQIECBAgEBhAgJIYdQmIkCAAAECBAgQIEBAAFEDBAgQIECAAAECBAgUJiCAFEZtIgIECBAgQIAAAQIEBBA1QIAAAQIECBAgQIBAYQICSGHUJiJAgAABAgQIECBAQABRAwQIECBAgAABAgQIFCYggBRGbSICBAgQIECAAAECBAQQNUCAAAECBAgQIECAQGECAkhh1CYiQIAAAQIECBAgQEAAUQMECBAgQIAAAQIECBQmIIAURm0iAgQIECBAgAABAgQEEDVAgAABAgQIECBAgEBhAgJIYdQmIkCAAAECBAgQIEBAAFEDBAgQIECAAAECBAgUJiCAFEZtIgIECBAgQIAAAQIEBBA1QIAAAQIECBAgQIBAYQICSGHUJiJAgAABAgQIECBAQABRAwQIECBAgAABAgQIFCYggBRGbSICBAgQIECAAAECBAQQNUCAAAECBAgQIECAQGECAkhh1CYiQIAAAQIECBAgQEAAUQMECBAgQIAAAQIECBQmIIAURm0iAgQIECBAgAABAgQEEDVAgAABAgQIECBAgEBhAgJIYdQmIkCAAAECBAgQIEBAAFEDBAgQIECAAAECBAgUJiCAFEZtIgIECBAgQIAAAQIEBBA1QIAAAQIECBAgQIBAYQICSGHUJiJAgAABAgQIECBAQABRAwQIECBAgAABAgQIFCYggBRGbSICBAgQIECAAAECBAQQNUCAAAECBAgQIECAQGECAkhh1CYiQIAAAQIECBAgQEAAUQMECBAgQIAAAQIECBQmIIAURm0iAgQIECBAgAABAgQEEDVAgAABAgQIECBAgEBhAgJIYdQmIkCAAAECBAgQIEBAAFEDBAgQIECAAAECBAgUJiCAFEZtIgIECBAgQIAAAQIEBBA1QIAAAQIECBAgQIBAYQICSGHUJiJAgAABAgQIECBAQABRAwQIECBAgAABAgQIFCYggBRGbSICBAgQIECAAAECBAQQNUCAAAEC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swAigPS+ysxAgACB3gkIIL2zdWcCBFoC0y666Iwln356v0Fz5gyuAsicRRed/cyIEceO2HHHf6/CetpZgwAigLRTN64hQIBAVQQEkKrshHUQqJnAfddeu84KDzxwydDp07vy5fJu8zy3zDLXPzFq1PtXede7Hu/2vXt9PwFEAOl1jbk/AQIEeikggPRS170JNFTg0YkT9xzx4INnt75gvmSVCV4cMuTxp0aN+tCKW245qcrrfP3aBBABJKd6tVYCBAi8XkAAURMECHRV4LErrjhyxP33f23wnDmLdPXGPbpZ60eyZj0xcuRBK2677Zk9mqLrtxVABJCuF5UbEiBAoEABAaRAbFMRqLvA07/85b8Pf+yxL+f2nHMXWWTus8st92/Dd9zx+BzWLoAIIDnUqTUSIEBgQQICiNogQKArAo9NnHjscvfd9/lcD5XWX1OPp0aO/OZy22//ma6A9PAmAogA0sPycmsCBAj0XCDXzwo9hzEBAQLpAq3wccByDzxwelV+01X6yv9+5LwQ8vSoUaePGDPms+3eo4jrBBABpIg6MwcBAgR6JSCA9ErWfQk0RODRq6/earm77rq09Z2PxeryyE9WvBMigAggdXnXPAcBAs0UEECaue+emkBXBO69994RK1933d8Wmzlzxa7csCI3qXonRAARQCryqlgGAQIE2hIQQNpicxEBAvMEnvnv/75y6UcffV8dNaocQgQQAaSO75xnIkCgOQICSHP22pMS6KrA1Kuv3n/kHXd8q86HyEshZNVVTxmxzTaHdBWvw5sJIAJIhyXkcgIECJQqUOfPDqXCmpxA3QVmXnDBo4vPnLlC3Z8zBg2a2/qL6d9YfrvtKhNCBBABpPbvnQckQKDWAgJIrbfXwxHojUDr732c0fp7Hwf05u7VvOuTK6982nJjxx5UhdUJIAJIFerQGggQINCugADSrpzrCDRU4Nprr11is7vvfmyRF14Y1iiCCnVCBBABpFHvnoclQKB2AgJI7bbUAxHorcATEyacMeKhhxrV/fhf0YqEEAFEAOntW+7uBAgQ6K2AANJbX3cnUDuBWT/60TOLzZixdO0ebAAP9OwKKxy99E47fXUAl3R1qAAigHS1oNyMAAECBQsIIAWDm45AzgIPT5q030r33js+52foytpL7oQIIAJIV+rYTQgQIFCSgABSErxpCeQoMO3nP79mqaee+qcc1971NZcYQgQQAaTr9eyGBAgQKFBAACkQ21QEchd48Qc/mNn68vniuT9HN9f/1KhRp44YM+bgbt5zYfcSQASQhdWI/50AAQJVFhBAqrw71kagQgJPXn75Py97//0/q9CSqrGUVifksZEjv7Hi2LGF/Z0QAUQAqUbxWwUBAgTaE4hC/i4AACAASURBVBBA2nNzFYHGCTz9q199f/gjj+zduAdPeeCCQ4gAIoCklKUxBAgQqKqAAFLVnbEuAhUTmP7Tn9457Jln1qrYsqqznAJDiAAigFSn8K2EAAECAxcQQAZu5goCjRR4/oILZg6ZOdP3P/rb/YJCiAAigDTyEPLQBAjURkAAqc1WehACvROYO3fu4Lnf+96Lg+bOdWYsjLmAECKACCALK0P/OwECBKos4MNElXfH2ghUROCpyy7bdJmpU6+vyHKqv4yXf0XvKctvt92hvVisACKA9KKu3JMAAQJFCQggRUmbh0DGAs9ccsnHln744e9k/AilLP2p0aNPGbHddl3/7VgCiABSSkGblAABAl0SEEC6BOk2BOos8OQllxy97MMPH1PnZ+zJs/WoEyKACCA9qVc3JUCAQEECAkhB0KYhkLPAkxMmnL7sQw8dmPMzlLb2HoQQAUQAKa2eTUyAAIEuCAggXUB0CwJ1ENgyxq2ySIz48JBYYs1FYujKg2PoCovFsOUXiyWXWWrxoSstvsTziy869NkYtvSMGLbUrFh6+KxYbsTsePM6c+Odbx1SB4KePsPTyy9/1LI77/z/ujHJ2Bh/4saxX0++X9KN9RV5j5vjzJMujf0PK3LOps+1VRy3Q+tc2GzRGNo6M4b+wyKx+HKt/3+FIbHk8Nb/PWRWzJj+Qkx/+oWY8ficmPnYizFz6gvx3D0z48kLrolxU5vu5/kJEIgQQFQBgQYLbBsnHzA81t5vuVhvveVi3aHtUjw3+MGYO+J/4i1vvy8O+Mgi7d6m3te9/NuxTmn9xfSOg4MA0lcqAkgxr83745yzlo213798vGXUsFhpcLuzPh5/nflk3PaXp+POb0yMQ7/b7n1cR4BA3gICSN77Z/UEBiwwJsattGisfvKo+KddOwkdC5p45uDHYthaV8ZH9ngyNlhHZ+TvnOb9ONbKK5+8/JgxHf0bewFEABnwi9/GBVvHsVuPiLecODq22HhoLNv1zwuPx59nTI0/XPBcTDnsyhj3VBtLdAkBApkKdP1AydTBsgnUXmCb+Pp2y8b6J6wa79toSAzv/fMOmhPPLvOb2HLs7fGh9wsirwV/KYR00AnxHZA+zclx1skT/Dha197ncTFu8LWx4n+sFO84cFS8a4UiflCi9aNZc++Pq254Mv5yyKT4wjVdexg3IkCgsgICSGW3xsIIdEdg6/jK6svHJpeuGWPXK+LDxPxWPW2JP8bHP319vHNDf0j9JZ9WJ+Tx0aOPX2Hbbf+tnV0WQASQdupmYddsFcfvtXbsfG7rRzLb/nHMhc3R3/8+N2bHnfHfNz0Rfxzb6og81sm9XEuAQLUFBJBq74/VEehI4P1x9rfWiV33WyKWq8S7PnidC+PYo2d09Ex1uXjuvB/HajOE+BGsvirwHZDO34jNY9yoUbHZ5WvG+9fv/G6d32F6PDjn9vjZMZfFgeM6v5s7ECBQRYFKfCipIow1EchZYJv4wj/8Q2z/x1XivStX7TmeXey2+ORhV8Xb3+zHsubtzbSVVvq34TvscNxA9kkHpE/Lj2ANpHLeOHbrOO5D68We5y0dqyza2Z26f/Vd8atbp8f1774kxj3T/bu7IwECZQoIIGXqm5tADwS2iRP2Xjd2/04VP1C8+rgvDHo21h/7w9h3j8p95unBjvR/y3Y6ITogfaY6IO2X7E7xvR+/Jf51t/bv0PsrW78x6/k746c7XBFHXdH72cxAgEBRAgJIUdLmIVCAwLZx6hEbxb7HtX4/fwGzdT7F4utfEF/+t+c7v1HmdxhoCNEB6dtwHZC2in/QB+OXt6wVO27Q1tUFXzTvS+r/E/+53xVx2NkFT206AgR6JCCA9AjWbQkULbB9nPXNDeMTnx4Uef0djudWvCyO/8r9sfjiea27F/v7xKhRJ6X8il4BRABpt/7eHYcssVb8y12j470j271HGdfNjufjf+I7/uhkGfjmJNADAQGkB6huSaBogbFx5mkbx76fKeu3XHX6vM8ufU2ceMJtMXTxtv++WadLqMT1qZ0QAUQAaadgx8ThS64Uu90zKv6x9et1c/zP3Lg5zv3WpbHvp3NcvTUTINAnIICoBgKZC2wbp33+7XHAsbl1Pl7PLoS8LJISQgQQAWSgx1b+4eOV96P1q3r/GN86+vL47FcHamA8AQLVERBAqrMXVkJgwAJbxYkffWcc+t0BX1jRC6YNvybOOPWOiq6u2GVNW3HFI4fvuOPx85vVl9D7VHwJPa0uPxLXPjIq3r1i2ujqj7ohTtzrijj8/Oqv1AoJEJifgACiLghkKvDe+MLKb40DplT5t121Q6sT8sq/6e3n74TogOiApL5bdel8vP55n4kpL/45xo/6bRz7aKqFcQQIVEdAAKnOXlgJgQEJ7BVXTV0ltqjc3/kY0EMsYLAQ0n8IEUAEkJT3rK7h49Vnvzsuvf3C2H7dFAtjCBColoAAUq39sBoCSQLbx/jjN4r9Dk8anOmgacN/2/pxrLsyXX13lz3vL6Yvv912R756Vz+C1efrR7AWXGt7xzUPj45/Wqm71Vitu/0pzhjX+ovpX6rWqqyGAIGFCQggCxPyvxOomMC8v3K+fhw8dVisVPtfGaUTMv9OiA6IDkh/x1LdOx+vffZnY+rsm+MbI66J46ZV7Ki2HAIE+hEQQJQHgcwEdouL/rh27PK2zJbd9nKFkDeGEAFEAFnQC9Wk8PGqwW3xk2t/Hh/cvO1DxoUECBQuIIAUTm5CAu0LbBFffvtmcdRNg2PR9m+S4ZVCSN+mPb3CCkfssdMDozaKTx2S4VZ2fcn+EnofaRPDx7ynnx2z4sY4ds0rY9w9XS8wNyRAoCcCAkhPWN2UQG8Edo2LblwndnlHb+5e7bsKIS/vz7y/E3L0+EVuePG6vd9Z7R0rZnUCyMvOTQ0fr1bZX+PCq34RH96ymKozCwECnQoIIJ0Kup5AQQLzfu3uO+PoqYvGEgXNWL1phJCX9+SsC2bHnRM+Ub0NKmFFAojwMa/sZsZTc/8aX136sjhhegllaEoCBAYoIIAMEMxwAmUJ7BjfvXCD+OgHy5q/KvMKIa0Acv6Lceel+1RlS0pdR9MDSNM7H68tvlvinPGXxL4HlFqQJidAIElAAEliMohA+QKfiMnTVogNlyp/JeWvoOkhRADpq8EmBxDh4+/Poqlx3aPfr/mvHS7/9LUCAt0REEC64+guBHoqsFV87X3vjKOu7Okkmd28ySFEABFAhI83HlhzW19Hvz6+4svomZ3llttMAQGkmfvuqTMT2Dm+//P1Y+8PZLbsni+3qSFEAGl2ABE+Fny0+DGsnh+7JiDQFQEBpCuMbkKgtwJ7xlUPrBpbjOrtLHnevYkhRABpbgARPvo/p+6KX93649hxgzxPM6sm0BwBAaQ5e+1JMxY4KJ6YMzRGeF8XsIdNCyECSDMDiPCx8EP8ibht5jmxXnN/VeDCiYwgUAkBH2gqsQ0WQWDBAu+Lr737XXHUtYz6F5g2/Ldxxql3NYLp7AtejDsm+C1Y8zb75jjzpEtj/8OasPF7xzUPj/Yl64Vu9c3xpeUvjXFPLHSgAQQIlCYggJRGb2ICaQJj4vSj3xYHHpM2utmjmtIJ0QFpVgdE52Ng59r1ccLuk+KIHw3sKqMJEChSQAApUttcBNoQ2Cn+62dviY/8cxuXNvKSeSHklJNui8UWG1zb5xdAmhNAhI+Bv8ZN/tXMA9dyBYFyBASQctzNSiBZYLe46I9rxy5vS77AwJi+7FVx+in31FbCj2D1bW3dfwTLj10N/DX+S5z3y4tj750HfqUrCBAoSkAAKUraPATaFPhQXHrbmjHmTW1e3tjLnl3mqjjlhLtq2QnRAal/B0Tno/2j669x4VW/iA9v2f4dXEmAQK8FBJBeC7s/gQ4F9oxJ960aW67S4W0aeXldQ4gAUu8AInx0dlzdET+//qfxL5t1dhdXEyDQSwEBpJe67k2gCwJ7x9Wt33yz+UpduFUjbzEvhHzzG/fU6tn9CFbfdtbxR7D82FVnr+ud8ctbfhI7b9TZXVxNgEAvBQSQXuq6N4EuCOwRk+5fLbYc3YVbNfYWdeuE6IDUswPy/vjs4svFh+8ZHe8Z2diXtQsPfnv87Pqfxa46IF2wdAsCvRIQQHol674EuiTw4bj0jjVizNpdul1jb1OnECKA1C+ACB/dO5r+Fj+adFHsvnX37uhOBAh0W0AA6bao+xHossBucfHktWOnDbt820beri6/HcuPYPWVb11+BGvv+M2Do+O9Oh9dOJluje///Jfxr//ShVu5BQECPRIQQHoE67YEuiWwS5w34c2x59hu3a/p96lDJ0QHpD4dEJ2P7p9Ik+PMEybE/kd0/87uSIBAtwQEkG5Jug+BHgmMjfEnbhz7Hdqj2zfytrmHEAGkHgFE+OjN8XNjnPihiXH4j3tzd3clQKAbAgJINxTdg0APBbaJE3ffJA69oIdTNPLWOYcQAST/ACJ89O7YuSb+bfg1cdy03s3gzgQIdCoggHQq6HoCPRaY90Fl/Thu5qIxtMczNe/2uYYQASTvACJ89O6seTLumHV2vGnx3s3gzgQIdENAAOmGonsQ6LHAx+KGJ/8hNlm2x9M08vY5hhABJN8AInz09pi5K/77lh/HTv4GSG+Z3Z1AxwICSMeEbkCg9wIfaP1ayfXiQ1v2fqZmzpBbCBFA8gwgwkfvzxdfQO+9sRkIdENAAOmGonsQ6LHA1nHCnpvGYef1eJpG3z6nECKA5BdAhI/eHy8vxsy4OY4ZOTG+9nDvZzMDAQKdCAggnei5lkCBAvvF3bOWiTUWK3DKxk2VSwgRQPIKIMJHMUfJlJh03wWx9WrFzGYWAgQ6ERBAOtFzLYECBT4QF1y+Xuy+TYFTNnKqHEKIAJJPABE+ijtGbo4zjrk0DvyP4mY0EwEC7QoIIO3KuY5AwQLvjXFveVd88c+DY9GCZ27edFUPIQJIHgFE+Cju7JgeD815JA4acmFcOLu4Wc1EgEC7AgJIu3KuI1CCwO5x2Z2rx3ZrlTB146ascggRQKofQISPYo+MP8d//fS/46O7FTur2QgQaFdAAGlXznUEShDYMo7dfrP4/CUlTN3IKasaQgSQagcQ4aPY42JmPDX3pvj68lfH/3uy2JnNRoBAuwICSLtyriNQksAeMen+1WLL0SVN37hpqxhCBJDqBhDho/gj4i9x3iUXx947FD+zGQkQaFdAAGlXznUEShLYKr62+SZx+NWDwy/EKmoLqhZCBJBqBhDho6g3sm+eaXH/7D/FSUtfFyc/V/zsZiRAoF0BAaRdOdcRKFFg17joxnVil3eUuITGTV2lECKAVC+ACB/lHAk3x1mnXBr7HVLO7GYlQKBdAQGkXTnXEShRYJs4avm1Y5+py8baQ0pcRuOmrkoIEUCqFUCEj3KOgvviN1PPj/f5cdRy+M1KoCMBAaQjPhcTKE/gffHVrd4RB12xWCxV3iIaOHMVQogAUp0AInyUcwg8FXfOujPOHTUxjn28nBWYlQCBTgQEkE70XEugZIEx8c1xb4tP/9+Sl9G46aePuDJOP/ne0p777AtejDsm7FPa/FWa+OY486RLY//DylrTXq1/C79KvHflsuZv4ryz4pn4U5yx1ZVx1JVNfH7PTKAOAgJIHXbRMzRaYIf49n+9NT7+kUYjlPDwZXZCdEDK74DofJTw0r005dy4KU4/8vI46PiyVmBeAgQ6FxBAOjd0BwKlC7S+lH5T60vpby99IQ1bQFkhRAApN4AIH+W96H+LH026KHbfurwVmJkAgW4ICCDdUHQPAhUQ2D1+fdfqse2aFVhKo5ZQxo9j+RGsvhIr40ew9oqrWj92tYUfuyr4Tb83Lr/7h7HdWgVPazoCBHogIID0ANUtCZQkMGj3mHjP6rH1aiXN39hpi+6E6ICU0wHR+SjvFb83rpjyw9hmjdYK5pa3CjMTINAtAQGkW5LuQ6AaAkJISfswL4R846S7Y9FFen+sCiDFBxDho6QXqzWt8FGevZkJ9Eqg9/+k7NXK3ZcAgQUJCCEl1cb05a6IU46f0vMQIoAUG0CEj5JeKOGjPHgzE+ixgADSY2C3J1CSgBBSEnwRIUQAKS6ACB8lvUjCR3nwZiZQgIAAUgCyKQiUJCCElATf6xAigBQTQISPkl4g4aM8eDMTKEhAACkI2jQEShIQQkqC72UIEUB6H0CEj5JeHOGjPHgzEyhQQAApENtUBEoSEEJKgu9VCBFAehtAhI+SXhjhozx4MxMoWEAAKRjcdARKEhBCSoLvRQgRQHoXQISPkl4U4aM8eDMTKEFAACkB3ZQEShIQQkqC73YIEUB6E0CEj5JeEOGjPHgzEyhJQAApGH6LOGbNIbHsXkNj2U2HxnLrDosVRrb+/2GLx9JDFo8Rg+ct5/l4cs7zMW3WrHhqxox49MGZ8eRtM+OpP8yMx8+7OsZNKXjJpquZQOsvpt/Z+ovp/ppwwfs6ffmJcfqJ93dlVn8JvY+xm38Jfc+48v5V432ju7JJbpIs0PoL53e1/sL52skXGEhgPgI+X+VVFgJIj/dryxi37JAY+YVlYtWdRsR6ay8baw/pZMon4/bnn4jbbp8WU35+WXz63zu5l2ubK7B7XH736i//VWH/KVBgxnIT47STOg8hAkj3A8hecdXUVWKLlQssB1O1BO6Niff8MLZdEwaBgQr4fDVQsWqNF0B6tB/bxqmfWyE2PHzl2Gz0YjGsJ7O8ENNjalx39yMxedykOOx7PZnETWsrIISUs7XdCCECSHcDiPBRzrsgfJTjnvusPl/lvoMvr18A6fI+bh0nf3KVeM9xI2PTEV2+db+3eyCufmhq/P7ASXH4T4uc11xZC/hOSEnb1+l3QnwHpG/jJsdZJ0+I/Q5tZyt956Mdte5cc29cMeWHL3dh53bnju5SdwGfr+q1wwJIl/ZzqzhmvZXi3RPK/bGWuXFX/OqWB+OG7a+JcVO79GhuU28BIaSk/e0khAggnQeQTeJTi705/nXK6HjPyJJKoLHTCh+N3fq2Htznq7bYKn+RANKFLdo+zjl73dh1n6ExohKe0+PBObfFz8b9Og48pguP5xYNEPDF9HI2ud0vpvsRrL79avdL6HvGpPtWjS1XKWfnmzurL5w3d+/beXKfr9pRy+OaSnxgzoPqjatsfQHqzavEmN+MinevWMVnuDsuu/3RuG7rK2Nc5996reIDWlM3BXRCuqk5gHu10wnRAWm/AzKv8/Gm2PPu1WJLv+1qAHXajaE6H91QbMY9fL6q/z4LIG3u8Zg4/eg3x95fbv063UobTo+H5twa533cl9Tb3OiGXeaL6eVs+EC/mK4D0n4HZI+YdL/wUXydtzofd7d+1a5f/108fXYz+nyV3Za1teBKf3hu64kKuGhsjD9xw9jn0MGxaAGzdT7FvN+W1fqi5icnxqHndn43d6i5gE5ISRs8kE6IDsjAOyA6HyUVdmtanY/y7HOb2eer3Has/fUKIAO02yHOPeet8Yl9BnhZ6cNfjOdaIeScQy6Pg04pfTEWUHkB3wkpZ4tmLP/rOO3Ehf/+CB2QgXdAfOejnJq+Jy6780cxdp1yZjdrTgI+X+W0W52vVQAZgGHrd08f8fY48LhB8dIfLM/uP/M6IX+KMz44KY78SXaLt+CiBXRCihZ/Zb6UTogOSHoHROejpELW+SgPPsOZfb7KcNM6XLIAkgi4VXx917fFgT9ZLJZMvKKaw2bEI3Mmx5lv+038xy3VXKFVVUhACClpMxYWQgSQtAAifJRUwMJHefAZzuzzVYab1oUlCyAJiFvHMWuvH5+8bckYmWfr43XP+Fjc+ty3Y4Pe/Hn2BE9D8hL4cFx6xxoxZu28Vp3/amcsf3nrx7EemO+D+BGsPpb+fg2vH7sq5z3wY1fluOc4q89XOe5ad9YsgCzE8UPxoUWGxecf/4d4xzLdIa/GXVp/sPDWH8eOG1RjNVZRcQGdkJI2aEGdEB2Q/jsgOh8lFazOR3nwGc7s81WGm9bFJQsgC8H8QPz46vVit827aF6ZW02Os0+bEJ86qDILspBKC+iElLM98/tiug5I/x2Q1q/andL6VburlrNjzZ1V56O5e9/Ok/t81Y5afa4RQPrZy3k/l7hpHPaTQbFIfXb8NU/yfDw998Y4ZY2rY9yUWj6gh+q2gE5It0UT7/f6TogOyPw7IDofiQXVg2F+1W4PUGt8S5+vary5iY8mgPQD9Ym4ZfoK8dZaf1fi7rjkLxfGDm9JrBfDCAghJdXAa0OIAPLGACJ8lFSYrWmFj/Lsc53Z56tcd6576xZAFmCZ6++jbqc0bogT97oiDj+/nWtd00yB1t8JuWv12HbNZj59eU/96hfT/QhW3x68+iV0Xzgvpy79hfNy3HOe1eernHeve2sXQOZjOSYOX3KdOOTppWJUPX/26nXPPDWuffT7sflK3Ssrd2qCwO4x8d7VY+vVmvCsVXrGeZ2QDTe5O+76dXZ/D7UnjJPjrFOXjXV3a33nY3RPJnDTBQq80vlYHRGBVAGfr1Kl6j9OAJnPHu8Y3zl/g/jYHvXf/r4nvDFO/j8T49DvNumZPWvnAr6Y3rlhO3eYsciUGDZb9ptnNy3un710rNKIf1nUTq306prWF87vaP2F8zf16v7uW08Bn6/qua/tPJUAMh+1A+K+F5v2D7T74sr7z4+t/NaYdt6ihl8jhDS8ADx+4wSEj8Ztedce2OerrlFmfyMB5HVbuE2cst8mcfD47Hd2gA8wO2bFH+JLo34bX3twgJcaTsAX09UAgYYI+MJ5Qza6B4/p85XPV68tKwHkdS/ZbnHx5LVjpw178O5V/pa3xDnjL4l9D6j8Qi2wigJCSBV3xZoIdFFA+OgiZgNv5fOVz1cCSD8v/mfikTnDYsVGBrMH4uqHfhDvXbmB56JH7pKA347VJUi3IVAxgdZvu7rrh7Hd2hVbluVkJODzlc9XAsgCXtht4oS9N4nDvp/R+9zVpb4Q0+OvcdTQS+K057t6YzdrkoBOSJN227M2QkDnoxHb3NOH9PnK56vXF1gj/03/gt6yneP8S9ePPcb09C2s+M1vilMPujwOPq3iy7S8agsIIdXeH6sjkCwgfCRTGdiPgM9XET5f/X2BCCCv8fho/P7xkbHZck0+Rf4WF155UXx4qyYbePbuCLR+HOvO1h8rXKs7d3MXAgSKFvBjV0WL13c+n68ifL4SQBb4hh8YD85eMkYOru8RsPAnmxKT7rvAH5dbOJQRKQI6ISlKxhCooIDORwU3JeMl+XwV4fOVADLfV3jzOHLpzePrz2T8fndl6Y/Frc99OzYY1pWbuQmBCCFEFRDITED4yGzDKr5cn69e3iCfrwSQ+b6qW8cJe24ah51X8fe458ubFc/EKbGMH83ruXSjJhBCGrXdHjZngXvj163fdjVmndYzzM35Oay9OgI+X728Fz5fCSDzfSu3jdO++I74zFeq88qWt5Ib4qtrXBFH31veCsxcQwEhpIab6pHqJSB81Gs/q/I0Pl/17YTPV30W/k33KxZj4oxj3hYHHF2VF7bMdVwfx+84KY78VZlrMHctBYSQWm6rh6qDgPBRh12s5jP4fNW3Lz5fCSBveEu3j/HHbxT7HV7N17fYVd0Ypx42MQ4+qdhZzdYQASGkIRvtMfMRED7y2ascV+rzVd+u+XwlgLzhHR4b40/cOPY7NMeXu9trnhxnnjAh9j+i2/d1PwKvCnw4Lr19jZd/ztx/CBAoUeDu1rt4YWy/bolLMHXNBXy+6ttgn68EkPl0QM48aaP41CE1PweSHm9ynHXyBGEsycqgtgV0QtqmcyGB7gjofHTH0V36F9g+fL56VcjnKwFEAOnnvPCC+MdJQQJCSEHQpiHwegHhQ00UJSCAvLYD4l/wvqrhS+ivSHhBvCBFHcbm+TsBIURBEChYQPgoGLzh0/l85fPV/F4BAUQAeUNd6IA0/J8WxT9+K4Rcdsfqsd1axU9tRgLNEhA+mrXfVXhaAUQAEUD6eRO9IF6QKhzUDV6DENLgzffoxQgIH8U4m+XvBXy+8vlKABFAks5FHZAkJoO6LyCEdN/UHQm8JCB8KISyBAQQAUQAEUCSzh8BJInJoN4ICCG9cXXXBgsIHw3e/Ao8ugAigAggAkjSUSSAJDEZ1DsBIaR3tu7cMAHho2EbXsHHFUAEEAFEAEk6mgSQJCaDeisghPTW190bICB8NGCTM3hEAUQAEUAEkKSjSgBJYjKo9wJCSO+NzVBTAeGjphub4WMJIAKIACKAJB1dAkgSk0HFCAghxTibpUYCwkeNNrMGjyKACCACiACSdJQJIElMBhUnIIQUZ22mzAWEj8w3sIbLF0AEEAFEAEk62gSQJCaDihUQQor1NluGAsJHhpvWgCULIAKIACKAJB11AkgSk0HFCwghxZubMRMB4SOTjWrgMgUQAUQAEUCSjj4BJInJoHIEhJBy3M1aYQHho8KbY2khgAggAogAknQUCiBJTAaVJyCElGdv5ooJCB8V2xDLeYOAACKACCACSNLRKIAkMRlUroAQUq6/2SsgIHxUYBMsYaECAogAIoAIIAs9KOYNEECSmAwqX0AIKX8PrKAkAeGjJHjTDlhAABFABBABJOngEECSmAyqhoAQUo19sIoCBYSPArFN1bGAACKACCACSNJBIoAkMRlUHQEhpDp7YSU9FhA+egzs9l0XEEAEEAFEAEk6WASQJCaDqiUghFRrP6ymBwLCRw9Q3bLnAgKIACKACCBJB40AksRkUPUEhJDq7YkVdUlA+OgSpNsULiCACCACiACSdPAIIElMBlVTQAip5r5YVQcCwkcHeC4tXUAAEUAEEAEk6SASQJKYDKqugBBS3b2xsgEKCB8DBDO8cgICiAAigAggSQeTAJLEZFC1BYSQau+P1SUICB8JSIZUXkAAEUAEEAEk6aASQJKYEM46PgAAIABJREFUDKq+gBBS/T2ywgUICB9Koy4CAogAIoAIIEnnmQCSxGRQHgJCSB77ZJWvERA+lEOdBAQQAUQAEUCSzjQBJInJoHwEhJB89qrxKxU+Gl8CtQMQQAQQAUQASTrYBJAkJoPyEhBC8tqvRq5W+Gjkttf+oQUQAUQAEUCSDjoBJInJoPwEhJD89qwxKxY+GrPVjXtQAUQAEUAEkKSDTwBJYjIoTwEhJM99q/WqhY9ab2/jH04AEUAEEAEk6SAUQJKYDMpXQAjJd+9qt3Lho3Zb6oFeJyCACCACiACSdDAKIElMBuUtIITkvX+1WL3wUYtt9BALERBABBABRABJOigFkCQmg/IXEELy38Nsn0D4yHbrLHyAAgKIACKACCBJx4YAksRkUD0EhJB67GNWTyF8ZLVdFtuhgAAigAggAkjSMSKAJDEZVB8BIaQ+e1n5JxE+Kr9FFthlAQFEABFABJCkY0UASWIyqF4CQki99rOSTyN8VHJbLKrHAgKIACKACCBJx4wAksRkUP0EhJD67Wllnkj4qMxWWEjBAgKIACKACCBJx44AksRkUD0FhJB67mupTyV8lMpv8pIFBBABRAARQJKOIQEkicmg+goIIfXd28Kf7K6Y8Ncfx/vf0pp4buGTm5BABQQEEAFEABFAko4iASSJyaB6Cwgh9d7fQp7ulfCxfiGTmYRARQUEEAFEABFAko4nASSJyaD6Cwgh9d/jnj2h8NEzWjfOTEAAEUAEEAEk6dgSQJKYDGqGgBDSjH3u6lMKH13ldLPMBQQQAUQAEUCSjjEBJInJoOYICCHN2euOn1T46JjQDWomIIAIIAKIAJJ0rAkgSUwGNUtACGnWfrf1tMJHW2wuqrmAACKACCACSNIxJ4AkMRnUPAEhpHl7nvzEwkcylYENExBABBABRABJOvYEkCQmg5opIIQ0c9/7fWrhQ1EQWLCAACKACCACSNIZKYAkMRnUXAEhpLl7/4YnFz4UA4H+BQQQAUQAEUCSzkkBJInJoGYLCCHN3v+Xnl74UAQEFi4ggAggAogAsvCTojVCAEliMoiAENLgGhA+Grz5Hn1AAgKIACKACCBJh4YAksRkEIF5AkJIA+tA+GjgpnvktgUEEAFEABFAkg4QASSJySACrwoIIQ2qBeGjQZvtUbsiIIAIIAKIAJJ0mAggSUwGEXitgBDSgHoQPhqwyR6x6wICiAAigAggSQeLAJLEZBCB1wsIITWuCeGjxpvr0XoqIIAIIAKIAJJ0yAggSUwGEZjvmbp7XHbH6rHdWnjqIyB81GcvPUnxAgKIACKACCBJJ48AksRkEIEFCeiE1Kg2hI8abaZHKUVAABFABBABJOnwEUCSmAwi0J+AEFKD+hA+arCJHqF0AQFEABFABJCkg0gASWIyiMDCBAZ9MC65da3Y/s0LG+h/r56A8FG9PbGiPAUEEAFEABFAkk4vASSJySACSQKtEPIXISSJqjKDhI/KbIWF1EBAABFABBABJOkoE0CSmAwikCwghCRTlT5Q+Ch9CyygZgICiAAigAggSceaAJLEZBCBAQkIIQPiKmWw8FEKu0lrLiCACCACiACSdMwJIElMBhEYsIAQMmCywi4QPgqjNlHDBAQQAUQAEUCSjj0BJInJIAJtCQghbbH19CLho6e8bt5wAQFEABFABJCkY1AASWIyiEDbAkJI23Rdv1D46DqpGxL4OwEBRAARQASQpGNRAEliMohARwJCSEd8XblY+OgKo5sQ6FdAABFABBABJOmYFECSmAwi0LGAENIxYds3ED7apnMhgQEJCCACiAAigCQdGgJIEpNBBLoiIIR0hXFANxE+BsRlMIGOBAQQAUQAEUCSDhEBJInJIAJdExBCuka50BsJHwslMoBAVwUEEAFEABFAkg4VASSJySACXRUQQrrKOd+bCR+9NzYDgdcLCCACiAAigCSdjAJIEpNBBLouIIR0nfR/byh89M7WnQn0JyCACCACiACSdEoKIElMBhHoiYAQ0n1W4aP7pu5IIFVAABFABBABJOm8EECSmAwi0DMBIaR7tMJH9yzdiUA7AgKIACKACCBJZ4cAksRkEIGeCgghnfMKH50bugOBTgUEEAFEABFAks4RASSJySACPRcQQtonFj7at3MlgW4KCCACiAAigCSdKQJIEpNBBAoREEIGzix8DNzMFQR6JSCACCACiACSdL4IIElMBhEoTEAISacWPtKtjCRQhIAAIoAIIAJI0lkjgCQxGUSgUAEhZOHcwsfCjYwgULSAACKACCACSNK5I4AkMRlEoHABIWTB5MJH4eVoQgJJAgKIACKACCBJh4UAksRkEIFSBISQN7ILH6WUokkJJAkIIAKIACKAJB0WAkgSk0EEShMQQvrohY/SytDEBJIEBBABRAARQJIOCwEkickgAqUKCCERwkepJWhyAkkCAogAIoAIIEmHhQCSxGQQgdIFmhxChI/Sy88CCCQJCCACiAAigCQdFgJIEpNBBCoh0MQQInxUovQsgkCSgAAigAggAkjSYSGAJDEZRKAyAk0KIcJHZcrOQggkCQggAogAIoAkHRYCSBKTQQQqJdCEEHJn/PKWn8TOG1UK3mIIEOhXQAARQAQQASTpmBRAkpgMIlA5gTqHEOGjcuVmQQSSBAQQAUQAEUCSDgsBJInJIAKVFKhjCBE+KllqFkUgSUAAEUAEEAEk6bAQQJKYDCJQWYE6hRDho7JlZmEEkgQEEAFEABFAkg4LASSJySAClRaoQwgRPipdYhZHIElAABFABJB+XpWxMf7EjWO/Q5PeppoPujnOPOnS2P+wmj+mxyNQe4Fd4xd/Wid23jjHB709LrrxZ/HPm+a4dmsmQKBPwOerPgufr/osBnlJXhaQ0CV07wKBOgrsHD/4xfqx584RuRz3c+Mvcf7FF8feu9RxPzwTgaYJ+Hzl85UOiA5I0rknoScxGUQgG4Ht46xTN4p9P5vDgv0IaA67ZI0E0gV0QHRABJB+3hcJXUJPP06NJJCfwFbx9V3Wi91/MjxWX7SKq38q7pp1W/zoA1fGUROquD5rIkCgPQGfr3y+EkB0QJJODx2QJCaDCGQnsGWMGzky3vO71WKb1au0+Ltjwt8ejd//45Ux7qkqrctaCBDoXEAHRAdEANEBSTpJ/AhEEpNBBLIV2Ca+cei6sevXl45VSu2GPBP3vnhH/PyQy+Nzp2eLaeEECPQroAOiAyKA6IAkHZM6IElMBhHIXmCX+OHE1WLrrYbFCoV+Q31GPDpnSlz+61/EXttnj+gBCBDoV0AHRAdEABFAko5JASSJySACtREYG2efMTre9a8rxIZL9fKhHo6bnnkorj/Hr/nupbJ7E6iWgAAigAgg/byTWoRahNU6sq2GQPECW8UJu64Qb/nKKrHF+ovFkl1ZwPPx9Nz74jeTn4i/fv7KONIXzLui6iYE8hHw+crnKwFEByTpxNIBSWIyiECtBbaLbxy8RIzcZXisttESsdzSi8eyiw2LlQb399DT46E5rcAx67l47OlpMeVP0+ORn02Mz51ZaygPR4BAvwI6IDogAogAknRMCiBJTAYRaKTAe2LcakNi8XUGx9A15sTcuXPi+bsjnr+j9Rus7m8kiIcmQEAASawBn6/6oAr94mHi/pQyTItQi7CUwjMpAQIECBCosYDPVz5f6YD084J7QbwgNT7/PRoBAgQIEChFwOcrn68EEAEk6fDxd0CSmAwiQIAAAQIEFiIggAggAogAknRQCiBJTAYRIECAAAECAkhyDfh81UflOyCvWEjoEnryCWIgAQIECBAgkCTg85XPVzogOiBJh4WEnsRkEAECBAgQIKADklwDPl/pgLyhWCR0CT35BDGQAAECBAgQSBLw+crnKx0QHZCkw0JCT2IyiAABAgQIENABSa4Bn690QHRA+nldvCDJZ4mBBAgQIECAgH/Bm1QDPl8JIAKIAJJ0WBhEgAABAgQItC/gR7D67AQQAUQAEUDaP01dSYAAAQIECCQJCCACyPwKxa/hfUXFC+IFSTpJDSJAgAABAgSSBXy+8vlKAOnndfGCeEGST1MDCRAgQIAAgSQBn698vhJABJCkw8LPKCYxGUSAAAECBAgsREAAEUAEEAEk6aAUQJKYDCJAgAABAgQEkOQa8Pmqj8p3QF6xkNAl9OQTxEACBAgQIEAgScDnK5+vdEB0QJIOCwk9ickgAgQIECBAQAckuQZ8vtIBeUOxSOgSevIJYiABAgQIECCQJODzlc9XOiA6IEmHhYSexGQQAQIECBAgoAOSXAM+X+mA6ID087p4QZLPEgMJECBAgAAB/4I3qQZ8vhJABBABJOmwMIgAAQIECBBoX8CPYPXZCSACiAAigLR/mrqSAAECBAgQSBIQQASQ+RWKX8P7iooXxAuSdJIaRIAAAQIECCQL+Hzl85UA0s/r4gXxgiSfpgYSIECAAAECSQI+X/l8JYAIIEmHhZ9RTGIyiAABAgQIEFiIgAAigAggAkjSQSmAJDEZRIAAAQIECAggyTXg81Ufle+AvGIhoUvoySeIgQQIECBAgECSgM9XPl/pgOiAJB0WEnoSk0EECBAgQICADkhyDfh8pQPyhmKR0CX05BPEQAIECBAgQCBJwOcrn690QHRAkg4LCT2JySACBAgQIEBAByS5Bny+0gHRAenndfGCJJ8lBhIgQIAAAQL+BW9SDfh8JYAIIAJI0mFhEAECBAgQINC+gB/B6rMTQAQQAaSfs2Taut/95VdOWWli+8eNKwkQIECAAAECEV886OEth9/xfz7AIkIAEUAEkH5OgtE7fv+5gz40ewmHBQECBAgQIECgE4FTzhv01IOXfXTZTu5Rl2sFEAFEABFA6nKeeQ4CBAgQIFBZAQGkb2sEEAFEABFAKntYWxgBAgQIEKiLgAAigMyvlv0l9FdUfEmqrzz8CFZdjn3PQYAAAQIEyhUQQAQQAaSfd1AAEUDKPaLNToAAAQIE6icggAggAogAknSy6YAkMRlEgAABAgQILERAABFABBABJOmgFECSmAwiQIAAAQIEBJDkGvAl9D4q3wF5xcKPYPUVhQCSfJYYSIAAAQIECPQjoAOiA6IDogOSdEgKIElMBhEgQIAAAQI6IMk1oAOiA/KGYtEB0QFJPkEMJECAAAECBJIEdEB0QHRAdECSDgsdkCQmgwgQIECAAAEdkOQa0AHRAdEB6ed1EUCSzxIDCRAgQIAAgX4EdEB0QHRAdECSDkkBJInJIAIECBAgQEAHJLkGdEB0QHRAdECSDwwDCRAgQIAAgfYEdEB0QHRAdECSTg8dkCQmgwgQIECAAAEdkOQa0AHRAdEB0QFJPjAMJECAAAECBNoT0AHRAdEB0QFJOj10QJKYDCJAgAABAgR0QJJrQAdEB0QHRAck+cAwkAABAgQIEGhPQAdEB0QHRAck6fTQAUliMogAAQIECBDQAUmuAR0QHRAdEB2Q5APDQAIECBAgQKA9AR0QHRAdEB2QpNNDBySJySACBAgQIEBAByS5BnRAdEB0QHRAkg8MAwkQIECAAIH2BHRAdEB0QHRAkk4PHZAkJoMIECBAgAABHZDkGtAB0QHRAdEBST4wDCRAgAABAgTaE9AB0QHRAdEBSTo9dECSmAwiQIAAAQIEdECSa0AHRAdEB0QHJPnAMJAAAQIECBBoT0AHRAdEB0QHJOn00AFJYjKIAAECBAgQ0AFJrgEdEB0QHRAdkOQDw0ACBAgQIECgPQEdEB0QHRAdkKTTQwckickgAgQIECBAQAckuQZ0QHRAdEB0QJIPDAMJECBAgACB9gR0QHRAdEB0QJJODx2QJCaDCBAgQIAAAR2Q5BrQAdEB0QHRAUk+MAwkQIAAAQIE2hPQAdEB0QHRAUk6PXRAkpgMIkCAAAECBHRAkmtAB0QHRAdEByT5wDCQAAECBAgQaE9AB0QHRAdEByTp9NABSWIyiAABAgQIENABSa4BHRAdEB0QHZDkA8NAAgQIECBAoD0BHRAdEB0QHZCk00MHJInJIAIECBAgQEAHJLkGdEB0QHRAdECSDwwDCRAgQIAAgfYEdEB0QHRAdECSTg8dkCQmgwgQIECAAAEdkOQa0AHRAdEB0QFJPjAMJECAAAECBNoT0AHRAdEB0QFJOj10QJKYDCJAgAABAgR0QJJrQAdEB0QHRAck+cAwkAABAgQIEGhPQAdEB0QHRAck6fTQAUliMogAAQIECBDQAUmuAR0QHRAdEB2Q5APDQAIECBAgQKA9AR0QHRAdEB2QpNNDBySJySACBAgQIEBAByS5BnRAdEB0QHRAkg8MAwkQIECAAIH2BHRAdEB0QHRAkk4PHZAkJoMIECBAgAABHZDkGtAB0QHRAdEBST4wDCRAgAABAgTaE9AB0QHRAdEBSTo9dECSmAwiQIAAAQIEdECSa0AHRAdEB0QHJPnAMJAAAQIECBBoT0AHRAdEB0QHJOn00AFJYjKIAAECBAgQ0AFJrgEdEB0QHRAdkOQDw0ACBAgQIECgPQEdEB0QHRAdkKTTQwckickgAgQIECBAQAckuQZ0QHRAdEB0QJIPDAMJECBAgACB9gR0QHRAdEB0QJJODx2QJCaDCBAgQIAAAR2Q5BrQAdEB0QHRAUk+MAwkQIAAAQIE2hPQAdEB0QHRAUk6PXRAkpgMIkCAAAECBHRAkmtAB0QHRAdEByT5wDCQAAECBAgQaE9AB0QHRAdEByTp9NABSWIyiAABAgQIENABSa4BHRAdEB0QHZDkA8NAAgQIECBAoD0BHRAdEB0QHZCk00MHJInJIAIECBAgQEAHJLkGdEB0QHRAdECSDwwDCRAgQIAAgfYEdEB0QHRAdECSTg8dkCQmgwgQIECAAAEdkOQa0AHRAdEB0QFJPjAMJECAAAECBNoT0AHRAdEB0QFJOj10QJKYDCJAgAABAgR0QJJrQAdEB0QHRAck+cAwkAABAgQIEGhPQAdEB0QHpJ93Z2yMP3Hj2O/Q9l6vel31zODbX3xh0Wdm1+upPA0BAgQIECBQtMBiLw5fZPicNy1a9LxVnO/mOPOkS2P/w6q4tqLXNKjoCas63/atotgoPnVIVddnXQQIECBAgAABAvkK+BGsvr0TQF6xEEDyfaGtnAABAgQIECBQdQEBRAB5Q40KIFV/ba2PAAECBAgQIJCvgAAigAgg+b6/Vk6AAAECBAgQyE5AABFABJDsXlsLJkCAAAECBAjkKyCACCACSL7vr5UTIECAAAECBLITEEAEEAEku9fWggkQIECAAAEC+QoIIAKIAJLv+2vlBAgQIECAAIHsBAQQAUQAye61tWACBAgQIECAQL4CAogAIoDk+/5aOQECBAgQIEAgOwEBRAARQLJ7bS2YAAECBAgQIJCvgAAigAgg+b6/Vk6AAAECBAgQyE5AABFABJDsXlsLJkCAAAECBAjkKyCACCACSL7vr5UTIECAAAECBLITEEAEEAEku9fWggkQIECAAAEC+QoIIAKIAJLv+2vlBAgQIECAAIHsBAQQAUQAye61tWACBAgQIECAQL4CAogAIoDk+/5aOQECBAgQIEAgOwEBRAARQLJ7bS2YAAECBAgQIJCvgAAigAgg+b6/Vk6AAAECBAgQyE5AABFABJDsXlsLJkCAAAECBAjkKyCACCACSL7vr5UTIECAAAECBLITEEAEEAEku9fWggkQIECAAAEC+QoIIAKIAJLv+2vlBAgQIECAAIHsBAQQAUQAye61tWACBAgQIECAQL4CAogAIoDk+/5aOQECBAgQIEAgOwEBRAARQLJ7bS2YAAECBAgQIJCvgAAigAgg+b6/Vk6AAAECBAgQyE5AABFABJDsXlsLJkCAAAECBAjkKyCACCACSL7vr5UTIECAAAECBLITEEAEEAEku9fWggkQIECAAAEC+QoIIAKIAJLv+2vlBAgQIECAAIHsBAQQAWQ+AWT88RvFfodnV80WTIAAAQIECBAgUHmByXHmCRNi/yMqv9ACFjiogDmymGJMfHPc2+LT/zeLxVokAQIECBAgQIBAVgJ/ijPGXRYHfimrRfdosQLIK7DbxTcOfnscdEqPnN2WAAECBAgQIECgwQI3xTc+e3l87vQGE/zvowsgr1BsEyfsvUkc9n1FQYAAAQIECBAgQKDbAtfHCbtPiiN+1O375ng/AeSVXdsqvvaud8ZRv8txE62ZAAECBAgQIECg2gI3xtc2nhhfnFztVRazOgHkNc6Hxcy5i8TixcibhQABAgQIECBAoBECL8T0ODmW8rn7ld0G8Zqy3yf+8tzy8eahjXgTPCQBAgQIECBAgEAhAo/GLdP/MzZaqpDJMphEAHnNJu0Rl9+zWmyzegb7ZokECBAgQIAAAQKZCNwdl95+YWy/bibL7fkyBZDXEO8Y3zl/g/jYHj1XNwEBAgQIECBAgEBjBCbHuedMiE/u25gHXsiDCiCvAdoyjttpszjiYsVBgAABAgQIECBAoFsC18WXNvhtjLu1W/fL/T4CyOt28DPx8OxhsdLg3DfW+gkQIECAAAECBMoXeDrueeHMWHNI+SupzgoEkNftxW7xi5vXjp03qs4WWQkBAgQIECBAgECuArfFT679eXxw81zX34t1CyCvU90mTvzIJnHof/UC2z0JECBAgAABAgSaJfD7OHaLq+ILv23WU/f/tALIfHz2jdtmjog3+YMg3hQCBAgQIECAAIG2BR6OPz7z3XjHMm3foKYXCiDz2dgd4tvffWt8/KM13XOPRYAAAQIECBAgUIDA5DjzhAmx/xEFTJXVFALIfLZrTBy+5Lpx5LRhsSKfrMrZYgkQIECAAAEC1RB4Ou5qffl87Xk/UTO3Giuqzip8wF7AXuwcP7h4/dhrp+pslZUQIECAAAECBAjkInBL/Oe3L4lP7JPLeotcpwCyAO3N48ilN46Dn1wqRi1S5IaYiwABAgQIECBAIG+BJ1rfJz4n1hum+zH/fRRA+qnv7eL0/3h7HPilvF8BqydAgAABAgQIEChOYG7cECd99Io43G9VXQC6ALKQatwjJt2/Wmw5uriiNRMBAgQIECBAgECuAnfGxZN/ErtsnOv6i1i3ALIQ5ffGF1Z+axwwZelYZdEiNsQcBAgQIECAAAECeQrM+9GrP8f45a6Lk5/L8wmKWbUAkuC8dRz3oXfE5340OBZLGG0IAQIECBAgQIBA0wSej6fj5jj93VfG0b9r2rMP9HkFkESxMXHG8RvHpw4fFL6TnkhmGAECBAgQIECgEQIvxIxo/c2PT06MQ89txAN3+JACyAAAx8aZp20c+34mAtsA2AwlQIAAAQIECNRWYHbManU+vvXZy+Nzp9f2Ibv8YD5JDxB0hzj3nLf6nc4DVDOcAAECBAgQIFBPgZvi9KMvj89+tZ5P15unEkDacB0b40/cMPY5dHD4XnobfC4hQIAAAQIECGQv8EJMb/3Y1dmfmRiHfDP7hyn4AQSQNsFb3wk5ZoP42NGLxby/MeM/BAgQIECAAAECTRF4Lh6fe2t87+Ot73x8tynP3M3nFEA60Nwyjt12vdj9V8vEmn49VgeOLiVAgAABAgQI5CLwSNw87a74xea/if+4JZc1V22dAkiHOzImxq20TPzj1WvG2Dd1eCuXEyBAgAABAgQIVFjgtvjpdT+P3f6pwkvMYmkCSJe2acs4bqdV4r3/OSr+cYUu3dJtCBAgQIAAAQIEShaYG3NiSky8a2pc/6HfxhdvKnk5tZheAOnyNm4bJx+wamx93Iqx0VJdvrXbESBAgAABAgQIFCYwN+6L3059OP6w/xVxxMWFTduAiQSQHm3y2Pjml1eIjfYdHe8Z2aMp3JYAAQIECBAgQKDLArPimXggfnf7Y3HL5yfF4T/t8u3driUggPS4DOZ9R2RwrPLlEbHOTiNj09GLxZI9ntHtCRAgQIAAAQIEBiIw77daPRi/u/WZuOeCy+IzXxnItcYOXEAAGbhZR1fM64wsGaO2HxYrrbFUjB4xPFb3x0Q6EnUxAQIECBAgQCBd4MWY2epxTJk5PR58fHo8csf0mHr+xPjcmel3MLJTAQGkU0HXEyBAgAABAgQIECCQLCCAJFMZSIAAAQIECBAgQIBApwICSKeCridAgAABAgQIECBAIFlAAEmmMpAAAQIECBAgQIAAgU4FBJBOBV1PgAABAgQIECBAgECygACSTGUgAQIECBAgQIAAAQKdCgggnQq6ngABAgQIECBAgACBZAEBJJnKQAIECBAgQIAAAQIEOhUQQDoVdD0BAgQIECBAgAABAskCAkgylYEECBAgQIAAAQIECHQqIIB0Kuh6AgQIECBAgAABAgSSBQSQZCoDCRAgQIAAAQIECBDoVEAA6VTQ9QQIECBAgAABAgQIJAsIIMlUBhIgQIAAAQIECBAg0KmAANKpoOsJECBAgAABAgQIEEgWEECSqQwkQIAAAQIECBAgQKBTAQGkU0HXEyBAgAABAgQIECCQLCCAJFMZSIAAAQIECBAgQIBApwICSKeCridAgAABAgQIECBAIFlAAEmmMpAAAQIECBAgQIAAgU4FBJBOBV1PgAABAgQIECBAgECygACSTGUgAQIECBAgQIAAAQKdCgggnQq6ngABAgQIECBAgACBZAEBJJnKQAIECBAgQIAAAQIEOhUQQDoVdD0BAgQIECBAgAABAskCAkgylYEECBAgQIAAAQIECHQqIIB0Kuh6AgQIECBAgAABAgSSBQSQZCoDCRAgQIAAAQIECBDoVEAA6VTQ9QQIECBAgAABAgQIJAsIIMlUBhIgQIAAAQIECBAg0KmAANKpoOsJECBAgAABAgQIEEgWEECSqQwkQIAAAQIECBAgQKBTAQGkU0HXEyBAgAABAgQIECCQLCCAJFMZSIAAAQIECBAgQIBApwICSKeCridAgAABAgQIECBAIFlAAEmmMpAAAQIECBAgQIAAgU4FBJAdyRc2AAAEpUlEQVROBV1PgAABAgQIECBAgECygACSTGUgAQIECBAgQIAAAQKdCgggnQq6ngABAgQIECBAgACBZAEBJJnKQAIECBAgQIAAAQIEOhUQQDoVdD0BAgQIECBAgAABAskCAkgylYEECBAgQIAAAQIECHQqIIB0Kuh6AgQIECBAgAABAgSSBQSQZCoDCRAgQIAAAQIECBDoVEAA6VTQ9QQIECBAgAABAgQIJAsIIMlUBhIgQIAAAQIECBAg0KmAANKpoOsJECBAgAABAgQIEEgWEECSqQwkQIAAAQIECBAgQKBTAQGkU0HXEyBAgAABAgQIECCQLCCAJFMZSIAAAQIECBAgQIBApwICSKeCridAgAABAgQIECBAIFlAAEmmMpAAAQIECBAgQIAAgU4FBJBOBV1PgAABAgQIECBAgECygACSTGUgAQIECBAgQIAAAQKdCgggnQq6ngABAgQIECBAgACBZAEBJJnKQAIECBAgQIAAAQIEOhUQQDoVdD0BAgQIECBAgAABAskCAkgylYEECBAgQIAAAQIECHQqIIB0Kuh6AgQIECBAgAABAgSSBQSQZCoDCRAgQIAAAQIECBDoVEAA6VTQ9QQIECBAgAABAgQIJAsIIMlUBhIgQIAAAQIECBAg0KmAANKpoOsJECBAgAABAgQIEEgWEECSqQwkQIAAAQIECBAgQKBTAQGkU0HXEyBAgAABAgQIECCQLCCAJFMZSIAAAQIECBAgQIBApwICSKeCridAgAABAgQIECBAIFlAAEmmMpAAAQIECBAgQIAAgU4FBJBOBV1PgAABAgQIECBAgECygACSTGUgAQIECBAgQIAAAQKdCgggnQq6ngABAgQIECBAgACBZAEBJJnKQAIECBAgQIAAAQIEOhUQQDoVdD0BAgQIECBAgAABAskCAkgylYEECBAgQIAAAQIECHQqIIB0Kuh6AgQIECBAgAABAgSSBQSQZCoDCRAgQIAAAQIECBDoVEAA6VTQ9QQIECBAgAABAgQIJAsIIMlUBhIgQIAAAQIECBAg0KmAANKpoOsJECBAgAABAgQIEEgWEECSqQwkQIAAAQIECBAgQKBTAQGkU0HXEyBAgAABAgQIECCQLCCAJFMZSIAAAQIECBAgQIBApwICSKeCridAgAABAgQIECBAIFlAAEmmMpAAAQIECBAgQIAAgf/ffh3aAAAAIAz7/2teIJmtB1O3KiBAqqA/AQIECBAgQIAAAQK3gAC5qQwJECBAgAABAgQIEKgCAqQK+hMgQIAAAQIECBAgcAsIkJvKkAABAgQIECBAgACBKiBAqqA/AQIECBAgQIAAAQK3gAC5qQwJECBAgAABAgQIEKgCAqQK+hMgQIAAAQIECBAgcAsIkJvKkAABAgQIECBAgACBKiBAqqA/AQIECBAgQIAAAQK3gAC5qQwJECBAgAABAgQIEKgCAqQK+hMgQIAAAQIECBAgcAsIkJvKkAABAgQIECBAgACBKiBAqqA/AQIECBAgQIAAAQK3gAC5qQwJECBAgAABAgQIEKgCAqQK+hMgQIAAAQIECBAgcAsIkJvKkAABAgQIECBAgACBKjBXjy7ykJetlAAAAABJRU5ErkJggg==
        <use xlink:href="#download-a"/>
      </mask>
      <use fill="#D8D8D8" fill-rule="nonzero" xlink:href="#download-a"/>
      <g fill="#FFA0A0" mask="url(#download-b)">
        <rect width="24" height="24" transform="translate(-4 -9)"/>
      </g>
    </g>
    <mask id="download-d" fill="#ffffff">
      <use xlink:href="#download-c"/>
    </mask>
    <use fill="#000000" fill-rule="nonzero" xlink:href="#download-c"/>
    <g fill="#7600FF" mask="url(#download-d)">
      <rect width="24" height="24" transform="translate(-2 -3)"/>
    </g>
  </g>
</svg>`

function svgToPng(svg, callback) {
    const url = getSvgUrl(svg);
    svgUrlToPng(url, (imgData) => {
        callback(imgData);
        URL.revokeObjectURL(url);
    });
}

function getSvgUrl(svg) {
    return URL.createObjectURL(new Blob([svg], {type: 'image/svg+xml'}));
}

function svgUrlToPng(svgUrl, callback) {
    const svgImage = document.createElement('img');
    // can't be display none, but also don't take up space
    svgImage.style.position = "fixed";
    svgImage.style.visibility = "hidden";
    document.body.appendChild(svgImage);
    svgImage.onload = function () {
        const canvas = document.createElement('canvas');
        canvas.width = svgImage.clientWidth;
        canvas.height = svgImage.clientHeight;
        const canvasCtx = canvas.getContext('2d');
        canvasCtx.drawImage(svgImage, 0, 0);
        const imgData = canvas.toDataURL('image/png');
        callback(imgData);
        // document.body.removeChild(imgPreview);
    };
    svgImage.src = svgUrl;
}


// Utility
//------------------------------

function error(str) {
    console.error(`[CSRD]: ${str}`);
}

function log(str) {
    console.log(`[CSRD]: ${str}`);
}

function info(str) {
    console.info(`[CSRD]: ${str}`);
}

function debug(str) {
    console.debug(`[CSRD]: ${str}`);
}

async function sleep(ms) {
    await new Promise(r => setTimeout(r, ms)); // give the browser a break - idle wait
}

// How to get the browser viewport dimensions?
// Thanks, ryanve
// https://stackoverflow.com/a/8876069
function viewportX() {
    return Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
}

function viewportY() {
    return Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
}

function digitCount(num) {
    return num.toString().length;
}

function dataUrlToData(base64Url) {
    return base64Url.substr(base64Url.indexOf(',') + 1);
}


// Reader Utility
//------------------------------

// positive flips forward (increase page number), negative flips backwards (decrease page number), 0 is to open menu
function flipPage(direction) {
    const screen = getCSRElement(ELEMENT.SCREEN_CONTROLLER);
    // click on the left, middle, or right of the screen depending on arg
    const x = (-0.99*direction + 1) * viewportX() / 2;
    screen.dispatchEvent(new PointerEvent("pointerdown", {buttons: 1, clientX: x, clientY: 100, bubbles: true}));
    screen.dispatchEvent(new PointerEvent("mousedown", {buttons: 1, clientX: x, clientY: 100, bubbles: true}));
    screen.dispatchEvent(new PointerEvent("pointerup", {buttons: 0, clientX: x, clientY: 100, bubbles: true}));
    screen.dispatchEvent(new PointerEvent("mouseup", {buttons: 0, clientX: x, clientY: 100, bubbles: true}));
    screen.dispatchEvent(new PointerEvent("click", {buttons: 0, clientX: x, clientY: 100, bubbles: true}));
}

async function waitForPageLoad() {
    if (isLoadingPage() === undefined) {
        await sleep(1000); // with no loader element, we have no idea if the page is loaded, so just wait a second
        return;
    }
    while (isLoadingPage()) {
        await sleep(100);
    }
}

function currentPage() {
    return +getCSRElement(ELEMENT.CURRENT_PAGE_COUNTER).textContent;
}

function totalPageCount() {
    if (options().pageCounterPercentage) return Infinity; // we can't know the total page count if it's a percentage
    return +getCSRElement(ELEMENT.TOTAL_PAGE_COUNTER).textContent;
}

function isLoadingPage() {
    return getCSRElement(ELEMENT.LOADER_SPINNER)?.classList.contains("onstage");
}

function isMenuOpen() {
    return getCSRElement(ELEMENT.MENU).style.display !== "none" && getCSRElement(ELEMENT.MENU).classList.contains("onstage");
}

function isColophonOpen() {
    return getCSRElement(ELEMENT.COLOPHON).style.display !== "none" && getCSRElement(ELEMENT.COLOPHON).classList.contains("onstage");
}

async function flipToFirstPage() {
    const slider = getCSRElement(ELEMENT.PAGE_SLIDER);
    if (slider === undefined && currentPage() > 1) {
        throw Error("This reader's automatic page slider is not supported, please move to page 1 manually and click the download button again");
    }
    if (currentPage() <= 1) {
        info(`already on first page`);
        return;
    }

    info(`flipping to first page`);

    if (!isMenuOpen()) {
        debug("opening menu to load scroller");
        flipPage(0); // open menu
        await sleep(200);
    }

    // click the very right of the page slider - page 1
    slider.dispatchEvent(new PointerEvent("pointerdown", {
        buttons: 1,
        clientX: viewportX() - 25,
        clientY: viewportY() - 70,
        bubbles: true
    }));
    slider.dispatchEvent(new PointerEvent("mousedown", {
        buttons: 1,
        clientX: viewportX() - 25,
        clientY: viewportY() - 70,
        bubbles: true
    }));
    slider.dispatchEvent(new PointerEvent("pointerup", {
        buttons: 0,
        clientX: viewportX() - 25,
        clientY: viewportY() - 70,
        bubbles: true
    }));
        slider.dispatchEvent(new PointerEvent("mouseup", {
        buttons: 0,
        clientX: viewportX() - 25,
        clientY: viewportY() - 70,
        bubbles: true
    }));
        slider.dispatchEvent(new PointerEvent("click", {
        buttons: 0,
        clientX: viewportX() - 25,
        clientY: viewportY() - 70,
        bubbles: true
    }));
    await sleep(100);
    await waitForPageLoad();
}


// Main
//------------------------------

const MAX_ZERO_PADS = 4; // 9999 pages should be enough for anyone

async function generatePageByPageZip() {
    const jsZip = new JSZip();

    let totalPages = totalPageCount();
    if (totalPages === 0) {
        throw Error("The total number of pages reported by the reader is 0, the page slider seems to have been opened incorrectly");
    }

    info(`there are ${totalPages === Infinity? 'an unknown number of' : totalPages} pages in total to save`);
    if (totalPages === Infinity) {
        addWarningMessage("the reader is in percentage mode, so the script cannot reliably track progress nor the total page count. Please check the zip after download");
    }
    const zeroPads = totalPages === Infinity? MAX_ZERO_PADS : digitCount(totalPages);

    let pageNumber = 1;
    let lastReportedReaderPageNumber; // this helps distinguish a 1 page spread on 2 canvases, vs the reader always rendering 1 page only because the viewport is too narrow
    // any time the spread is not 2 canvases, this will no longer match pageNumber. But that is ok, we prioritize continuity of pageNumber instead (e.g. page 01, 02, 04 is bad!)
    let expectedPagesInSpread = 2; // a 2 page spread is normal. But we have to handle when the viewport is narrow enough that only 1 is shown

    log("==== downloading pages: ====");

    if (isLoadingPage() === undefined) {
        addWarningMessage("the reader does not have a reliable loading spinner, so this script cannot detect when a page is fully loaded. It will wait 1 second per page instead, which may be too long or too short");
    }

    while (true) {
        const element_spread = getCSRElement(ELEMENT.PAGE_SPREAD);
        // Right to left means reverse the array
        for (const canvas of [...element_spread.children].toReversed()) {
            if (canvas.style.visibility === "hidden" || canvas.style.display === "none" || canvas.style.position === "absolute") {
                debug(`page before ${pageNumber} is a hidden or junk page, skipping it`);
                //possibly the unseen half of a 1 page spread on 2 canvases. Need to check after this canvas spread is completed and the page is flipped, what to do
                expectedPagesInSpread--;
                continue;
            }

            info(`saving page ${pageNumber}`);
            jsZip.file(`${pageNumber.toString().padStart(zeroPads, "0")}.png`, dataUrlToData(canvas.toDataURL()), {base64: true});
            pageNumber++;
        }

        if (totalPages === Infinity && isColophonOpen()) {
            break;
        }
        if (pageNumber > totalPages) {
            break;
        }

        // Need to check after this canvas spread is completed and the page is flipped, what to do
        lastReportedReaderPageNumber = currentPage();
        await sleep(100);
        flipPage(1);
        await sleep(100);
        await waitForPageLoad();

        debug(`currentPage() - lastReportedReaderPageNumber = ${currentPage()} - ${lastReportedReaderPageNumber}`);
        if (currentPage() - lastReportedReaderPageNumber > expectedPagesInSpread) {
            // there are less pages than we thought! - the reader reports that currentPage() - lastDownloadedPageNumber passed (usually 2)
            // but we only saw expectedPages and incremented pageNumber by that much (usually 1 when this branch is entered)! decrement totalPages so we don't run over the totalPages with pageNumber
            totalPages -= (currentPage() - lastReportedReaderPageNumber) - expectedPagesInSpread;
            debug(`cutting ${(currentPage() - lastReportedReaderPageNumber) - expectedPagesInSpread} from total pages, now ${totalPages}`)
        }
        expectedPagesInSpread = 2; // reset this

        await sleep(100);
    }

    return jsZip;
}

async function generateDirectZip() {
    const jsZip = new JSZip();
    const pageHrefs = getCSRPageHrefs();
    const totalPages = pageHrefs.length;
    info(`there are ${totalPages} pages in total to save`);
    const zeroPads = digitCount(totalPages);
    log("==== downloading pages: ====");

    for (const [i, href] of pageHrefs.entries()) {
        jsZip.file(`${i.toString().padStart(zeroPads, "0")}.png`, await (await fetch(href)).blob());
    }

    return jsZip;
}

function addWarningMessage(message) {
    if (document.getElementById(warningMessagesId) === null) {
        const container = document.createElement("div");
        document.body.appendChild(container).id = warningMessagesId;
        container.style["position"] = "fixed";
        container.style["z-index"] = 900;
        container.style["bottom"] = "250px";
        container.style["right"] = "32px";
        container.style["display"] = "flex";
        container.style["flex-direction"] = "column";
        container.style["align-items"] = "flex-end";
    }
    const container = document.getElementById(warningMessagesId);

    const div = document.createElement("div");
    div.style["position"] = "relative";
    div.style["background"] = "black";
    div.style["border"] = "3px solid purple";
    div.style["padding"] = "8px";
    div.style["color"] = "yellow";
    container.appendChild(div);
    const text = document.createTextNode(message);
    div.appendChild(text);
}

function injectErrorMessage(message) {
    if (document.getElementById(errorMessageId) !== null) {
        document.getElementById(errorMessageId).remove();
    }

    const div = document.createElement("div");
    div.id = errorMessageId;
    div.style["position"] = "fixed";
    div.style["z-index"] = 901;
    div.style["bottom"] = "150px";
    div.style["right"] = "32px";
    div.style["background"] = "black";
    div.style["border"] = "3px solid purple";
    div.style["padding"] = "8px";
    div.style["color"] = "red";
    document.body.appendChild(div);
    const text = document.createTextNode(message);
    div.appendChild(text);
}

async function downloadBookAsZip() {
    let jsZip;
    try {
        switch (downloadMode()) {
            case DOWNLOAD_MODE.PAGE_BY_PAGE:
                if (!isMenuOpen()) {
                    info("opening menu to load scroller");
                    flipPage(0); // open menu
                    await sleep(200);
                }
                await flipToFirstPage();

                jsZip = await generatePageByPageZip();
                break;
            case DOWNLOAD_MODE.DIRECT:
                jsZip = await generateDirectZip();
                break;
        }
    } catch (exception) {
        injectErrorMessage(exception.message);
        throw exception;
    }

    log("generating zip file, rename it however you like - this script cannot figure out the book's name");
    await jsZip.generateAsync({type: "blob"}).then(blob => saveAs(blob, "Clip_Studio_Reader_Downloader_RENAME_ME.zip"));
    log("==== all done! Enjoy your book ^_^ ====");

}

function injectDownloadButton() {
    log("reader is loaded, download button injected");
    const parent = document.body;
    const div = document.createElement("div");
    parent.appendChild(div);
    div.id = downloadButtonId;
    // not all sites use tailwind
    div.style["position"] = "fixed";
    div.style["border-radius"] = "50%";
    div.style["z-index"] = 900;
    div.style["bottom"] = "32px";
    div.style["right"] = "32px";
    div.style["background"] = "black";
    div.style["border"] = "3px solid purple";
    div.style["padding"] = "8px";
    div.style["cursor"] = "pointer";
    div.addEventListener("pointerdown", downloadBookAsZip);
    svgToPng(downloadSvg, (imgData) => {
        const image = document.createElement('img');
        image.style.height = "64px";
        div.appendChild(image);
        image.src = imgData;
    });
}

function checkReaderLoad(observer, timeoutId) {
    if (currentPage() !== 0 || totalPageCount() === Infinity) { // if totalPageCount is infinity (likely percentage mode), then current page != 0 can't be used to check if the reader is loaded
        observer.disconnect();
        // stop the 30 second timeout
        clearTimeout(timeoutId);
        injectDownloadButton();
    }
}

// Userscript to wait for page to load before executing code techniques?
// Thanks, goweon
// https://stackoverflow.com/a/47406751
function checkPageLoad(observer) {
    if (getCSRElement(ELEMENT.PAGE_SPREAD)) {
        observer.disconnect();
        log("==== Clip Studio Reader Downloader ====");
        log("https://github.com/MrCocoNuat/clip-studio-reader-downloader");
        log("waiting up to 30 seconds for reader to load");

        switch (downloadMode()) {
            case DOWNLOAD_MODE.PAGE_BY_PAGE:
                let readerObserver;
                const timeoutId = setTimeout(async () => {
                    error("ERR: reader load timeout. the reader seems to have started incorrectly or the reader may have taken too long to load - do you need to reopen the book?");
                    readerObserver.disconnect();
                }, 30000);
                readerObserver = new MutationObserver((changes, innerObserver) => checkReaderLoad(innerObserver, timeoutId));
                readerObserver.observe(getCSRElement(ELEMENT.CURRENT_PAGE_COUNTER), {childList: true, subtree: true});
                break;
            case DOWNLOAD_MODE.DIRECT:
                injectDownloadButton(); // not much to wait for
                break;
        }
    }
}


function init() {
    if (siteIsSupported()) {
        if (options().poorSupport){
            addWarningMessage("the current reader site is poorly supported, some features may not work reliably. Feel free to open an issue on GitHub if you encounter any problems outside of the known warnings");
        }
        (new MutationObserver((changes, observer) => checkPageLoad(observer))).observe(document, {
            childList: true,
            subtree: true
        });
    } else {
        log("No instance of Clip Studio Reader found on the current page");
    }
}