// ==UserScript==
// @name         RetroAchievements+ (HLTB + Tweaks)
// @namespace    https://bento.me/thevers
// @version      8
// @description  Adds how long to beat widget, as well as bunch of other tweaks.
// @author       VERS
// @icon         https://i.imgur.com/IYwhfMf.png
// @match        https://retroachievements.org/*
// @grant        GM_xmlhttpRequest
// @grant        GM.getValue
// @grant        GM.setValue
// @connect      www.steamgriddb.com
// @connect      howlongtobeat.com
// @license      MIT
// ==/UserScript==
(function () {
    'use strict';
    const AppState = {
        currentGameId: null,
        currentGameName: null,
        lastPath: window.location.pathname,
        observers: new Set(),
        intervals: new Set(),
        timeouts: new Set(),
        initialized: false,
        apiCallCache: new Map(),
        lastApiCall: new Map()
    };
    const myrientConsoleUrls = {
        "Game Boy": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Game%20Boy/"
        },
        "Game Boy Color": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Game%20Boy%20Color/"
        },
        "Game Boy Advance": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Game%20Boy%20Advance/"
        },
        "Arcade": {
            "FBNeo Non-Merged": "https://myrient.erista.me/files/Internet%20Archive/chadmaster/fbnarcade-fullnonmerged/arcade/"
        },
        "NES/Famicom": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Nintendo%20Entertainment%20System%20%28Headered%29/"
        },
        "SNES/Super Famicom": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Super%20Nintendo%20Entertainment%20System/"
        },
        "Nintendo 64": {
            "BigEndian": "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Nintendo%2064%20%28BigEndian%29/",
            "ByteSwapped": "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Nintendo%2064%20%28ByteSwapped%29/"
        },
        "GameCube": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Non-Redump%20-%20Nintendo%20-%20Nintendo%20GameCube/"
        },
        "Nintendo DS": {
            "Decrypted": "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Nintendo%20DS%20%28Decrypted%29/"
        },
        "Nintendo DSi": {
            "Decrypted": "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Nintendo%20DSi%20%28Decrypted%29/"
        },
        "Pokemon Mini": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Pokemon%20Mini/"
        },
        "Virtual Boy": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Virtual%20Boy/"
        },
        "PlayStation": {
            "Redump": "https://myrient.erista.me/files/Redump/Sony%20-%20PlayStation/"
        },
        "PlayStation 2": {
            "Redump": "https://myrient.erista.me/files/Redump/Sony%20-%20PlayStation%202/"
        },
        "PlayStation Portable": {
            "Redump": "https://myrient.erista.me/files/Redump/Sony%20-%20PlayStation%20Portable/"
        },
        "Atari 2600": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Atari%20-%20Atari%202600/"
        },
        "Atari 7800": {
            "BIN": "https://myrient.erista.me/files/No-Intro/Atari%20-%20Atari%207800%20%28BIN%29/"
        },
        "Atari Jaguar": {
            "ROM": "https://myrient.erista.me/files/No-Intro/Atari%20-%20Atari%20Jaguar%20%28ROM%29/",
            "J64": "https://myrient.erista.me/files/No-Intro/Atari%20-%20Atari%20Jaguar%20%28J64%29/"
        },
        "Atari Jaguar CD": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Non-Redump%20-%20Atari%20-%20Atari%20Jaguar%20CD/"
        },
        "Atari Lynx": {
            "LNX": "https://myrient.erista.me/files/No-Intro/Atari%20-%20Atari%20Lynx%20%28LNX%29/",
            "LYX": "https://myrient.erista.me/files/No-Intro/Atari%20-%20Atari%20Lynx%20%28LYX%29/"
        },
        "SG-1000": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Sega%20-%20SG-1000%20-%20SC-3000/"
        },
        "Master System": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Sega%20-%20Master%20System%20-%20Mark%20III/"
        },
        "Game Gear": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Sega%20-%20Game%20Gear/"
        },
        "Genesis/Mega Drive": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Sega%20-%20Mega%20Drive%20-%20Genesis/"
        },
        "Sega CD": {
            "Redump": "https://myrient.erista.me/files/Redump/Sega%20-%20Mega%20CD%20%26%20Sega%20CD/"
        },
        "32X": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Sega%20-%2032X/"
        },
        "Saturn": {
            "Redump": "https://myrient.erista.me/files/Redump/Sega%20-%20Saturn/"
        },
        "Dreamcast": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Non-Redump%20-%20Sega%20-%20Dreamcast/"
        },
        "PC Engine/TurboGrafx-16": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/NEC%20-%20PC%20Engine%20-%20TurboGrafx-16/"
        },
        "Amstrad CPC": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Amstrad%20-%20CPC%20%28Flux%29/"
        },
        "Apple II": {
            "A2R": "https://myrient.erista.me/files/No-Intro/Apple%20-%20II%20%28A2R%29/"
        },
        "Arcadia 2001": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Emerson%20-%20Arcadia%202001/"
        },
        "ColecoVision": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Coleco%20-%20ColecoVision/"
        },
        "Intellivision": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Mattel%20-%20Intellivision/"
        },
        "WonderSwan": {
            "NoIntro": "https://myrient.erista.me/files/No-Intro/Bandai%20-%20WonderSwan/"
        }
    };
    function cleanup() {
        console.log('[RA+] Cleaning up resources...');
        // Clear all observers
        AppState.observers.forEach(observer => {
            try {
                observer.disconnect();
            } catch (e) {
                console.error('[RA+] Error disconnecting observer:', e);
            }
        });
        AppState.observers.clear();
        AppState.intervals.forEach(intervalId => clearInterval(intervalId));
        AppState.intervals.clear();
        AppState.timeouts.forEach(timeoutId => clearTimeout(timeoutId));
        AppState.timeouts.clear();
        const bgStyle = document.getElementById('steamgriddb-bg-style');
        if (bgStyle) bgStyle.remove();
        if (AppState.apiCallCache.size > 100) {
            AppState.apiCallCache.clear();
        }
    }
    function createTrackedInterval(callback, delay) {
        const intervalId = setInterval(callback, delay);
        AppState.intervals.add(intervalId);
        return intervalId;
    }
    function createTrackedTimeout(callback, delay) {
        const timeoutId = setTimeout(() => {
            callback();
            AppState.timeouts.delete(timeoutId);
        }, delay);
        AppState.timeouts.add(timeoutId);
        return timeoutId;
    }
    function createTrackedObserver(callback, options) {
        const observer = new MutationObserver(callback);
        AppState.observers.add(observer);
        return observer;
    }
    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }
    function throttle(func, limit) {
        let inThrottle;
        return function(...args) {
            if (!inThrottle) {
                func.apply(this, args);
                inThrottle = true;
                setTimeout(() => inThrottle = false, limit);
            }
        };
    }
    function waitForElement(selector, timeout = 10000) {
        return new Promise((resolve, reject) => {
            const element = document.querySelector(selector);
            if (element) {
                resolve(element);
                return;
            }
            const observer = new MutationObserver(() => {
                const element = document.querySelector(selector);
                if (element) {
                    observer.disconnect();
                    resolve(element);
                }
            });
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
            createTrackedTimeout(() => {
                observer.disconnect();
                reject(new Error(`Timeout waiting for ${selector}`));
            }, timeout);
        });
    }
    function cleanFilename(name, removeBrackets, removeExtension) {
        if (!name) return '';
        let clean = name.trim();
        if (removeBrackets) {
            clean = clean.replace(/\(.*?\)|\{.*?\}|\[.*?\]/g, '').trim();
        }
        if (removeExtension) {
            clean = clean.replace(/\.[^.]+$/, '');
        }
        return clean;
    }
// Storage
    const CACHE_EXPIRATION = 7 * 24 * 60 * 60 * 1000; // 7 days
    const MAX_CACHE_SIZE = 1000;
    async function getCachedValue(key, maxAge = CACHE_EXPIRATION) {
        try {
            const cached = await GM.getValue(key, null);
            if (!cached) return null;
            if (cached.timestamp && (Date.now() - cached.timestamp > maxAge)) {
                await GM.setValue(key, null);
                return null;
            }
            return cached.data;
        } catch (e) {
            console.error(`[RA+] Error getting cached value for ${key}:`, e);
            return null;
        }
    }
    async function setCachedValue(key, data) {
        try {
            await GM.setValue(key, {
                data,
                timestamp: Date.now()
            });
        } catch (e) {
            console.error(`[RA+] Error setting cached value for ${key}:`, e);
        }
    }
    const API_RATE_LIMIT = 1000;
    function canMakeApiCall(apiName) {
        const lastCall = AppState.lastApiCall.get(apiName);
        if (!lastCall) return true;
        return (Date.now() - lastCall) > API_RATE_LIMIT;
    }
    function recordApiCall(apiName) {
        AppState.lastApiCall.set(apiName, Date.now());
    }
    async function cleanupOldCache() {
        try {
            const playtimeCache = await GM.getValue('playtime_cache', {});
            const backgrounds = await GM.getValue('steamgriddb_backgrounds', {});
            const playtimeKeys = Object.keys(playtimeCache);
            if (playtimeKeys.length > MAX_CACHE_SIZE) {
                const sortedKeys = playtimeKeys.slice(0, MAX_CACHE_SIZE / 2);
                const newCache = {};
                sortedKeys.forEach(key => {
                    newCache[key] = playtimeCache[key];
                });
                await GM.setValue('playtime_cache', newCache);
                console.log(`[RA+] Cleaned playtime cache: ${playtimeKeys.length} -> ${sortedKeys.length}`);
            }
            const bgKeys = Object.keys(backgrounds);
            if (bgKeys.length > MAX_CACHE_SIZE) {
                const sortedBgKeys = bgKeys.slice(0, MAX_CACHE_SIZE / 2);
                const newBgCache = {};
                sortedBgKeys.forEach(key => {
                    newBgCache[key] = backgrounds[key];
                });
                await GM.setValue('steamgriddb_backgrounds', newBgCache);
                console.log(`[RA+] Cleaned background cache: ${bgKeys.length} -> ${sortedBgKeys.length}`);
            }
        } catch (e) {
            console.error('[RA+] Error cleaning cache:', e);
        }
    }
