Torn Education Tracker

Native Torn-styled education progress, personal course planner, and cached active education perk tracker.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Advertisement:

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

Advertisement:

// ==UserScript==
// @name         Torn Education Tracker
// @namespace    https://www.torn.com/
// @version      1.3.0
// @description  Native Torn-styled education progress, personal course planner, and cached active education perk tracker.
// @author       ST4TIC / Jade
// @license      All Rights Reserved
// @match        https://www.torn.com/page.php?sid=education*
// @match        https://www.torn.com/page.php*sid=education*
// @match        https://www.torn.com/index.php*
// @grant        GM_xmlhttpRequest
// @connect      api.torn.com
// @run-at       document-idle
// ==/UserScript==

/*
 * Copyright © 2026 ST4TIC.
 * All Rights Reserved.
 *
 * This software may not be copied, modified, redistributed,
 * republished, sublicensed, or used to create derivative works
 * without the express written permission of the copyright owner.
 */

(() => {
    'use strict';
    /* =========================================================
       CORE CONFIGURATION
       ========================================================= */

    const TRACKER_ID = 'tet-education-tracker';
    const STYLE_ID = 'tet-education-tracker-styles';
    const MODAL_ID = 'tet-education-plan-modal';
    const API_DIALOG_ID = 'tet-api-dialog';

    const API_BASE = 'https://api.torn.com/v2';

    const STORAGE = {
        apiKey: 'tet_education_api_key',
        plan: 'tet_education_plan_v1',
        collapsed: 'tet_education_collapsed',
        catalogueCache: 'tet_education_catalogue_cache_v1',
        userCache: 'tet_education_user_cache_v1',
        perkCache: 'tet_education_home_perks_cache_v1'
    };

    const CACHE_DURATION = 10 * 60 * 1000;
    const REQUEST_TIMEOUT = 30000;

    const state = {
        loading: false,
        error: '',
        catalogue: [],
        completedCourseIds: new Set(),
        currentCourse: null,
        plan: loadPlan(),
        countdownTimer: null,
        courseTimestampObserver: null,
        courseTimestampPollTimer: null,
        courseTimestampStopTimer: null,
        outcomeHarvestRunning: false,
        lastUrl: window.location.href
    };

    /* =========================================================
       GENERAL UTILITIES
       ========================================================= */

    function escapeHtml(value) {
        return String(value ?? '').replace(
            /[&<>"']/g,
            character => ({
                '&': '&amp;',
                '<': '&lt;',
                '>': '&gt;',
                '"': '&quot;',
                "'": '&#39;'
            })[character]
        );
    }

    function number(value) {
        const parsed = Number(
            String(value ?? 0).replace(/,/g, '')
        );

        return Number.isFinite(parsed)
            ? parsed
            : 0;
    }

    function normaliseText(value) {
        return String(value ?? '')
            .replace(/\s+/g, ' ')
            .trim();
    }

    function formatNumber(value) {
        return number(value).toLocaleString('en-AU');
    }

    function formatDateTime(timestamp) {
        if (!timestamp) {
            return '—';
        }

        return new Date(
            number(timestamp) * 1000
        ).toLocaleString(
            'en-AU',
            {
                day: '2-digit',
                month: '2-digit',
                year: 'numeric',
                hour: '2-digit',
                minute: '2-digit',
                second: '2-digit',
                hour12: false
            }
        );
    }

    function formatDuration(totalSeconds) {
        const seconds = Math.max(
            0,
            Math.floor(number(totalSeconds))
        );

        if (seconds <= 0) {
            return 'Complete';
        }

        const days = Math.floor(
            seconds / 86400
        );

        const hours = Math.floor(
            (seconds % 86400) / 3600
        );

        const minutes = Math.floor(
            (seconds % 3600) / 60
        );

        const remainingSeconds =
            seconds % 60;

        const parts = [];

        if (days > 0) {
            parts.push(`${days}d`);
        }

        if (
            hours > 0 ||
            days > 0
        ) {
            parts.push(`${hours}h`);
        }

        parts.push(`${minutes}m`);
        parts.push(`${remainingSeconds}s`);

        return parts.join(' ');
    }

    function formatPlanDuration(totalSeconds) {
        const seconds = Math.max(
            0,
            Math.floor(number(totalSeconds))
        );

        if (seconds <= 0) {
            return '0h 0m';
        }

        const days = Math.floor(
            seconds / 86400
        );

        const hours = Math.floor(
            (seconds % 86400) / 3600
        );

        const minutes = Math.floor(
            (seconds % 3600) / 60
        );

        const parts = [];

        if (days > 0) {
            parts.push(`${days}d`);
        }

        parts.push(`${hours}h`);
        parts.push(`${minutes}m`);

        return parts.join(' ');
    }

    function isEducationPage() {
        const url =
            new URL(window.location.href);

        return (
            url.pathname.endsWith('/page.php') &&
            url.searchParams.get('sid') === 'education'
        );
    }


    function isTornHomePage() {
        const url =
            new URL(window.location.href);

        return (
            (
                url.pathname === '/' ||
                url.pathname.endsWith('/index.php')
            ) &&
            !url.searchParams.get('sid')
        );
    }

    function loadPerkCache() {
        try {
            const saved = JSON.parse(
                localStorage.getItem(
                    STORAGE.perkCache
                ) || 'null'
            );

            if (
                !saved ||
                !Array.isArray(saved.perks)
            ) {
                return {
                    savedAt: 0,
                    perks: []
                };
            }

            return {
                savedAt:
                    number(saved.savedAt),

                perks:
                    saved.perks
                        .map(normaliseText)
                        .filter(Boolean)
            };

        } catch {
            return {
                savedAt: 0,
                perks: []
            };
        }
    }

    function savePerkCache(perks) {
        const cleanPerks = [
            ...new Set(
                perks
                    .map(normaliseText)
                    .filter(Boolean)
            )
        ];

        if (!cleanPerks.length) {
            return;
        }

        try {
            localStorage.setItem(
                STORAGE.perkCache,
                JSON.stringify({
                    savedAt: Date.now(),
                    perks: cleanPerks
                })
            );
        } catch {
            // Perk cache failure does not stop Torn.
        }
    }

    function readEducationPerksFromHomeDom() {
        const category =
            document.querySelector(
                '.perk-category-container[data-category="Education"]'
            );

        if (!category) {
            return [];
        }

        const list =
            category.querySelector(
                'ul.perk-category-perks-list'
            );

        if (!list) {
            return [];
        }

        return [
            ...list.querySelectorAll(
                ':scope > li'
            )
        ]
            .map(item => {
                const wrapper =
                    item.querySelector(
                        '.perk-description-wrapper'
                    );

                if (!wrapper) {
                    return '';
                }

                const directSpans = [
                    ...wrapper.querySelectorAll(
                        ':scope > span'
                    )
                ];

                return normaliseText(
                    directSpans[1]?.textContent ||
                    ''
                );
            })
            .filter(Boolean);
    }

    function captureEducationPerksFromHome() {
        if (!isTornHomePage()) {
            return;
        }

        let settled = false;

        const tryCapture = () => {
            const perks =
                readEducationPerksFromHomeDom();

            if (!perks.length) {
                return false;
            }

            savePerkCache(perks);
            settled = true;

            observer.disconnect();
            clearInterval(pollTimer);
            clearTimeout(stopTimer);

            return true;
        };

        const observer =
            new MutationObserver(
                () => tryCapture()
            );

        observer.observe(
            document.documentElement,
            {
                childList: true,
                subtree: true
            }
        );

        const pollTimer =
            window.setInterval(
                () => tryCapture(),
                500
            );

        const stopTimer =
            window.setTimeout(
                () => {
                    if (!settled) {
                        observer.disconnect();
                        clearInterval(pollTimer);
                    }
                },
                30000
            );

        tryCapture();
    }

    function buildActivePerks() {
        return loadPerkCache()
            .perks
            .map(perk => ({
                type: 'unique',
                title: perk,
                amount: null,
                courses: []
            }));
    }

    /* =========================================================
       STORAGE
       ========================================================= */

    function getApiKey() {
        return (
            localStorage.getItem(
                STORAGE.apiKey
            ) || ''
        ).trim();
    }

    function saveApiKey(apiKey) {
        localStorage.setItem(
            STORAGE.apiKey,
            String(apiKey || '').trim()
        );
    }

    function loadPlan() {
        try {
            const saved = JSON.parse(
                localStorage.getItem(
                    STORAGE.plan
                ) || '[]'
            );

            return Array.isArray(saved)
                ? saved
                : [];

        } catch {
            return [];
        }
    }

    function savePlan() {
        localStorage.setItem(
            STORAGE.plan,
            JSON.stringify(state.plan)
        );
    }

    function readCache(storageKey) {
        try {
            const cached = JSON.parse(
                localStorage.getItem(
                    storageKey
                ) || 'null'
            );

            if (
                !cached ||
                !cached.savedAt ||
                !cached.data
            ) {
                return null;
            }

            if (
                Date.now() - cached.savedAt >
                CACHE_DURATION
            ) {
                return null;
            }

            return cached.data;

        } catch {
            return null;
        }
    }

    function writeCache(
        storageKey,
        data
    ) {
        try {
            localStorage.setItem(
                storageKey,
                JSON.stringify({
                    savedAt: Date.now(),
                    data
                })
            );

        } catch {
            // Cache failure does not stop tracker.
        }
    }

    /* =========================================================
       TORN API
       ========================================================= */

    function apiRequest(path) {
        const apiKey =
            getApiKey();

        if (!apiKey) {
            return Promise.reject(
                new Error(
                    'A Torn API key is required to load education progress.'
                )
            );
        }

        const separator =
            path.includes('?')
                ? '&'
                : '?';

        const url =
            `${API_BASE}${path}${separator}` +
            `key=${encodeURIComponent(apiKey)}`;

        return new Promise(
            (
                resolve,
                reject
            ) => {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url,
                    timeout: REQUEST_TIMEOUT,

                    onload(response) {
                        let data;

                        try {
                            data = JSON.parse(
                                response.responseText
                            );

                        } catch {
                            reject(
                                new Error(
                                    `Torn returned an invalid response (${response.status}).`
                                )
                            );

                            return;
                        }

                        if (data?.error) {
                            reject(
                                new Error(
                                    data.error.error ||
                                    data.error.message ||
                                    'Torn API request failed.'
                                )
                            );

                            return;
                        }

                        if (
                            response.status < 200 ||
                            response.status >= 300
                        ) {
                            reject(
                                new Error(
                                    `Torn API request failed (${response.status}).`
                                )
                            );

                            return;
                        }

                        resolve(data);
                    },

                    onerror() {
                        reject(
                            new Error(
                                'The Torn API request failed.'
                            )
                        );
                    },

                    ontimeout() {
                        reject(
                            new Error(
                                'The Torn API request timed out.'
                            )
                        );
                    }
                });
            }
        );
    }

    /* =========================================================
       CATALOGUE NORMALISATION
       ========================================================= */

    function normaliseCatalogueResponse(data) {
        const source =
            data.education ||
            data.courses ||
            data.education_courses ||
            data;

        const categories = [];

        if (Array.isArray(source)) {
            for (const item of source) {
                if (
                    item?.courses &&
                    Array.isArray(item.courses)
                ) {
                    categories.push(item);
                }
            }

        } else if (
            source &&
            typeof source === 'object'
        ) {
            for (
                const [key, value]
                of Object.entries(source)
            ) {
                if (Array.isArray(value)) {
                    categories.push({
                        id: key,
                        name: key,
                        courses: value
                    });

                    continue;
                }

                if (
                    value &&
                    typeof value === 'object' &&
                    Array.isArray(value.courses)
                ) {
                    categories.push({
                        id:
                            value.id ??
                            value.category_id ??
                            key,

                        name:
                            value.name ??
                            value.category_name ??
                            key,

                        ...value
                    });
                }
            }
        }

        const courses = [];

        for (
            const category
            of categories
        ) {
            const categoryName =
                category.name ||
                category.category_name ||
                category.title ||
                'Unknown';

            const rawCourses =
                category.courses ||
                category.education ||
                [];

            for (
                const rawCourse
                of rawCourses
            ) {
                const id = String(
                    rawCourse.id ??
                    rawCourse.course_id ??
                    rawCourse.education_id ??
                    ''
                );

                const name =
                    rawCourse.name ||
                    rawCourse.course_name ||
                    rawCourse.title ||
                    '';

                if (
                    !id ||
                    !name
                ) {
                    continue;
                }

                courses.push({
                    id,

                    name:
                        normaliseText(name),

                    category:
                        normaliseText(
                            categoryName
                        ),

                    categoryId:
                        String(
                            category.id ??
                            category.category_id ??
                            ''
                        ),

                    duration:
                        number(
                            rawCourse.duration ??
                            rawCourse.time ??
                            rawCourse.course_time ??
                            0
                        ),

                    prerequisites:
                        rawCourse.prerequisites ||
                        rawCourse.prerequisite ||
                        [],

                    raw:
                        rawCourse
                });
            }
        }

        if (!courses.length) {
            const flatCandidates = [
                data.education,
                data.courses,
                data.education_courses
            ];

            for (
                const candidate
                of flatCandidates
            ) {
                if (
                    !Array.isArray(candidate)
                ) {
                    continue;
                }

                for (
                    const rawCourse
                    of candidate
                ) {
                    const id = String(
                        rawCourse.id ??
                        rawCourse.course_id ??
                        rawCourse.education_id ??
                        ''
                    );

                    const name =
                        rawCourse.name ||
                        rawCourse.course_name ||
                        rawCourse.title ||
                        '';

                    if (
                        !id ||
                        !name
                    ) {
                        continue;
                    }

                    courses.push({
                        id,

                        name:
                            normaliseText(name),

                        category:
                            normaliseText(
                                rawCourse.category ||
                                rawCourse.category_name ||
                                'Unknown'
                            ),

                        categoryId:
                            String(
                                rawCourse.category_id ||
                                ''
                            ),

                        duration:
                            number(
                                rawCourse.duration ??
                                rawCourse.time ??
                                rawCourse.course_time ??
                                0
                            ),

                        prerequisites:
                            rawCourse.prerequisites ||
                            rawCourse.prerequisite ||
                            [],

                        raw:
                            rawCourse
                    });
                }
            }
        }

        return courses.sort(
            (
                a,
                b
            ) => {
                const categoryCompare =
                    a.category.localeCompare(
                        b.category
                    );

                if (
                    categoryCompare !== 0
                ) {
                    return categoryCompare;
                }

                return a.name.localeCompare(
                    b.name
                );
            }
        );
    }

    /* =========================================================
       USER EDUCATION NORMALISATION
       ========================================================= */

    function recursivelyCollectCompletedIds(
        value,
        completedIds
    ) {
        if (Array.isArray(value)) {
            for (
                const item
                of value
            ) {
                if (
                    typeof item === 'number' ||
                    typeof item === 'string'
                ) {
                    completedIds.add(
                        String(item)
                    );

                    continue;
                }

                recursivelyCollectCompletedIds(
                    item,
                    completedIds
                );
            }

            return;
        }

        if (
            !value ||
            typeof value !== 'object'
        ) {
            return;
        }

        const status =
            String(
                value.status ||
                value.state ||
                value.course_status ||
                ''
            ).toLowerCase();

        const completed =
            value.completed === true ||
            value.complete === true ||
            status === 'completed' ||
            status === 'complete' ||
            status === 'finished';

        const id =
            value.id ??
            value.course_id ??
            value.education_id;

        if (
            completed &&
            id !== undefined &&
            id !== null
        ) {
            completedIds.add(
                String(id)
            );
        }

        for (
            const [key, child]
            of Object.entries(value)
        ) {
            const keyLower =
                key.toLowerCase();

            if (
                keyLower.includes('complete') &&
                Array.isArray(child)
            ) {
                for (
                    const childId
                    of child
                ) {
                    if (
                        typeof childId === 'string' ||
                        typeof childId === 'number'
                    ) {
                        completedIds.add(
                            String(childId)
                        );
                    }
                }
            }

            recursivelyCollectCompletedIds(
                child,
                completedIds
            );
        }
    }

    function findCurrentCourseObject(data) {
        const directCandidates = [
            data.current,
            data.current_course,
            data.education?.current,
            data.education?.current_course,
            data.course,
            data.education
        ];

        for (
            const candidate
            of directCandidates
        ) {
            if (
                !candidate ||
                typeof candidate !== 'object' ||
                Array.isArray(candidate)
            ) {
                continue;
            }

            const id =
                candidate.id ??
                candidate.course_id ??
                candidate.education_id;

            const name =
                candidate.name ||
                candidate.course_name ||
                candidate.title;

            const endTimestamp =
                number(
                    candidate.end ??
                    candidate.ends ??
                    candidate.end_time ??
                    candidate.completion_time ??
                    candidate.complete_at ??
                    candidate.finish_time ??
                    candidate.timestamp
                );

            if (
                id !== undefined ||
                name ||
                endTimestamp
            ) {
                return {
                    id:
                        id !== undefined &&
                        id !== null
                            ? String(id)
                            : '',

                    name:
                        normaliseText(
                            name || ''
                        ),

                    category:
                        normaliseText(
                            candidate.category ||
                            candidate.category_name ||
                            ''
                        ),

                    endTimestamp,

                    raw:
                        candidate
                };
            }
        }

        return null;
    }

    function normaliseUserEducationResponse(data) {
        const completedIds =
            new Set();

        recursivelyCollectCompletedIds(
            data,
            completedIds
        );

        return {
            completedIds,

            currentCourse:
                findCurrentCourseObject(
                    data
                )
        };
    }

    /* =========================================================
       EDUCATION DOM
       ========================================================= */

    function getEducationRoot() {
        return document.querySelector(
            '#education-root'
        );
    }

    function readCurrentCourseFromDom() {
        const pageText =
            normaliseText(
                document.body?.innerText ||
                document.body?.textContent ||
                ''
            );

        const courseMatch =
            pageText.match(
                /You are taking the\s+(.+?)\s+education course/i
            );

        const completionMatch =
            pageText.match(
                /This course will be completed in\s+(.+?)\s+\((\d{2}\.\d{2}\.\d{4}\s+\d{2}:\d{2}:\d{2})\)/i
            );

        let endTimestamp = 0;

        if (
            completionMatch?.[2]
        ) {
            const timestampText =
                completionMatch[2];

            const timestampMatch =
                timestampText.match(
                    /^(\d{2})\.(\d{2})\.(\d{4})\s+(\d{2}):(\d{2}):(\d{2})$/
                );

            if (timestampMatch) {
                const [
                    ,
                    day,
                    month,
                    year,
                    hour,
                    minute,
                    second
                ] = timestampMatch.map(Number);

                const completionDate =
                    new Date(
                        year,
                        month - 1,
                        day,
                        hour,
                        minute,
                        second
                    );

                if (
                    Number.isFinite(
                        completionDate.getTime()
                    )
                ) {
                    endTimestamp =
                        Math.floor(
                            completionDate.getTime() /
                            1000
                        );
                }
            }
        }

        if (
            !courseMatch &&
            !completionMatch
        ) {
            return null;
        }

        return {
            id: '',

            name:
                normaliseText(
                    courseMatch?.[1] ||
                    ''
                ),

            category: '',

            endTimestamp,

            rawRemainingText:
                normaliseText(
                    completionMatch?.[1] ||
                    ''
                )
        };
    }

    function enrichCurrentCourse(
        currentCourse
    ) {
        if (!currentCourse) {
            return null;
        }

        let catalogueCourse = null;

        if (
            currentCourse.id
        ) {
            catalogueCourse =
                state.catalogue.find(
                    course =>
                        course.id ===
                        String(
                            currentCourse.id
                        )
                );
        }

        if (
            !catalogueCourse &&
            currentCourse.name
        ) {
            catalogueCourse =
                state.catalogue.find(
                    course =>
                        course.name
                            .toLowerCase() ===
                        currentCourse.name
                            .toLowerCase()
                );
        }

        if (!catalogueCourse) {
            return currentCourse;
        }

        return {
            ...currentCourse,

            id:
                currentCourse.id ||
                catalogueCourse.id,

            name:
                currentCourse.name ||
                catalogueCourse.name,

            category:
                currentCourse.category ||
                catalogueCourse.category,

            duration:
                catalogueCourse.duration,

            raw:
                catalogueCourse.raw
        };
    }

    function stopCurrentCourseTimestampWatch() {
        if (
            state.courseTimestampObserver
        ) {
            state.courseTimestampObserver
                .disconnect();

            state.courseTimestampObserver =
                null;
        }

        if (
            state.courseTimestampPollTimer
        ) {
            clearInterval(
                state.courseTimestampPollTimer
            );

            state.courseTimestampPollTimer =
                null;
        }

        if (
            state.courseTimestampStopTimer
        ) {
            clearTimeout(
                state.courseTimestampStopTimer
            );

            state.courseTimestampStopTimer =
                null;
        }
    }

    function captureCurrentCourseTimestamp() {
        if (
            !isEducationPage()
        ) {
            stopCurrentCourseTimestampWatch();
            return false;
        }

        const domCurrentCourse =
            readCurrentCourseFromDom();

        if (
            !domCurrentCourse?.endTimestamp
        ) {
            return false;
        }

        const previousEndTimestamp =
            number(
                state.currentCourse?.endTimestamp
            );

        const previousName =
            normaliseText(
                state.currentCourse?.name ||
                ''
            );

        state.currentCourse =
            enrichCurrentCourse({
                ...(
                    state.currentCourse ||
                    {}
                ),

                name:
                    domCurrentCourse.name ||
                    previousName,

                endTimestamp:
                    domCurrentCourse.endTimestamp,

                rawRemainingText:
                    domCurrentCourse.rawRemainingText
            });

        if (
            previousEndTimestamp !==
                domCurrentCourse.endTimestamp ||
            (
                !previousName &&
                domCurrentCourse.name
            )
        ) {
            renderTracker();
        }

        stopCurrentCourseTimestampWatch();

        return true;
    }

    function watchForCurrentCourseTimestamp() {
        stopCurrentCourseTimestampWatch();

        if (
            captureCurrentCourseTimestamp()
        ) {
            return;
        }

        state.courseTimestampObserver =
            new MutationObserver(
                () => {
                    captureCurrentCourseTimestamp();
                }
            );

        state.courseTimestampObserver.observe(
            document.body,
            {
                childList: true,
                subtree: true,
                characterData: true
            }
        );

        state.courseTimestampPollTimer =
            window.setInterval(
                () => {
                    captureCurrentCourseTimestamp();
                },
                250
            );

        state.courseTimestampStopTimer =
            window.setTimeout(
                () => {
                    stopCurrentCourseTimestampWatch();
                },
                30000
            );
    }

    /* =========================================================
       CATEGORY PROGRESS
       ========================================================= */

    function calculateCategoryProgress() {
        const currentCourse =
            state.currentCourse;

        if (
            !currentCourse ||
            !currentCourse.category
        ) {
            return {
                category: 'Unknown',
                total: 0,
                completed: 0,
                remaining: 0
            };
        }

        const categoryCourses =
            state.catalogue.filter(
                course =>
                    course.category
                        .toLowerCase() ===
                    currentCourse.category
                        .toLowerCase()
            );

        const completed =
            categoryCourses.filter(
                course =>
                    state.completedCourseIds
                        .has(course.id)
            ).length;

        return {
            category:
                currentCourse.category,

            total:
                categoryCourses.length,

            completed,

            remaining:
                Math.max(
                    0,
                    categoryCourses.length -
                    completed
                )
        };
    }

    /* =========================================================
       PLAN HELPERS
       ========================================================= */

    function synchronisePlan() {
        let changed = false;

        state.plan =
            state.plan.map(
                item => {
                    const catalogueCourse =
                        state.catalogue.find(
                            course =>
                                course.id ===
                                String(item.id)
                        );

                    const isComplete =
                        state.completedCourseIds
                            .has(
                                String(item.id)
                            );

                    const updated = {
                        ...item,

                        name:
                            catalogueCourse?.name ||
                            item.name,

                        category:
                            catalogueCourse?.category ||
                            item.category,

                        duration:
                            number(
                                catalogueCourse?.duration ??
                                item.duration ??
                                0
                            ),

                        complete:
                            isComplete
                    };

                    if (
                        updated.name !== item.name ||
                        updated.category !== item.category ||
                        updated.duration !==
                            number(item.duration) ||
                        updated.complete !== item.complete
                    ) {
                        changed = true;
                    }

                    return updated;
                }
            );

        if (changed) {
            savePlan();
        }
    }

    function isCurrentPlanItem(item) {
        if (
            !state.currentCourse
        ) {
            return false;
        }

        if (
            state.currentCourse.id &&
            String(item.id) ===
            String(
                state.currentCourse.id
            )
        ) {
            return true;
        }

        return (
            item.name.toLowerCase() ===
            state.currentCourse.name
                .toLowerCase()
        );
    }

    function getNextPlanItem() {
        return (
            state.plan.find(
                item =>
                    !item.complete &&
                    !isCurrentPlanItem(item)
            ) ||
            null
        );
    }

    function getPlanTimeRemaining() {
        return state.plan.reduce(
            (
                total,
                item
            ) => {
                if (
                    item.complete ||
                    isCurrentPlanItem(item)
                ) {
                    return total;
                }

                const catalogueCourse =
                    state.catalogue.find(
                        course =>
                            course.id ===
                            String(item.id)
                    );

                return (
                    total +
                    number(
                        catalogueCourse?.duration ??
                        item.duration ??
                        0
                    )
                );
            },
            0
        );
    }

    function getRemainingPlanCount() {
        return state.plan.filter(
            item =>
                !item.complete &&
                !isCurrentPlanItem(item)
        ).length;
    }

    function addCourseToPlan(
        courseId
    ) {
        const course =
            state.catalogue.find(
                item =>
                    item.id ===
                    String(courseId)
            );

        if (!course) {
            return;
        }

        if (
            state.plan.some(
                item =>
                    String(item.id) ===
                    course.id
            )
        ) {
            return;
        }

        state.plan.push({
            id:
                course.id,

            name:
                course.name,

            category:
                course.category,

            duration:
                course.duration,

            complete:
                state.completedCourseIds
                    .has(course.id)
        });

        savePlan();
        renderTracker();
        renderPlanModal();
    }

    function removeCourseFromPlan(
        courseId
    ) {
        state.plan =
            state.plan.filter(
                item =>
                    String(item.id) !==
                    String(courseId)
            );

        savePlan();
        renderTracker();
        renderPlanModal();
    }

    function movePlanItem(
        courseId,
        direction
    ) {
        const currentIndex =
            state.plan.findIndex(
                item =>
                    String(item.id) ===
                    String(courseId)
            );

        if (
            currentIndex < 0
        ) {
            return;
        }

        const nextIndex =
            currentIndex + direction;

        if (
            nextIndex < 0 ||
            nextIndex >=
            state.plan.length
        ) {
            return;
        }

        const updated = [
            ...state.plan
        ];

        [
            updated[currentIndex],
            updated[nextIndex]
        ] = [
            updated[nextIndex],
            updated[currentIndex]
        ];

        state.plan = updated;

        savePlan();
        renderTracker();
        renderPlanModal();
    }

    function clearCompletedPlanItems() {
        state.plan =
            state.plan.filter(
                item =>
                    !item.complete
            );

        savePlan();
        renderTracker();
        renderPlanModal();
    }

    /* =========================================================
       NATIVE TORN STYLES
       ========================================================= */

    function injectStyles() {
        if (
            document.getElementById(
                STYLE_ID
            )
        ) {
            return;
        }

        const style =
            document.createElement(
                'style'
            );

        style.id =
            STYLE_ID;

        style.textContent = `
            #${TRACKER_ID},
            #${TRACKER_ID} * {
                box-sizing: border-box;
            }

            #${TRACKER_ID} {
                --tet-bg: #202020;
                --tet-bg-dark: #181818;
                --tet-panel: #333333;
                --tet-panel-dark: #2b2b2b;
                --tet-panel-soft: #3a3a3a;
                --tet-border: #444444;
                --tet-border-soft: rgba(255, 255, 255, 0.08);
                --tet-text: #f0f0f0;
                --tet-muted: #aaa;
                --tet-muted-dark: #777;
                --tet-link: #74b9e8;
                --tet-green: #70b33f;
                --tet-green-bright: #9bd85f;
                --tet-danger: #c96b6b;
                --tet-radius: 5px;

                width: 100%;
                margin: 10px 0 50px;
                color: var(--tet-text);
                font-family:
                    Arial,
                    Helvetica,
                    sans-serif;
            }

            #${TRACKER_ID} button,
            #${TRACKER_ID} input,
            #${TRACKER_ID} select {
                font: inherit;
            }

            .tet-shell {
                overflow: hidden;
                border: 1px solid #111;
                border-radius: var(--tet-radius);
                background: var(--tet-bg);
                box-shadow:
                    0 1px 0 rgba(255, 255, 255, 0.04);
            }

            .tet-header {
                min-height: 52px;
                display: flex;
                align-items: center;
                gap: 12px;
                padding: 8px 12px;
                border-bottom: 1px solid #111;
                background:
                    linear-gradient(
                        180deg,
                        #3c3c3c,
                        #303030
                    );
            }

            .tet-brand-mark {
                width: 38px;
                height: 38px;
                flex: 0 0 38px;
                display: grid;
                place-items: center;
                border: 1px solid #4b4b4b;
                border-radius: 4px;
                background:
                    linear-gradient(
                        180deg,
                        #484848,
                        #343434
                    );
                color: #ddd;
                font-size: 19px;
                text-shadow:
                    0 1px 1px #000;
            }

            .tet-brand {
                min-width: 0;
            }

            .tet-brand-title {
                display: block;
                margin: 0;
                color: #eee;
                font-size: 14px;
                font-weight: 700;
                line-height: normal;
            }

            .tet-brand p {
                margin: 4px 0 0;
                color: #999;
                font-size: 10px;
                letter-spacing: 0.8px;
            }

            .tet-spacer {
                flex: 1;
            }

            .tet-header-button {
                min-width: 34px;
                height: 32px;
                padding: 0 10px;
                border: 1px solid #222;
                border-radius: 4px;
                background:
                    linear-gradient(
                        180deg,
                        #484848,
                        #333
                    );
                color: #bbb;
                cursor: pointer;
                box-shadow:
                    inset 0 1px 0 rgba(255,255,255,0.08);
            }

            .tet-header-button:hover {
                background:
                    linear-gradient(
                        180deg,
                        #555,
                        #3b3b3b
                    );
                color: #fff;
            }

            .tet-body {
                padding: 10px;
                background: #202020;
            }

            .tet-error {
                margin-bottom: 10px;
                padding: 10px 12px;
                border: 1px solid #7b4141;
                border-radius: 4px;
                background: #3d2828;
                color: #efb0b0;
                font-size: 12px;
            }

            .tet-loading {
                min-height: 160px;
                display: grid;
                place-items: center;
                padding: 30px;
                color: #aaa;
                text-align: center;
                background: #202020;
            }

            .tet-loading strong {
                display: block;
                margin-bottom: 8px;
                color: #eee;
                font-size: 15px;
            }

            .tet-summary-grid {
                display: grid;
                grid-template-columns:
                    minmax(220px, 1.65fr)
                    repeat(4, minmax(125px, 1fr));
                gap: 8px;
            }

            .tet-card {
                min-width: 0;
                padding: 12px;
                border: 1px solid #222;
                border-radius: 4px;
                background:
                    linear-gradient(
                        180deg,
                        #383838,
                        #303030
                    );
                box-shadow:
                    inset 0 1px 0 rgba(255,255,255,0.035);
            }

            .tet-card-label {
                display: block;
                min-height: 22px;
                margin-bottom: 5px;
                color: #bbb;
                font-size: 10px;
                font-weight: 700;
                letter-spacing: 0.6px;
                line-height: 1.05;
            }

            .tet-current-course {
                color: #f4f4f4;
                font-size: 15px;
                line-height: 1.3;
                text-shadow:
                    0 1px 1px #000;
            }

            .tet-current-category {
                display: inline-flex;
                margin-top: 7px;
                padding: 3px 7px;
                border: 1px solid #555;
                border-radius: 3px;
                background: #292929;
                color: #ccc;
                font-size: 9px;
                font-weight: 700;
                letter-spacing: 0.4px;
            }

            .tet-stat-value {
                color: #f4f4f4;
                font-size: 19px;
                line-height: 1.1;
                text-shadow:
                    0 1px 1px #000;
            }

            .tet-time-value {
                color: #ddd;
            }

            .tet-plan-time-value {
                color: var(--tet-green-bright);
            }

            .tet-stat-subtext {
                display: block;
                margin-top: 6px;
                color: #888;
                font-size: 9px;
                line-height: 1.25;
            }

            .tet-course-progress {
                margin-top: 8px;
                height: 8px;
                overflow: hidden;
                border: 1px solid #111;
                border-radius: 4px;
                background:
                    linear-gradient(
                        180deg,
                        #171717,
                        #242424
                    );
                box-shadow:
                    inset 0 1px 2px rgba(0,0,0,0.7);
            }

            .tet-course-progress-fill {
                height: 100%;
                background:
                    linear-gradient(
                        180deg,
                        #9ad94f,
                        #4d9d27
                    );
                box-shadow:
                    inset 0 1px 0 rgba(255,255,255,0.3);
            }

            .tet-section {
                margin-top: 10px;
                overflow: hidden;
                border: 1px solid #222;
                border-radius: 4px;
                background: #303030;
            }

            .tet-section-header {
                min-height: 42px;
                display: flex;
                align-items: center;
                gap: 8px;
                padding: 7px 10px;
                border-bottom: 1px solid #222;
                background:
                    linear-gradient(
                        180deg,
                        #3e3e3e,
                        #333
                    );
            }

            .tet-section-header strong {
                color: #eee;
                font-size: 11px;
                letter-spacing: 0.5px;
                text-shadow:
                    0 1px 1px #000;
            }

            .tet-section-header span {
                color: #888;
                font-size: 9px;
            }

            .tet-primary-button,
            .tet-secondary-button,
            .tet-danger-button {
                min-height: 30px;
                padding: 0 11px;
                border-radius: 4px;
                cursor: pointer;
                font-size: 10px;
                font-weight: 700;
                letter-spacing: 0.3px;
            }

            .tet-primary-button {
                border: 1px solid #1d1d1d;
                background:
                    linear-gradient(
                        180deg,
                        #555,
                        #393939
                    );
                color: #eee;
                box-shadow:
                    inset 0 1px 0 rgba(255,255,255,0.1);
            }

            .tet-primary-button:hover {
                background:
                    linear-gradient(
                        180deg,
                        #626262,
                        #444
                    );
            }

            .tet-secondary-button {
                border: 1px solid #222;
                background:
                    linear-gradient(
                        180deg,
                        #484848,
                        #343434
                    );
                color: #bbb;
            }

            .tet-secondary-button:hover {
                color: #fff;
            }

            .tet-danger-button {
                border: 1px solid #693b3b;
                background:
                    linear-gradient(
                        180deg,
                        #5a3838,
                        #412929
                    );
                color: #e1a0a0;
            }

            .tet-plan-list {
                padding: 7px 10px;
                background: #303030;
            }

            .tet-plan-row {
                min-height: 44px;
                display: grid;
                grid-template-columns:
                    28px
                    minmax(0, 1fr)
                    auto;
                align-items: center;
                gap: 9px;
                padding: 7px 9px;
                border-bottom:
                    1px solid rgba(255,255,255,0.06);
            }

            .tet-plan-row:last-child {
                border-bottom: 0;
            }

            .tet-plan-row:hover {
                background:
                    rgba(255,255,255,0.025);
            }

            .tet-plan-row.tet-complete {
                opacity: 0.55;
            }

            .tet-plan-row.tet-current {
                background:
                    linear-gradient(
                        90deg,
                        rgba(112,179,63,0.12),
                        transparent
                    );
                box-shadow:
                    inset 3px 0 0 #70b33f;
            }

            .tet-plan-status {
                width: 24px;
                height: 24px;
                display: grid;
                place-items: center;
                border: 1px solid #4a4a4a;
                border-radius: 50%;
                background: #252525;
                color: #999;
                font-size: 11px;
                font-weight: 700;
            }

            .tet-complete .tet-plan-status {
                border-color: #598b37;
                background: #314326;
                color: #a8dc71;
            }

            .tet-current .tet-plan-status {
                border-color: #699c45;
                color: #a8dc71;
            }

            .tet-plan-course {
                min-width: 0;
            }

            .tet-plan-course strong {
                display: block;
                overflow: hidden;
                color: #eee;
                font-size: 12px;
                line-height: 1.25;
                text-overflow: ellipsis;
                white-space: nowrap;
            }

            .tet-plan-course span {
                display: block;
                margin-top: 3px;
                color: #888;
                font-size: 9px;
            }

            .tet-plan-state {
                padding: 3px 7px;
                border: 1px solid #555;
                border-radius: 3px;
                background: #292929;
                color: #aaa;
                font-size: 9px;
                font-weight: 700;
                letter-spacing: 0.3px;
            }

            .tet-state-current {
                border-color: #638e45;
                background: #33432a;
                color: #a8dc71;
            }

            .tet-state-complete {
                border-color: #527839;
                background: #2f3d27;
                color: #9bce69;
            }


            .tet-perks-grid {
                display: grid;
                grid-template-columns: repeat(2, minmax(0, 1fr));
                gap: 5px;
                padding: 7px;
                background: #303030;
            }

            .tet-perk-card {
                min-width: 0;
                padding: 7px 9px;
                border: 1px solid #222;
                border-radius: 4px;
                background: linear-gradient(180deg, #383838, #2e2e2e);
                box-shadow: inset 0 1px 0 rgba(255,255,255,0.035);
            }

            .tet-perk-top {
                display: flex;
                align-items: flex-start;
                gap: 7px;
            }

            .tet-perk-icon {
                width: 22px;
                height: 22px;
                flex: 0 0 22px;
                display: grid;
                place-items: center;
                border: 1px solid #4a4a4a;
                border-radius: 50%;
                background: #252525;
                color: #9bd85f;
                font-size: 11px;
            }

            .tet-perk-content {
                min-width: 0;
            }

            .tet-perk-content strong {
                display: block;
                color: #eee;
                font-size: 10px;
                line-height: 1.25;
            }

            .tet-perk-amount {
                display: inline-block;
                margin-top: 5px;
                color: #9bd85f;
                font-size: 17px;
                font-weight: 700;
                text-shadow: 0 1px 1px #000;
            }

            .tet-perk-courses {
                display: block;
                margin-top: 6px;
                color: #888;
                font-size: 9px;
                line-height: 1.4;
            }

            @media (max-width: 680px) {
                .tet-perks-grid {
                    grid-template-columns: 1fr;
                }
            }

            .tet-empty {
                padding: 24px 14px;
                color: #999;
                text-align: center;
                font-size: 11px;
            }

            .tet-empty strong {
                display: block;
                margin-bottom: 6px;
                color: #eee;
                font-size: 13px;
            }

            .tet-footer {
                min-height: 34px;
                display: flex;
                align-items: center;
                padding: 7px 10px;
                border-top: 1px solid #111;
                background: #282828;
                color: #777;
                font-size: 9px;
            }

            .tet-link-button {
                padding: 0;
                border: 0;
                background: transparent;
                color: var(--tet-link);
                cursor: pointer;
                font-size: 9px;
                font-weight: 700;
            }

            .tet-collapsed .tet-body,
            .tet-collapsed .tet-footer {
                display: none;
            }

            #${MODAL_ID} {
                position: fixed;
                inset: 0;
                z-index: 100000;
                display: grid;
                place-items: center;
                padding: 18px;
                background: rgba(0,0,0,0.72);
                backdrop-filter: blur(3px);
            }

            #${MODAL_ID},
            #${MODAL_ID} * {
                box-sizing: border-box;
            }

            .tet-modal {
                width: min(820px, 96vw);
                max-height: min(780px, 90vh);
                display: flex;
                flex-direction: column;
                overflow: hidden;
                border: 1px solid #111;
                border-radius: 5px;
                background: #202020;
                color: #eee;
                box-shadow:
                    0 20px 70px rgba(0,0,0,0.65);
                font-family:
                    Arial,
                    Helvetica,
                    sans-serif;
            }

            .tet-modal-header {
                min-height: 52px;
                display: flex;
                align-items: center;
                gap: 10px;
                padding: 9px 12px;
                border-bottom: 1px solid #111;
                background:
                    linear-gradient(
                        180deg,
                        #3c3c3c,
                        #303030
                    );
            }

            .tet-modal-header strong {
                display: block;
                color: #eee;
                font-size: 14px;
            }

            .tet-modal-header span {
                display: block;
                margin-top: 3px;
                color: #999;
                font-size: 10px;
            }

            .tet-modal-close {
                width: 34px;
                height: 32px;
                border: 1px solid #222;
                border-radius: 4px;
                background:
                    linear-gradient(
                        180deg,
                        #484848,
                        #333
                    );
                color: #bbb;
                cursor: pointer;
                font-size: 18px;
            }

            .tet-modal-controls {
                display: grid;
                grid-template-columns:
                    minmax(0, 1fr)
                    210px;
                gap: 8px;
                padding: 10px;
                border-bottom: 1px solid #222;
                background: #292929;
            }

            .tet-modal-controls input,
            .tet-modal-controls select {
                width: 100%;
                height: 34px;
                padding: 0 10px;
                border: 1px solid #555;
                border-radius: 4px;
                outline: none;
                background: #222;
                color: #eee;
                font-size: 11px;
            }

            .tet-modal-controls input:focus,
            .tet-modal-controls select:focus {
                border-color: #777;
            }

            .tet-modal-content {
                min-height: 0;
                display: grid;
                grid-template-columns:
                    minmax(0, 1fr)
                    minmax(300px, 0.75fr);
                overflow: hidden;
            }

            .tet-catalogue-pane,
            .tet-plan-pane {
                min-height: 0;
                display: flex;
                flex-direction: column;
            }

            .tet-catalogue-pane {
                border-right: 1px solid #222;
            }

            .tet-pane-title {
                min-height: 38px;
                display: flex;
                align-items: center;
                padding: 8px 10px;
                border-bottom: 1px solid #222;
                background: #333;
                color: #bbb;
                font-size: 10px;
                font-weight: 700;
            }

            .tet-course-results,
            .tet-modal-plan-list {
                min-height: 0;
                overflow-y: auto;
                padding: 7px;
                background: #292929;
            }

            .tet-course-option {
                min-height: 46px;
                display: grid;
                grid-template-columns:
                    minmax(0, 1fr)
                    auto;
                align-items: center;
                gap: 8px;
                padding: 8px;
                border-bottom:
                    1px solid rgba(255,255,255,0.06);
            }

            .tet-course-option strong {
                display: block;
                color: #eee;
                font-size: 11px;
            }

            .tet-course-option span {
                display: block;
                margin-top: 3px;
                color: #888;
                font-size: 9px;
            }

            .tet-course-option button {
                min-width: 58px;
                height: 27px;
                border: 1px solid #222;
                border-radius: 4px;
                background:
                    linear-gradient(
                        180deg,
                        #555,
                        #393939
                    );
                color: #eee;
                cursor: pointer;
                font-size: 9px;
                font-weight: 700;
            }

            .tet-course-option button:disabled {
                background: #333;
                color: #777;
                cursor: default;
            }

            .tet-modal-plan-row {
                display: grid;
                grid-template-columns:
                    minmax(0, 1fr)
                    auto;
                gap: 7px;
                align-items: center;
                padding: 8px;
                border-bottom:
                    1px solid rgba(255,255,255,0.06);
            }

            .tet-modal-plan-row strong {
                display: block;
                overflow: hidden;
                color: #eee;
                font-size: 11px;
                text-overflow: ellipsis;
                white-space: nowrap;
            }

            .tet-modal-plan-row span {
                display: block;
                margin-top: 3px;
                color: #888;
                font-size: 9px;
            }

            .tet-row-actions {
                display: flex;
                gap: 4px;
            }

            .tet-row-actions button {
                width: 27px;
                height: 27px;
                border: 1px solid #222;
                border-radius: 4px;
                background:
                    linear-gradient(
                        180deg,
                        #484848,
                        #333
                    );
                color: #bbb;
                cursor: pointer;
            }

            .tet-row-actions button:hover {
                color: #fff;
            }

            .tet-row-actions button.tet-remove {
                color: #d88;
            }

            .tet-modal-footer {
                min-height: 44px;
                display: flex;
                align-items: center;
                gap: 8px;
                padding: 8px 10px;
                border-top: 1px solid #222;
                background: #292929;
            }

            @media (max-width: 1000px) {
                .tet-summary-grid {
                    grid-template-columns:
                        repeat(
                            2,
                            minmax(0, 1fr)
                        );
                }

                .tet-current-card {
                    grid-column:
                        1 / -1;
                }
            }

            @media (max-width: 820px) {
                .tet-modal-content {
                    display: block;
                    overflow-y: auto;
                }

                .tet-catalogue-pane {
                    border-right: 0;
                    border-bottom:
                        1px solid #222;
                }

                .tet-course-results,
                .tet-modal-plan-list {
                    max-height: 310px;
                }
            }

            @media (max-width: 540px) {
                .tet-brand p {
                    display: none;
                }

                .tet-summary-grid {
                    grid-template-columns:
                        1fr;
                }

                .tet-current-card {
                    grid-column: auto;
                }

                .tet-section-header {
                    flex-wrap: wrap;
                }

                .tet-modal-controls {
                    grid-template-columns:
                        1fr;
                }
            }
        `;

        document.head.appendChild(
            style
        );
    }

    /* =========================================================
       TRACKER CREATION
       ========================================================= */

    function createTracker() {
        if (
            document.getElementById(
                TRACKER_ID
            )
        ) {
            return;
        }

        const educationRoot =
            getEducationRoot();

        if (!educationRoot) {
            return;
        }

        const tracker =
            document.createElement(
                'section'
            );

        tracker.id =
            TRACKER_ID;

        educationRoot.insertAdjacentElement(
            'afterend',
            tracker
        );

        renderTracker();
    }

    /* =========================================================
       TRACKER RENDER
       ========================================================= */

    function renderTracker() {
        const tracker =
            document.getElementById(
                TRACKER_ID
            );

        if (!tracker) {
            return;
        }

        const collapsed =
            localStorage.getItem(
                STORAGE.collapsed
            ) === 'true';

        if (state.loading) {
            tracker.innerHTML = `
                <div class="tet-shell">
                    <header class="tet-header">
                        <div class="tet-brand-mark">
                            🎓
                        </div>

                        <div class="tet-brand">
                            <strong class="tet-brand-title">
                                EDUCATION TRACKER
                            </strong>

                            <p>
                                COURSE PROGRESS & PERSONAL EDUCATION PLAN
                            </p>
                        </div>
                    </header>

                    <div class="tet-loading">
                        <div>
                            <strong>
                                Loading Education Data
                            </strong>

                            Reading current course and education progress…
                        </div>
                    </div>
                </div>
            `;

            return;
        }

        const categoryProgress =
            calculateCategoryProgress();

        const currentCourse =
            state.currentCourse;

        const nowTimestamp =
            Math.floor(
                Date.now() / 1000
            );

        const secondsRemaining =
            currentCourse?.endTimestamp
                ? Math.max(
                    0,
                    currentCourse.endTimestamp -
                    nowTimestamp
                )
                : 0;

        const completedPlanCount =
            state.plan.filter(
                item =>
                    item.complete
            ).length;

        const nextPlanItem =
            getNextPlanItem();

        const planTimeRemaining =
            getPlanTimeRemaining();

        const remainingPlanCount =
            getRemainingPlanCount();

        const activePerks =
            buildActivePerks();

        const categoryPercentage =
            categoryProgress.total > 0
                ? (
                    categoryProgress.completed /
                    categoryProgress.total
                ) * 100
                : 0;

        const planRows =
            state.plan.map(
                (
                    item,
                    index
                ) => {
                    const current =
                        isCurrentPlanItem(
                            item
                        );

                    let statusText =
                        'PLANNED';

                    let statusClass =
                        '';

                    let statusIcon =
                        index + 1;

                    if (item.complete) {
                        statusText =
                            'COMPLETE';

                        statusClass =
                            'tet-state-complete';

                        statusIcon =
                            '✓';

                    } else if (current) {
                        statusText =
                            'IN PROGRESS';

                        statusClass =
                            'tet-state-current';

                        statusIcon =
                            '◉';
                    }

                    return `
                        <div
                            class="
                                tet-plan-row
                                ${
                                    item.complete
                                        ? 'tet-complete'
                                        : ''
                                }
                                ${
                                    current
                                        ? 'tet-current'
                                        : ''
                                }
                            "
                        >
                            <div class="tet-plan-status">
                                ${escapeHtml(
                                    statusIcon
                                )}
                            </div>

                            <div class="tet-plan-course">
                                <strong>
                                    ${escapeHtml(
                                        item.name
                                    )}
                                </strong>

                                <span>
                                    ${escapeHtml(
                                        item.category
                                    )}

                                    ${
                                        (
                                            current &&
                                            currentCourse?.endTimestamp
                                        )
                                            ? ` · ${escapeHtml(
                                                formatPlanDuration(
                                                    secondsRemaining
                                                )
                                            )}`
                                            : (
                                                item.duration
                                                    ? ` · ${escapeHtml(
                                                        formatPlanDuration(
                                                            item.duration
                                                        )
                                                    )}`
                                                    : ''
                                            )
                                    }
                                </span>
                            </div>

                            <div
                                class="
                                    tet-plan-state
                                    ${statusClass}
                                "
                            >
                                ${statusText}
                            </div>
                        </div>
                    `;
                }
            ).join('');

        tracker.innerHTML = `
            <div
                class="
                    tet-shell
                    ${
                        collapsed
                            ? 'tet-collapsed'
                            : ''
                    }
                "
            >
                <header class="tet-header">
                    <div class="tet-brand-mark">
                        🎓
                    </div>

                    <div class="tet-brand">
                        <strong class="tet-brand-title">
                                EDUCATION TRACKER
                            </strong>

                        <p>
                            COURSE PROGRESS & PERSONAL EDUCATION PLAN
                        </p>
                    </div>

                    <div class="tet-spacer"></div>

                    <button
                        id="tet-refresh"
                        class="tet-header-button"
                        type="button"
                        title="Refresh education data"
                    >
                        ↻
                    </button>

                    <button
                        id="tet-settings"
                        class="tet-header-button"
                        type="button"
                        title="API settings"
                    >
                        ⚙
                    </button>

                    <button
                        id="tet-collapse"
                        class="tet-header-button"
                        type="button"
                    >
                        ${
                            collapsed
                                ? '⌄'
                                : '⌃'
                        }
                    </button>
                </header>

                <div class="tet-body">
                    ${
                        state.error
                            ? `
                                <div class="tet-error">
                                    ${escapeHtml(
                                        state.error
                                    )}
                                </div>
                            `
                            : ''
                    }

                    <div class="tet-summary-grid">
                        <div class="tet-card tet-current-card">
                            <span class="tet-card-label">
                                CURRENT COURSE
                            </span>

                            <strong class="tet-current-course">
                                ${
                                    currentCourse?.name
                                        ? escapeHtml(
                                            currentCourse.name
                                        )
                                        : 'No active education course'
                                }
                            </strong>

                            ${
                                currentCourse?.category
                                    ? `
                                        <span class="tet-current-category">
                                            ${escapeHtml(
                                                currentCourse.category
                                                    .toUpperCase()
                                            )}
                                        </span>
                                    `
                                    : ''
                            }
                        </div>

                        <div class="tet-card">
                            <span class="tet-card-label">
                                TIME REMAINING
                            </span>

                            <strong
                                id="tet-time-remaining"
                                class="
                                    tet-stat-value
                                    tet-time-value
                                "
                            >
                                ${
                                    currentCourse?.endTimestamp
                                        ? escapeHtml(
                                            formatDuration(
                                                secondsRemaining
                                            )
                                        )
                                        : '—'
                                }
                            </strong>

                            <span class="tet-stat-subtext">
                                ${
                                    currentCourse?.endTimestamp
                                        ? `Finishes ${escapeHtml(
                                            formatDateTime(
                                                currentCourse.endTimestamp
                                            )
                                        )}`
                                        : 'No completion time available'
                                }
                            </span>
                        </div>

                        <div class="tet-card">
                            <span class="tet-card-label">
                                CATEGORY REMAINING
                            </span>

                            <strong class="tet-stat-value">
                                ${formatNumber(
                                    categoryProgress.remaining
                                )}
                            </strong>

                            <span class="tet-stat-subtext">
                                ${formatNumber(
                                    categoryProgress.completed
                                )}
                                of
                                ${formatNumber(
                                    categoryProgress.total
                                )}
                                completed
                            </span>

                            <div class="tet-course-progress">
                                <div
                                    class="tet-course-progress-fill"
                                    style="
                                        width:
                                        ${Math.max(
                                            0,
                                            Math.min(
                                                100,
                                                categoryPercentage
                                            )
                                        )}%;
                                    "
                                ></div>
                            </div>
                        </div>

                        <div class="tet-card">
                            <span class="tet-card-label">
                                NEXT PLANNED COURSE
                            </span>

                            <strong
                                class="tet-current-course"
                                style="font-size: 13px;"
                            >
                                ${
                                    nextPlanItem
                                        ? escapeHtml(
                                            nextPlanItem.name
                                        )
                                        : 'No course queued'
                                }
                            </strong>

                            <span class="tet-stat-subtext">
                                ${
                                    nextPlanItem
                                        ? escapeHtml(
                                            nextPlanItem.category
                                        )
                                        : 'Build your personal course plan'
                                }
                            </span>
                        </div>

                        <div class="tet-card">
                            <span class="tet-card-label">
                                PLANNED TIME REMAINING
                            </span>

                            <strong
                                class="
                                    tet-stat-value
                                    tet-plan-time-value
                                "
                            >
                                ${escapeHtml(
                                    formatPlanDuration(
                                        planTimeRemaining
                                    )
                                )}
                            </strong>

                            <span class="tet-stat-subtext">
                                ${formatNumber(
                                    remainingPlanCount
                                )}
                                planned
                                ${
                                    remainingPlanCount === 1
                                        ? 'course'
                                        : 'courses'
                                }
                                remaining
                            </span>
                        </div>
                    </div>

                    <section class="tet-section">
                        <header class="tet-section-header">
                            <strong>
                                MY EDUCATION PLAN
                            </strong>

                            <span>
                                ${formatNumber(
                                    completedPlanCount
                                )}
                                /
                                ${formatNumber(
                                    state.plan.length
                                )}
                                COMPLETE
                            </span>

                            <div class="tet-spacer"></div>

                            ${
                                completedPlanCount > 0
                                    ? `
                                        <button
                                            id="tet-clear-completed"
                                            class="tet-secondary-button"
                                            type="button"
                                        >
                                            CLEAR COMPLETED
                                        </button>
                                    `
                                    : ''
                            }

                            <button
                                id="tet-manage-plan"
                                class="tet-primary-button"
                                type="button"
                            >
                                MANAGE PLAN
                            </button>
                        </header>

                        ${
                            state.plan.length
                                ? `
                                    <div class="tet-plan-list">
                                        ${planRows}
                                    </div>
                                `
                                : `
                                    <div class="tet-empty">
                                        <strong>
                                            Your education plan is empty.
                                        </strong>

                                        Add courses to create
                                        a personal ordered checklist.
                                    </div>
                                `
                        }
                    </section>

                    <section class="tet-section">
                        <header class="tet-section-header">
                            <strong>
                                ACTIVE EDUCATION PERKS
                            </strong>

                            <span>
                                ${formatNumber(activePerks.length)} ACTIVE
                            </span>
                        </header>

                        ${
                            activePerks.length
                                ? `
                                    <div class="tet-perks-grid">
                                        ${activePerks.map(perk => `
                                            <div class="tet-perk-card">
                                                <div class="tet-perk-top">
                                                    <div class="tet-perk-icon">
                                                        ✓
                                                    </div>

                                                    <div class="tet-perk-content">
                                                        <strong>
                                                            ${escapeHtml(
                                                                perk.title
                                                            )}
                                                        </strong>

                                                        ${
                                                            perk.type === 'percentage'
                                                                ? `
                                                                    <span class="tet-perk-amount">
                                                                        +${formatNumber(perk.amount)}% ACTIVE
                                                                    </span>
                                                                `
                                                                : ''
                                                        }
                                                    </div>
                                                </div>
                                            </div>
                                        `).join('')}
                                    </div>
                                `
                                : `
                                    <div class="tet-empty">
                                        <strong>
                                            No cached education perks found.
                                        </strong>

                                        Visit the Torn home page once to sync
                                        your active Education perks.
                                    </div>
                                `
                        }
                    </section>
                </div>

                <footer class="tet-footer">
                    <span>
                        Completed courses are checked automatically
                        from your Torn education data.
                    </span>

                    <div class="tet-spacer"></div>

                    <button
                        id="tet-footer-settings"
                        class="tet-link-button"
                        type="button"
                    >
                        API SETTINGS
                    </button>
                </footer>
            </div>
        `;

        wireTrackerEvents();
        startCountdown();
    }

    /* =========================================================
       TRACKER EVENTS
       ========================================================= */

    function wireTrackerEvents() {
        document.getElementById(
            'tet-refresh'
        )?.addEventListener(
            'click',
            () =>
                loadEducationData(true)
        );

        document.getElementById(
            'tet-settings'
        )?.addEventListener(
            'click',
            showApiKeyDialog
        );

        document.getElementById(
            'tet-footer-settings'
        )?.addEventListener(
            'click',
            showApiKeyDialog
        );

        document.getElementById(
            'tet-collapse'
        )?.addEventListener(
            'click',
            () => {
                const currentlyCollapsed =
                    localStorage.getItem(
                        STORAGE.collapsed
                    ) === 'true';

                localStorage.setItem(
                    STORAGE.collapsed,
                    String(
                        !currentlyCollapsed
                    )
                );

                renderTracker();
            }
        );

        document.getElementById(
            'tet-manage-plan'
        )?.addEventListener(
            'click',
            openPlanModal
        );

        document.getElementById(
            'tet-clear-completed'
        )?.addEventListener(
            'click',
            () => {
                if (
                    !window.confirm(
                        'Remove all completed courses from your education plan?'
                    )
                ) {
                    return;
                }

                clearCompletedPlanItems();
            }
        );
    }

    /* =========================================================
       CURRENT COURSE COUNTDOWN
       ========================================================= */

    function startCountdown() {
        if (
            state.countdownTimer
        ) {
            clearInterval(
                state.countdownTimer
            );
        }

        state.countdownTimer =
            window.setInterval(
                () => {
                    const element =
                        document.getElementById(
                            'tet-time-remaining'
                        );

                    if (
                        !element ||
                        !state.currentCourse
                            ?.endTimestamp
                    ) {
                        return;
                    }

                    const nowTimestamp =
                        Math.floor(
                            Date.now() /
                            1000
                        );

                    const secondsRemaining =
                        Math.max(
                            0,
                            state.currentCourse
                                .endTimestamp -
                            nowTimestamp
                        );

                    element.textContent =
                        formatDuration(
                            secondsRemaining
                        );

                    if (
                        secondsRemaining <= 0
                    ) {
                        clearInterval(
                            state.countdownTimer
                        );

                        window.setTimeout(
                            () =>
                                loadEducationData(
                                    true
                                ),
                            3000
                        );
                    }
                },
                1000
            );
    }

    /* =========================================================
       API KEY DIALOG
       ========================================================= */

    function showApiKeyDialog() {
        document.getElementById(
            API_DIALOG_ID
        )?.remove();

        const overlay =
            document.createElement(
                'div'
            );

        overlay.id =
            API_DIALOG_ID;

        overlay.style.cssText = `
            position: fixed;
            inset: 0;
            z-index: 100001;
            display: grid;
            place-items: center;
            padding: 18px;
            background: rgba(0,0,0,0.72);
            backdrop-filter: blur(3px);
        `;

        overlay.innerHTML = `
            <div
                style="
                    width: min(440px, 96vw);
                    padding: 14px;
                    border: 1px solid #111;
                    border-radius: 5px;
                    background: #303030;
                    color: #eee;
                    box-shadow:
                        0 20px 70px rgba(0,0,0,0.65);
                    font-family:
                        Arial,
                        Helvetica,
                        sans-serif;
                "
            >
                <div
                    style="
                        color: #eee;
                        font-size: 14px;
                        font-weight: 700;
                    "
                >
                    EDUCATION TRACKER API KEY
                </div>

                <p
                    style="
                        margin: 8px 0 12px;
                        color: #aaa;
                        font-size: 11px;
                        line-height: 1.45;
                    "
                >
                    Enter your Torn API key.
                    The key is stored only in this browser.
                </p>

                <input
                    id="tet-api-key-input"
                    type="password"
                    autocomplete="off"
                    value="${escapeHtml(
                        getApiKey()
                    )}"
                    placeholder="Enter Torn API key"
                    style="
                        width: 100%;
                        height: 36px;
                        padding: 0 10px;
                        border: 1px solid #555;
                        border-radius: 4px;
                        outline: none;
                        background: #222;
                        color: #eee;
                        font-size: 12px;
                    "
                >

                <div
                    style="
                        display: flex;
                        justify-content: flex-end;
                        gap: 8px;
                        margin-top: 12px;
                    "
                >
                    <button
                        id="tet-api-cancel"
                        type="button"
                        style="
                            min-height: 30px;
                            padding: 0 11px;
                            border: 1px solid #222;
                            border-radius: 4px;
                            background: #444;
                            color: #bbb;
                            cursor: pointer;
                        "
                    >
                        CANCEL
                    </button>

                    <button
                        id="tet-api-save"
                        type="button"
                        style="
                            min-height: 30px;
                            padding: 0 11px;
                            border: 1px solid #222;
                            border-radius: 4px;
                            background:
                                linear-gradient(
                                    180deg,
                                    #555,
                                    #393939
                                );
                            color: #eee;
                            cursor: pointer;
                            font-weight: 700;
                        "
                    >
                        SAVE & LOAD
                    </button>
                </div>
            </div>
        `;

        document.body.appendChild(
            overlay
        );

        const input =
            document.getElementById(
                'tet-api-key-input'
            );

        input?.focus();

        document.getElementById(
            'tet-api-cancel'
        )?.addEventListener(
            'click',
            () =>
                overlay.remove()
        );

        document.getElementById(
            'tet-api-save'
        )?.addEventListener(
            'click',
            () => {
                const apiKey =
                    input?.value.trim() ||
                    '';

                if (
                    !/^[A-Za-z0-9]{16}$/
                        .test(apiKey)
                ) {
                    window.alert(
                        'Please enter a valid 16-character Torn API key.'
                    );

                    return;
                }

                saveApiKey(
                    apiKey
                );

                localStorage.removeItem(
                    STORAGE.catalogueCache
                );

                localStorage.removeItem(
                    STORAGE.userCache
                );

                overlay.remove();

                loadEducationData(
                    true
                );
            }
        );

        overlay.addEventListener(
            'click',
            event => {
                if (
                    event.target ===
                    overlay
                ) {
                    overlay.remove();
                }
            }
        );
    }

    /* =========================================================
       PLAN MODAL
       ========================================================= */

    function openPlanModal() {
        document.getElementById(
            MODAL_ID
        )?.remove();

        const modal =
            document.createElement(
                'div'
            );

        modal.id =
            MODAL_ID;

        document.body.appendChild(
            modal
        );

        renderPlanModal();
    }

    function closePlanModal() {
        document.getElementById(
            MODAL_ID
        )?.remove();
    }

    function renderPlanModal() {
        const modal =
            document.getElementById(
                MODAL_ID
            );

        if (!modal) {
            return;
        }

        const previousSearch =
            modal.querySelector(
                '#tet-course-search'
            )?.value ||
            '';

        const previousCategory =
            modal.querySelector(
                '#tet-course-category'
            )?.value ||
            'All Categories';

        const categories = [
            'All Categories',
            ...new Set(
                state.catalogue.map(
                    course =>
                        course.category
                )
            )
        ];

        modal.innerHTML = `
            <div class="tet-modal">
                <header class="tet-modal-header">
                    <div class="tet-brand-mark">
                        🎓
                    </div>

                    <div>
                        <strong>
                            MANAGE EDUCATION PLAN
                        </strong>

                        <span>
                            Add courses and arrange them
                            in your preferred order.
                        </span>
                    </div>

                    <div class="tet-spacer"></div>

                    <button
                        id="tet-modal-close"
                        class="tet-modal-close"
                        type="button"
                    >
                        ×
                    </button>
                </header>

                <div class="tet-modal-controls">
                    <input
                        id="tet-course-search"
                        type="search"
                        value="${escapeHtml(
                            previousSearch
                        )}"
                        placeholder="Search courses…"
                    >

                    <select
                        id="tet-course-category"
                    >
                        ${
                            categories.map(
                                category => `
                                    <option
                                        value="${escapeHtml(
                                            category
                                        )}"
                                        ${
                                            category ===
                                            previousCategory
                                                ? 'selected'
                                                : ''
                                        }
                                    >
                                        ${escapeHtml(
                                            category
                                        )}
                                    </option>
                                `
                            ).join('')
                        }
                    </select>
                </div>

                <div class="tet-modal-content">
                    <section class="tet-catalogue-pane">
                        <div class="tet-pane-title">
                            AVAILABLE COURSES
                        </div>

                        <div
                            id="tet-course-results"
                            class="tet-course-results"
                        ></div>
                    </section>

                    <section class="tet-plan-pane">
                        <div class="tet-pane-title">
                            MY ORDERED PLAN
                        </div>

                        <div class="tet-modal-plan-list">
                            ${
                                state.plan.length
                                    ? state.plan.map(
                                        (
                                            item,
                                            index
                                        ) => `
                                            <div class="tet-modal-plan-row">
                                                <div>
                                                    <strong>
                                                        ${index + 1}.
                                                        ${escapeHtml(
                                                            item.name
                                                        )}
                                                    </strong>

                                                    <span>
                                                        ${escapeHtml(
                                                            item.category
                                                        )}

                                                        ${
                                                            item.duration
                                                                ? ` · ${escapeHtml(
                                                                    formatPlanDuration(
                                                                        item.duration
                                                                    )
                                                                )}`
                                                                : ''
                                                        }

                                                        ${
                                                            item.complete
                                                                ? ' · COMPLETE'
                                                                : ''
                                                        }
                                                    </span>
                                                </div>

                                                <div class="tet-row-actions">
                                                    <button
                                                        type="button"
                                                        data-plan-up="${escapeHtml(
                                                            item.id
                                                        )}"
                                                    >
                                                        ↑
                                                    </button>

                                                    <button
                                                        type="button"
                                                        data-plan-down="${escapeHtml(
                                                            item.id
                                                        )}"
                                                    >
                                                        ↓
                                                    </button>

                                                    <button
                                                        type="button"
                                                        class="tet-remove"
                                                        data-plan-remove="${escapeHtml(
                                                            item.id
                                                        )}"
                                                    >
                                                        ×
                                                    </button>
                                                </div>
                                            </div>
                                        `
                                    ).join('')
                                    : `
                                        <div class="tet-empty">
                                            No courses added yet.
                                        </div>
                                    `
                            }
                        </div>
                    </section>
                </div>

                <footer class="tet-modal-footer">
                    <button
                        id="tet-modal-clear"
                        class="tet-danger-button"
                        type="button"
                    >
                        CLEAR PLAN
                    </button>

                    <div class="tet-spacer"></div>

                    <button
                        id="tet-modal-done"
                        class="tet-primary-button"
                        type="button"
                    >
                        DONE
                    </button>
                </footer>
            </div>
        `;

        wirePlanModalEvents();
        renderCourseResults();
    }

    function renderCourseResults() {
        const modal =
            document.getElementById(
                MODAL_ID
            );

        const results =
            modal?.querySelector(
                '#tet-course-results'
            );

        if (!results) {
            return;
        }

        const search =
            normaliseText(
                modal.querySelector(
                    '#tet-course-search'
                )?.value ||
                ''
            ).toLowerCase();

        const category =
            modal.querySelector(
                '#tet-course-category'
            )?.value ||
            'All Categories';

        const filtered =
            state.catalogue.filter(
                course => {
                    const searchMatches =
                        !search ||
                        course.name
                            .toLowerCase()
                            .includes(search) ||
                        course.category
                            .toLowerCase()
                            .includes(search);

                    const categoryMatches =
                        category ===
                        'All Categories' ||
                        course.category ===
                        category;

                    return (
                        searchMatches &&
                        categoryMatches
                    );
                }
            );

        results.innerHTML =
            filtered.length
                ? filtered.map(
                    course => {
                        const alreadyAdded =
                            state.plan.some(
                                item =>
                                    String(item.id) ===
                                    course.id
                            );

                        const complete =
                            state.completedCourseIds
                                .has(course.id);

                        return `
                            <div class="tet-course-option">
                                <div>
                                    <strong>
                                        ${escapeHtml(
                                            course.name
                                        )}
                                    </strong>

                                    <span>
                                        ${escapeHtml(
                                            course.category
                                        )}

                                        ${
                                            course.duration
                                                ? ` · ${escapeHtml(
                                                    formatPlanDuration(
                                                        course.duration
                                                    )
                                                )}`
                                                : ''
                                        }

                                        ${
                                            complete
                                                ? ' · ALREADY COMPLETE'
                                                : ''
                                        }
                                    </span>
                                </div>

                                <button
                                    type="button"
                                    data-course-add="${escapeHtml(
                                        course.id
                                    )}"
                                    ${
                                        alreadyAdded
                                            ? 'disabled'
                                            : ''
                                    }
                                >
                                    ${
                                        alreadyAdded
                                            ? 'ADDED'
                                            : 'ADD'
                                    }
                                </button>
                            </div>
                        `;
                    }
                ).join('')
                : `
                    <div class="tet-empty">
                        No matching courses found.
                    </div>
                `;

        results.querySelectorAll(
            '[data-course-add]'
        ).forEach(
            button => {
                button.addEventListener(
                    'click',
                    () => {
                        addCourseToPlan(
                            button.dataset
                                .courseAdd
                        );
                    }
                );
            }
        );
    }

    function wirePlanModalEvents() {
        const modal =
            document.getElementById(
                MODAL_ID
            );

        if (!modal) {
            return;
        }

        modal.querySelector(
            '#tet-modal-close'
        )?.addEventListener(
            'click',
            closePlanModal
        );

        modal.querySelector(
            '#tet-modal-done'
        )?.addEventListener(
            'click',
            closePlanModal
        );

        modal.querySelector(
            '#tet-course-search'
        )?.addEventListener(
            'input',
            renderCourseResults
        );

        modal.querySelector(
            '#tet-course-category'
        )?.addEventListener(
            'change',
            renderCourseResults
        );

        modal.querySelectorAll(
            '[data-plan-up]'
        ).forEach(
            button => {
                button.addEventListener(
                    'click',
                    () => {
                        movePlanItem(
                            button.dataset
                                .planUp,
                            -1
                        );
                    }
                );
            }
        );

        modal.querySelectorAll(
            '[data-plan-down]'
        ).forEach(
            button => {
                button.addEventListener(
                    'click',
                    () => {
                        movePlanItem(
                            button.dataset
                                .planDown,
                            1
                        );
                    }
                );
            }
        );

        modal.querySelectorAll(
            '[data-plan-remove]'
        ).forEach(
            button => {
                button.addEventListener(
                    'click',
                    () => {
                        removeCourseFromPlan(
                            button.dataset
                                .planRemove
                        );
                    }
                );
            }
        );

        modal.querySelector(
            '#tet-modal-clear'
        )?.addEventListener(
            'click',
            () => {
                if (
                    !state.plan.length ||
                    !window.confirm(
                        'Clear your entire education plan?'
                    )
                ) {
                    return;
                }

                state.plan = [];

                savePlan();
                renderTracker();
                renderPlanModal();
            }
        );

        modal.addEventListener(
            'click',
            event => {
                if (
                    event.target ===
                    modal
                ) {
                    closePlanModal();
                }
            }
        );
    }

    /* =========================================================
       DATA LOADING
       ========================================================= */

    async function loadEducationData(
        forceRefresh = false
    ) {
        if (
            state.loading
        ) {
            return;
        }

        const apiKey =
            getApiKey();

        if (
            !/^[A-Za-z0-9]{16}$/
                .test(apiKey)
        ) {
            state.error =
                'Enter your Torn API key to load education progress.';

            renderTracker();
            showApiKeyDialog();

            return;
        }

        state.loading = true;
        state.error = '';

        renderTracker();

        try {
            let catalogueData =
                forceRefresh
                    ? null
                    : readCache(
                        STORAGE.catalogueCache
                    );

            let userData =
                forceRefresh
                    ? null
                    : readCache(
                        STORAGE.userCache
                    );

            if (
                !catalogueData
            ) {
                catalogueData =
                    await apiRequest(
                        '/torn/education'
                    );

                writeCache(
                    STORAGE.catalogueCache,
                    catalogueData
                );
            }

            if (
                !userData
            ) {
                userData =
                    await apiRequest(
                        '/user/education'
                    );

                writeCache(
                    STORAGE.userCache,
                    userData
                );
            }

            state.catalogue =
                normaliseCatalogueResponse(
                    catalogueData
                );

            const userEducation =
                normaliseUserEducationResponse(
                    userData
                );

            state.completedCourseIds =
                userEducation.completedIds;

            const domCurrentCourse =
                readCurrentCourseFromDom();

            state.currentCourse =
                enrichCurrentCourse(
                    userEducation.currentCourse ||
                    domCurrentCourse
                );

            if (
                state.currentCourse &&
                domCurrentCourse?.endTimestamp
            ) {
                state.currentCourse
                    .endTimestamp =
                    domCurrentCourse
                        .endTimestamp;
            }

            if (
                state.currentCourse &&
                !state.currentCourse.name &&
                domCurrentCourse?.name
            ) {
                state.currentCourse.name =
                    domCurrentCourse.name;
            }

            state.currentCourse =
                enrichCurrentCourse(
                    state.currentCourse
                );

            synchronisePlan();

            if (
                !state.catalogue.length
            ) {
                throw new Error(
                    'Torn returned education data, but the course catalogue could not be read.'
                );
            }

        } catch (error) {
            state.error =
                error.message ||
                'Education data could not be loaded.';

            const domCurrentCourse =
                readCurrentCourseFromDom();

            if (
                domCurrentCourse
            ) {
                state.currentCourse =
                    enrichCurrentCourse(
                        domCurrentCourse
                    );
            }

        } finally {
            state.loading = false;

            renderTracker();
            watchForCurrentCourseTimestamp();

            if (
                state.catalogue.length &&
                state.completedCourseIds.size
            ) {
                window.setTimeout(
                    runOutcomeHarvestInBackground,
                    0
                );
            }
        }
    }

    /* =========================================================
       PAGE OBSERVATION
       ========================================================= */

    function handlePageChange() {
        if (
            !isEducationPage()
        ) {
            stopCurrentCourseTimestampWatch();

            document.getElementById(
                TRACKER_ID
            )?.remove();

            document.getElementById(
                MODAL_ID
            )?.remove();

            return;
        }

        injectStyles();
        createTracker();
        watchForCurrentCourseTimestamp();

        if (
            document.getElementById(
                TRACKER_ID
            ) &&
            !state.loading &&
            !state.catalogue.length
        ) {
            loadEducationData(
                false
            );
        }
    }

    const pageObserver =
        new MutationObserver(
            () => {
                if (
                    window.location.href !==
                    state.lastUrl
                ) {
                    state.lastUrl =
                        window.location.href;

                    window.setTimeout(
                        handlePageChange,
                        250
                    );

                    return;
                }

                if (
                    isEducationPage() &&
                    !document.getElementById(
                        TRACKER_ID
                    ) &&
                    getEducationRoot()
                ) {
                    createTracker();

                    loadEducationData(
                        false
                    );
                }
            }
        );

    pageObserver.observe(
        document.documentElement,
        {
            childList: true,
            subtree: true
        }
    );

    /* =========================================================
       INITIALISE
       ========================================================= */

    function initialise() {

        if (isTornHomePage()) {
            captureEducationPerksFromHome();
            return;
        }

        if (
            !isEducationPage()
        ) {
            return;
        }

        injectStyles();

        const waitForEducation =
            window.setInterval(
                () => {
                    if (
                        !getEducationRoot()
                    ) {
                        return;
                    }

                    clearInterval(
                        waitForEducation
                    );

                    createTracker();

                    loadEducationData(
                        false
                    );
                },
                250
            );

        window.setTimeout(
            () => {
                clearInterval(
                    waitForEducation
                );
            },
            15000
        );
    }

    initialise();

})();