// ==UserScript== // @name KissGrabber // @namespace thorou // @version 2.4.1 // @description extracts embed links from kiss sites // @author Thorou // @license GPLv3 - http://www.gnu.org/licenses/gpl-3.0.txt // @copyright 2019 Leon Timm // @homepageURL https://github.com/thorio/KGrabber/ // @match https://kissanime.ru/* // @match https://kimcartoon.to/* // @match https://kissasian.sh/* // @run-at document-end // @noframes // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @connect rapidvideo.com // @connect googleusercontent.com // @connect googlevideo.com // @downloadURL none // ==/UserScript== if (!unsafeWindow.jQuery) { console.error("KG: jQuery not present"); return; } unsafeWindow.KG = {}; KG.knownServers = { "rapidvideo": { regex: '"https://www.rapidvideo.com/e/.*?"', name: "RapidVideo (no captcha)", linkType: "embed", customStep: "turboBegin", }, "nova": { regex: '"https://www.novelplanet.me/v/.*?"', name: "Nova Server", linkType: "embed", }, "beta2": { regex: '"https://lh3.googleusercontent.com/.*?"', name: "Beta2 Server", linkType: "direct", }, "p2p": { regex: '"https://p2p2.replay.watch/public/dist/index.html\\\\?id=.*?"', name: "P2P Server", linkType: "embed", }, "openload": { regex: '"https://openload.co/embed/.*?"', name: "Openload", linkType: "embed", }, "mp4upload": { regex: '"https://www.mp4upload.com/embed-.*?"', name: "Mp4Upload", linkType: "embed", }, "streamango": { regex: '"https://streamango.com/embed/.*?"', name: "Streamango", linkType: "embed", }, "beta": { regex: '"https://lh3.googleusercontent.com/.*?"', name: "Beta Server", linkType: "direct", }, } KG.serverOverrides = { "kissanime.ru": {}, "kimcartoon.to": { "rapidvideo": null, "p2p": null, "beta2": null, "nova": null, "mp4upload": null, "rapid": { regex: '"https://www.rapidvideo.com/e/.*?"', name: "RapidVideo", linkType: "embed", experimentalCustomStep: "turboBegin", }, "fs": { regex: '"https://video.xx.fbcdn.net/v/.*?"', name: "FS (fbcdn.net)", linkType: "direct", experimentalCustomStep: "turboBegin", }, "gp": { regex: '"https://lh3.googleusercontent.com/.*?"', name: "GP (googleusercontent.com)", linkType: "direct", experimentalCustomStep: "turboBegin", }, "fe": { regex: '"https://www.luxubu.review/v/.*?"', name: "FE (luxubu.review)", linkType: "embed", experimentalCustomStep: "turboBegin", }, }, "kissasian.sh": { "rapidvideo": null, "p2p": null, "beta2": null, "nova": null, "mp4upload": null, "streamango": null, "beta": null, //should work, but script can't load data because of https/http session storage separation "rapid": { regex: '"https://www.rapidvideo.com/e/.*?"', name: "RapidVideo", linkType: "embed", experimentalCustomStep: "turboBegin", }, "fe": { regex: '"https://www.gaobook.review/v/.*?"', name: "FE (gaobook.review)", linkType: "embed", experimentalCustomStep: "turboBegin", }, "mp": { regex: '"https://www.mp4upload.com/embed-.*?"', name: "MP (mp4upload.com)", linkType: "embed", experimentalCustomStep: "turboBegin", }, }, } KG.supportedSites = { "kissanime.ru": { contentPath: "/Anime/*", noCaptchaServer: "rapidvideo", buttonColor: "#548602", buttonTextColor: "#fff", }, "kimcartoon.to": { contentPath: "/Cartoon/*", noCaptchaServer: "rapid", buttonColor: "#ecc835", buttonTextColor: "#000", optsPosition: 1, fixes: ["kimcartoon.to_UIFix"], }, "kissasian.sh": { contentPath: "/Drama/*", noCaptchaServer: "rapid", buttonColor: "#F5B54B", buttonTextColor: "#000", fixes: ["kissasian.sh_UIFix"], }, } KG.preferences = { general: { quality_order: "1080, 720, 480, 360", enable_automatic_actions: true, }, internet_download_manager: { idm_path: "C:\\Program Files (x86)\\Internet Download Manager\\IDMan.exe", arguments: "", }, compatibility: { force_default_grabber: false, enable_experimental_grabbers: false, }, } //entry function KG.siteLoad = () => { if (!KG.supportedSites[location.hostname]) { console.warn("KG: site not supported"); return; } KG.applySiteOverrides(); if (KG.if(location.pathname, KG.supportedSites[location.hostname].contentPath) && $(".bigBarContainer .bigChar").length != 0) { KG.injectWidgets(); } KG.loadPreferences(); if (KG.loadStatus()) { KG.steps[KG.status.func](); } } //saves data to session storage KG.saveStatus = () => { sessionStorage["KG-status"] = JSON.stringify(KG.status); } //attempts to load data from session storage KG.loadStatus = () => { if (!sessionStorage["KG-status"]) { return false; } try { KG.status = JSON.parse(sessionStorage["KG-status"]); } catch (e) { console.error("KG: unable to parse JSON"); return false; } return true; } //clears data from session storage KG.clearStatus = () => { sessionStorage.clear("KG-data"); } KG.loadPreferences = () => { try { var prefs = JSON.parse(GM_getValue("KG-preferences", "")); for (var i in prefs) { //load values while not removing new defaults if (KG.preferences[i] != undefined) { for (var j in prefs[i]) { if (KG.preferences[i][j] != undefined) { KG.preferences[i][j] = prefs[i][j]; } } } } } catch (e) { //no preferences saved, using defaults } if ($("#KG-preferences").length == 0) { return; } for (var i in KG.preferences) { var group = KG.preferences[i]; var $group = $(`
`); for (var j in KG.preferences[i]) { var html = ""; switch (typeof group[j]) { case "string": html = `
${j.replace(/_/g, " ")}:
`; break; case "boolean": html = `
${j.replace(/_/g, " ")}:
`; break; case "number": html = `
${j.replace(/_/g, " ")}:
`; break; default: console.error(`unknown type "${typeof group[j]}" of KG.preferences.${i}.${j}`); } $group.append(html); } var headerTitle = i.replace(/_/g, " ").replace(/[a-z]+/g, (s) => {return s.charAt(0).toUpperCase() + s.slice(1)}); $("#KG-preferences-container-outer").append(`
${headerTitle}
`) .append($group); } } KG.savePreferences = () => { $("#KG-preferences-container input").each((i, obj) => { var ids = obj.id.slice(14).match(/[^-]+/g); var value; switch (obj.type) { case "checkbox": value = obj.checked; break; default: value = obj.value; break; } KG.preferences[ids[0]][ids[1]] = value; }); GM_setValue("KG-preferences", JSON.stringify(KG.preferences)); } KG.resetPreferences = () => { GM_setValue("KG-preferences", ""); location.reload(); } //patches the knownServers object based on the current url KG.applySiteOverrides = () => { var over = KG.serverOverrides[location.hostname] for (var i in over) { if (KG.knownServers[i]) { if (over[i] === null) { //server should be removed delete KG.knownServers[i]; } else { //server should be patched console.err("KG: patching server entries not implemented"); } } else { //server should be added KG.knownServers[i] = over[i]; } } } //injects element into page KG.injectWidgets = () => { var site = KG.supportedSites[location.hostname]; var epCount = $(".listing a").length; //css $(document.head).append(``); //KissGrabber Box $(`#rightside .clear2:eq(${site.optsPosition || 0})`).after(optsHTML); $("#KG-input-to").val(epCount) .attr("max", epCount); $("#KG-input-from").attr("max", epCount); for (var i in KG.knownServers) { $(``); for (var i in KG.exporters) { var $exporter = $(``).appendTo("#KG-input-export"); if ((KG.exporters[i].requireSamePage && !onSamePage) || (KG.exporters[i].requireDirectLinks && KG.status.linkType != "direct") ) { $exporter.attr("disabled", true); } } //actions $("#KG-action-container .KG-button").remove(); for (var i in KG.actions) { if ( (!KG.actions[i].requireLinkType || KG.status.linkType == KG.actions[i].requireLinkType) && KG.actions[i].servers.includes(KG.status.server) ) { if (KG.actions[i].automatic && KG.preferences.general.enable_automatic_actions && !KG.status.automaticDone) { KG.status.automaticDone = true; KG.actions[i].execute(KG.status); } if (KG.status.automaticDone) { continue; } $("#KG-action-container") .append(``); } } //colors again KG.applyColors(); $("#KG-linkdisplay").show(); } //invokes a exporter KG.exportData = (exporter) => { $("#KG-input-export").val(""); var text = KG.exporters[exporter].export(KG.status); $("#KG-linkdisplay-export-text").text(text); $("#KG-input-export-download").attr({ href: `data:text/plain;charset=utf-8,${encodeURIComponent(text)}`, download: `${KG.status.title}.${KG.exporters[exporter].extension}`, }) $("#KG-linkdisplay-export").show(); } KG.showSpinner = () => { $("#KG-linkdisplay-text").html(`
Loading...
`); } KG.spinnerText = (str) => { $("#KG-spinner-text").text(str); } //hides the linkdisplay KG.closeLinkdisplay = () => { $("#KG-linkdisplay").slideUp(); KG.clearStatus(); } //saves a new preferred server KG.updatePreferredServer = () => { localStorage["KG-preferredServer"] = $("#KG-input-server").val(); } //loads preferred server KG.loadPreferredServer = () => { $("#KG-input-server").val(localStorage["KG-preferredServer"]); } KG.showPreferences = () => { $("#KG-preferences").slideDown(); } KG.closePreferences = () => { KG.savePreferences(); $("#KG-preferences").slideUp(); } //applies regex to html page to find a link KG.findLink = (html, regexString) => { var re = new RegExp(regexString); var result = html.match(re); if (result && result.length > 0) { return result[0].split('"')[1]; } return ""; } //wildcard-enabled string comparison KG.if = (str, rule) => { return new RegExp("^" + rule.split("*").join(".*") + "$").test(str); } //iterates over an array with supplied function //either (array, min, max, func) //or (array, func) KG.for = (array, min, max, func) => { if (typeof min == "function") { func = min; max = array.length - 1; } min = Math.max(0, min) || 0; max = Math.min(array.length - 1, max); for (var i = min; i <= max; i++) { func(i, array[i]); } } KG.get = (url) => { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: url, onload: (o) => { resolve(o.response); }, onerror: () => { reject(); } }); }); } KG.head = (url) => { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "HEAD", url: url, onload: (o) => { resolve(o.status); }, onerror: () => { reject(); } }); }); } //allows multiple different approaches to collecting links, if sites differ greatly KG.steps = {}; //default KG.steps.defaultBegin = () => { KG.status.func = "defaultGetLink"; KG.saveStatus(); location.href = KG.status.episodes[KG.status.current].kissLink + `&s=${KG.status.server}`; } KG.steps.defaultGetLink = () => { if (!KG.if(location.pathname, KG.supportedSites[location.hostname].contentPath)) { //captcha return; } link = KG.findLink(document.body.innerHTML, KG.knownServers[KG.status.server].regex); KG.status.episodes[KG.status.current].grabLink = link || "error (selected server may not be available)"; KG.status.current++; if (KG.status.current >= KG.status.episodes.length) { KG.status.func = "defaultFinished"; location.href = KG.status.url; } else { location.href = KG.status.episodes[KG.status.current].kissLink + `&s=${KG.status.server}`; } KG.saveStatus(); } KG.steps.defaultFinished = () => { KG.displayLinks(); } KG.steps.turboBegin = async () => { $("#KG-linkdisplay").slideDown(); KG.showSpinner(); var progress = 0; var func = async (ep) => { var html = await KG.get(ep.kissLink + `&s=${KG.status.server}`); var link = KG.findLink(html, KG.knownServers[KG.status.server].regex); ep.grabLink = link || "error: server not available or captcha"; progress++; KG.spinnerText(`${progress}/${promises.length}`) }; var promises = []; KG.for(KG.status.episodes, (i, obj) => { promises.push(func(obj)); }); KG.spinnerText(`0/${promises.length}`) await Promise.all(promises); KG.status.func = "defaultFinished"; KG.saveStatus(); KG.displayLinks(); } //allows for multiple ways to export collected data KG.exporters = {}; KG.exporters.list = { name: "list", extension: "txt", requireSamePage: false, requireDirectLinks: false, export: (data) => { var str = ""; for (var i in data.episodes) { str += data.episodes[i].grabLink + "\n"; } return str; } } KG.exporters.m3u = { name: "m3u8 playlist", extension: "m3u8", requireSamePage: true, requireDirectLinks: true, export: (data) => { var listing = $(".listing a").get().reverse(); var str = "#EXTM3U\n"; KG.for(data.episodes, (i, obj) => { str += `#EXTINF:0,${listing[obj.num-1].innerText}\n${obj.grabLink}\n`; }); return str; } } KG.exporters.json = { name: "json", extension: "json", requireSamePage: true, requireDirectLinks: false, export: (data) => { var listing = $(".listing a").get().reverse(); var json = { title: data.title, server: data.server, linkType: data.linkType, episodes: [] }; for (var i in data.episodes) { json.episodes.push({ number: data.episodes[i].num, name: listing[data.episodes[i].num - 1].innerText, link: data.episodes[i].grabLink }); } return JSON.stringify(json); }, } KG.exporters.html = { name: "html list", extension: "html", requireSamePage: true, requireDirectLinks: true, export: (data) => { var listing = $(".listing a").get().reverse(); var str = "\n \n"; KG.for(data.episodes, (i, obj) => { str += ` ${listing[obj.num-1].innerText}
\n`; }); str += " \n\n"; return str; } } KG.exporters.csv = { name: "csv", extension: "csv", requireSamePage: true, requireDirectLinks: false, export: (data) => { var listing = $(".listing a").get().reverse(); var str = "episode, name, url\n"; for (var i in data.episodes) { str += `${data.episodes[i].num}, ${listing[data.episodes[i].num-1].innerText}, ${data.episodes[i].grabLink}\n`; } return str; } } KG.exporters.aria2c = { name: "aria2c file", extension: "txt", requireSamePage: false, requireDirectLinks: true, export: (data) => { var listing = $(".listing a").get().reverse(); var str = ""; KG.for(data.episodes, (i, obj) => { str += `${obj.grabLink}\n out=${listing[obj.num-1].innerText}.mp4\n`; }); return str; } } KG.exporters.idmbat = { name: "IDM bat file", extension: "bat", requireSamePage: true, requireDirectLinks: true, export: (data) => { var listing = $(".listing a").get().reverse(); var str = `::download and double click me! @echo off set title=${data.title} set idm=${KG.preferences.internet_download_manager.idm_path} set args=${KG.preferences.internet_download_manager.arguments} set path=%~dp0 if not exist "%idm%" echo IDM not found && echo check your IDM path in preferences && goto end mkdir "%title%" > nul\n\n`; KG.for(data.episodes, (i, obj) => { str += `"%idm%" /n /p "%path%\\%title%" /f "${listing[obj.num-1].innerText}.mp4" /d "${obj.grabLink}" %args%\n`; }); str += "\n:end\necho done.\necho.\npause"; return str; } } //further options after grabbing, such as converting embed to direct links KG.actions = {}; KG.actionAux = {}; KG.actions.rapidvideo_getDirect = { name: "get direct links", requireLinkType: "embed", servers: ["rapidvideo", "rapid"], execute: async (data) => { KG.showSpinner(); var promises = []; var progress = [0]; for (var i in data.episodes) { promises.push(KG.actionAux["rapidvideo_getDirect"](data.episodes[i], progress, promises)); } KG.spinnerText(`0/${promises.length}`); await Promise.all(promises); data.linkType = "direct"; KG.saveStatus(); KG.displayLinks(); }, } //additional function to reduce clutter //asynchronously gets the direct link KG.actionAux.rapidvideo_getDirect = async (ep, progress, promises) => { $html = $(await KG.get(ep.grabLink)); $sources = $html.find("source"); if ($sources.length == 0) { ep.grabLink = "error: no sources found"; return; } var sources = {}; KG.for($sources, (i, obj) => { sources[obj.dataset.res] = obj.src; }); progress[0]++; KG.spinnerText(`${progress[0]}/${promises.length}`); var parsedQualityPrefs = KG.preferences.general.quality_order.replace(/\ /g, "").split(","); for (var i of parsedQualityPrefs) { if (sources[i]) { ep.grabLink = sources[i]; return; } } ep.grabLink = "error: preferred qualities not found"; } KG.actions.beta_setQuality = { name: "set quality", requireLinkType: "direct", servers: ["beta", "beta2"], automatic: true, execute: async (data) => { KG.showSpinner(); var promises = []; var progress = [0]; for (var i in data.episodes) { promises.push(KG.actionAux["beta_tryGetQuality"](data.episodes[i], progress, promises)); } KG.spinnerText(`0/${promises.length}`); await Promise.all(promises); data.automaticDone = true; KG.saveStatus(); KG.displayLinks(); }, } KG.actionAux.beta_tryGetQuality = async (ep, progress, promises) => { var rawLink = ep.grabLink.slice(0, -4); var qualityStrings = {"1080": "=m37", "720": "=m22", "360": "=m18"}; var parsedQualityPrefs = KG.preferences.general.quality_order.replace(/\ /g, "").split(","); for (var i of parsedQualityPrefs) { if (qualityStrings[i]) { if (await KG.head(rawLink + qualityStrings[i]) == 200) { ep.grabLink = rawLink + qualityStrings[i]; progress[0]++; KG.spinnerText(`${progress[0]}/${promises.length}`); return; } } } } //if something doesn't look right on a specific site, a fix can be written here KG.fixes = {} KG.fixes["kimcartoon.to_UIFix"] = () => { //linkdisplay var $ld = $("#KG-linkdisplay"); $ld.find(".barTitle").removeClass("barTitle") .css({ "height": "20px", "padding": "5px", }); $("#KG-linkdisplay-title").css({ "font-size": "20px", "color": $("a.bigChar").css("color"), }) $ld.find(".arrow-general").remove(); //preference panel var $pf = $("#KG-preferences"); $pf.find(".barTitle").removeClass("barTitle") .css({ "height": "20px", "padding": "5px", }); $("#KG-linkdisplay-title").css({ "font-size": "20px", "color": $("a.bigChar").css("color"), }); $pf.find(".arrow-general").remove(); //opts var $opts = $("#KG-opts-widget"); var title = $opts.find(".barTitle").html(); $opts.before(`
${title}
`); $(".icon:eq(1)").css({ "width": "100%", "box-sizing": "border-box" }); $(".KG-preferences-button").css("margin-top", "5px"); $opts.find(".barTitle").remove(); $opts.find(".arrow-general").remove(); //general $(".KG-dialog-title").css("font-size", "18px"); } KG.fixes["kissasian.sh_UIFix"] = () => { $(".KG-preferences-button").css("filter", "invert(0.7)"); $(".KG-dialog-close").css("color", "#000"); $(".KG-dialog-close").hover((e) => { $(e.target).css("color", e.type == "mouseenter" ? "#fff" : "#000"); }); } //HTML and CSS pasted here because Tampermonkey apparently doesn't allow resources to be updated //if you have a solution for including extra files that are updated when the script is reinstalled please let me know: thorio.git@gmail.com //the grabber widget injected into the page var optsHTML = `
KissGrabber
 