// Time widget
    function extractMasteredTime() {
        try {
            const playtimeStats = document.querySelector('[data-testid="playtime-statistics"]');
            if (!playtimeStats) {
                console.log('[RA+] Playtime statistics element not found');
                return null;
            }
            const masteredSection = Array.from(playtimeStats.querySelectorAll('.flex.items-center.justify-between'))
                .find(section => {
                    const text = section.querySelector('.text-xs');
                    return text && text.textContent.trim() === 'Mastered';
                });
            if (!masteredSection) {
                console.log('[RA+] Mastered section not found');
                return null;
            }
            const timeElement = masteredSection.querySelector('.text-sm.text-neutral-300');
            if (!timeElement) {
                console.log('[RA+] Time element not found in mastered section');
                return null;
            }
            const timeText = timeElement.textContent.trim();
            console.log('[RA+] Found mastered time:', timeText);
            return timeText;
        } catch (e) {
            console.error('[RA+] Error extracting mastered time:', e);
            return null;
        }
    }
    function extractPlaytimeTimes() {
        try {
            const playtimeStats = document.querySelector('[data-testid="playtime-statistics"]');
            if (!playtimeStats) {
                console.log('[RA+] Playtime statistics element not found');
                return null;
            }
            const sections = Array.from(playtimeStats.querySelectorAll('.flex.items-center.justify-between'));
            let masteredTime = null;
            let beatTime = null;
            sections.forEach(section => {
                const labelElement = section.querySelector('.text-xs');
                if (!labelElement) return;
                const label = labelElement.textContent.trim();
                const timeElement = section.querySelector('.text-sm.text-neutral-300');
                if (timeElement) {
                    const timeText = timeElement.textContent.trim();
                    if (label === 'Mastered') {
                        masteredTime = timeText;
                        console.log('[RA+] Found mastered time:', timeText);
                    } else if (label === 'Beat the game') {
                        beatTime = timeText;
                        console.log('[RA+] Found beat time:', timeText);
                    }
                }
            });
            return { masteredTime, beatTime };
        } catch (e) {
            console.error('[RA+] Error extracting playtime:', e);
            return null;
        }
    }
    function parseTimeToHours(timeStr) {
        try {
            let totalHours = 0;
            const hoursMatch = timeStr.match(/(\d+)h/);
            if (hoursMatch) {
                totalHours += parseInt(hoursMatch[1]);
            }
            const minutesMatch = timeStr.match(/(\d+)m/);
            if (minutesMatch) {
                totalHours += parseInt(minutesMatch[1]) / 60;
            }
            return totalHours;
        } catch (e) {
            console.error('[RA+] Error parsing time:', e);
            return 0;
        }
    }
    function updatePlaytimeButton(button, times) {
        try {
            const { masteredTime, beatTime } = times;
            const primaryTime = masteredTime || beatTime;
            if (!primaryTime) {
                button.textContent = 'Playtime N/A';
                return;
            }
            let dotColor = 'gray';
            const totalHours = parseTimeToHours(primaryTime);
            if (totalHours < 30) {
                dotColor = 'green';
            } else if (totalHours <= 70) {
                dotColor = 'orange';
            } else {
                dotColor = 'red';
            }
            let html = '';
            if (masteredTime && beatTime) {
                html = `
                    <img src="https://i.imgur.com/c55HqhO.png" style="width:16px;height:16px;margin-left:7px;margin-right:4px;" alt="Beat">
                    ${beatTime}
                    <img src="https://i.imgur.com/1OubeR9.png" style="width:16px;height:16px;margin-left:7px;margin-right:4px;" alt="Mastered">
                    ${masteredTime}
                    <span style="display:inline-block;width:11px;height:11px;background-color:${dotColor};border-radius:50%;margin-left:7px;"></span>
                `;
            } else if (masteredTime) {
                html = `
                    <img src="https://i.imgur.com/1OubeR9.png" style="width:16px;height:16px;margin-left:7px;margin-right:4px;" alt="Mastered">
                    ${masteredTime}
                    <span style="display:inline-block;width:11px;height:11px;background-color:${dotColor};border-radius:50%;margin-left:7px;"></span>
                `;
            } else if (beatTime) {
                html = `
                    <img src="https://i.imgur.com/c55HqhO.png" style="width:16px;height:16px;margin-left:7px;margin-right:7px;" alt="Beat">
                    ${beatTime}
                `;
            }
            button.innerHTML = html;
            button.style.cursor = 'default';
        } catch (e) {
            console.error('[RA+] Error updating playtime button:', e);
        }
    }
    async function fetchPlaytimeData(gameId, button) {
        try {
            button.textContent = '⏳ Fetching…';
            button.style.cursor = 'default';
            console.log('[RA+] Fetching fresh playtime data for game:', gameId);
            await waitForElement('[data-testid="playtime-statistics"]', 10000);
            await new Promise(resolve => setTimeout(resolve, 500));
            const times = extractPlaytimeTimes();
            if (times && (times.masteredTime || times.beatTime)) {
                console.log('[RA+] Found playtime data for game:', gameId, '=', times);
                updatePlaytimeButton(button, times);
            } else {
                console.log('[RA+] No playtime data found for game:', gameId);
                button.textContent = 'Playtime N/A';
            }
        } catch (e) {
            console.error('[RA+] Playtime fetch error for game', gameId, ':', e);
            button.textContent = 'Playtime N/A';
        }
    }
// steamgriddb
    async function getSteamGridDBKey() {
        try {
            return await GM.getValue('steamgriddb_api_key', '');
        } catch (e) {
            console.error('[RA+] Error getting SteamGridDB key:', e);
            return '';
        }
    }
    async function setSteamGridDBKey(key) {
        try {
            // Basic validation
            const trimmedKey = key.trim();
            if (trimmedKey && trimmedKey.length < 10) {
                throw new Error('API key seems too short');
            }
            await GM.setValue('steamgriddb_api_key', trimmedKey);
            return true;
        } catch (e) {
            console.error('[RA+] Error setting SteamGridDB key:', e);
            return false;
        }
    }
    async function clearAllBackgrounds() {
        try {
            const backgrounds = await GM.getValue('steamgriddb_backgrounds', {});
            const keys = Object.keys(backgrounds);
            await GM.setValue('steamgriddb_backgrounds', {});
            const style = document.getElementById('steamgriddb-bg-style');
            if (style) style.remove();
            return keys.length;
        } catch (e) {
            console.error('[RA+] Error clearing backgrounds:', e);
            return 0;
        }
    }
    async function applyBackground(gameId, imageUrl) {
        try {
            if (!gameId || !imageUrl) return;
            const backgrounds = await GM.getValue('steamgriddb_backgrounds', {});
            backgrounds[gameId] = imageUrl;
            await GM.setValue('steamgriddb_backgrounds', backgrounds);
            let existingStyle = document.getElementById('steamgriddb-bg-style');
            if (!existingStyle) {
                existingStyle = document.createElement('style');
                existingStyle.id = 'steamgriddb-bg-style';
                document.head.appendChild(existingStyle);
            }
            existingStyle.textContent = `
                body {
                    background-image: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), url('${imageUrl}');
                    background-repeat: no-repeat;
                    background-attachment: fixed;
                    background-position: center center;
                    background-size: cover;
                    image-rendering: auto;
                }
            `;
        } catch (e) {
            console.error('[RA+] Error applying background:', e);
        }
    }
    async function loadSavedBackground(gameId) {
        try {
            if (!gameId) return;
            const backgrounds = await GM.getValue('steamgriddb_backgrounds', {});
            const imageUrl = backgrounds[gameId];
            if (imageUrl) {
                let style = document.getElementById('steamgriddb-bg-style');
                if (!style) {
                    style = document.createElement('style');
                    style.id = 'steamgriddb-bg-style';
                    document.head.appendChild(style);
                }
                style.textContent = `
                    body {
                        background-image: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), url('${imageUrl}');
                        background-repeat: no-repeat;
                        background-attachment: fixed;
                        background-position: center center;
                        background-size: cover;
                        image-rendering: auto;
                    }
                `;
            }
        } catch (e) {
            console.error('[RA+] Error loading saved background:', e);
        }
    }
    function steamGridDBApiRequest(url, apiKey) {
        return new Promise((resolve, reject) => {
            if (!url || !apiKey) {
                reject(new Error('Invalid SteamGridDB parameters'));
                return;
            }
            const timeoutId = setTimeout(() => reject(new Error('SteamGridDB timeout')), 15000);
            GM_xmlhttpRequest({
                method: 'GET',
                url,
                headers: { 'Authorization': `Bearer ${apiKey}` },
                onload: res => {
                    clearTimeout(timeoutId);
                    try {
                        if (res.status === 200) {
                            resolve(JSON.parse(res.responseText));
                        } else if (res.status === 401) {
                            reject(new Error('Invalid API key'));
                        } else {
                            reject(new Error(`SteamGridDB error: ${res.status}`));
                        }
                    } catch (e) {
                        reject(new Error('Failed to parse SteamGridDB response'));
                    }
                },
                onerror: () => {
                    clearTimeout(timeoutId);
                    reject(new Error('SteamGridDB network error'));
                },
                ontimeout: () => {
                    clearTimeout(timeoutId);
                    reject(new Error('SteamGridDB timeout'));
                }
            });
        });
    }
    async function showSteamGridDBPopup(gameId, gameName) {
        try {
            const apiKey = await getSteamGridDBKey();
            const popup = document.createElement('div');
            popup.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#272b30;color:#fff;padding:25px;border-radius:10px;z-index:10000;max-width:500px;box-shadow:0 0 30px rgba(0,0,0,0.8);';
            popup.innerHTML = `
                <div style="text-align:center;margin-bottom:20px;">
                    <img src="https://cdn2.steamgriddb.com/logo_thumb/a478c2b6c235580960cbae4a4ca4745e.png"
                         style="width:150px;height:auto;margin-bottom:10px;" alt="SteamGridDB">
                    <div style="font-size:12px;color:#fff;">Add your API key to load backgrounds on pages using SteamGridDB. It first fetches an image url from steamgriddb and on subsequent loads it loads the url from memory. So it might be slow on the first load.</div>
                </div>
                <div style="margin-bottom:15px;">
                    <label style="display:block;margin-bottom:5px;font-weight:bold;">API Key:</label>
                    <input type="text" id="sgdb-api-input" placeholder="Enter your API key or leave empty to disable"
                           style="width:100%;padding:8px;background:#1a1f24;border:1px solid #3a3f44;color:#fff;border-radius:5px;"
                           value="${apiKey}">
                    <p style="font-size:12px;color:#aaa;margin-top:5px;">
                        Get your API key from
                        <a href="https://www.steamgriddb.com/profile/preferences/api" target="_blank" style="color:#4a9eff;">
                            steamgriddb.com/profile/preferences/api
                        </a>
                    </p>
                </div>
                <div style="display:flex;gap:10px;justify-content:space-between;flex-wrap:wrap;">
                    <button id="sgdb-close" class="btn" style="background:#b24953;color:#fff;font-weight:bold;padding:10px;display:flex;align-items:center;gap:5px;">
                        <img src="https://i.imgur.com/spBpaqo.png" style="width:16px;height:16px;" alt="Close">
                        Close
                    </button>
                    <div style="display:flex;gap:10px;flex-wrap:wrap;">
                        <button id="sgdb-clear-all" class="btn" style="background:#3473a0;color:#fff;font-weight:bold;padding:10px;display:flex;align-items:center;gap:5px;">
                            <img src="https://i.imgur.com/TTCjGme.png" style="width:16px;height:16px;" alt="Clear">
                            Remove All Backgrounds
                        </button>
                        <button id="sgdb-save-key" class="btn" style="background:#2eab6a;color:#fff;font-weight:bold;padding:10px;display:flex;align-items:center;gap:5px;">
                            <img src="https://i.imgur.com/MU5BOFR.png" style="width:16px;height:16px;" alt="Save">
                            Save API Key
                        </button>
                    </div>
                </div>
                <div id="sgdb-status" style="margin-top:15px;font-size:13px;"></div>
            `;
            document.body.appendChild(popup);
            const statusDiv = popup.querySelector('#sgdb-status');
            popup.querySelector('#sgdb-save-key').addEventListener('click', async () => {
                const key = popup.querySelector('#sgdb-api-input').value.trim();
                const success = await setSteamGridDBKey(key);
                if (success) {
                    if (key) {
                        statusDiv.innerHTML = '<span style="color:#4ade80;">✓ API Key saved - Reload the page to see changes.</span>';
                    } else {
                        statusDiv.innerHTML = '<span style="color:#4ade80;">✓ API Key removed - SteamGridDB disabled.</span>';
                    }
                } else {
                    statusDiv.innerHTML = '<span style="color:#f87171;">✗ Failed to save API key</span>';
                }
            });
            popup.querySelector('#sgdb-clear-all').addEventListener('click', async () => {
                if (!confirm('Remove all saved backgrounds?')) return;
                try {
                    const count = await clearAllBackgrounds();
                    statusDiv.innerHTML = `<span style="color:#4ade80;">✓ Removed ${count} background(s).</span>`;
                } catch (err) {
                    console.error('[RA+] Error removing backgrounds:', err);
                    statusDiv.innerHTML = `<span style="color:#f87171;">✗ Failed to remove backgrounds</span>`;
                }
            });
            popup.querySelector('#sgdb-close').addEventListener('click', () => popup.remove());
        } catch (e) {
            console.error('[RA+] Error showing SteamGridDB popup:', e);
        }
    }
