[Userscript] Wanikani Review SRS/Level Indicator

I found that I was using the level indicator as a clue, and it was aiding my memory. However, I still wanted the information for after I give up, to know if I should let myself take a second-guess or not. If it’s above Guru, then I cut myself no slack, etc. Seeing the indicator gave me a clue sometimes that it was a recent failure, or a word I hadn’t seen a while, and this sometimes influenced my ability to “guess” which version of a similar word it was.

So, I made a version of the script that only shows you the level when you hover over it. I also moved it so it’s below the “Your Response” input field…

I guess the right way to do this would be to make it a user settings since most people probably don’t want this behavior. But, until that happens, here’s my version in case someone would prefer it…

// ==UserScript==
// @name        Wanikani Review SRS/Level Indicator (2025 Fixed + Hover)
// @namespace   mempo
// @author      Megamind
// @description Show current SRS level of the item you are reviewing (hidden until hover)
// @run-at      document-end
// @match       https://www.wanikani.com/subjects/review*
// @match       https://www.wanikani.com/subjects/extra_study*
// @version     2.3
// @grant       none
// @license     MIT
// ==/UserScript==

(function () {
    'use strict';

    const append_css = (css) => {
        const style = document.createElement("style");
        style.type = "text/css";
        style.id = "showsrs_css";
        style.appendChild(document.createTextNode(css));
        document.head.appendChild(style);
    };

    const CONTAINER_CLASSNAME = "showsrs__container";
    const CIRCLE_CLASSNAME = "showsrs__circle";

    const srs_stages = {
        A1: 1,
        A2: 2,
        A3: 3,
        A4: 4,
        G1: 5,
        G2: 6,
        M: 7,
        E: 8,
    };

    const colours = {
        APPRENTICE: "221, 0, 147",
        GURU: "135, 45, 158",
        MASTER: "41, 77, 219",
        ENLIGHTENED: "0, 147, 221",
        DEFAULT: "255, 255, 255",
    };

    const TRANSPARENCY = 1.0;
    let id_to_srs = {};

    // Look for new WK JSON element
    const id_srs_element = document.querySelector('script[data-quiz-queue-target="subjects"]');
    if (!id_srs_element) {
        console.error("⚠️ [SRS Indicator] Failed to find <script data-quiz-queue-target=\"subjects\">");
        return;
    }

    // Parse the subjects JSON (no longer includes SRS info)
    let id_srs_array;
    try {
        id_srs_array = JSON.parse(id_srs_element.textContent);
        if (!Array.isArray(id_srs_array)) {
            console.error("⚠️ [SRS Indicator] Unexpected data format:", id_srs_array);
            return;
        }
        // Default all subjects to SRS stage 1
        id_to_srs = Object.fromEntries(id_srs_array.map(item => [item.id, 1]));
    } catch (error) {
        console.error("⚠️ [SRS Indicator] JSON parse error:", error);
        return;
    }

    // Find the input wrapper
    const meaning_element = document.getElementsByClassName("quiz-input__input-wrapper");
    if (!meaning_element[0]) {
        console.error("⚠️ [SRS Indicator] Meaning element not found.");
        return;
    }

    // Create indicator container
    const container = document.createElement("div");
    container.className = CONTAINER_CLASSNAME;
    meaning_element[0].appendChild(container);

    // Add CSS (hidden until hover)
    append_css(`
        .${CONTAINER_CLASSNAME} {
            display: flex;
            justify-content: center;
            gap: 5px;
            padding-top: 10px;
            opacity: 0;
            transition: opacity 0.2s ease;
        }

        /* Show indicators when hovering over input box */
        .quiz-input__input-wrapper:hover .${CONTAINER_CLASSNAME} {
            opacity: 1;
        }

        .${CIRCLE_CLASSNAME} {
            width: 10px;
            height: 10px;
            border: 2px solid;
            border-radius: 50%;
        }
    `);

    // Create 8 circles (Apprentice 1–4, Guru 1–2, Master, Enlightened)
    for (const [key, value] of Object.entries(srs_stages)) {
        const circle = document.createElement("div");
        circle.className = CIRCLE_CLASSNAME;

        if (value <= srs_stages.A4) {
            circle.style.borderColor = `rgba(${colours.APPRENTICE}, ${TRANSPARENCY})`;
        } else if (value <= srs_stages.G2) {
            circle.style.borderColor = `rgba(${colours.GURU}, ${TRANSPARENCY})`;
        } else if (value === srs_stages.M) {
            circle.style.borderColor = `rgba(${colours.MASTER}, ${TRANSPARENCY})`;
        } else if (value === srs_stages.E) {
            circle.style.borderColor = `rgba(${colours.ENLIGHTENED}, ${TRANSPARENCY})`;
        }

        container.appendChild(circle);
    }

    const circles = document.getElementsByClassName(CIRCLE_CLASSNAME);

    // Update on next question
    window.addEventListener("willShowNextQuestion", (e) => {
        if (e.detail && e.detail.subject && e.detail.subject.id) {
            const item_id = e.detail.subject.id;
            const srs_stage = id_to_srs[item_id] || 1; // default Apprentice 1

            for (let i = 0; i < circles.length; i++) {
                const corrected_index = i + 1;
                circles[i].style.backgroundColor =
                    corrected_index <= srs_stage
                        ? `rgba(${colours.APPRENTICE}, ${TRANSPARENCY})`
                        : "";
            }
        }
    });
})();

I edited the previous post’s script because WK just changed something (JSON format changed) that broke it. It’s now working again.