from to

`; //initially hidden HTML that is revealed and filled in by the grabber script var linkListHTML = ``; //initially hidden HTML that is revealed and filled in by the grabber script var prefsHTML = ``; //css to make it all look good var grabberCSS = `.KG-episodelist-header { width: 3%; text-align: center !important; } .KG-episodelist-number { text-align: right; padding-right: 4px; } .KG-episodelist-button { background-color: #527701; color: #ffffff; border: none; cursor: pointer; } .KG-input-episode { width: 40px; border: 1px solid #666666; background: #393939; padding: 3px; color: #ffffff; } .KG-input-text { width: 150px; border: 1px solid #666666; background: #393939; padding: 3px; margin-left: 5px; color: #ffffff; } .KG-input-checkbox { height: 22px; } #KG-input-server { width: 100%; font-size: 14.5px; color: #fff; } #KG-input-export { margin: 6px; float: left; color: #fff; } .KG-button { background-color: #548602; color: #ffffff; border: none; padding: 5px; padding-left: 12px; padding-right: 12px; font-size: 15px; margin: 3px; float: left; } .KG-button-container { margin-top: 10px; height: 34px; } .KG-dialog-title { width: 80%; float: left; } #KG-linkdisplay-text { word-break: break-all; } .KG-linkdisplay-row { display: flex; flex-direction: row; } .KG-linkdisplay-episodenumber { min-width: 30px; text-align: right; user-select: none; margin-right: 5px; } #KG-linkdisplay-export { margin-top: 10px; } #KG-linkdisplay-export-text { width: 100%; height: 150px; min-height: 40px; resize: vertical; background-color: #222; color: #fff; border: none; } .KG-dialog-close { float: right; cursor: pointer; font-size: 17px; } .KG-dialog-close:hover { color: #eee; } #KG-preferences-container-outer { overflow: auto; } .KG-preferences-header { font-size: 17px; letter-spacing: 0px; width: 100%; margin: 10px 0 5px 0; } #KG-preferences-container { overflow: auto; } #KG-preferences-container div { box-sizing: border-box; height: 26px; width: 50%; padding: 0 5px; margin: 2px 0; float: left; line-height: 26px; font-size: 14px; } #KG-preferences-container div span { padding-top: 5px; } .KG-preferences-button { width: 18px; height: 18px; margin: 3px; float: right; border: none; background-color: #0000; opacity: 0.7; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAC4jAAAuIwF4pT92AAAFHGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTA1LTMxVDE0OjQ5OjI5KzAyOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wNS0zMVQxNToxMzozNCswMjowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wNS0zMVQxNToxMzozNCswMjowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpkYTg5NGI2Mi1lOWEwLTg2NGYtYTg0Mi1lM2JkOTY3ZWI4ZTgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ZGE4OTRiNjItZTlhMC04NjRmLWE4NDItZTNiZDk2N2ViOGU4IiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6ZGE4OTRiNjItZTlhMC04NjRmLWE4NDItZTNiZDk2N2ViOGU4Ij4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpkYTg5NGI2Mi1lOWEwLTg2NGYtYTg0Mi1lM2JkOTY3ZWI4ZTgiIHN0RXZ0OndoZW49IjIwMTktMDUtMzFUMTQ6NDk6MjkrMDI6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE4IChXaW5kb3dzKSIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7gRsl1AAAB4klEQVRIia2WO2tUQRiGn91EIUJQCzGFREwX0MUbKBaBSBRNYSGCLFY2goXgpVH8KbYighArsRDWSrAJBizSJBgtRFQMJlrENXkszi7OTuacHSUvDJzzXd75LjPfOahkrAl1WV0J1uUc3zp5OA3sAoaDNZnjmNqgCcwAewLZjoTd7uB5CHgK3NtkFaXU9C++qcfUo+qCm/FZnVIb6odAfjvkDMmvJkj+Fze7vDW1m8wb4HBOXTNRBwx7cAH4sUXk54Ei8qgHp9R2Rerr6lqf8ty34piuALVERKvADWC0sy4B8yXRfwlfauo4MAE0gIvASIL8CLAYyQeA18DxRJAPgFlgjoyUr1l+Uw/18W3X1I2SsnRxAFiq0H8C9pboNupAu8IZYL2Pvsp/rQ5s70NwokK3H9hXoR+qqVPAGYpSnAV2RkbvgYOk78gzYDqS/QZawDvgVdy0k+qvRLMW1XPqYMeuob4oaeytkHMw2n2hE8G2SD4GPAc+dvSjFWX52vMW7Dagvu1z7HKwqo6bmKYPt4C8i7ZFwD2jolWR9r/iCSXD7koUyR2Lb+9SIsqf6nV1Uv0eyB+HnKnr31Tn1elAdjexQSvQj6hz6qOYL+ePAot5FGMmxzc+pmWYBZahx/5ljuMfM3Ph5QSIQroAAAAASUVORK5CYII="); background-size: cover; cursor: pointer; } .KG-preferences-button:hover { opacity: 1; } .right { float: right; } #KG-spinner-text { width: 100%; text-align: center; margin-top: -40px; margin-bottom: 40px; min-height: 20px; } /* https://projects.lukehaas.me/css-loaders/ */ .loader, .loader:after { border-radius: 50%; width: 10em; height: 10em; } .loader { margin: 0px auto; font-size: 5px; position: relative; text-indent: -9999em; border-top: 1.1em solid rgba(255, 255, 255, 0.2); border-right: 1.1em solid rgba(255, 255, 255, 0.2); border-bottom: 1.1em solid rgba(255, 255, 255, 0.2); border-left: 1.1em solid #ffffff; -webkit-transform: translateZ(0); -ms-transform: translateZ(0); transform: translateZ(0); -webkit-animation: load8 1.1s infinite linear; animation: load8 1.1s infinite linear; } @-webkit-keyframes load8 { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } @keyframes load8 { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } }`; KG.siteLoad();