// game id
    function getRetroAchievementsGameId() {
        try {
            const app = document.querySelector('#app');
            if (!app) return null;
            const data = app.getAttribute('data-page');
            if (!data) return null;
            const parsed = JSON.parse(data);
            const gameId =
                parsed?.props?.game?.id ||
                parsed?.props?.backingGame?.id ||
                parsed?.props?.ziggy?.location?.match(/\/game\d*\/(\d+)/)?.[1] ||
                null;
            return gameId ? String(gameId) : null;
        } catch (e) {
            console.error('[RA+] Failed to parse game ID:', e);
            return null;
        }
    }
// page elements
    async function initGamePage() {
        try {
            const headerRoot = document.querySelector('[data-testid="playable-header"]');
            if (!headerRoot) {
                console.log('[RA+] Header not found, skipping init');
                return;
            }
            if (headerRoot.dataset.raPlusInit === 'true') {
                console.log('[RA+] Already initialized, skipping');
                return;
            }
            const gameNameElement = headerRoot.querySelector('h1 span');
            if (!gameNameElement) {
                console.log('[RA+] Game name element not found');
                return;
            }
            const gameName = gameNameElement.textContent.trim();
            if (!gameName) {
                console.log('[RA+] Game name is empty');
                return;
            }
            const gameId = getRetroAchievementsGameId();
            console.log('[RA+] Initializing game page:', gameName, 'ID:', gameId);
            headerRoot.dataset.raPlusInit = 'true';
            AppState.currentGameId = gameId;
            AppState.currentGameName = gameName;
            if (gameId) {
                await loadSavedBackground(gameId);
                const apiKey = await getSteamGridDBKey();
                if (apiKey && canMakeApiCall('steamgriddb')) {
                    recordApiCall('steamgriddb');
                    try {
                        const searchUrl = `https://www.steamgriddb.com/api/v2/search/autocomplete/${encodeURIComponent(gameName)}`;
                        const searchResult = await steamGridDBApiRequest(searchUrl, apiKey);
                        if (searchResult?.data?.length) {
                            const sgdbGame = searchResult.data[0];
                            const sgdbGameId = sgdbGame.id;
                            const heroUrl = `https://www.steamgriddb.com/api/v2/heroes/game/${sgdbGameId}`;
                            const heroResult = await steamGridDBApiRequest(heroUrl, apiKey);
                            if (heroResult?.data?.length) {
                                const topHero = heroResult.data.sort((a, b) => (b.score || 0) - (a.score || 0))[0];
                                await applyBackground(gameId, topHero.url);
                                console.log(`[RA+] Applied background from ${topHero.url}`);
                            }
                        }
                    } catch (error) {
                        console.warn('[RA+] Failed to load background:', error.message);
                    }
                }
            }
            let buttonContainer = headerRoot.querySelector('.hidden.flex-wrap.gap-x-2');
            if (!buttonContainer) {
                buttonContainer = document.createElement('div');
                buttonContainer.className = 'hidden flex-wrap gap-x-2 gap-y-1 text-neutral-300 sm:flex';
                headerRoot.appendChild(buttonContainer);
            }
            buttonContainer.querySelectorAll('.ra-plus-btn').forEach(btn => btn.remove());
            const currentUrl = window.location.href;
            let urlWithoutQuery = currentUrl.split('?')[0];
            urlWithoutQuery = urlWithoutQuery.replace(/\/game\d*\//, '/game/');
            const hashesUrl = urlWithoutQuery.endsWith('/')
                ? `${urlWithoutQuery}hashes`
                : `${urlWithoutQuery}/hashes`;
            const gamefaqsbuttondefaultImg = "https://i.imgur.com/lrnidRF.png";
            const gamefaqsbuttonhoverImg = "https://i.imgur.com/H9jfCjO.png";
            const downloadBtndefaultImg = "https://i.imgur.com/HaOSEPW.png";
            const downloadBtnhoverImg = "https://i.imgur.com/5utafhc.png";
            const steamgriddbbuttondefaultImg = "https://i.imgur.com/GXxzUtx.png";
            const steamgriddbbuttonhoverImg = "https://i.imgur.com/QQAWdZK.png";
            const downloadBtn = document.createElement('button');
            downloadBtn.className = 'btn flex items-center gap-1 ra-plus-btn';
            downloadBtn.title = `Go to ${gameName} hashes page`;
            downloadBtn.innerHTML = `<img src="https://i.imgur.com/HaOSEPW.png" style="width:16px;height:16px;" alt="Hashes"> Hashes`;
            downloadBtn.style.color = '#32a852';
            downloadBtn.style.borderRadius = '9999px';
            downloadBtn.style.background = '#1c1c1c';
            downloadBtn.style.border = '1px solid rgba(50, 168, 82)';
            downloadBtn.addEventListener('click', () => window.location.href = hashesUrl);
            downloadBtn.addEventListener('mouseenter', () => {
                downloadBtn.style.color = '#fff';
                downloadBtn.style.backgroundColor = '#2e2e2e';
                downloadBtn.style.border = '1px solid #fff';
                downloadBtn.querySelector('img').src = downloadBtnhoverImg;
            });
            downloadBtn.addEventListener('mouseleave', () => {
                downloadBtn.style.color = '#32a852';
                downloadBtn.style.backgroundColor = '#1c1c1c';
                downloadBtn.style.border = '1px solid rgba(50, 168, 82)';
                downloadBtn.querySelector('img').src = downloadBtndefaultImg;
            });
            const gamefaqsButton = document.createElement('button');
            gamefaqsButton.className = 'btn flex items-center gap-1 ra-plus-btn';
            gamefaqsButton.title = `Search ${gameName} on GameFAQs`;
            gamefaqsButton.style.backgroundColor = '#1c1c1c';
            gamefaqsButton.style.color = '#606bf6';
            gamefaqsButton.style.borderRadius = '9999px';
            gamefaqsButton.style.border = '1px solid rgba(96,107,246)';
            gamefaqsButton.innerHTML = `<img src="${gamefaqsbuttondefaultImg}" style="width:16px;height:16px;" alt="GameFAQs"> GameFAQs`;
            gamefaqsButton.addEventListener('click', () => {
                window.open(`https://gamefaqs.gamespot.com/search?game=${encodeURIComponent(gameName)}`, '_blank');
            });
            gamefaqsButton.addEventListener('mouseenter', () => {
                gamefaqsButton.style.color = '#fff';
                gamefaqsButton.style.backgroundColor = '#2e2e2e';
                gamefaqsButton.style.border = '1px solid #fff';
                gamefaqsButton.querySelector('img').src = gamefaqsbuttonhoverImg;
            });
            gamefaqsButton.addEventListener('mouseleave', () => {
                gamefaqsButton.style.color = '#606bf6';
                gamefaqsButton.style.backgroundColor = '#1c1c1c';
                gamefaqsButton.style.border = '1px solid rgba(96,107,246)';
                gamefaqsButton.querySelector('img').src = gamefaqsbuttondefaultImg;
            });
            const steamgriddbButton = document.createElement('button');
            steamgriddbButton.className = 'btn flex items-center gap-1 ra-plus-btn';
            steamgriddbButton.title = `SteamGridDB Settings`;
            steamgriddbButton.style.backgroundColor = '#1c1c1c';
            steamgriddbButton.style.borderRadius = '9999px';
            steamgriddbButton.innerHTML = `<img src="https://i.imgur.com/GXxzUtx.png" style="width:16px;height:16px;" alt="SteamGridDB">`;
            steamgriddbButton.addEventListener('click', async () => showSteamGridDBPopup(gameId, gameName));
            steamgriddbButton.style.border = '1px solid rgba(86, 182, 235)';
            steamgriddbButton.addEventListener('mouseenter', () => {
                steamgriddbButton.style.backgroundColor = '#2e2e2e';
                steamgriddbButton.style.border = '1px solid #fff';
                steamgriddbButton.querySelector('img').src = steamgriddbbuttonhoverImg;
            });
            steamgriddbButton.addEventListener('mouseleave', () => {
                steamgriddbButton.style.backgroundColor = '#1c1c1c';
                steamgriddbButton.style.border = '1px solid rgba(86, 182, 235)';
                steamgriddbButton.querySelector('img').src = steamgriddbbuttondefaultImg;
            });
            const playtimeButton = document.createElement('button');
            playtimeButton.className = 'btn flex items-center gap-1 ra-plus-btn';
            playtimeButton.textContent = '⏳ Fetching…';
            playtimeButton.style.border = 'none';
            playtimeButton.style.border = '1px solid rgba(62, 62, 62, 1)';
            playtimeButton.style.borderRadius = '9999px';
            playtimeButton.style.color = '#ffffff';
            buttonContainer.appendChild(playtimeButton);
            buttonContainer.appendChild(gamefaqsButton);
            buttonContainer.appendChild(downloadBtn);
            buttonContainer.appendChild(steamgriddbButton);
            if (gameId) {
                fetchPlaytimeData(gameId, playtimeButton);
            }
        } catch (e) {
            console.error('[RA+] Error in initGamePage:', e);
        }
    }
// Navigation
    const handleNavigationChange = debounce(async function() {
        const currentPath = window.location.pathname;
        if (!currentPath.startsWith('/game/') && !currentPath.match(/\/game\d+\//)) {
            const bgStyle = document.getElementById('steamgriddb-bg-style');
            if (bgStyle) bgStyle.remove();
            AppState.currentGameId = null;
            AppState.currentGameName = null;
            document.querySelectorAll('[data-testid="playable-header"]').forEach(header => {
                delete header.dataset.raPlusInit;
            });
            return;
        }
        const newGameId = getRetroAchievementsGameId();
        if (newGameId && newGameId !== AppState.currentGameId) {
            console.log('[RA+] Navigation detected game change:', AppState.currentGameId, '->', newGameId);
            document.querySelectorAll('[data-testid="playable-header"]').forEach(header => {
                delete header.dataset.raPlusInit;
            });
        }
        try {
            await waitForElement('[data-testid="playable-header"]', 5000);
            await initGamePage();
            initRAGuides();
        } catch (e) {
            console.log('[RA+] Header not found on navigation');
        }
    }, 300);
// Game name change watcher
    const watchGameNameChanges = throttle(function() {
        const currentGameId = getRetroAchievementsGameId();
        if (!currentGameId) return;
        const headerRoot = document.querySelector('[data-testid="playable-header"]');
        if (!headerRoot) return;
        const span = headerRoot.querySelector('h1 span');
        if (!span) return;
        const currentGameName = span.textContent.trim();
        if (currentGameId !== AppState.currentGameId && currentGameId) {
            console.log('[RA+] Game ID changed:', AppState.currentGameId, '->', currentGameId);
            AppState.currentGameId = currentGameId;
            AppState.currentGameName = currentGameName;
            delete headerRoot.dataset.raPlusInit;
            const buttonRow = headerRoot.querySelector('.hidden.flex-wrap.gap-x-2');
            if (buttonRow) {
                buttonRow.querySelectorAll('.ra-plus-btn').forEach(btn => btn.remove());
            }
            const bgStyle = document.getElementById('steamgriddb-bg-style');
            if (bgStyle) bgStyle.remove();
            initGamePage();
            initRAGuides();
        }
    }, 1000);
// console name
    function getConsoleName() {
        try {
            const breadcrumb = document.querySelector('div.navpath.mb-3 nav ol');
            if (!breadcrumb) return null;
            const items = Array.from(breadcrumb.querySelectorAll('li a'));
            for (const a of items) {
                const text = a.textContent.trim().toLowerCase();
                const consoleKey = Object.keys(myrientConsoleUrls).find(key =>
                    key.toLowerCase() === text
                );
                if (consoleKey) return consoleKey;
            }
            return null;
        } catch (e) {
            console.error('[RA+] Error getting console name:', e);
            return null;
        }
    }
// buttons
    async function addMyrientButtons() {
        try {
            const hashList = document.querySelector('[data-testid="named-hashes"]');
            if (!hashList) return;
            const sources = [
                { name: 'Myrient (Recommended)', color: '#1c2020', url: '', removeBrackets: false, removeExtension: false },
                { name: 'Myrient (Mahou.one search)', color: '#1f2937', url: 'https://myrient.mahou.one/search?s=%3D&q=', removeBrackets: false, removeExtension: false },
                { name: 'Crocdb', color: '#16a34a', url: 'https://crocdb.net/search/?title=', removeBrackets: true, removeExtension: true },
                { name: 'Vimm\'s Lair', color: '#83000a', url: 'https://vimm.net/vault/?p=list&q=', removeBrackets: true, removeExtension: true }
            ];
            hashList.querySelectorAll('li').forEach(li => {
                if (li.dataset.raButtonsAdded === 'true') return;
                li.dataset.raButtonsAdded = 'true';
                const span = li.querySelector('span.font-bold');
                if (!span) return;
                let buttonContainer = li.querySelector('.button-container');
                if (!buttonContainer) {
                    buttonContainer = document.createElement('div');
                    buttonContainer.className = 'button-container flex ml-auto gap-2';
                    buttonContainer.style.display = 'flex';
                    buttonContainer.style.marginLeft = 'auto';
                    const pTag = li.querySelector('p');
                    if (pTag) {
                        pTag.appendChild(buttonContainer);
                    }
                }
                let isPatchEntry = false;
                const patchLink = Array.from(li.querySelectorAll('a')).find(a =>
                    a.href.includes('RAPatches') ||
                    a.href.toLowerCase().includes('patch') ||
                    a.textContent.trim().toLowerCase().includes('download patch file')
                );
                if (patchLink) {
                    isPatchEntry = true;
                    const patchUrl = patchLink.href;
                    const romButton = document.createElement('button');
                    romButton.className = 'download-button btn flex items-center gap-1';
                    romButton.innerHTML = `
                        <img src="https://i.imgur.com/Mm6bwIu.png"
                             style="width:16px;height:16px;"
                             alt="Rom Patcher"> Rom Patcher
                    `;
                    romButton.style.cursor = 'pointer';
                    romButton.style.backgroundColor = '#09b1af';
                    romButton.style.color = '#ffffff';
                    romButton.title = 'Rom Patcher';
                    romButton.addEventListener('click', () => {
                        const patcherUrl = `https://www.marcrobledo.com/RomPatcher.js/?patch=${encodeURIComponent(patchUrl)}`;
                        window.open(patcherUrl, '_blank');
                    });
                    buttonContainer.appendChild(romButton);
                }
                if (isPatchEntry) {
                    li.dataset.raButtonsAdded = "true";
                    return;
                }
                sources.forEach(source => {
                    const filename = cleanFilename(span.textContent.trim(), source.removeBrackets, source.removeExtension);
                    const button = document.createElement('button');
                    button.className = 'download-button btn flex items-center gap-1';
                    button.style.cursor = 'pointer';
                    button.style.backgroundColor = source.color;
                    button.style.color = '#fff';
                    button.title = source.name;
                    button.innerHTML = `<img src="https://i.imgur.com/TxQ5llG.png" style="width:16px;height:16px;" alt="Download"> ${source.name}`;
                    button.addEventListener('click', () => {
                        if (source.name === 'Myrient (Recommended)') {
                            showMyrientPopup(filename);
                        } else {
                            const searchUrl = source.url + encodeURIComponent(filename);
                            window.open(searchUrl, '_blank');
                        }
                    });
                    buttonContainer.appendChild(button);
                });
            });
        } catch (e) {
            console.error('[RA+] Error adding Myrient buttons:', e);
        }
    }
    function showMyrientPopup(filename) {
        try {
            const consoleName = getConsoleName();
            const myrientData = consoleName && myrientConsoleUrls[consoleName]
                ? myrientConsoleUrls[consoleName]
                : null;
            const popup = document.createElement('div');
            popup.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#1c2020;color:#fff;padding:20px;border-radius:10px;z-index:9999;max-width:90%;text-align:center;box-shadow:0 0 20px #000,0 0 40px #cf9833;border:2px solid #cf9833;';
            let contentHTML = `
                <img src="https://i.imgur.com/R29J43x.png" alt="Myrient Logo" style="margin-bottom:10px;height:70px;">
                <p><strong>Console:</strong> ${consoleName || 'Unknown'}</p>
                <p><strong>File name:</strong> ${filename}</p>
                <p></p>
            `;
            if (!myrientData || myrientData === 'error') {
                contentHTML += `
                    <p style="color:#f87171;font-weight:bold;">ERROR</p>
                    <p>This console is not supported by Myrient. Message me on Discord <strong>The_vers</strong> if you believe this is an error.</p>
                `;
            } else {
                contentHTML += `
                    <p style="color:red;">⚠️ Warning!</p>
                    <p>Clicking a "Copy and Open" button will copy the game's name and open the corresponding Myrient page. You will have to manually paste the name in.</p>
                    <div id="multiSourceButtons" style="display:flex;justify-content:center;flex-wrap:wrap;gap:10px;margin-top:10px;"></div>
                `;
            }
            contentHTML += `
                <div style="display:flex;justify-content:center;gap:10px;flex-wrap:wrap;margin-top:15px;">
                    <button id="closePopup" class="btn" style="background:#f87171;color:#fff;font-weight:bold;">
                        <img src="https://i.imgur.com/spBpaqo.png" style="width:20px;height:20px;margin-right:5px;" alt="Close"> Close
                    </button>
                    <button id="myriadListBtn" class="btn" style="background:#60a5fa;color:#fff;font-weight:bold;">
                        <img src="https://i.imgur.com/B9iVOVE.png" style="width:20px;height:20px;margin-right:5px;" alt="Wiki"> CD ROM WIKI
                    </button>
                </div>
            `;
            popup.innerHTML = contentHTML;
            document.body.appendChild(popup);
            if (typeof myrientData === 'object' && myrientData !== null) {
                const multiSourceContainer = popup.querySelector('#multiSourceButtons');
                Object.entries(myrientData).forEach(([key, url]) => {
                    const btn = document.createElement('button');
                    btn.className = 'btn';
                    btn.style.cssText = 'background:#2eab6a;color:#fff;font-weight:bold;';
                    btn.innerHTML = `<img src="https://i.imgur.com/doMocJX.png" style="width:20px;height:20px;margin-right:5px;" alt="Copy"> Copy and Open (${key.charAt(0).toUpperCase() + key.slice(1)})`;
                    btn.addEventListener('click', () => {
                        const filenameWithoutExtension = filename.replace(/\.[^.]+$/, '');
                        navigator.clipboard.writeText(filenameWithoutExtension).then(() => {
                            window.open(url, '_blank');
                            popup.remove();
                        }).catch(e => {
                            console.error('[RA+] Clipboard error:', e);
                            alert('Failed to copy to clipboard');
                        });
                    });
                    multiSourceContainer.appendChild(btn);
                });
            }
            popup.querySelector('#myriadListBtn').addEventListener('click', () => {
                window.open('https://r-roms.github.io/', '_blank');
                popup.remove();
            });
            popup.querySelector('#closePopup').addEventListener('click', () => popup.remove());
        } catch (e) {
            console.error('[RA+] Error showing Myrient popup:', e);
        }
    }
    function addLegalDisclaimer() {
        try {
            const targetDiv = document.querySelector('div.-mx-3.rounded.bg-embed.px-3.py-4.sm\\:mx-0.sm\\:px-4.flex.flex-col.gap-4');
            if (!targetDiv || document.querySelector('.legal-disclaimer-box')) return;
            const warningBox = document.createElement('div');
            warningBox.className = '-mx-3 rounded bg-embed px-3 py-4 sm:mx-0 sm:px-4 flex flex-col legal-disclaimer-box';
            const firstLine = document.createElement('p');
            firstLine.className = 'font-bold';
            firstLine.style.color = 'red';
            firstLine.textContent = 'Legal disclaimer';
            const secondLine = document.createElement('p');
            secondLine.style.color = 'white';
            secondLine.style.fontWeight = 'normal';
            secondLine.textContent = 'The use of this script is for personal use only. The button links are for games in public domain that you are legally allowed to download. I don\'t take any responsibility for the files downloaded.';
            warningBox.appendChild(firstLine);
            warningBox.appendChild(secondLine);
            targetDiv.parentNode.insertBefore(warningBox, targetDiv);
        } catch (e) {
            console.error('[RA+] Error adding legal disclaimer:', e);
        }
    }
    const initHashesPage = debounce(async function() {
        try {
            await waitForElement('[data-testid="named-hashes"]', 5000);
            addLegalDisclaimer();
            await addMyrientButtons();
        } catch (e) {
            console.log('[RA+] Hashes page elements not found');
        }
    }, 300);
// guides
    function initRAGuides() {
        try {
            if (document.querySelector('[data-raguides-injected]')) {
                return;
            }
        const guidesData = window.RAGUIDES ||
        [
          {
            "name": "Harry Potter and the Chamber of Secrets",
            "link": "https://github.com/RetroAchievements/guides/wiki/(WIP)-Harry-Potter-and-the-Chamber-of-Secrets",
            "subcategory": "WIP",
            "icon": "https://cdn2.steamgriddb.com/icon/a49ffb16c3666d2f693bc7b5fdca969d.ico"
          },
          {
            "name": "Keio Flying Squadron 2",
            "link": "https://github.com/RetroAchievements/guides/wiki/(WIP)-Keio-Flying-Squadron-2",
            "subcategory": "WIP",
            "icon": ""
          },
          {
            "name": "Metal Gear Solid",
            "link": "https://github.com/RetroAchievements/guides/wiki/(WIP)-Metal-Gear-Solid-(Playstation)",
            "subcategory": "WIP",
            "icon": "https://cdn2.steamgriddb.com/icon/839ab46820b524afda05122893c2fe8e.png"
          },
          {
            "name": "Open Season",
            "link": "https://github.com/RetroAchievements/guides/wiki/(WIP)-Open-Season-(GBA)",
            "subcategory": "WIP",
            "icon": "https://cdn2.steamgriddb.com/icon/82bb153bc3037adf373b43babcfffd03.ico"
          },
          {
            "name": "Pokémon XD: Gale of Darkness",
            "link": "https://github.com/RetroAchievements/guides/wiki/(WIP)-Pok%C3%A9mon-XD:-Gale-of-Darkness",
            "subcategory": "WIP",
            "icon": "https://cdn2.steamgriddb.com/icon/902447c6ce9d4f39e12a463b4d4ff759.png"
          },
          {
            "name": "Shadow Tower Abyss",
            "link": "https://github.com/RetroAchievements/guides/wiki/(WIP)-Shadow-Tower-Abyss-(PS2)",
            "subcategory": "WIP",
            "icon": "https://cdn2.steamgriddb.com/icon/88bccac4be340a681f5eff6d5cbde9d2.png"
          },
          {
            "name": "Tetris",
            "link": "https://github.com/RetroAchievements/guides/wiki/(WIP)-Tetris-(Wonderswan)",
            "subcategory": "WIP",
            "icon": "https://cdn2.steamgriddb.com/icon/c9758b9ceb9543ef87d38e31dc03a8fd.png"
          },
          {
            "name": "The Incredible Hulk: Ultimate Destruction",
            "link": "https://github.com/RetroAchievements/guides/wiki/(WIP)-The-Incredible-Hulk:-Ultimate-Destruction",
            "subcategory": "WIP",
            "icon": "https://cdn2.steamgriddb.com/icon/b555f4a2901228cc826327079761e696.png"
          },
          {
            "name": "Xenogears",
            "link": "https://github.com/RetroAchievements/guides/wiki/(WIP)-Xenogears-(PSX)",
            "subcategory": "WIP",
            "icon": "https://cdn2.steamgriddb.com/icon/be4dfce0bd450fdd57fda1bd637ad712.png"
          },
          {
            "name": "Pokémon STRIKE! Yellow Version",
            "link": "https://github.com/RetroAchievements/guides/wiki/(WIP)-~Hack~-Pok%C3%A9mon-STRIKE!-Yellow-Version",
            "subcategory": "WIP,Hack",
            "icon": ""
          },
          {
            "name": "Hime's Quest",
            "link": "https://github.com/RetroAchievements/guides/wiki/(WIP)-~Homebrew~-Hime's-Quest",
            "subcategory": "WIP,Homebrew",
            "icon": ""
          },
          {
            "name": "Yu‐Gi‐Oh! Forbidden Memories",
            "link": "https://github.com/RetroAchievements/guides/wiki/(WIP)Yu%E2%80%90Gi%E2%80%90Oh!-Forbidden-Memories",
            "subcategory": "WIP",
            "icon": "https://cdn2.steamgriddb.com/icon/efe937780e95574250dabe07151bdc23.png"
          },
          {
            "name": "Serial Experiments Lain",
            "link": "https://github.com/RetroAchievements/guides/wiki/%5BWIP%5D-Serial-Experiments-Lain",
            "subcategory": "WIP",
            "icon": "https://cdn2.steamgriddb.com/icon/b5f30ed55c1e0b0c8be8d25edacb6f94.png"
          },
          {
            "name": "Arc The Lad",
            "link": "https://github.com/RetroAchievements/guides/wiki/Arc-The-Lad-(Playstation)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/97f832f6f334d64de3e89769806e56b8.png"
          },
          {
            "name": "Baten Kaitos Origins Magnus Upgrade Checklist",
            "link": "https://github.com/RetroAchievements/guides/wiki/Baten-Kaitos-Origins-Magnus-Upgrade-Checklist",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/bd22c2ef9e6f0fa97825c6be879f8fa4.png"
          },
          {
            "name": "Brightis",
            "link": "https://github.com/RetroAchievements/guides/wiki/Brightis",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/3e26eea5b2d1c81a64daed871801e79b.png"
          },
          {
            "name": "Castlevania II: Simons Quest",
            "link": "https://github.com/RetroAchievements/guides/wiki/Castlevania-II:-Simons-Quest-%5BSubset-%E2%80%90-Bonus%5D",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/dbb422937d7ff56e049d61da730b3e11.ico"
          },
          {
            "name": "Chrono Trigger Achievement Guide",
            "link": "https://github.com/RetroAchievements/guides/wiki/Chrono-Trigger-Achievement-Guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/241dfe5e876da942ca1dc3cd5986e093.png"
          },
          {
            "name": "Crash Bandicoot 2: Cortex Strikes Back",
            "link": "https://github.com/RetroAchievements/guides/wiki/Crash-Bandicoot-2:-Cortex-Strikes-Back-(Playstation)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/a537c315b8774b9881ff1d80c2741fe7.ico"
          },
          {
            "name": "Dragon Quest VIII ‐ Journey of the Cursed King",
            "link": "https://github.com/RetroAchievements/guides/wiki/Dragon-Quest-VIII-%E2%80%90-Journey-of-the-Cursed-King",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/4b0a0290ad7df100b77e86839989a75e.png"
          },
          {
            "name": "Dynasty Warriors 5",
            "link": "https://github.com/RetroAchievements/guides/wiki/Dynasty-Warriors-5:-Xtreme-Legends-%E2%80%90-Achievement-Guide-%5BFull%5D",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/2aeb1a8f8475cef63900be5d0780e872.png"
          },
          {
            "name": "EarthBound Guide ‐ Main Set and Subsets",
            "link": "https://github.com/RetroAchievements/guides/wiki/EarthBound-Guide-%E2%80%90-Main-Set-and-Subsets",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/3cf166c6b73f030b4f67eeaeba301103.png"
          },
          {
            "name": "Eternal Eyes",
            "link": "https://github.com/RetroAchievements/guides/wiki/Eternal-Eyes",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/07c24da9292451154d8d0d62c01efa38.png"
          },
          {
            "name": "FF12 Gameplay Mechanics",
            "link": "https://github.com/RetroAchievements/guides/wiki/FF12-Gameplay-Mechanics",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "FF12 Hunt‐Related & Misc. Missables",
            "link": "https://github.com/RetroAchievements/guides/wiki/FF12-Hunt%E2%80%90Related-&-Misc.-Missables",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "FF12 Monster Drops Checklist",
            "link": "https://github.com/RetroAchievements/guides/wiki/FF12-Monster-Drops-Checklist",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "FF12 Tricks & Exploits",
            "link": "https://github.com/RetroAchievements/guides/wiki/FF12-Tricks-&-Exploits",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Final Fantasy IV",
            "link": "https://github.com/RetroAchievements/guides/wiki/Final-Fantasy-IV:-The-Complete-Collection-Missable-Achievements-Guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/e43d2d0b56531786e5974103334b805d.png"
          },
          {
            "name": "Final Fantasy V Missable Achievements Guide",
            "link": "https://github.com/RetroAchievements/guides/wiki/Final-Fantasy-V-Missable-Achievements-Guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/62ee154324b735c70197bcf7f666a6a7.ico"
          },
          {
            "name": "Final Fantasy V Treasure Checklist",
            "link": "https://github.com/RetroAchievements/guides/wiki/Final-Fantasy-V-Treasure-Checklist",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/62ee154324b735c70197bcf7f666a6a7.ico"
          },
          {
            "name": "Final Fantasy VII",
            "link": "https://github.com/RetroAchievements/guides/wiki/Final-Fantasy-VII-(WIP)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/dd4729902a3476b2bc9675e3530a852c.ico"
          },
          {
            "name": "Final Fantasy XII",
            "link": "https://github.com/RetroAchievements/guides/wiki/Final-Fantasy-XII:-International-Zodiac-Job-System-Missable-Achievements-Guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/72349ac571f8827f9c63855f9e09569d.ico"
          },
          {
            "name": "Final Fantasy: Mystic Quest",
            "link": "https://github.com/RetroAchievements/guides/wiki/Final-Fantasy:-Mystic-Quest",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/1f1acf13c15f74ef4844e44e5f10ae2a.png"
          },
          {
            "name": "Fire Emblem",
            "link": "https://github.com/RetroAchievements/guides/wiki/Fire-Emblem:-The-Sacred-Stones-Guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/13d2b7361a27dbc9960ae158598a6a96.ico"
          },
          {
            "name": "Firefighter F.D. 18",
            "link": "https://github.com/RetroAchievements/guides/wiki/Firefighter-F.D.-18-(Playstation-2)",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Ghost Rider",
            "link": "https://github.com/RetroAchievements/guides/wiki/Ghost-Rider",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/e0642d7280878ea1fb17bf73a5232767.png"
          },
          {
            "name": "Half‐Minute Hero",
            "link": "https://github.com/RetroAchievements/guides/wiki/Half%E2%80%90Minute-Hero",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/390e982518a50e280d8e2b535462ec1f.png"
          },
          {
            "name": "Harvest Moon DS Cute Retroachievements",
            "link": "https://github.com/RetroAchievements/guides/wiki/Harvest-Moon-DS-Cute-Retroachievements",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/628454d202cf0a8b6e7f2a62a1c36d46.png"
          },
          {
            "name": "Harvest Moon: Friends of Mineral Town",
            "link": "https://github.com/RetroAchievements/guides/wiki/Harvest-Moon:-Friends-of-Mineral-Town",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/184c81c95b6329b9cee766cf72c0f34c.png"
          },
          {
            "name": "Kao the Kangaroo: Round 2",
            "link": "https://github.com/RetroAchievements/guides/wiki/Kao-the-Kangaroo:-Round-2-(PS2)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/4a3f8e93a3de2a2650a129d7fa060bb4.ico"
          },
          {
            "name": "Kou Dai Guai Shou II",
            "link": "https://github.com/RetroAchievements/guides/wiki/Kou-Dai-Guai-Shou-II",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Legend of Zelda, The ‐ Oracle of Ages and Oracle of Seasons",
            "link": "https://github.com/RetroAchievements/guides/wiki/Legend-of-Zelda,-The-%E2%80%90-Oracle-of-Ages-and-Oracle-of-Seasons-(GBC)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/61d77652c97ef636343742fc3dcf3ba9.png"
          },
          {
            "name": "LEGO Batman 2: DC Super Heroes",
            "link": "https://github.com/RetroAchievements/guides/wiki/LEGO-Batman-2:-DC-Super-Heroes-(DS)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/202cb962ac59075b964b07152d234b70.ico"
          },
          {
            "name": "Lego Island 2: The Brickster's Revenge",
            "link": "https://github.com/RetroAchievements/guides/wiki/Lego-Island-2:-The-Brickster's-Revenge",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/d48da2445baf1640d0b54e229e860c16.png"
          },
          {
            "name": "List of RAGuides Featured in RANews",
            "link": "https://github.com/RetroAchievements/guides/wiki/List-of-RAGuides-Featured-in-RANews",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Lost in Blue Achievement Guide",
            "link": "https://github.com/RetroAchievements/guides/wiki/Lost-in-Blue-Achievement-Guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/50b6da85112e21710d78b44f2cc8a8a3.png"
          },
          {
            "name": "Lost in Blue Girl's Story Guide",
            "link": "https://github.com/RetroAchievements/guides/wiki/Lost-in-Blue-Girl's-Story-Guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/50b6da85112e21710d78b44f2cc8a8a3.png"
          },
          {
            "name": "Lost in Blue Scrapbook Entries",
            "link": "https://github.com/RetroAchievements/guides/wiki/Lost-in-Blue-Scrapbook-Entries",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/50b6da85112e21710d78b44f2cc8a8a3.png"
          },
          {
            "name": "Lufia 2: Rise of the Sinistrals",
            "link": "https://github.com/RetroAchievements/guides/wiki/Lufia-2:-Rise-of-the-Sinistrals",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/a7ebb97ca0c5906d0c7f10c20ac03300.ico"
          },
          {
            "name": "Lunar 2",
            "link": "https://github.com/RetroAchievements/guides/wiki/Lunar-2:-Eternal-Blue-Complete-Missables-Guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/8426a7e0990d49b465b05a6f508355b4.png"
          },
          {
            "name": "Magna Braban: Henreki no Yuusha",
            "link": "https://github.com/RetroAchievements/guides/wiki/Magna-Braban:-Henreki-no-Yuusha",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Mahjong Keiji",
            "link": "https://github.com/RetroAchievements/guides/wiki/Mahjong-Keiji",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/60dfc9f44b250ed03992a2a1b39a30c1.ico"
          },
          {
            "name": "Medal of Honor: Rising Sun",
            "link": "https://github.com/RetroAchievements/guides/wiki/Medal-of-Honor:-Rising-Sun-(PS2)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/9917c3b8ac1b209796960d2a2f0f7931.png"
          },
          {
            "name": "Meitantei Conan: Karakuri Jiin Satsujin Jiken walkthrough",
            "link": "https://github.com/RetroAchievements/guides/wiki/Meitantei-Conan:-Karakuri-Jiin-Satsujin-Jiken-walkthrough",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Meitantei Conan: Kigantou Hihou Densetsu walkthrough",
            "link": "https://github.com/RetroAchievements/guides/wiki/Meitantei-Conan:-Kigantou-Hihou-Densetsu-walkthrough",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Mobile Suit Gundam Side Story I: Senritsu no Blue | Blue Fear",
            "link": "https://github.com/RetroAchievements/guides/wiki/Mobile-Suit-Gundam-Side-Story-I:-Senritsu-no-Blue-%7C-Blue-Fear",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/83daf814c3f89387be1f449166d6f38f.ico"
          },
          {
            "name": "Mobile Suit Gundam Side Story II: Ao o Uketsugu Mono | Heir to the Blue",
            "link": "https://github.com/RetroAchievements/guides/wiki/Mobile-Suit-Gundam-Side-Story-II:-Ao-o-Uketsugu-Mono-%7C-Heir-to-the-Blue",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/3c89308f7c344de64ddbf085405a0192.ico"
          },
          {
            "name": "Nakoruru: Ano Hito kara no Okurimono | Nakoruru: The Gift She Gave Me",
            "link": "https://github.com/RetroAchievements/guides/wiki/Nakoruru:-Ano-Hito-kara-no-Okurimono-%7C-Nakoruru:-The-Gift-She-Gave-Me",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/ebc8854c9c916986d13fcdcfc039d5bd.png"
          },
          {
            "name": "Need for Speed: Hot Pursuit 2",
            "link": "https://github.com/RetroAchievements/guides/wiki/Need-for-Speed:-Hot-Pursuit-2-%5BSubset-%E2%80%90-Extreme-Challenges%5D",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/3d2c3ec9b0b199a105b514368ca8a8db.png"
          },
          {
            "name": "Ogre Battle: March of the Black Queen Routing",
            "link": "https://github.com/RetroAchievements/guides/wiki/Ogre-Battle:-March-of-the-Black-Queen-Routing",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/8d725eb1826fee2e7d5f852a1d63d14f.png"
          },
          {
            "name": "Old Towers",
            "link": "https://github.com/RetroAchievements/guides/wiki/Old-Towers-(Genesis)",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Phantom Brave: The Hermuda Triangle",
            "link": "https://github.com/RetroAchievements/guides/wiki/Phantom-Brave:-The-Hermuda-Triangle-(PSP)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/c42918d4e77ce2fdb6ec62aeeff8352c.png"
          },
          {
            "name": "Pokemon FireRed Version Gotta Catch Em All Achievement Guide",
            "link": "https://github.com/RetroAchievements/guides/wiki/Pokemon-FireRed-Version-Gotta-Catch-Em-All-Achievement-Guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/2fbbc6323c4882fbac23831adefc628a.png"
          },
          {
            "name": "Pokémon Trading Card Game 2: The Invasion of Team GR!",
            "link": "https://github.com/RetroAchievements/guides/wiki/Pok%C3%A9mon-Trading-Card-Game-2:-The-Invasion-of-Team-GR!",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/6dcfff2b73388f6307994658463a9341.ico"
          },
          {
            "name": "Quest of Hat",
            "link": "https://github.com/RetroAchievements/guides/wiki/Quest-of-Hat",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Resident Evil Outbreak",
            "link": "https://github.com/RetroAchievements/guides/wiki/Resident-Evil-Outbreak-(PS2)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/2705e8fde87cd2883e9fc1f00335685f.png"
          },
          {
            "name": "Retreon Setup Guide",
            "link": "https://github.com/RetroAchievements/guides/wiki/Retreon-Setup-Guide",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Rule of Rose Achievement Guide",
            "link": "https://github.com/RetroAchievements/guides/wiki/Rule-of-Rose-Achievement-Guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/47fd3c87f42f55d4b233417d49c34783.ico"
          },
          {
            "name": "Shadow Hearts",
            "link": "https://github.com/RetroAchievements/guides/wiki/Shadow-Hearts",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/106fac81e07001e1825eadbb39fcde11.png"
          },
          {
            "name": "Sherlock Holmes",
            "link": "https://github.com/RetroAchievements/guides/wiki/Sherlock-Holmes:-Consulting-Detective-Vols.-I-&-II-guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/674da13c4ebcc4cf9e4b8987942e4d2c.ico"
          },
          {
            "name": "Silent Hill: Origins",
            "link": "https://github.com/RetroAchievements/guides/wiki/Silent-Hill:-Origins",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/e6e6538d5e7266e8a6d25bfaf74d5b29.ico"
          },
          {
            "name": "Silent Hill: Shattered Memories",
            "link": "https://github.com/RetroAchievements/guides/wiki/Silent-Hill:-Shattered-Memories",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/70a76cfa6eaab7715104a01c9fa47620.ico"
          },
          {
            "name": "SimAnimals",
            "link": "https://github.com/RetroAchievements/guides/wiki/SimAnimals-(DS)",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Solar Jetman",
            "link": "https://github.com/RetroAchievements/guides/wiki/Solar-Jetman",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "SpongeBobs Truth or Square",
            "link": "https://github.com/RetroAchievements/guides/wiki/SpongeBobs-Truth-or-Square-(PSP)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/e2e115339d25c6437efd1583788d8c4c.ico"
          },
          {
            "name": "Standalone",
            "link": "https://github.com/RetroAchievements/guides/wiki/Standalone",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Star Ocean",
            "link": "https://github.com/RetroAchievements/guides/wiki/Star-Ocean:-Second-Evolution-Missable-Achievements-Guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/8dcfde48a694c2cdbc0c2110c24dd948.png"
          },
          {
            "name": "Star Ocean",
            "link": "https://github.com/RetroAchievements/guides/wiki/Star-Ocean:-Second-Evolution-Treasures-Guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/8dcfde48a694c2cdbc0c2110c24dd948.png"
          },
          {
            "name": "Star Ocean: The Second Story In‐Depth Gameplay Analysis",
            "link": "https://github.com/RetroAchievements/guides/wiki/Star-Ocean:-The-Second-Story-In%E2%80%90Depth-Gameplay-Analysis",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/149e15ab5d2ef772dbcaebe142982669.ico"
          },
          {
            "name": "Star Ocean",
            "link": "https://github.com/RetroAchievements/guides/wiki/Star-Ocean:-The-Second-Story-Missable-Achievements-Guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/8dcfde48a694c2cdbc0c2110c24dd948.png"
          },
          {
            "name": "Starflight",
            "link": "https://github.com/RetroAchievements/guides/wiki/Starflight",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Suikoden II Bonus Subset Missables Guide",
            "link": "https://github.com/RetroAchievements/guides/wiki/Suikoden-II-Bonus-Subset-Missables-Guide-(Playstation)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/4324e8d0d37b110ee1a4f1633ac52df5.png"
          },
          {
            "name": "Swingerz Golf",
            "link": "https://github.com/RetroAchievements/guides/wiki/Swingerz-Golf",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/47267ca39f652c0de27a4b27c5e11c40.png"
          },
          {
            "name": "Tales of Symphonia",
            "link": "https://github.com/RetroAchievements/guides/wiki/Tales-of-Symphonia-%5BGung-Ho-Run-%E2%80%90-New-Game-subset%5D",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/a02f16dc538480b69d8b8e53ee8b3d26.png"
          },
          {
            "name": "Tecmos Deception: Invitation to Darkness",
            "link": "https://github.com/RetroAchievements/guides/wiki/Tecmos-Deception:-Invitation-to-Darkness-(Playstation)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/4f7d333ea44502605726decff16876f0.png"
          },
          {
            "name": "The Incredible Hulk: Ultimate Destruction (GCN) ‐ Smash Point Token Locations",
            "link": "https://github.com/RetroAchievements/guides/wiki/The-Incredible-Hulk:-Ultimate-Destruction-(GCN)-%E2%80%90-Smash-Point-Token-Locations",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/b555f4a2901228cc826327079761e696.png"
          },
          {
            "name": "The Legend of Zelda: Twilight Princess",
            "link": "https://github.com/RetroAchievements/guides/wiki/The-Legend-of-Zelda:-Twilight-Princess-(Gamecube)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/feecee9f1643651799ede2740927317a.ico"
          },
          {
            "name": "Tim Burtons The Nightmare Before Christmas: Oogies Revenge",
            "link": "https://github.com/RetroAchievements/guides/wiki/Tim-Burtons-The-Nightmare-Before-Christmas:-Oogies-Revenge-(PS2)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/cb45a1110562681fbcf8dc2cead7bd23.ico"
          },
          {
            "name": "Tomba! 2 ‐ Spyromba Guide",
            "link": "https://github.com/RetroAchievements/guides/wiki/Tomba!-2-%E2%80%90-Spyromba-Guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/c928d86ff00aeb89a39bd4a80e652a38.ico"
          },
          {
            "name": "Treasure Conflix",
            "link": "https://github.com/RetroAchievements/guides/wiki/Treasure-Conflix",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Twisted Metal 2",
            "link": "https://github.com/RetroAchievements/guides/wiki/Twisted-Metal-2:-The-Deathless-Guide",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/c4fac8fb3c9e17a2f4553a001f631975.png"
          },
          {
            "name": "Valkyrie no Densetsu",
            "link": "https://github.com/RetroAchievements/guides/wiki/Valkyrie-no-Densetsu",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/a137c57e3649cc7e5f555285623afcc6.png"
          },
          {
            "name": "Valkyrie Profile",
            "link": "https://github.com/RetroAchievements/guides/wiki/Valkyrie-Profile-(Playstation)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/99be9f83741d1275639df2c1e4d0072f.png"
          },
          {
            "name": "Virtual Hydlide",
            "link": "https://github.com/RetroAchievements/guides/wiki/Virtual-Hydlide",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Warsong",
            "link": "https://github.com/RetroAchievements/guides/wiki/Warsong",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/47d288215c79c95a062b84eb57b96058.png"
          },
          {
            "name": "Wedding Peach",
            "link": "https://github.com/RetroAchievements/guides/wiki/Wedding-Peach",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Yu‐Gi‐Oh! Forbidden Memories II Ultimate",
            "link": "https://github.com/RetroAchievements/guides/wiki/Yu%E2%80%90Gi%E2%80%90Oh!-Forbidden-Memories-II-Ultimate-(Playstation)",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/efe937780e95574250dabe07151bdc23.png"
          },
          {
            "name": "Zelda II: The Adventure of Link",
            "link": "https://github.com/RetroAchievements/guides/wiki/Zelda-II:-The-Adventure-of-Link",
            "subcategory": "",
            "icon": "https://cdn2.steamgriddb.com/icon/7fd804295ef7f6a2822bf4c61f9dc4a8.ico"
          },
          {
            "name": "Zillion",
            "link": "https://github.com/RetroAchievements/guides/wiki/Zillion-(SMS)",
            "subcategory": "",
            "icon": ""
          },
          {
            "name": "Final Fantasy IV",
            "link": "https://github.com/RetroAchievements/guides/wiki/~Hack~-Final-Fantasy-IV:-Ultima-Achievement-Guide",
            "subcategory": "Hack",
            "icon": "https://cdn2.steamgriddb.com/icon/e43d2d0b56531786e5974103334b805d.png"
          },
          {
            "name": "Final Fantasy IV: Ultima Bestiary Entries Checklist",
            "link": "https://github.com/RetroAchievements/guides/wiki/~Hack~-Final-Fantasy-IV:-Ultima-Bestiary-Entries-Checklist",
            "subcategory": "Hack",
            "icon": "https://cdn2.steamgriddb.com/icon/b9f1a83c68c36088e57821bdb90f77f2.png"
          },
          {
            "name": "Final Fantasy IV",
            "link": "https://github.com/RetroAchievements/guides/wiki/~Hack~-Final-Fantasy-IV:-Ultima-Full-Guide-&-Checklists",
            "subcategory": "Hack",
            "icon": "https://cdn2.steamgriddb.com/icon/e43d2d0b56531786e5974103334b805d.png"
          },
          {
            "name": "Final Fantasy IV: Ultima Monster Scan Locations",
            "link": "https://github.com/RetroAchievements/guides/wiki/~Hack~-Final-Fantasy-IV:-Ultima-Monster-Scan-Locations",
            "subcategory": "Hack",
            "icon": "https://cdn2.steamgriddb.com/icon/b9f1a83c68c36088e57821bdb90f77f2.png"
          },
          {
            "name": "Final Fantasy IV",
            "link": "https://github.com/RetroAchievements/guides/wiki/~Hack~-Final-Fantasy-IV:-Ultima-Superboss-&-Boss-Rush-guide",
            "subcategory": "Hack",
            "icon": "https://cdn2.steamgriddb.com/icon/e43d2d0b56531786e5974103334b805d.png"
          },
          {
            "name": "Final Fantasy IV",
            "link": "https://github.com/RetroAchievements/guides/wiki/~Hack~-Final-Fantasy-IV:-Ultima-Treasure-Locations-guide",
            "subcategory": "Hack",
            "icon": "https://cdn2.steamgriddb.com/icon/e43d2d0b56531786e5974103334b805d.png"
          },
          {
            "name": "Pokemon Sword and Shield Ultimate Plus",
            "link": "https://github.com/RetroAchievements/guides/wiki/~Hack~-Pokemon-Sword-and-Shield-Ultimate-Plus",
            "subcategory": "Hack",
            "icon": ""
          },
          {
            "name": "10 Lines Princess",
            "link": "https://github.com/RetroAchievements/guides/wiki/~Homebrew~-10-Lines-Princess",
            "subcategory": "Homebrew",
            "icon": ""
          },
          {
            "name": "Wizardry: Proving Grounds of the Mad Overlord",
            "link": "https://github.com/RetroAchievements/guides/wiki/~Subset~-Wizardry:-Proving-Grounds-of-the-Mad-Overlord-%E2%80%90-Item-Drops",
            "subcategory": "Subset",
            "icon": ""
          }
        ];
            const badgeColors = {
                wip: '#FF8973',
                hack: '#73BEFF',
                homebrew: '#FFF173',
                subset: '#FFF173',
                prototype: '#FFF173',
            };
            function injectStyles() {
                if (document.getElementById('ra-guides-styles')) return;
                const css = `
                .ra-guides-navitem { position: relative; }
                .ra-guides-dropdown {
                    display: none;
                    position: fixed;
                    top: 0;
                    left: 50px;
                    right: 50px;
                    width: auto;
                    max-height: 60vh;
                    overflow-y: auto;
                    background: #2a2a2a;
                    border: 1px solid rgba(0,0,0,0.08);
                    box-shadow: 0 6px 24px rgba(0,0,0,0.12);
                    padding: 12px;
                    box-sizing: border-box;
                    z-index: 99999;
                    border-radius: 8px;
                }
                .ra-guides-dropdown.open { display: block; }
                .ra-guides-grid {
                    display: grid;
                    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
                    gap: 12px;
                }
                .ra-guide-item {
                    display: flex;
                    align-items: center;
                    gap: 10px;
                    padding: 8px;
                    border-bottom: 1px solid rgba(255,255,255,0.1);
                    text-decoration: none;
                    color: inherit;
                    border-radius: 6px;
                }
                .ra-guide-item:hover { background: rgba(255,255,255,0.05); }
                .ra-guide-icon {
                    width: 36px;
                    height: 36px;
                    object-fit: contain;
                    flex: 0 0 36px;
                    border-radius: 4px;
                }
                .ra-guide-meta {
                    display: flex;
                    align-items: center;
                    gap: 6px;
                    min-width: 0;
                    width: 100%;
                    flex-wrap: wrap;
                }
                .ra-guide-title {
                    font-size: 14px;
                    font-weight: 600;
                    white-space: nowrap;
                    overflow: hidden;
                    text-overflow: ellipsis;
                }
                .ra-guide-badge {
                    font-size: 12px;
                    padding: 2px 8px;
                    border-radius: 999px;
                    white-space: nowrap;
                    flex: 0 0 auto;
                    color: white;
                }
                .ra-guides-dropdown::-webkit-scrollbar { width: 10px; }
                .ra-guides-dropdown::-webkit-scrollbar-track { background: rgba(255,255,255,0.05); border-radius: 5px; }
                .ra-guides-dropdown::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 5px; }
                .ra-guides-dropdown::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.35); }
                @media (max-width: 640px) {
                    .ra-guides-dropdown { left: 8px; right: 8px; }
                    .ra-guides-grid { grid-template-columns: repeat(auto-fill, minmax(100%, 1fr)); }
                }
                `;
                const style = document.createElement('style');
                style.id = 'ra-guides-styles';
                style.textContent = css;
                document.head.appendChild(style);
            }
            function buildDropdown(guides) {
                const dropdown = document.createElement('div');
                dropdown.className = 'ra-guides-dropdown';
                dropdown.setAttribute('role', 'menu');
                const header = document.createElement('div');
                header.style.cssText = 'text-align:center;margin-bottom:12px;';
                const headerImg = document.createElement('img');
                headerImg.src = "https://docs.retroachievements.org/ra-logo-big-shadow.png";
                headerImg.alt = "RA Logo";
                headerImg.style.cssText = 'width:80px;height:80px;object-fit:contain;margin-bottom:8px;';
                const headerText = document.createElement('p');
                headerText.style.cssText = 'font-size:14px;color:#ddd;line-height:1.4;';
                headerText.textContent = "Because I have to add this list manually, it might not be up to date. If the game appears multiple times it means it has multiple specific guides.";
                header.appendChild(headerImg);
                header.appendChild(headerText);
                dropdown.appendChild(header);
                const grid = document.createElement('div');
                grid.className = 'ra-guides-grid';
                guides.forEach(g => {
                    const a = document.createElement('a');
                    a.className = 'ra-guide-item';
                    a.href = g.link || '#';
                    a.target = '_self';
                    a.rel = 'noopener noreferrer';
                    a.setAttribute('role', 'menuitem');
                    const img = document.createElement('img');
                    img.className = 'ra-guide-icon';
                    img.src = g.icon && g.icon.trim() !== ""
                              ? g.icon
                              : "https://docs.retroachievements.org/ra-logo-big-shadow.png";
                    img.alt = (g.name ? g.name + ' icon' : 'guide icon');
                    const meta = document.createElement('div');
                    meta.className = 'ra-guide-meta';
                    const title = document.createElement('div');
                    title.className = 'ra-guide-title';
                    title.textContent = g.name || 'Untitled';
                    meta.appendChild(title);
                    if (g.subcategory) {
                        const tags = g.subcategory.split(',').map(t => t.trim());
                        tags.forEach(tag => {
                            if (tag) {
                                const badge = document.createElement('span');
                                badge.className = 'ra-guide-badge';
                                const color = badgeColors[tag.toLowerCase()];
                                if (color) badge.style.backgroundColor = color;
                                badge.textContent = tag;
                                meta.appendChild(badge);
                            }
                        });
                    }
                    a.appendChild(img);
                    a.appendChild(meta);
                    grid.appendChild(a);
                });
                dropdown.appendChild(grid);
                return dropdown;
            }
            function createRAGuidesButton(guides) {
                const navItem = document.createElement('div');
                navItem.className = 'nav-item ra-guides-navitem';
                navItem.setAttribute('data-raguides-injected', 'true');
                const link = document.createElement('a');
                link.className = 'nav-link';
                link.href = 'https://github.com/RetroAchievements/guides/wiki';
                link.target = '_self';
                link.title = 'Guides';
                link.setAttribute('aria-haspopup', 'true');
                link.setAttribute('aria-expanded', 'false');
                link.innerHTML = `
                    <img src="https://i.imgur.com/yg5v9Ej.png"
                         alt="RA Guides"
                         width="18"
                         height="18"
                         style="vertical-align:middle">
                    <span class="ml-1 hidden sm:inline-block" style="margin-left:8px;vertical-align:middle">
                        RA Guides
                    </span>
                `;
                navItem.appendChild(link);
                const dropdown = buildDropdown(guides || guidesData);
                document.body.appendChild(dropdown);
                function showDropdown() {
                    const rect = link.getBoundingClientRect();
                    dropdown.style.top = (rect.bottom + 4) + 'px';
                    dropdown.style.left = rect.left + 'px';
                    dropdown.classList.add('open');
                    link.setAttribute('aria-expanded', 'true');
                }
                function hideDropdown() {
                    dropdown.classList.remove('open');
                    link.setAttribute('aria-expanded', 'false');
                }
                let hideTimer = null;
                [navItem, dropdown].forEach(el => {
                    el.addEventListener('mouseenter', () => {
                        clearTimeout(hideTimer);
                        showDropdown();
                    });
                    el.addEventListener('mouseleave', () => {
                        clearTimeout(hideTimer);
                        hideTimer = setTimeout(hideDropdown, 150);
                    });
                });
                link.addEventListener('keydown', (ev) => {
                    if (ev.key === 'Enter' || ev.key === ' ') {
                        ev.preventDefault();
                        const open = dropdown.classList.toggle('open');
                        link.setAttribute('aria-expanded', open ? 'true' : 'false');
                        if (open) showDropdown();
                    } else if (ev.key === 'Escape') {
                        hideDropdown();
                    }
                });
                let isTouch = false;
                window.addEventListener('touchstart', function onFirstTouch() {
                    isTouch = true;
                    window.removeEventListener('touchstart', onFirstTouch, false);
                }, false);
                link.addEventListener('click', (ev) => {
                    if (isTouch) {
                        ev.preventDefault();
                        const open = dropdown.classList.toggle('open');
                        link.setAttribute('aria-expanded', open ? 'true' : 'false');
                        if (open) showDropdown();
                    }
                });
                document.addEventListener('click', (ev) => {
                    if (!navItem.contains(ev.target) && !dropdown.contains(ev.target)) {
                        hideDropdown();
                    }
                });
                return navItem;
            }
            function addButtonToNavbar(container) {
                if (container.querySelector('[data-raguides-injected]')) return;
                const navItem = createRAGuidesButton(window.RAGUIDES || guidesData);
                const downloadButton = container.querySelector('.nav-item a[href*="downloads"]')?.closest('.nav-item');
                if (downloadButton) {
                    downloadButton.before(navItem);
                } else {
                    const mlAuto = container.querySelector('.ml-auto');
                    const notifications = container.querySelector('[wire\\:id]');
                    if (mlAuto) mlAuto.before(navItem);
                    else if (notifications) notifications.before(navItem);
                    else container.appendChild(navItem);
                }
            }
            function processAllNavbars() {
                const containers = document.querySelectorAll('.flex-1.mx-2, .flex.items-center.bg-embedded');
                containers.forEach(c => {
                    if (c.querySelector('.nav-item')) {
                        addButtonToNavbar(c);
                    }
                });
            }
            injectStyles();
            processAllNavbars();
            const observer = createTrackedObserver((mutations) => {
                mutations.forEach(mutation => {
                    if (!mutation.addedNodes.length) return;
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType !== Node.ELEMENT_NODE) return;
                        if (node.matches && (node.matches('.flex-1.mx-2') || node.matches('.flex.items-center.bg-embedded'))) {
                            if (node.querySelector('.nav-item')) addButtonToNavbar(node);
                        }
                        if (node.querySelectorAll) {
                            node.querySelectorAll('.flex-1.mx-2, .flex.items-center.bg-embedded').forEach(navbar => {
                                if (navbar.querySelector('.nav-item')) addButtonToNavbar(navbar);
                            });
                        }
                    });
                });
            });
            observer.observe(document.body, { childList: true, subtree: true });
        } catch (e) {
            console.error('[RA+] Error initializing RA Guides:', e);
        }
    }
