您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Improves the levels overview popup with progress indications
// ==UserScript== // @name Wanikani Levels Overview Plus // @namespace Mercieral // @description Improves the levels overview popup with progress indications // @match https://www.wanikani.com/* // @match https://preview.wanikani.com/* // @version 2.2.4 // @run-at document-end // @grant none // ==/UserScript== (function() { // Require the WK open resource if (!window.wkof) { alert('"Wanikani Levels Overview Plus" script requires Wanikani Open Framework.\nYou will now be forwarded to installation instructions.'); window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549'; return; } // Initialize the level stage data array const levelCounts = []; for (let i = 0; i < 61; i++) { levelCounts.push({ locked: 0, unlocked: 0, apprentice: 0, guru: 0, master: 0, enlighten: 0, burn: 0, nextReviewItem: null, longestReviewItem: null, }); } window.wkof.include('ItemData,Settings'); window.wkof.ready('document,ItemData,Settings') .then(load_settings) .then(initCSS) .then(sortData) .then(finishUI) .catch(loadError); /** * Loads the saved settings for this script */ function load_settings () { return window.wkof.Settings.load('levelOverviewPlus', { showNextReview: true }) } /** * Parse the data once the document and WKOF data is ready */ function sortData () { if (window.wkof.ItemData == null) { return Promise.reject(); } const config = { wk_items: { options: {assignments: true} } }; return window.wkof.ItemData.get_items(config).then(function (processItems) { if (processItems == null) { return Promise.reject(); } for (let i = 0, itemsLen = processItems.length; i < itemsLen; i++) { const item = processItems[i]; if (item != null) { const srsLevel = item.assignments && item.assignments.srs_stage; const unlockedDate = item.assignments && item.assignments.unlocked_at; const level = item.data && item.data.level; const levelStageCounts = levelCounts[level]; if (levelStageCounts == null) { // skip this item... continue; } // Increment the appropriate level's stage counter for this item if (srsLevel == null || srsLevel === 0) { // Checking for unlocked date allows "unlocked but unstarted" items to be considered separately if needed if (unlockedDate == null) { levelStageCounts.locked++; } else { levelStageCounts.unlocked++; } } else if (srsLevel < 5) { levelStageCounts.apprentice++; } else if (srsLevel < 7) { levelStageCounts.guru++; } else if (srsLevel === 7) { levelStageCounts.master++; } else if (srsLevel === 8) { levelStageCounts.enlighten++; } else if (srsLevel === 9) { levelStageCounts.burn++; } // Check if this is the furthest item from burning let nextReviewDate = item.assignments && item.assignments.available_at && new Date(item.assignments.available_at); if ((nextReviewDate == null && srsLevel == 8 && levelStageCounts.longestReviewItem == null) || (nextReviewDate != null && ( levelStageCounts.longestReviewItem == null || srsLevel < levelStageCounts.longestReviewItem.level || (srsLevel == levelStageCounts.longestReviewItem.level && nextReviewDate > levelStageCounts.longestReviewItem.nextReviewDate) ))) { levelStageCounts.longestReviewItem = { level: srsLevel, nextReviewDate: nextReviewDate, }; } // Check if this is the next item for review for its level if (nextReviewDate != null && (levelStageCounts.nextReviewItem == null || nextReviewDate < levelStageCounts.nextReviewItem.reviewDate)) { levelStageCounts.nextReviewItem = { reviewDate: nextReviewDate, characters: item.data.characters, type: item.object, } if (item.data.characters == null) { const itemImg = item.data.character_images.filter((img) => { return (img.content_type === 'image/svg+xml' && !img.metadata.inline_styles); })[0]; levelStageCounts.nextReviewItem.imgUrl = itemImg && itemImg.url; levelStageCounts.nextReviewItem.slug = item.data.slug; } } } } return Promise.resolve(); }); } /** * Create the UI once the data has been parsed into the data array */ function finishUI () { // Get the HTML square elements for each level in the popout const levelBlocks = document.querySelectorAll('.sitemap__expandable-chunk--levels .sitemap__grouped-pages > ol > li > a'); for (let levelBlock of levelBlocks) { overwriteLevelBlock(levelBlock); } createSettings(); updateLevelHeader(); /** * Overwrite the level block with the custom elements and tooltip */ function overwriteLevelBlock (levelBlock) { levelBlock.classList.add('level-block'); const originalLevelText = levelBlock.textContent; const levelStageCounts = levelCounts[Number(originalLevelText)]; const levelTotal = levelStageCounts.locked + levelStageCounts.unlocked + levelStageCounts.apprentice + levelStageCounts.guru + levelStageCounts.master + levelStageCounts.enlighten + levelStageCounts.burn; // Create the overlay elements const levelText = `<span class="level-block-text">${originalLevelText}</span>`; const lockedDiv = `<div class="level-block-item level-block-locked" style="width:${(levelStageCounts.locked + levelStageCounts.unlocked)/levelTotal*100}%"></div>`; const apprenticeDiv = `<div class="level-block-item level-block-apprentice" style="width:${levelStageCounts.apprentice/levelTotal*100}%"></div>`; const guruDiv = `<div class="level-block-item level-block-guru" style="width:${levelStageCounts.guru/levelTotal*100}%"></div>`; const masterDiv = `<div class="level-block-item level-block-master" style="width:${levelStageCounts.master/levelTotal*100}%"></div>`; const enlightenedDiv = `<div class="level-block-item level-block-enlightened" style="width:${levelStageCounts.enlighten/levelTotal*100}%"></div>`; const burnDiv = `<div class="level-block-item level-block-burn" style="width:${levelStageCounts.burn/levelTotal*100}%"></div>`; // Create the tooltip const lockedText = `<div class="locked-tooltip tooltip-section"><p class="tooltip-section-title">Locked</p><p class="tooltip-section-count">${levelStageCounts.locked + levelStageCounts.unlocked}</p></div>`; const apprenticeText = `<div class="apprentice-tooltip tooltip-section"><p class="tooltip-section-title">Apprentice</p><p class="tooltip-section-count">${levelStageCounts.apprentice}</p></div>`; const guruText = `<div class="guru-tooltip tooltip-section"><p class="tooltip-section-title">Guru</p><p class="tooltip-section-count">${levelStageCounts.guru}</p></div>`; const masterText = `<div class="master-tooltip tooltip-section"><p class="tooltip-section-title">Master</p><p class="tooltip-section-count">${levelStageCounts.master}</p></div>`; const enlightenedText = `<div class="enlightened-tooltip tooltip-section"><p class="tooltip-section-title">Enlightened</p><p class="tooltip-section-count">${levelStageCounts.enlighten}</p></div>`; const burnText = `<div class="burn-tooltip tooltip-section"><p class="tooltip-section-title">Burn</p><p class="tooltip-section-count">${levelStageCounts.burn}</p></div>`; const totalText = `<div class="total-tooltip tooltip-section"><p class="tooltip-section-title">Total</p><p class="tooltip-section-count">${levelTotal}</p></div>`; let nextReviewDateText = "N/A"; let nextReviewChars = "N/A"; const nextReview = levelStageCounts.nextReviewItem; let loadImgData; if (nextReview != null) { // Get the time amount until review const nextReviewMinutes = (new Date(nextReview.reviewDate) - new Date()) / (1000 * 60); const nextReviewHours = nextReviewMinutes / 60; if (nextReviewHours <= 0) { nextReviewDateText = "now"; } else if (nextReviewHours <= 1) { const minutes = Math.floor(nextReviewMinutes); nextReviewDateText = minutes + " minute" + (minutes !== 1 ? "s" : ''); } else if (nextReviewHours >= 24) { const days = Math.floor(nextReviewHours / 24); nextReviewDateText = days + " day" + (days !== 1 ? "s" : ''); } else { const hours = Math.floor(nextReviewHours); nextReviewDateText = hours + " hour" + (hours !== 1 ? "s" : ''); } // Get the review item characters let itemClass = "guru-tooltip"; if (nextReview.type === "radical") { itemClass = "enlightened-tooltip"; } else if (nextReview.type === "kanji") { itemClass = "apprentice-tooltip"; } if (nextReview.characters) { nextReviewChars = `<span class="${itemClass} next-review-chars">${nextReview.characters}</span>`; } else if (nextReview.imgUrl && nextReview.slug) { nextReviewChars = `<span class="${itemClass} next-review-chars" id="svg_${nextReview.slug}"></span>`; loadImgData = nextReview; } } let longestReviewDate = 'N/A'; if (levelStageCounts.burn === levelTotal) { longestReviewDate = 'Done!'; } else if (levelStageCounts.longestReviewItem && levelStageCounts.locked == 0) { longestReviewDate = levelStageCounts.longestReviewItem.nextReviewDate; const currLevel = levelStageCounts.longestReviewItem.level; if (longestReviewDate == null) { longestReviewDate = 'Now!'; } else { let hoursToAdd = 0; let daysToAdd = 0; let monthsToAdd = 0; /* Apprentice 1 → 4 hours → Apprentice 2 Apprentice 2 → 8 hours → Apprentice 3 Apprentice 3 → 1 day → Apprentice 4 Apprentice 4 → 2 days → Guru 1 Guru 1 → 1 week → Guru 2 Guru 2 → 2 weeks → Master Master → 1 month → Enlightened Enlightened → 4 months → Burned For Level 1 & 2 the SRS timings are accelerated for the Apprentice stage. Apprentice 1 → 2 hours → Apprentice 2 Apprentice 2 → 4 hours → Apprentice 3 Apprentice 3 → 8 hours → Apprentice 4 Apprentice 4 → 1 day → Guru 1 */ if (currLevel <= 7) monthsToAdd += 4; if (currLevel <= 6) monthsToAdd += 1; if (currLevel <= 5) daysToAdd += 14; if (currLevel <= 4) daysToAdd += 7; if (Number(originalLevelText) <= 2) { if (currLevel <= 3) daysToAdd += 1; if (currLevel <= 2) hoursToAdd += 8; if (currLevel <= 1) hoursToAdd += 4; } else { if (currLevel <= 3) daysToAdd += 2; if (currLevel <= 2) daysToAdd += 1; if (currLevel <= 1) hoursToAdd += 8; } if (monthsToAdd) { longestReviewDate.setMonth(longestReviewDate.getMonth() + monthsToAdd); } if (daysToAdd) { longestReviewDate.setDate(longestReviewDate.getDate() + daysToAdd); } if (hoursToAdd) { longestReviewDate.setHours(longestReviewDate.getHours() + hoursToAdd); } longestReviewDate = longestReviewDate.toLocaleString([], { month: 'numeric', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', }); } } const nextReviewText = `<p class="tooltip-extra-info next-review-text">Next: ${nextReview ? `${nextReviewChars} (${nextReviewDateText})` : "N/A"}</p>`; const burnDateText = `<p class="tooltip-extra-info next-review-text">Burn: ${longestReviewDate}</p>`; const tooltip = `<div class="level-tooltip"><p class="tooltip-level-text">Level ${originalLevelText}</p>${lockedText}${apprenticeText}${guruText}${masterText}${enlightenedText}${burnText}${totalText}${nextReviewText}${burnDateText}</div>`; if (levelStageCounts.burn === levelTotal) { // Fully burned level, add the checkbox to the div levelBlock.classList.add('level-block-complete'); } if (levelStageCounts.locked === levelTotal) { // Fully locked level, add the padlock to the div levelBlock.classList.add('level-block-full-locked'); } // Rewrite the level block's HTML with the custom elements levelBlock.innerHTML = `<div class="level-block-container">${apprenticeDiv}${guruDiv}${masterDiv}${enlightenedDiv}${burnDiv}${lockedDiv}</div>${levelText}${tooltip}`; if (loadImgData != null && loadImgData.imgUrl) { wkof.load_file(loadImgData.imgUrl).then((svgData) => { const destSpan = document.getElementById(`svg_${loadImgData.slug}`); if (destSpan) { destSpan.innerHTML = svgData; } }); } } /** * Create the settings button and panel with contents */ function createSettings () { // Create the settings popop const settingsButton = document.createElement('div'); settingsButton.id = "settings-button"; const settingsPanel = document.createElement('div'); settingsPanel.id = "settings-panel" settingsPanel.innerHTML = `<div id='settings-title'>Levels Overview Plus Settings</div><div id="showNextRow" class="settings-row"><input id="showNextCb" type="checkbox"><span>Show Next Review</span></div>`; document.querySelector('.sitemap__grouped-pages').append(settingsButton, settingsPanel); const showNextToggle = document.getElementById("showNextCb"); const showNextSetting = document.getElementById("showNextRow"); // Handling hiding/showing the settings panel let settingsPanelActive = false; function handleBodyClick (e) { if (settingsPanelActive && !e.target.closest("#settings-panel") && e.target.id !== 'settings-button') { settingsPanel.style.display = 'none'; settingsButton.style.backgroundColor = ""; settingsPanelActive = false; } } document.body.addEventListener('mouseup', handleBodyClick); settingsButton.onclick = () => { settingsPanelActive = !settingsPanelActive; if (settingsPanelActive) { settingsPanel.style.display = 'block'; settingsButton.style.backgroundColor = "#b0b0b0"; } else { settingsPanel.style.display = 'none'; settingsButton.style.backgroundColor = ""; } }; // Handle the "toggle next review" checkbox const nextReviewTexts = Array.from(document.getElementsByClassName('next-review-text')); const updateNextReviewTexts = () => { const checked = window.wkof.settings.levelOverviewPlus.showNextReview; if(checked) { nextReviewTexts.forEach((nrt) => nrt.style.display = 'block'); } else { nextReviewTexts.forEach((nrt) => nrt.style.display = 'none'); } } const showNextReviewChanged = () => { window.wkof.settings.levelOverviewPlus.showNextReview = showNextToggle.checked; window.wkof.Settings.save('levelOverviewPlus'); updateNextReviewTexts(); } showNextToggle.onchange = showNextReviewChanged; updateNextReviewTexts(); showNextToggle.checked = window.wkof.settings.levelOverviewPlus.showNextReview; showNextSetting.onclick = () => { showNextToggle.checked = !window.wkof.settings.levelOverviewPlus.showNextReview; showNextReviewChanged(); }; } /** * Append a "+" to the level nav text to indicate script success */ function updateLevelHeader () { document.querySelectorAll('.navigation > .sitemap > li:first-child > .sitemap__section-header > span').forEach((levelSpan) => levelSpan.append('+')); } } /** * Create the CSS style sheet and append it to the document */ function initCSS() { const styling = document.createElement('style'); styling.innerHTML = ` .level-block { position: relative; height: 46px; border-width: 0 !important; } .level-block-text { width: 100%; position: absolute; top: 0; left: 0; text-align: center; line-height: 46px; } #settings-button { width: 28px; height: 28px; position: absolute; top: 8px; right: 8px; cursor: pointer; border-radius: 5px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABaklEQVR4Xu2aTUoDQRCFv2yNP9cInsCtbkWCl9EraE4jwWXIVo8gXiIkxL0URNBON3Yyq6p6s00z9Hv1VVdPpUYkf0bJ9SMDREByB5QCyQHQIagUUAokd0Ap0ABgAjwBN8CZc0i+gCXwAHyUWmoEmPh34MK58HL7a+CqNKFmwAswDSb+R8681FYzYBMA+1b8tqW2mgG2aByUgC4DDJO7oAZYet//1pbpEFztDsHP/wyw360SPO/K4KlzGgz7BfAI/BFvunQRch7dwdsXAYMtdP4CEeA8gIO3LwIGW+j8BS0CLoEZcA2cONd4cD/AxL8B586FH90PeAVug4k/qB9gyHjHXv2AhgPqB6gfUHwSt8qg+gFBq8CeLF2Fs0S6pVMEiIDkDigFkgPQ/F9A/QD1A2LmRtd8gPoBmg/QfEDIISnNB2g+oFLZdBWOWe77VYmAfq9irhQBMePar0oE9HsVc6UIiBnXflXpCfgGC8dCQbbkoGgAAAAASUVORK5CYII='); background-position: center !important; background-repeat: no-repeat !important; background-size: 18px !important; } #settings-panel { display: none; position: absolute; padding: 10px 15px; background-color: #2a2a2a; border-radius: 4px; top: 40px; right: 8px; user-select: none; } #settings-title { font-weight: bold; font-size: 14px; } .settings-row { padding: 8px 0 0 10px; font-size: 12px; line-height: 20px; cursor: pointer; } .settings-row input { margin: 0 3px 0 0; width: 20px; height: 20px; } .level-tooltip { color: #eeeeee; visibility: hidden; background-color: rgba(0,0,0,0.8); text-align: center; padding: 0 10px 2px 10px; margin-left: 52px; margin-top: -5px; border-radius: 6px; position: absolute; z-index: 2; width: 160px; font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif !important; pointer-events: none; box-sizing: content-box; } .tooltip-level-text { font-size: 16px; margin: 5px 0; font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif !important; line-height: 26px; } .tooltip-extra-info { font-size: 12px; text-align: left; margin: 0; font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif !important; white-space: normal; text-shadow: none; text-indent: -20px; padding-left: 20px; padding-bottom: 6px; line-height: 18px; } .next-review-chars { text-shadow: none; padding: 0 3px; font-size: 14px; } svg.radical { fill:none; stroke:#fff; stroke-linecap:square; stroke-miterlimit:2; stroke-width:68px; height: 15px; vertical-align: top; margin-top: 1.5px; } .tooltip-section { clear: both; overflow: auto; padding: 3px 10px; line-height: 18px; } .tooltip-section-title { float: left; margin: 0; font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif !important; font-size: 11px; font-weight: bold; text-shadow: none; } .tooltip-section-count { float: right; margin: 0; font-weight: bold; font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif !important; font-size: 11px; text-shadow: none; } .locked-tooltip { background-color: #666; background-image: linear-gradient(to bottom, #666, #444); } .apprentice-tooltip { background-color: #dd0093; background-image: linear-gradient(to bottom, #ff00aa, #b30077); } .guru-tooltip { background-color: #882d9e; background-image: linear-gradient(to bottom, #aa38c7, #662277); } .master-tooltip { background-color: #294ddb; background-image: linear-gradient(to bottom, #516ee1, #2142c4); } .enlightened-tooltip { background-color: #0093dd; background-image: linear-gradient(to bottom, #00aaff, #0077b3); } .burn-tooltip { background-color: #fbc042; background-image: linear-gradient(to bottom, #fbc550, #c88a04); color: #ffffff; } .total-tooltip { background-color: #efefef; background-image: linear-gradient(to bottom, #efefef, #cfcfcf); color: #000000; margin-bottom: 10px; } .level-block:hover .level-tooltip { visibility: visible; } .sitemap__pages--levels .sitemap__page--current-level a .level-block-container { border: medium solid black !important; } .sitemap__pages--levels .sitemap__page a { border-radius: 4px; } .sitemap__pages--levels .sitemap__page a .level-block-container:hover { background-color: rgba(255,255,255,0.5); } .sitemap__grouped-pages, .sitemap__expandable-chunk>*:first-child { overflow: visible !important; } .level-block-container { height: 100%; width: 100%; position: absolute; top: 0; left: 0; overflow: hidden; border-radius: 4px; border: thin solid black !important; box-sizing: border-box; } .level-block-complete { background-position: center !important; background-repeat: no-repeat !important; background-size: 34px !important; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE2LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgd2lkdGg9IjM1Mi42MnB4IiBoZWlnaHQ9IjM1Mi42MnB4IiB2aWV3Qm94PSIwIDAgMzUyLjYyIDM1Mi42MiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzUyLjYyIDM1Mi42MjsiDQoJIHhtbDpzcGFjZT0icHJlc2VydmUiIGZpbGw9IiMwMDY2MDAiIHN0cm9rZT0iIzAwNjYwMCI+DQo8Zz4NCgk8cGF0aCBkPSJNMzM3LjIyMiwyMi45NTJjLTE1LjkxMi04LjU2OC0zMy42Niw3Ljk1Ni00NC4wNjQsMTcuNzQ4Yy0yMy44NjcsMjMuMjU2LTQ0LjA2Myw1MC4xODQtNjYuNzA4LDc0LjY2NA0KCQljLTI1LjA5MiwyNi45MjgtNDguMzQ4LDUzLjg1Ni03NC4wNTIsODAuMTczYy0xNC42ODgsMTQuNjg4LTMwLjYsMzAuNi00MC4zOTIsNDguOTZjLTIyLjAzMi0yMS40MjEtNDEuMDA0LTQ0LjY3Ny02NS40ODQtNjMuNjQ4DQoJCWMtMTcuNzQ4LTEzLjQ2NC00Ny4xMjQtMjMuMjU2LTQ2LjUxMiw5LjE4YzEuMjI0LDQyLjIyOSwzOC41NTYsODcuNTE3LDY2LjA5NiwxMTYuMjhjMTEuNjI4LDEyLjI0LDI2LjkyOCwyNS4wOTIsNDQuNjc2LDI1LjcwNA0KCQljMjEuNDIsMS4yMjQsNDMuNDUyLTI0LjQ4LDU2LjMwNC0zOC41NTZjMjIuNjQ1LTI0LjQ4LDQxLjAwNS01Mi4wMjEsNjEuODEyLTc3LjExMmMyNi45MjgtMzMuMDQ4LDU0LjQ2OC02NS40ODUsODAuNzg0LTk5LjE0NQ0KCQlDMzI2LjIwNiw5Ni4zOTIsMzc4LjIyNiw0NC45ODMsMzM3LjIyMiwyMi45NTJ6IE0yNi45MzcsMTg3LjU4MWMtMC42MTIsMC0xLjIyNCwwLTIuNDQ4LDAuNjExDQoJCWMtMi40NDgtMC42MTEtNC4yODQtMS4yMjQtNi43MzItMi40NDhsMCwwQzE5LjU5MywxODQuNTIsMjIuNjUzLDE4NS4xMzIsMjYuOTM3LDE4Ny41ODF6Ii8+DQo8L2c+DQo8L3N2Zz4NCg==') !important; } .level-block-full-locked { background-position: center !important; background-repeat: no-repeat !important; background-size: 36px !important; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE2LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgd2lkdGg9IjM0cHgiIGhlaWdodD0iMzRweCIgdmlld0JveD0iMCAwIDM0IDM0IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAzNCAzNDsiIHhtbDpzcGFjZT0icHJlc2VydmUiIHN0cm9rZT0iIzY2NjY2NiIgZmlsbD0iIzY2NjY2NiI+DQo8Zz4NCgk8cGF0aCBkPSJNMjYuODMzLDEzLjMzM1Y5YzAtNC45NjItNC4wMzgtOS05LTloLTEuNjY2Yy00Ljk2MiwwLTksNC4wMzgtOSw5djQuMzMzSDQuMzMzVjM0aDI1LjMzNFYxMy4zMzNIMjYuODMzeiBNMTEuMTY3LDkNCgkJYzAtMi43NTcsMi4yNDMtNSw1LTVoMS42NjZjMi43NTcsMCw1LDIuMjQzLDUsNXY0LjMzM0gxMS4xNjdWOXoiLz4NCjwvZz4NCjwvc3ZnPg0K') !important; } .level-block-item { height: 100%; display: inline-block; } .level-block-locked { background-color: rgba(0, 0, 0, 0.3); } .level-block-apprentice { background-color: rgba(221, 0, 147, 0.4); } .level-block-guru { background-color: rgba(136, 45, 158, 0.4); } .level-block-master { background-color: rgba(41, 77, 219, 0.4); } .level-block-enlightened { background-color: rgba(0, 147, 221, 0.4); } .level-block-burn { background-color: rgba(251, 192, 66, 0.4); } `; document.head.append(styling); return Promise.resolve(); } /** * log an error if any part of the wkof data request failed * @param {*} [e] - The error to log if it exists */ function loadError (e) { console.error('Failed to load data from WKOF for "Wanikani Levels Overview Plus"', e); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址