// beta
    function injectBetaNotice() {
        try {
            const gameInfo = document.querySelector('div.flex.flex-col.sm\\:flex-row.sm\\:w-full.gap-x-4.gap-y-2.items-center.mb-4');
            if (!gameInfo || document.querySelector('.ra-plus-beta-notice')) return;
            const notice = document.createElement('p');
            notice.className = 'ra-plus-beta-notice';
            notice.style.cssText = 'color:red;font-weight:600;margin-top:0.5rem;';
            notice.innerHTML = 'For the script to work, please check "Enable beta features" in the <a href="https://retroachievements.org/settings" style="color:#079bf3;text-decoration:underline;">settings</a>.';
            const metadata = gameInfo.querySelector('div.flex.flex-col.w-full.gap-1');
            if (metadata) {
                metadata.after(notice);
            } else {
                gameInfo.appendChild(notice);
            }
        } catch (e) {
            console.error('[RA+] Error injecting beta notice:', e);
        }
    }
// news
    async function showWhatsNew() {
        try {
            const scriptVersion = '8';
            const lastSeenVersion = await GM.getValue('whatsNewVersion', null);
            if (lastSeenVersion === scriptVersion) return;
            const headerColors = {
                h1: '#FFD700',
                h2: '#FFD700',
                h3: '#FFFFFF'
            };
            const headerSizes = {
                h1: '40px',
                h2: '24px',
                h3: '20px'
            };
            const headerWeights = {
                h1: '700',
                h2: '900',
                h3: '300'
            };
            const textColor = '#FFFFFF';
            const popupBackground = '#2a2a2a';
            const lineColor = '#5a5a5a';
            const popup = document.createElement('div');
            popup.style.cssText = `
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background: ${popupBackground};
                color: ${textColor};
                padding: 25px;
                border-radius: 12px;
                z-index: 99999;
                max-width: 900px;
                text-align: center;
                box-shadow: 0 0 20px rgba(0,0,0,0.8);
            `;
            popup.innerHTML = `
                <h1 style="margin-bottom: 10px;">
                  <img src="https://i.imgur.com/3UZpapk.png" alt="RetroAchievements+" style="height: 70px; vertical-align: middle;">
                </h1>
                <hr style="border:none; height:2px; background:${lineColor}; margin-bottom: 15px;">
                <h3 style="margin-bottom: 10px; font-size: ${headerSizes.h1}; font-weight: ${headerWeights.h2}; color: ${headerColors.h2};">Future of Retroachievements +</h3>
                <ul style="text-align:left; margin-bottom: 20px; padding-left: 20px; color: ${textColor};">
                    <ul>Hey, as some of you might know this script was originally banned on r/retroachievements. The reason is because of the ROM finder, as it currently breaks their rules. It's completely understandable. And so any posts about it can results in ban.</ul>
                    <ul><br>I talked to the mods and they were completely reasonable and this was not forced in any way. I made the choice to remove the rom finder in the near future as I want the script to be sharable. They don't have an issue with it existing, the issue is just posting in the community. As a way to make it up to you I will be adding more features in the future.</ul>
                    <ul><br>I will be taking a break from the project, I will most likely start working on it during december.</ul>
                    <ul style="color: red;">
                      <br>Please refrain from sharing the script on the official RetroAchievement discord / reddit / website. And also do not take it up on the retroachievements mods, this was my choice.
                    </ul>
                </ul>
                <h3 style="margin-bottom: 10px; font-size: ${headerSizes.h2}; font-weight: ${headerWeights.h2}; color: ${headerColors.h2};">Update 8</h3>
                    <ul>Changed the look of the buttons.</ul>
                <button id="whatsNewOkBtn" style="
                    padding: 8px 15px;
                    border:none;
                    background:#225ad9;
                    color:#FFFFFF;
                    font-weight:bold;
                    border-radius:6px;
                    cursor:pointer;
                ">OK</button>
            `;
            document.body.appendChild(popup);
            document.getElementById('whatsNewOkBtn').addEventListener('click', async () => {
                await GM.setValue('whatsNewVersion', scriptVersion);
                popup.remove();
            });
        } catch (e) {
            console.error('[RA+] Error showing what\'s new:', e);
        }
    }
// main
    function init() {
        console.log('[RA+] Initializing RetroAchievements+ v4.2');
        cleanup();
        showWhatsNew();
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', injectBetaNotice);
        } else {
            injectBetaNotice();
        }
        initRAGuides();
        const navObserver = createTrackedObserver(() => {
            if (window.location.pathname !== AppState.lastPath) {
                AppState.lastPath = window.location.pathname;
                handleNavigationChange();
            }
        });
        navObserver.observe(document.body, { childList: true, subtree: true });
        handleNavigationChange();
        const nameWatchInterval = createTrackedInterval(watchGameNameChanges, 2000);
        const cacheCleanupInterval = createTrackedInterval(cleanupOldCache, 5 * 60 * 1000);
        if (window.location.pathname.endsWith('/hashes')) {
            initHashesPage();
        }
        const hashesObserver = createTrackedObserver(() => {
            const currentPath = window.location.pathname;
            if (currentPath.endsWith('/hashes') && AppState.lastPath !== currentPath) {
                AppState.lastPath = currentPath;
                initHashesPage();
            }
        });
        hashesObserver.observe(document.body, { childList: true, subtree: true });
        window.addEventListener('beforeunload', cleanup);
        console.log('[RA+] Initialization complete');
    }
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();