VNDB Friends List

Add friends list and friend votes display for VNDB

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         VNDB Friends List
// @namespace    http://tampermonkey.net/
// @version      1.77
// @description  Add friends list and friend votes display for VNDB
// @author       ALVIBO
// @match        https://vndb.org/v*
// @match        https://vndb.org/u*
// @match        https://vndb.org/t/u*
// @match        https://vndb.org/w?u=u*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addValueChangeListener
// @grant        GM_deleteValue
// @require      https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js
// @connect      api.vndb.org
// @license     http://creativecommons.org/licenses/by-nc-sa/4.0/
// @thanks     For the cover preview on mouseover, I drew some inspiration and used a few lines from the original VNDB Cover Preview script by Kuro_scripts
// ==/UserScript==

(function() {
    'use strict';

    let bc;
    if ('BroadcastChannel' in window) {
        bc = new BroadcastChannel('vndb_friends_channel');        bc.onmessage = function(e) {
            if (e.data && e.data.type === 'friends_update') {
                friends = e.data.friends;
                friendsCache = e.data.friendsCache || friendsCache;
                console.log('Friends list updated from another tab:', friends);

                const userPageMatch = location.pathname.match(/^\/u(\d+)/) ||
                                     location.pathname.match(/^\/t\/u(\d+)/) ||
                                     location.search.match(/[?&]u=u(\d+)/);
                if (userPageMatch) {
                    const userId = userPageMatch[1];
                    const friendId = 'u' + userId;
                    const friendLinks = document.querySelectorAll('header nav menu li a[href="#"]');
                    const friendBtn = Array.from(friendLinks).find(link => !link.textContent.toLowerCase().includes('friends'));
                    if (friendBtn) {
                        friendBtn.textContent = friends.includes(friendId) ? 'remove the friend' : 'add a friend';
                    }
                }
            }
        };
    }

    window.addEventListener('storage', event => {
        if (event.key === 'vndb_friends') {
            try {                const newFriends = JSON.parse(event.newValue);
                friends = newFriends;
                console.log('Friends list updated via storage event:', friends);

                const userPageMatch = location.pathname.match(/^\/u(\d+)/) ||
                                     location.pathname.match(/^\/t\/u(\d+)/) ||
                                     location.search.match(/[?&]u=u(\d+)/);
                if (userPageMatch) {
                    const userId = userPageMatch[1];
                    const friendId = 'u' + userId;
                    const friendLinks = document.querySelectorAll('header nav menu li a[href="#"]');
                    const friendBtn = Array.from(friendLinks).find(link => !link.textContent.toLowerCase().includes('friends'));
                    if (friendBtn) {
                        friendBtn.textContent = friends.includes(friendId) ? 'remove the friend' : 'add a friend';
                    }
                }
            } catch (e) {
                console.error(e);
            }
        }
    });

    // --- API Request Counting Variables and Function ---
    const API_REQUEST_LOG_WINDOW = 5 * 60 * 1000; // 5 minutes in milliseconds

    function logApiRequestCount(category) {
        const now = Date.now();
        let gmKey;
        let categoryName;

        if (category === 'vn') {
            gmKey = 'vndb_friends_vn_api_timestamps';
            categoryName = "VN page votes";
        } else if (category === 'user') {
            gmKey = 'vndb_friends_user_api_timestamps';
            categoryName = "User page features (friends list/activity)";
        } else {
            console.error("VNDB Friends List: Unknown API request category:", category);
            return;
        }

        // 1. Read current timestamps from GM storage
        let timestampsArray = GM_getValue(gmKey, []);
        if (!Array.isArray(timestampsArray)) { // Sanity check / initialize if malformed
            timestampsArray = [];
        }

        // 2. Add the new timestamp
        timestampsArray.push(now);

        // 3. Prune old timestamps (older than API_REQUEST_LOG_WINDOW)
        let i = 0;
        while (i < timestampsArray.length && now - timestampsArray[i] > API_REQUEST_LOG_WINDOW) {
            i++;
        }
        if (i > 0) {
            timestampsArray.splice(0, i);
        }

        // 4. Write the updated array back to GM storage
        GM_setValue(gmKey, timestampsArray);

        // 5. Log the count
        console.log(`VNDB Friends List: ${timestampsArray.length} API request(s) for ${categoryName} in the last 5 minutes (across all tabs).`);
    }
    // --- End API Request Counting ---


    let friends = [];
    const gmFriends = GM_getValue('vndb_friends', []);
    if (Array.isArray(gmFriends) && gmFriends.length > 0) {
        friends = gmFriends;
    }
    if (friends.length === 0) {
        try {
            const localFriends = JSON.parse(localStorage.getItem('vndb_friends') || '[]');
            if (Array.isArray(localFriends) && localFriends.length > 0) {
                friends = localFriends;
            }
        } catch (e) {
            console.error('Error reading from localStorage:', e);
        }
    }
    if (friends.length === 0) {
        try {
            let friendsCacheFromGM = GM_getValue('vndb_friends_cache', {});
            if (Object.keys(friendsCacheFromGM).length === 0) {
                try {
                    friendsCacheFromGM = JSON.parse(localStorage.getItem('vndb_friends_cache') || '{}');
                } catch (e) {
                    console.error('Error reading cache from localStorage:', e);
                }
            }
            if (Object.keys(friendsCacheFromGM).length > 0) {
                friends = Object.keys(friendsCacheFromGM).filter(key => /^u\d+$/.test(key));
            }
        } catch (e) {
            console.error('Error reading from cache:', e);
        }
    }    let friendsCache = GM_getValue('vndb_friends_cache', {});
    for (const key in friendsCache) {
        if (!/^u\d+$/.test(key) && friendsCache[key] && /^u\d+$/.test(friendsCache[key].id)) {
            const properKey = friendsCache[key].id;
            friendsCache[properKey] = friendsCache[key];
            delete friendsCache[key];
        }
    }

    // Clean up cache entries for friends that are no longer in the friends list
    function cleanupStaleCache() {
        let cacheUpdated = false;
        for (const key in friendsCache) {
            if (/^u\d+$/.test(key) && !friends.includes(key)) {
                delete friendsCache[key];
                cacheUpdated = true;
            }
        }
        if (cacheUpdated) {
            GM_setValue('vndb_friends_cache', friendsCache);
            localStorage.setItem('vndb_friends_cache', JSON.stringify(friendsCache));
        }
    }

    // Run cleanup on initialization
    cleanupStaleCache();

    GM_setValue('vndb_friends_cache', friendsCache);
    localStorage.setItem('vndb_friends_cache', JSON.stringify(friendsCache));

    (function() { // VN Page Specific Logic
        const vnPageMatch = location.pathname.match(/^\/v(\d+)/);
        if (!vnPageMatch) return;

        let settings = GM_getValue('vndb_friends_settings', {
            textColor: null,
            buttonTextColor: null,
            backgroundColor: null,
            buttonBackgroundColor: null,
            titleColor: null,
            borderColor: null,
            separatorColor: null,
            fontSize: 17,
            buttonFontSize: 16,
            tabFontSize: 18,
            opacity: null,
            cacheDuration: 3,
            gamesPerFriend: 5,
            maxActivities: 51,
            friendsVotesEnabled: true,
            vnVoteCacheDuration: 5
        });

        const vnId = 'v' + vnPageMatch[1];
        let updateNeeded = false;
        let processingInProgress = false;
        let vnVoteCache = {};
        let currentVNVoteCacheDuration = (settings.vnVoteCacheDuration || 5) * 60 * 1000;
        const NO_VOTE_MARKER = '___NO_VOTE_CACHED___'; // Special marker for cached non-votes

        try {
            const storedVNVoteCache = JSON.parse(sessionStorage.getItem(`vndb_vn_vote_cache_${vnId}`) || '{}');
            const now = Date.now();
            for (const key in storedVNVoteCache) {
                if (storedVNVoteCache[key] && storedVNVoteCache[key].timestamp &&
                    (now - storedVNVoteCache[key].timestamp < currentVNVoteCacheDuration)) {
                    vnVoteCache[key] = storedVNVoteCache[key];
                }
            }
        } catch (e) {
            vnVoteCache = {};
            console.error(`Error loading VN vote cache for ${vnId} from session storage:`, e);
        }


        async function doProcessFriends() {
            if (document.hidden) {
                console.log(`VNDB Friend Votes (${vnId}): Tab is hidden, deferring processing. Marking updateNeeded.`);
                updateNeeded = true;
                return;
            }
            if (processingInProgress) {
                console.log(`VNDB Friend Votes (${vnId}): Processing already in progress.`);
                return;
            }
            if (!settings.friendsVotesEnabled) {
                console.log(`VNDB Friend Votes (${vnId}): Disabled by settings.`);
                // Ensure any existing section is removed if disabled
                const wrapper = document.querySelector('[data-vndb-friends-wrapper="true"]');
                if (wrapper) {
                    const oldTagSection = wrapper.querySelector('.friends-votes-tag-section');
                    if (oldTagSection) oldTagSection.remove();
                }
                return;
            }
            if (friends.length === 0) {
                console.log(`VNDB Friend Votes (${vnId}): No friends found.`);
                 // Ensure any existing section is removed if no friends
                const wrapper = document.querySelector('[data-vndb-friends-wrapper="true"]');
                if (wrapper) {
                    const oldTagSection = wrapper.querySelector('.friends-votes-tag-section');
                    if (oldTagSection) oldTagSection.remove();
                }
                return;
            }

            console.log(`VNDB Friend Votes (${vnId}): Starting processing for ${friends.length} friends.`);
            processingInProgress = true;
            updateNeeded = false; // Reset before processing

            try {
                await processFriends(friends);
            } catch (err) {
                console.error(`VNDB Friend Votes (${vnId}): Error during processFriends:`, err);
            } finally {
                processingInProgress = false;
                console.log(`VNDB Friend Votes (${vnId}): Processing finished.`);
                if (updateNeeded && !document.hidden) {
                    console.log(`VNDB Friend Votes (${vnId}): Update requested during processing, re-triggering.`);
                    setTimeout(doProcessFriends, 100);
                }
            }
        }


        if (!document.hidden) {
            console.log(`VNDB Friend Votes (${vnId}): Tab is active on initial load. Triggering initial processing.`);
            doProcessFriends();
        } else {
            console.log(`VNDB Friend Votes (${vnId}): Tab is hidden on initial load. Will process when active or data changes.`);
            updateNeeded = true;
        }

        document.addEventListener('visibilitychange', () => {
            if (!document.hidden) {
                console.log(`VNDB Friend Votes (${vnId}): Tab became visible.`);
                if (updateNeeded) {
                    console.log(`VNDB Friend Votes (${vnId}): Update was pending, processing now.`);
                    doProcessFriends();
                }
            } else {
                console.log(`VNDB Friend Votes (${vnId}): Tab became hidden.`);
            }
        });


        async function processFriends(friendsList) {
            currentVNVoteCacheDuration = (settings.vnVoteCacheDuration || 5) * 60 * 1000;

            function fetchFriendVote(userId) {
                return new Promise(resolve => {
                    const cacheKey = `${userId}_${vnId}`;
                    const cachedEntry = vnVoteCache[cacheKey];

                    if (cachedEntry && (Date.now() - cachedEntry.timestamp < currentVNVoteCacheDuration)) {
                        console.log(`VNDB Friend Votes (${vnId}): Using cached entry for ${userId}`);
                        if (cachedEntry.vote === NO_VOTE_MARKER) {
                            resolve(null); // Cached non-vote
                        } else {
                            resolve({ userId, vote: cachedEntry.vote });
                        }
                        return;
                    }

                    GM_xmlhttpRequest({
                        method: 'POST',
                        url: 'https://api.vndb.org/kana/ulist',
                        headers: { 'Content-Type': 'application/json' },
                        data: JSON.stringify({
                            user: userId,
                            filters: ['id', '=', vnId],
                            fields: 'id,vote'
                        }),
                        onload(resp) {
                            logApiRequestCount('vn');
                            try {
                                const data = JSON.parse(resp.responseText);
                                if (data.results && data.results.length > 0 && data.results[0].vote != null) {
                                    vnVoteCache[cacheKey] = { vote: data.results[0].vote, timestamp: Date.now() };
                                    resolve({ userId, vote: data.results[0].vote });
                                } else {
                                    vnVoteCache[cacheKey] = { vote: NO_VOTE_MARKER, timestamp: Date.now() };
                                    resolve(null);
                                }
                                try {
                                    sessionStorage.setItem(`vndb_vn_vote_cache_${vnId}`, JSON.stringify(vnVoteCache));
                                } catch (se) { console.error("Error saving VN vote cache to session storage:", se); }
                            } catch (e) {
                                console.error(`Error processing response for ${userId} on ${vnId}:`, e);
                                resolve(null);
                            }
                        },
                        onerror() {
                            logApiRequestCount('vn');
                            console.error(`Request failed for ${userId} on ${vnId}`);
                            resolve(null);
                        }
                    });
                });
            }

            async function ensureUsernames(votes) {
                let localFriendsCache = GM_getValue('vndb_friends_cache', {});
                 if (Object.keys(localFriendsCache).length === 0) {
                    try {
                        const storedCache = JSON.parse(localStorage.getItem('vndb_friends_cache') || '{}');
                        if (Object.keys(storedCache).length > 0) {
                            localFriendsCache = storedCache;
                        }
                    } catch (e) {
                        console.error('VNDB Friend Votes: Error reading cache from localStorage:', e);
                    }
                }

                const missingUserIds = votes.filter(v => !localFriendsCache[v.userId] || !localFriendsCache[v.userId].username)
                                           .map(v => v.userId);

                if (missingUserIds.length > 0) {
                    console.log(`VNDB Friend Votes (${vnId}): Fetching ${missingUserIds.length} missing usernames`);
                    const CHUNK_SIZE = 50;
                    for (let i = 0; i < missingUserIds.length; i += CHUNK_SIZE) {
                        const chunk = missingUserIds.slice(i, i + CHUNK_SIZE);
                        await new Promise(resolveChunk => {
                            GM_xmlhttpRequest({
                                method: 'POST',
                                url: 'https://api.vndb.org/kana/user',
                                headers: { 'Content-Type': 'application/json' },
                                data: JSON.stringify({
                                    filters: ['id', 'in', chunk],
                                    fields: 'id,username'
                                }),
                                onload(r) {
                                    logApiRequestCount('vn');
                                    try {
                                        const data = JSON.parse(r.responseText);
                                        if (data.results && data.results.length > 0) {
                                            data.results.forEach(user => {
                                                localFriendsCache[user.id] = { username: user.username, id: user.id, lastUpdate: Date.now() };
                                            });
                                        }
                                    } catch (e) {
                                        console.error(`Error processing usernames chunk:`, e);
                                        chunk.forEach(uid => {
                                            if (!localFriendsCache[uid]) localFriendsCache[uid] = { username: uid, id: uid, lastUpdate: Date.now() };
                                        });
                                    }
                                    resolveChunk();
                                },
                                onerror() {
                                    logApiRequestCount('vn');
                                    console.error(`Username request failed for chunk starting with ${chunk[0]}`);
                                    chunk.forEach(uid => {
                                        if (!localFriendsCache[uid]) localFriendsCache[uid] = { username: uid, id: uid, lastUpdate: Date.now() };
                                    });
                                    resolveChunk();
                                }
                            });
                        });
                    }
                    GM_setValue('vndb_friends_cache', localFriendsCache);
                    friendsCache = localFriendsCache;
                }
                return votes.map(v => ({
                    userId: v.userId,
                    username: localFriendsCache[v.userId]?.username || v.userId,
                    vote: v.vote
                }));
            }


            function formatVote(vote) {
                if (vote >= 10) {
                    return (vote / 10).toFixed(1);
                }
                return vote.toFixed(1);
            }

            async function renderFriendVotes(friendVotesWithUsernames) { // Renamed parameter for clarity
                // This function is now only called if friendVotesWithUsernames.length > 0
                const data = friendVotesWithUsernames; // Already has usernames
                data.sort((a, b) => b.vote - a.vote);

                const statsContainer = document.querySelector('.votestats');
                if (!statsContainer) {
                    console.error(`VNDB Friend Votes (${vnId}): could not locate .votestats`);
                    return;
                }

                let wrapper = statsContainer.closest('[data-vndb-friends-wrapper="true"]');

                if (!wrapper) {
                    wrapper = document.createElement('div');
                    wrapper.dataset.vndbFriendsWrapper = 'true';

                    wrapper.style.display = 'flex';
                    wrapper.style.flexWrap = 'wrap';
                    wrapper.style.alignItems = 'flex-start';
                    wrapper.style.gap = '1em';

                    wrapper.style.justifyContent = 'center';
                    wrapper.style.maxWidth = '850px';
                    wrapper.style.margin = '0 auto';

                    const statsArticle = statsContainer.closest('article#stats');
                    const parentElement = statsArticle || statsContainer.parentNode;

                    if (parentElement) {
                        parentElement.insertBefore(wrapper, statsContainer);
                        wrapper.appendChild(statsContainer);
                    } else {
                        console.error(`VNDB Friend Votes (${vnId}): Could not find suitable parent for wrapper.`);
                        statsContainer.parentNode.insertBefore(wrapper, statsContainer.nextSibling);
                        wrapper.appendChild(statsContainer);
                    }
                } else {
                    wrapper.style.justifyContent = 'center';
                    wrapper.style.maxWidth = '850px';
                    wrapper.style.margin = '0 auto';
                }

                const oldTagSection = wrapper.querySelector('.friends-votes-tag-section');
                if (oldTagSection) oldTagSection.remove();

                const voteColors = {};
                const voteGraphTable = statsContainer.querySelector('table.votegraph');
                const defaultVoteColor = '#555';

                if (voteGraphTable) {
                    const numberCells = voteGraphTable.querySelectorAll('tbody td.number');
                    numberCells.forEach(cell => {
                        const voteNumber = cell.textContent.trim();
                        if (voteNumber && !isNaN(voteNumber)) {
                            voteColors[voteNumber] = window.getComputedStyle(cell).color;
                        }
                    });
                } else {
                    console.warn(`VNDB Friend Votes (${vnId}): Could not find votegraph to extract colors.`);
                }

                const friendsTagSection = document.createElement('div');
                friendsTagSection.className = 'friends-votes-tag-section';
                friendsTagSection.style.flexBasis = '300px';
                friendsTagSection.style.flexGrow = '1';
                friendsTagSection.style.minWidth = '250px';
                friendsTagSection.style.display = 'flex';
                friendsTagSection.style.flexDirection = 'column';
                friendsTagSection.style.alignItems = 'center';

                const header = document.createElement('h3');
                header.textContent = `Friends' votes (${data.length})`;
                const recentVotesHeaderCell = statsContainer.querySelector('table.recentvotes.stripe thead td');

                if (recentVotesHeaderCell) {
                    const sourceStyle = window.getComputedStyle(recentVotesHeaderCell);
                    header.style.fontSize = sourceStyle.fontSize;
                    header.style.fontWeight = sourceStyle.fontWeight;
                    header.style.fontFamily = sourceStyle.fontFamily;
                    header.style.color = sourceStyle.color;
                    header.style.lineHeight = sourceStyle.lineHeight;
                    header.style.letterSpacing = sourceStyle.letterSpacing;
                    header.style.margin = '0';
                    header.style.marginTop = '1em';
                    header.style.marginBottom = '0.5em';
                    header.style.borderBottom = '1px dotted #ccc';
                    header.style.paddingBottom = '0.3em';
                    header.style.textAlign = 'center';
                    header.style.width = '100%';
                    header.style.maxWidth = 'calc(100% - 1em)';
                    header.style.boxSizing = 'border-box';
                } else {
                    console.warn("Could not find Recent votes header cell to copy style.");
                    header.style.marginTop = '1em';
                    header.style.marginBottom = '0.5em';
                    header.style.fontSize = '1.1em';
                    header.style.borderBottom = '1px dotted #ccc';
                    header.style.paddingBottom = '0.3em';
                    header.style.textAlign = 'center';
                    header.style.width = '100%';
                    header.style.maxWidth = 'calc(100% - 1em)';
                    header.style.boxSizing = 'border-box';
                }
                friendsTagSection.appendChild(header);

                const tagContainer = document.createElement('div');
                tagContainer.className = 'friends-votes-tag-container';
                tagContainer.style.display = 'flex';
                tagContainer.style.flexWrap = 'wrap';
                tagContainer.style.gap = '0.3em 0.7em';
                tagContainer.style.justifyContent = 'center';

                data.forEach(friend => {
                    const friendSpan = document.createElement('span');
                    friendSpan.className = 'friend-vote-tag';
                    friendSpan.style.whiteSpace = 'nowrap';

                    const nameLink = document.createElement('a');
                    nameLink.href = `/u${friend.userId.slice(1)}`;
                    nameLink.textContent = friend.username;

                    const voteSmall = document.createElement('small');
                    voteSmall.textContent = formatVote(friend.vote);
                    voteSmall.style.marginLeft = '0.4em';

                    let voteKey;
                    if (friend.vote >= 10) {
                        voteKey = '10';
                    } else if (friend.vote >= 1) {
                        voteKey = Math.floor(friend.vote).toString();
                    } else {
                        voteKey = '1';
                    }
                    voteSmall.style.color = voteColors[voteKey] || defaultVoteColor;

                    friendSpan.appendChild(nameLink);
                    friendSpan.appendChild(voteSmall);
                    tagContainer.appendChild(friendSpan);
                });
                friendsTagSection.appendChild(tagContainer);

                if (wrapper.contains(statsContainer)) {
                    statsContainer.parentNode.insertBefore(friendsTagSection, statsContainer.nextSibling);
                } else {
                    wrapper.appendChild(friendsTagSection);
                }

                console.log(`VNDB Friend Votes (${vnId}): UI rendered successfully (Centered Tag Layout, After Stats)`);
            }

            console.log(`VNDB Friend Votes (${vnId}): Starting API requests for votes`);
            Promise.all(friendsList.map(fetchFriendVote))
                .then(results => results.filter(Boolean))
                .then(async friendVotes => { // Made this async to await ensureUsernames
                    const wrapper = document.querySelector('[data-vndb-friends-wrapper="true"]');
                    const oldTagSection = wrapper ? wrapper.querySelector('.friends-votes-tag-section') : null;

                    if (friendVotes.length === 0) {
                        console.log(`VNDB Friend Votes (${vnId}): No friend votes found for this VN. Section will not be displayed.`);
                        if (oldTagSection) {
                            oldTagSection.remove();
                        }
                        return;
                    }

                    console.log(`VNDB Friend Votes (${vnId}): Found ${friendVotes.length} friend votes, ensuring usernames.`);
                    const friendVotesWithUsernames = await ensureUsernames(friendVotes);

                    if (friendVotesWithUsernames.length === 0) {
                        console.log(`VNDB Friend Votes (${vnId}): No friend votes after username enrichment. Section will not be displayed.`);
                         if (oldTagSection) {
                            oldTagSection.remove();
                        }
                        return;
                    }

                    return renderFriendVotes(friendVotesWithUsernames);
                })
                .catch(err => {
                    console.error(`VNDB Friend Votes (${vnId}): Error in vote processing chain:`, err);
                });
        }

        GM_addValueChangeListener('vndb_friends', (name, old_value, new_value, isRemote) => {
            if (isRemote || JSON.stringify(old_value) !== JSON.stringify(new_value)) {
                console.log(`VNDB Friend Votes (${vnId}): Friends list changed, triggering update.`);
                friends = new_value;
                vnVoteCache = {};
                try { sessionStorage.removeItem(`vndb_vn_vote_cache_${vnId}`); } catch(e){}
                updateNeeded = true;
                doProcessFriends();
            }
        });

        GM_addValueChangeListener('vndb_friends_cache', (name, old_value, new_value, isRemote) => {
             if (isRemote || JSON.stringify(old_value) !== JSON.stringify(new_value)) {
                console.log(`VNDB Friend Votes (${vnId}): Friends cache changed, triggering update if needed for usernames.`);
                friendsCache = new_value;
                updateNeeded = true;
                doProcessFriends();
            }
        });
         GM_addValueChangeListener('vndb_friends_settings', (name, old_value, new_value, isRemote) => {
            if (isRemote || JSON.stringify(old_value) !== JSON.stringify(new_value)) {
                console.log(`VNDB Friend Votes (${vnId}): Settings changed.`);
                const oldSettings = settings;
                settings = new_value;
                currentVNVoteCacheDuration = (settings.vnVoteCacheDuration || 5) * 60 * 1000;

                if (oldSettings.friendsVotesEnabled !== settings.friendsVotesEnabled ||
                    oldSettings.vnVoteCacheDuration !== settings.vnVoteCacheDuration) {
                    updateNeeded = true;
                    doProcessFriends();
                }
            }
        });
    })();

    (function() { // User Page Specific Logic
        const userPageMatch = location.pathname.match(/^\/u(\d+)/) ||
                             location.pathname.match(/^\/t\/u(\d+)/) ||
                             location.search.match(/[?&]u=u(\d+)/);
        if (!userPageMatch) return;

        const userId = userPageMatch[1];
        let activityTabClicked = false;
        let editLink = document.querySelector('header nav menu li a[href$="/edit"]');        GM_addValueChangeListener('vndb_friends', (name, old_value, new_value, isRemote) => {
            if (isRemote) {
                friends = new_value;
                if (document.querySelector('.friends-container')?.style.display === 'block') {
                    displayFriendsList();
                }
                // Update the friend button text when page is visible and active
                if (friendBtn && !document.hidden && document.hasFocus()) {
                    const isAlreadyFriend = friends.includes(friendId);
                    friendBtn.textContent = isAlreadyFriend ? 'remove the friend' : 'add a friend';
                }
            }
        });
        GM_addValueChangeListener('vndb_friends_cache', (name, old_value, new_value, isRemote) => {
            if (isRemote) {
                friendsCache = new_value;
                 if (document.querySelector('.friends-container')?.style.display === 'block') {
                    displayFriendsList();
                }
            }
        });

        async function batchFetchUsersByUsername(usernames) {
            if (!usernames || usernames.length === 0) return [];
            const CHUNK_SIZE = 50;
            let results = [];
            for (let i = 0; i < usernames.length; i += CHUNK_SIZE) {
                const chunk = usernames.slice(i, i + CHUNK_SIZE);
                try {
                    const response = await new Promise((resolve, reject) => {
                        GM_xmlhttpRequest({
                            method: 'POST',
                            url: 'https://api.vndb.org/kana/user',
                            headers: { 'Content-Type': 'application/json' },
                            data: JSON.stringify({
                                filters: ['username', 'in', chunk],
                                fields: 'id,username'
                            }),
                            onload: (resp) => {
                                logApiRequestCount('user');
                                try {
                                    const data = JSON.parse(resp.responseText);
                                    resolve(data.results || []);
                                } catch (e) {
                                    console.error("Error parsing batch user by username response:", e, resp.responseText);
                                    resolve([]);
                                }
                            },
                            onerror: (err) => {
                                logApiRequestCount('user');
                                console.error("Error in batch user by username request:", err);
                                reject(err);
                            }
                        });
                    });
                    results = results.concat(response);
                } catch (error) {
                    console.error(`Error fetching batch of usernames starting with ${chunk[0]}:`, error);
                }
            }
            return results;
        }

        async function batchFetchUsersById(userIds) {
            if (!userIds || userIds.length === 0) return [];
            const CHUNK_SIZE = 50;
            let results = [];
            for (let i = 0; i < userIds.length; i += CHUNK_SIZE) {
                const chunk = userIds.slice(i, i + CHUNK_SIZE);
                try {
                    const response = await new Promise((resolve, reject) => {
                        GM_xmlhttpRequest({
                            method: 'POST',
                            url: 'https://api.vndb.org/kana/user',
                            headers: { 'Content-Type': 'application/json' },
                            data: JSON.stringify({
                                filters: ['id', 'in', chunk],
                                fields: 'id,username'
                            }),
                            onload: (resp) => {
                                logApiRequestCount('user');
                                try {
                                    const data = JSON.parse(resp.responseText);
                                    resolve(data.results || []);
                                } catch (e) {
                                    console.error("Error parsing batch user by ID response:", e, resp.responseText);
                                    resolve([]);
                                }
                            },
                            onerror: (err) => {
                                logApiRequestCount('user');
                                console.error("Error in batch user by ID request:", err);
                                reject(err);
                            }
                        });
                    });
                    results = results.concat(response);
                } catch (error) {
                    console.error(`Error fetching batch of user IDs starting with ${chunk[0]}:`, error);
                }
            }
            return results;
        }


        (async function migrateIfNeeded() {
            const usernamesToMigrate = friends.filter(f => !/^u\d+$/.test(f));
            if (usernamesToMigrate.length > 0) {
                console.log("Migrating old friend entries (usernames to IDs):", usernamesToMigrate);
                const fetchedUsers = await batchFetchUsersByUsername(usernamesToMigrate);
                const newFriends = friends.filter(f => /^u\d+$/.test(f));

                fetchedUsers.forEach(userData => {
                    if (userData && userData.id) {
                        if (!newFriends.includes(userData.id)) {
                            newFriends.push(userData.id);
                        }
                        friendsCache[userData.id] = { id: userData.id, username: userData.username, lastUpdate: Date.now() };
                    }
                });

                usernamesToMigrate.forEach(oldUsername => {
                    const indexInNew = newFriends.indexOf(oldUsername);
                    if (indexInNew > -1) newFriends.splice(indexInNew, 1);
                });


                console.log("Migration result - New friends list:", newFriends);
                friends = newFriends;
                GM_setValue('vndb_friends', friends);
                GM_setValue('vndb_friends_cache', friendsCache);
                localStorage.setItem('vndb_friends', JSON.stringify(friends));
                localStorage.setItem('vndb_friends_cache', JSON.stringify(friendsCache));
                if (document.querySelector('.friends-container')?.style.display === 'block') {
                    displayFriendsList();
                }
            }
        })();


        let currentPage = parseInt(localStorage.getItem('vndb_friends_current_page')) || 1;
        const friendsPerPage = 10;
        let settings = GM_getValue('vndb_friends_settings', {
            textColor: null,
            buttonTextColor: null,
            backgroundColor: null,
            buttonBackgroundColor: null,
            titleColor: null,
            borderColor: null,
            separatorColor: null,
            fontSize: 17,
            buttonFontSize: 16,
            tabFontSize: 18,
            opacity: null,
            cacheDuration: 3,
            gamesPerFriend: 5,
            maxActivities: 51,
            friendsVotesEnabled: true,
            vnVoteCacheDuration: 5
        });

        let isUpdatingActivity = false;
        let currentRequestId = null;
        let reloadTimeout;
        const baseUserUrl = location.pathname.split('/')[1] + (location.pathname.split('/')[2] || '');

        function getBackgroundColor() {
            const bodyBg = window.getComputedStyle(document.body).backgroundColor;
            const rgb = bodyBg.match(/\d+/g);
            return rgb ? rgb.map(Number) : [255, 255, 255];
        }

        function createThemeColors() {
            const bgColor = getBackgroundColor();
            const mainTextColor = window.getComputedStyle(document.body).color;
            const articleH1 = document.querySelector('article h1');
            const titleColor = articleH1 ? window.getComputedStyle(articleH1).color : mainTextColor;
            const opacity = settings.opacity || 0.70;

            return {
                containerBg: settings.backgroundColor ?
                    `rgba(${parseInt(settings.backgroundColor.slice(1,3),16)},
                          ${parseInt(settings.backgroundColor.slice(3,5),16)},
                          ${parseInt(settings.backgroundColor.slice(5,7),16)},
                          ${opacity})` :
                    `rgba(${bgColor[0]}, ${bgColor[1]}, ${bgColor[2]}, ${opacity})`,
                borderColor: mainTextColor,
                textColor: settings.textColor || mainTextColor,
                linkColor: settings.titleColor || titleColor
            };
        }

        const friendsContainer = document.createElement('div');
        const themeColors = createThemeColors();

        friendsContainer.innerHTML = `
            <style>
                .friends-container {
                    display: none;
                    position: fixed;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                    padding: 20px;
                    border: 1px solid ${themeColors.borderColor};
                    z-index: 1000;
                    min-width: 300px;
                    font-size: ${settings.fontSize || '17px'};
                    max-height: 80vh;
                    max-width: 90vw;
                    overflow-y: auto;
                }
                .friends-container::before {
                    content: '';
                    position: absolute;
                    top: 0;
                    left: 0;
                    right: 0;
                    bottom: 0;
                    backdrop-filter: blur(5px);
                    z-index: -1;
                }
                .friends-settings {
                    margin-top: 10px;
                    border-top: 1px solid ${themeColors.borderColor};
                    padding-top: 10px;
                    display: none;
                }
                .settings-group {
                    margin: 5px 0;
                    display: flex;
                    align-items: center;
                    gap: 5px;
                }
                .settings-group label {
                    min-width: 120px;
                }
                .color-inputs {
                    display: flex;
                    gap: 5px;
                    align-items: center;
                }
                .color-inputs input[type="text"],
                .color-inputs input[type="number"] {
                    width: 70px;
                    padding: 2px 4px;
                    border: 1px solid;
                    border-radius: 3px;
                    background: inherit;
                }
                .settings-toggle {
                    margin-top: 10px;
                    text-align: center;
                }
                .friends-container h2,
                .friends-container h3 {
                    color: ${themeColors.linkColor};
                }
                .friends-container .friend-link {
                    color: ${themeColors.textColor} !important;
                }
                .tab-buttons {
                    display: flex;
                    margin-bottom: 15px;
                    border-bottom: 1px solid ${themeColors.borderColor};
                }
                .tab-button {
                    padding: 8px 16px;
                    border: none;
                    background: none;
                    color: ${themeColors.textColor};
                    cursor: pointer;
                }
                .tab-button.active {
                    border-bottom: 2px solid ${themeColors.linkColor};
                }
                .tab-content {
                    display: none;
                }
                .tab-content.active {
                    display: block;
                }
                .activity-item {
                    margin: 8px 0;
                    padding: 8px;
                    border-bottom: 1px solid ${settings.separatorColor || themeColors.borderColor};
                    word-break: break-word;
                    overflow-wrap: break-word;
                }
                .activity-item:first-child {
                    padding-top: 0;
                }
                .activity-date {
                    color: ${themeColors.textColor};
                    opacity: 0.8;
                    font-size: 0.9em;
                }
                .friends-container::-webkit-scrollbar {
                    width: 8px;
                }
                .friends-container::-webkit-scrollbar-track {
                    background: rgba(0, 0, 0, 0.1);
                }
                .friends-container::-webkit-scrollbar-thumb {
                    background: rgba(128, 128, 128, 0.5);
                    border-radius: 4px;
                }
                #activityFeed {
                    max-height: calc(80vh - 300px);
                    overflow-y: auto;
                    margin-bottom: 15px;
                    will-change: transform;
                    contain: layout style; /* Optimization hint */
                }
                #activityFeed::-webkit-scrollbar {
                    width: 8px;
                }
                #activityFeed::-webkit-scrollbar-track {
                    background: rgba(0, 0, 0, 0.1);
                }
                #activityFeed::-webkit-scrollbar-thumb {
                    background: rgba(128, 128, 128, 0.5);
                    border-radius: 4px;
                }
                .activity-controls {
                    margin-top: 10px;
                    text-align: center;
                }
                .friends-container button:not(.tab-button) {
                    font-size: ${settings.buttonFontSize ? `${settings.buttonFontSize}px` : '16px'} !important;
                }
                .tab-button {
                    font-size: ${settings.tabFontSize ? `${settings.tabFontSize}px` : '18px'} !important;
                }
            </style>
            <div class="friends-container">
                <h2>Friends List</h2>
                <div class="tab-buttons">
                    <button class="tab-button active" data-tab="friendsList">Friends List</button>
                    <button class="tab-button" data-tab="activityFeed">Recent Activity</button>
                </div>
                <div id="friendsList" class="tab-content active"></div>
                <div id="activityFeed" class="tab-content"></div>
                <div class="activity-controls tab-content" data-tab="activityFeed">
                    <button id="reloadActivity">Reload Activity</button>
                </div>
                <div id="pagination" style="margin-top: 10px; text-align: center;"></div>
                <div style="margin-top: 10px;">
                    <input type="text" id="newFriend" placeholder="Username" style="margin-right: 5px;">
                    <button id="addFriend">Add Friend</button>
                </div>
                <div class="settings-toggle">
                    <button id="toggleSettings">Show Settings</button>
                </div>
                <div class="friends-settings">
                    <h3>Settings</h3>
                    <div class="settings-group">
                        <label>Title Color:</label>
                        <div class="color-inputs">
                            <input type="color" id="titleColor">
                            <input type="text" id="titleColorHex" placeholder="#hex">
                        </div>
                        <button class="resetButton" data-setting="titleColor">Reset</button>
                    </div>
                    <div class="settings-group">
                        <label>Text Color:</label>
                        <div class="color-inputs">
                            <input type="color" id="textColor">
                            <input type="text" id="textColorHex" placeholder="#hex">
                        </div>
                        <button class="resetButton" data-setting="textColor">Reset</button>
                    </div>
                    <div class="settings-group">
                        <label>Button Text:</label>
                        <div class="color-inputs">
                            <input type="color" id="buttonTextColor">
                            <input type="text" id="buttonTextColorHex" placeholder="#hex">
                        </div>
                        <button class="resetButton" data-setting="buttonTextColor">Reset</button>
                    </div>
                    <div class="settings-group">
                        <label>Background:</label>
                        <div class="color-inputs">
                            <input type="color" id="backgroundColor">
                            <input type="text" id="backgroundColorHex" placeholder="#hex">
                        </div>
                        <button class="resetButton" data-setting="backgroundColor">Reset</button>
                    </div>
                    <div class="settings-group">
                        <label>Button Color:</label>
                        <div class="color-inputs">
                            <input type="color" id="buttonBackgroundColor">
                            <input type="text" id="buttonBackgroundColorHex" placeholder="#hex">
                        </div>
                        <button class="resetButton" data-setting="buttonBackgroundColor">Reset</button>
                    </div>
                    <div class="settings-group">
                        <label>Border Color:</label>
                        <div class="color-inputs">
                            <input type="color" id="borderColor">
                            <input type="text" id="borderColorHex" placeholder="#hex">
                        </div>
                        <button class="resetButton" data-setting="borderColor">Reset</button>
                    </div>
                    <div class="settings-group">
                        <label>Separator Color:</label>
                        <div class="color-inputs">
                            <input type="color" id="separatorColor">
                            <input type="text" id="separatorColorHex" placeholder="#hex">
                        </div>
                        <button class="resetButton" data-setting="separatorColor">Reset</button>
                    </div>
                    <div class="settings-group">
                        <label>Font Size:</label>
                        <div class="color-inputs">
                            <input type="number" id="fontSize" min="8" max="24" step="1">
                            <span>px</span>
                        </div>
                        <button class="resetButton" data-setting="fontSize">Reset</button>
                    </div>
                    <div class="settings-group">
                        <label>Button Text Size:</label>
                        <div class="color-inputs">
                            <input type="number" id="buttonFontSize" min="8" max="24" step="1">
                            <span>px</span>
                        </div>
                        <button class="resetButton" data-setting="buttonFontSize">Reset</button>
                    </div>
                    <div class="settings-group">
                        <label>Tab Text Size:</label>
                        <div class="color-inputs">
                            <input type="number" id="tabFontSize" min="8" max="24" step="1">
                            <span>px</span>
                        </div>
                        <button class="resetButton" data-setting="tabFontSize">Reset</button>
                    </div>
                    <div class="settings-group">
                        <label>Opacity:</label>
                        <input type="range" id="opacity" min="0" max="100" step="5">
                        <span id="opacityValue"></span>%
                        <button class="resetButton" data-setting="opacity">Reset</button>
                    </div>
                    <div class="settings-group">
                        <label>Activity Cache:</label>
                        <div class="color-inputs">
                            <input type="number" id="cacheDuration" min="1" max="60" step="1">
                            <span>minutes</span>
                        </div>
                        <button class="resetButton" data-setting="cacheDuration">Reset</button>
                    </div>
                     <div class="settings-group">
                        <label>VN Vote Cache:</label>
                        <div class="color-inputs">
                            <input type="number" id="vnVoteCacheDuration" min="1" max="60" step="1">
                            <span>minutes</span>
                        </div>
                        <button class="resetButton" data-setting="vnVoteCacheDuration">Reset</button>
                    </div>
                    <div class="settings-group">
                        <label>Games per Friend:</label>
                        <div class="color-inputs">
                            <input type="number" id="gamesPerFriend" min="1" max="50" step="1">
                            <span>games</span>
                        </div>
                        <button class="resetButton" data-setting="gamesPerFriend">Reset</button>
                    </div>
                    <div class="settings-group">
                        <label>Max Activities:</label>
                        <div class="color-inputs">
                            <input type="number" id="maxActivities" min="5" max="100" step="1">
                            <span>total</span>
                        </div>
                        <button class="resetButton" data-setting="maxActivities">Reset</button>
                    </div>
                    <div class="settings-group">
                        <label>Show Friends' Votes on VN Pages:</label>
                        <input type="checkbox" id="friendsVotesToggle">
                    </div>
                </div>
                <button id="closeFriends" style="margin-top: 10px;">Close</button>
            </div>
        `;

        const STATE_UPDATE_KEY = 'vndb_friends_state_update';
        let lastStateUpdate = Date.now();

        if (!document.querySelector('.friends-container')) {
            document.body.appendChild(friendsContainer);
        }

        const container = friendsContainer.querySelector('.friends-container');
        const settingsPanel = container.querySelector('.friends-settings');
        updateContainerStyle();

        const domCache = {
            friendsList: document.getElementById('friendsList'),
            activityFeed: document.getElementById('activityFeed'),
            pagination: document.getElementById('pagination'),
            newFriend: document.getElementById('newFriend'),
            reloadActivity: document.getElementById('reloadActivity'),
            closeFriends: document.getElementById('closeFriends'),
            toggleSettings: document.getElementById('toggleSettings'),
            addFriend: document.getElementById('addFriend')
        };

        function updateContainerStyle() {
            const themeColors = createThemeColors();
            container.style.border = `1px solid ${settings.borderColor || themeColors.borderColor}`;
            container.style.background = themeColors.containerBg;
            container.style.color = settings.textColor || themeColors.textColor;
            container.style.fontSize = settings.fontSize ? `${settings.fontSize}px` : '17px';

            const titles = container.querySelectorAll('h2, h3');
            titles.forEach(title => {
                title.style.setProperty('color', settings.titleColor || themeColors.linkColor, 'important');
            });

            const friendLinks = container.querySelectorAll('.friend-link');
            friendLinks.forEach(link => {
                link.style.setProperty('color', settings.textColor || themeColors.textColor, 'important');
            });
        }

        const dynamicStyles = document.createElement('style');
        document.head.appendChild(dynamicStyles);

        function updateDynamicStyles() {
            const themeColors = createThemeColors();
            const opacity = settings.opacity || 0.70;
            let backgroundStyle = themeColors.containerBg;
            if (settings.backgroundColor) {
                const hex = settings.backgroundColor.replace('#', '');
                const r = parseInt(hex.substring(0, 2), 16);
                const g = parseInt(hex.substring(2, 4), 16);
                const b = parseInt(hex.substring(4, 6), 16);
                backgroundStyle = `rgba(${r}, ${g}, ${b}, ${opacity})`;
            }
            dynamicStyles.textContent = `
                .friends-container {
                    border: 1px solid ${settings.borderColor || themeColors.borderColor} !important;
                    background: ${backgroundStyle} !important;
                }
                .friends-container .friend-link {
                    color: ${settings.textColor || themeColors.textColor} !important;
                }
                .friends-container h2,
                .friends-container h3 {
                    color: ${settings.titleColor || themeColors.linkColor} !important;
                }
                .friends-container button {
                    background-color: ${settings.buttonBackgroundColor || 'inherit'} !important;
                    color: ${settings.buttonTextColor || themeColors.textColor} !important;
                    font-size: ${settings.buttonFontSize ? `${settings.buttonFontSize}px` : '16px'} !important;
                }
                .friends-settings {
                    border-top: 1px solid ${settings.separatorColor || themeColors.borderColor} !important;
                }
                .activity-item {
                    border-bottom: 1px solid ${settings.separatorColor || themeColors.borderColor} !important;
                }
                .activity-date {
                    color: ${settings.textColor || themeColors.textColor} !important;
                    opacity: 0.8;
                }
                .tab-button {
                    font-size: ${settings.tabFontSize ? `${settings.tabFontSize}px` : '18px'} !important;
                }
                .tab-buttons {
                    border-bottom: 1px solid ${settings.separatorColor || themeColors.borderColor} !important;
                }
                .tab-button.active {
                    border-bottom: 2px solid ${themeColors.linkColor} !important;
                }
            `;
        }

        let lastStyleState = null;
        function forceStyleUpdate() {
            lastStyleState = JSON.stringify(settings);
            updateContainerStyle();
            updateDynamicStyles();

            const activityItems = document.querySelectorAll('.activity-item');
            if (activityItems.length > 0) {
                const themeColors = createThemeColors();
                activityItems.forEach(item => {
                    item.style.borderBottom = `1px solid ${settings.separatorColor || themeColors.borderColor}`;
                });

                const activityDates = document.querySelectorAll('.activity-date');
                activityDates.forEach(date => {
                    date.style.color = settings.textColor || themeColors.textColor;
                });
            }

            const buttons = container.querySelectorAll('button:not(.tab-button)');
            buttons.forEach(button => {
                if (settings.buttonFontSize) {
                    button.style.setProperty('font-size', `${settings.buttonFontSize}px`, 'important');
                } else {
                    button.style.removeProperty('font-size');
                }
            });

            const tabButtons = container.querySelectorAll('.tab-button');
            tabButtons.forEach(tab => {
                if (settings.tabFontSize) {
                    tab.style.setProperty('font-size', `${settings.tabFontSize}px`, 'important');
                } else {
                    tab.style.removeProperty('font-size');
                }
            });
        }

        const themeObserver = new MutationObserver((mutations) => {
            if (container.style.display !== 'block') return;

            for (const mutation of mutations) {
                // Check if the mutation target is one of our UI elements or their children.
                if (mutation.target.closest('.friends-container, #friendsPopover')) {
                    continue; // Ignore mutations inside our UI.
                }

                // Check if a node being added is one of our UI elements. This is key for the popover.
                let isInternalNodeChange = false;
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === 1 && (node.id === 'friendsPopover' || node.classList.contains('friends-container'))) {
                        isInternalNodeChange = true;
                        break;
                    }
                }
                if (isInternalNodeChange) {
                    continue; // Ignore the addition of our UI elements to the DOM.
                }

                // If we reach here, it's a genuine page change (like a theme switch).
                requestAnimationFrame(forceStyleUpdate);
                return; // We found a valid trigger, so we can stop checking other mutations.
            }
        });

        themeObserver.observe(document.body, { attributes: true, childList: true, subtree: true });

        function debounce(func, wait) {
            let timeout;
            return function executedFunction(...args) {
                const later = () => {
                    clearTimeout(timeout);
                    func.apply(this, args);
                };
                clearTimeout(timeout);
                timeout = setTimeout(later, wait);
            };
        }

        const debouncedForceStyleUpdate = debounce(forceStyleUpdate, 300);
        setInterval(() => {
            debouncedForceStyleUpdate();
        }, 1000);

        function resetSetting(setting) {
            switch(setting) {
                case 'cacheDuration':
                    settings[setting] = 3;
                    document.getElementById('cacheDuration').value = 3;
                    activityCache.timestamp = 0;
                    localStorage.removeItem('vndb_activity_cache');
                    if (document.querySelector('.tab-button[data-tab="activityFeed"]').classList.contains('active')) {
                        updateActivityFeed();
                    }
                    break;
                case 'vnVoteCacheDuration':
                    settings[setting] = 5;
                    document.getElementById('vnVoteCacheDuration').value = 5;
                    break;
                case 'gamesPerFriend':
                    settings[setting] = 5;
                    document.getElementById('gamesPerFriend').value = 5;
                    break;
                case 'maxActivities':
                    settings[setting] = 51;
                    document.getElementById('maxActivities').value = 51;
                    break;
                case 'fontSize':
                    settings[setting] = 17;
                    document.getElementById('fontSize').value = 17;
                    break;
                case 'buttonFontSize':
                    settings[setting] = 16;
                    document.getElementById('buttonFontSize').value = 16;
                    break;
                case 'tabFontSize':
                    settings[setting] = 18;
                    document.getElementById('tabFontSize').value = 18;
                    break;
                case 'opacity':
                    settings[setting] = 0.70;
                    document.getElementById('opacity').value = 70;
                    document.getElementById('opacityValue').textContent = '70';
                    break;
                default:
                    settings[setting] = null;
                    document.getElementById(setting).value = '#000000';
                    document.getElementById(setting + 'Hex').value = '';
            }
            GM_setValue('vndb_friends_settings', settings);
            forceStyleUpdate();
        }

        const settingsInputs = {
            textColor: document.getElementById('textColor'),
            backgroundColor: document.getElementById('backgroundColor'),
            fontSize: document.getElementById('fontSize'),
            opacity: document.getElementById('opacity')
        };

        Object.entries(settingsInputs).forEach(([setting, input]) => {
            if (settings[setting]) {
                if (setting === 'opacity') {
                    input.value = settings[setting] * 100;
                    document.getElementById('opacityValue').textContent = input.value;
                } else if (setting === 'fontSize') {
                    input.value = settings[setting];
                } else {
                    input.value = settings[setting];
                }
            } else if (setting === 'opacity') {
                input.value = 70;
                document.getElementById('opacityValue').textContent = '70';
            }

            input.addEventListener('change', function() {
                let value = this.value;
                if (setting === 'opacity') {
                    value = this.value / 100;
                    document.getElementById('opacityValue').textContent = this.value;
                } else if (setting === 'fontSize') {
                    value = parseInt(this.value);
                }
                settings[setting] = value;
                GM_setValue('vndb_friends_settings', settings);
                updateContainerStyle();
            });
        });

        const menu = document.querySelector('header nav menu');
        if (editLink && !menu.querySelector('li a[href="#"]')) {
            const friendsLink = document.createElement('li');
            friendsLink.innerHTML = `<a href="#">friends</a>`;
            menu.appendChild(friendsLink);
        }        const isNotifiesPage = location.pathname.includes('/notifies');
        let friendBtn = null;
        const friendId = 'u' + userId;
        if (!editLink && !isNotifiesPage) {
            const friendLi = document.createElement('li');
            friendBtn = document.createElement('a');
            friendBtn.href = "#";
            const isAlreadyFriend = friends.includes(friendId);

            friendBtn.textContent = isAlreadyFriend ? 'remove the friend' : 'add a friend';
            friendLi.appendChild(friendBtn);
            menu.appendChild(friendLi);            friendBtn.addEventListener('click', (e) => {
                e.preventDefault();

                if (friends.includes(friendId)) {
                    removeFriend(friendId);
                    friendBtn.textContent = 'add a friend';
                } else {
                    addFriend(friendId);
                    friendBtn.textContent = 'remove the friend';
                }
            });
        }

        function updatePagination() {
            const totalPages = Math.ceil(friends.length / friendsPerPage);
            const pagination = document.getElementById('pagination');
            const activeTabElement = document.querySelector('.tab-button.active');
            const activeTab = activeTabElement ? activeTabElement.dataset.tab : 'friendsList';

            if (activeTab === 'friendsList' && totalPages > 1) {
                pagination.style.display = 'block';
                pagination.innerHTML = `
                    ${currentPage > 1 ? `<button class="pageButton" data-page="${currentPage - 1}">←</button>` : ''}
                    Page ${currentPage} of ${totalPages}
                    ${currentPage < totalPages ? `<button class="pageButton" data-page="${currentPage + 1}">→</button>` : ''}
                `;
                const pageButtons = pagination.querySelectorAll('.pageButton');
                pageButtons.forEach(button => {
                    button.addEventListener('click', function() {
                        changePage(parseInt(this.dataset.page));
                    });
                });
            } else {
                pagination.style.display = 'none';
            }
        }

        function handleTabSwitch(tabId) {
            const pagination = document.getElementById('pagination');
            if (!pagination) return;
            try {
                if (tabId === 'activityFeed') {
                    pagination.style.display = 'none';
                } else if (tabId === 'friendsList') {
                    const friendsList = document.getElementById('friendsList');
                    if (friendsList) friendsList.offsetHeight;
                    updatePagination();
                }
            } catch (e) {
                console.warn('Error during tab switch:', e);
                if (pagination) pagination.style.display = 'none';
            }
        }

        document.querySelectorAll('.tab-button').forEach(button => {
            button.addEventListener('click', () => {
                const tabId = button.dataset.tab;
                try {
                    localStorage.setItem('vndb_friends_active_tab', tabId);
                } catch (e) {
                    console.warn('Failed to save active tab state:', e);
                }

                document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));
                document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));

                button.classList.add('active');
                document.querySelectorAll(`.tab-content[data-tab="${tabId}"], #${tabId}`).forEach(content => {
                    content.classList.add('active');
                });

                if (tabId === 'activityFeed') {
                    activityTabClicked = true;
                    const pagination = document.getElementById('pagination');
                    if (pagination) pagination.style.display = 'none';
                    updateActivityFeed();
                } else if (tabId === 'friendsList') {
                    sessionStorage.removeItem('vndb_activity_scroll');
                    const friendsList = document.getElementById('friendsList');
                    if (friendsList) friendsList.offsetHeight;
                    updatePagination();
                }
            });
        });

        window.addEventListener('load', () => {
            const activeTab = localStorage.getItem('vndb_friends_active_tab') || 'friendsList';
            const pagination = document.getElementById('pagination');
            if (activeTab === 'activityFeed') {
                if (pagination) pagination.style.display = 'none';
            } else {
                handleTabSwitch(activeTab);
                updatePagination();
            }
        });

        function changePage(newPage) {
            currentPage = newPage;
            localStorage.setItem('vndb_friends_current_page', currentPage.toString());
            displayFriendsList();
        }

        function displayFriendsList() {
            const friendsList = document.getElementById('friendsList');
            friendsList.innerHTML = '';

            const totalPages = Math.ceil(friends.length / friendsPerPage);
            if (currentPage > totalPages) {
                currentPage = Math.max(1, totalPages);
                localStorage.setItem('vndb_friends_current_page', currentPage.toString());
            }

            const startIndex = (currentPage - 1) * friendsPerPage;
            const endIndex = startIndex + friendsPerPage;
            const currentFriends = friends.slice(startIndex, endIndex);

            for (const friendId of currentFriends) {
                const userData = friendsCache[friendId];
                if (userData && userData.id && userData.username) {
                    const friendDiv = document.createElement('div');
                    friendDiv.style.margin = '5px 0';
                    friendDiv.innerHTML = `
                        <a href="/${userData.id}" class="friend-link">${userData.username}</a>
                        <button class="removeFriend" data-friendid="${userData.id}" style="margin-left: 10px;">Remove</button>
                    `;
                    friendsList.appendChild(friendDiv);
                } else {
                    console.warn(`Missing cache for friend ID: ${friendId}. Attempting to preload.`);
                    preloadFriendData([friendId]).then(() => displayFriendsList());
                }
            }

            forceStyleUpdate();
            setTimeout(forceStyleUpdate, 100);
            setTimeout(forceStyleUpdate, 300);

            const removeButtons = friendsList.querySelectorAll('.removeFriend');
            removeButtons.forEach(button => {
                button.addEventListener('click', function() {
                    removeFriend(this.dataset.friendid);
                });
            });

            updatePagination();
        }

        async function fetchFriendDataByUsername(username) {
            try {
                const response = await new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: `https://api.vndb.org/kana/user?q=${encodeURIComponent(username)}&fields=id,username`,
                        headers: { 'Content-Type': 'application/json' },
                        onload: function(response) {
                            logApiRequestCount('user');
                            try {
                                const data = JSON.parse(response.responseText);
                                if (data.results && data.results.length > 0) {
                                    resolve(data.results[0]);
                                } else {
                                    resolve(null);
                                }
                            } catch (e) {
                                console.error("Error parsing user data by username:", e, response.responseText);
                                reject(e);
                            }
                        },
                        onerror: function(err) {
                            logApiRequestCount('user');
                            reject(err);
                        }
                    });
                });
                return response;
            } catch (error) {
                console.error(`Error fetching data for friend ${username}:`, error);
                return null;
            }
        }


        function updateFriends(newFriends, newCache) {
            friends = newFriends.filter((item, pos, self) => self.indexOf(item) == pos);
            if (newCache) {
                friendsCache = newCache;
            }

            GM_setValue('vndb_friends', friends);
            GM_setValue('vndb_friends_cache', friendsCache);
            localStorage.setItem('vndb_friends', JSON.stringify(friends));
            localStorage.setItem('vndb_friends_cache', JSON.stringify(friendsCache));

            if (bc) {
                bc.postMessage({
                    type: 'friends_update',
                    friends: friends,
                    friendsCache: friendsCache
                });
            }
        }

        async function fetchFriendData(username, forceUpdate = false) {
            try {
                const cachedData = friendsCache[username];
                const now = Date.now();
                if (!forceUpdate && cachedData && cachedData.lastUpdate &&
                    (now - cachedData.lastUpdate) < 24 * 60 * 60 * 1000) {
                    return cachedData;
                }

                const response = await new Promise((resolve, reject) => {
                    const requestId = GM_xmlhttpRequest({
                        method: 'GET',
                        url: `https://api.vndb.org/kana/user?q=${encodeURIComponent(username)}`,
                        headers: { 'Content-Type': 'application/json' },
                        onload: function(response) {
                            resolve(JSON.parse(response.responseText));
                        },
                        onerror: reject
                    });
                    currentRequestId = requestId;
                });

                if (response[username]) {
                    friendsCache[username] = { ...response[username], lastUpdate: Date.now() };
                    GM_setValue('vndb_friends_cache', friendsCache);
                    return friendsCache[username];
                }
                return null;
            } catch (error) {
                console.error(`Error fetching data for friend ${username}:`, error);
                return null;
            }
        }

        async function addFriend(username) {
            if (!username) return;

            const isAlreadyInList = friends.some(f => f.toLowerCase() === username.toLowerCase()) ||
                                    Object.values(friendsCache).some(user =>
                                        user.id.toLowerCase() === username.toLowerCase() ||
                                        user.username.toLowerCase() === username.toLowerCase()
                                    );
            if (isAlreadyInList) {
                removeFriend(username);
                return;
            }

            const userData = await fetchFriendData(username);
            if (userData) {
                const updatedFriends = [...friends];
                if (!updatedFriends.includes(userData.id)) {
                    updatedFriends.push(userData.id);
                }

                const updatedCache = {...friendsCache};
                updatedCache[userData.id] = userData;

                updateFriends(updatedFriends, updatedCache);

                currentPage = Math.ceil(friends.length / friendsPerPage);
                displayFriendsList();
                updatePagination();

            } else {
                alert('User not found!');
            }
        }

        async function addFriendByUsername(username) {
            if (!username) return;

            const existingFriendByUsername = Object.values(friendsCache).find(user => user.username.toLowerCase() === username.toLowerCase());
            if (existingFriendByUsername && friends.includes(existingFriendByUsername.id)) {
                alert('User is already in your friends list.');
                return;
            }

            const userData = await fetchFriendDataByUsername(username);
            if (userData && userData.id) {
                if (friends.includes(userData.id)) {
                    alert('User is already in your friends list.');
                    return;
                }
                const updatedFriends = [...friends, userData.id];
                const updatedCache = {...friendsCache};
                updatedCache[userData.id] = { id: userData.id, username: userData.username, lastUpdate: Date.now() };

                updateFriends(updatedFriends, updatedCache);

                currentPage = Math.ceil(friends.length / friendsPerPage);
                displayFriendsList();
                updatePagination();
            } else {
                alert('User not found!');
            }
        }

        async function addFriendById(friendId) {
            if (!friendId || !/^u\d+$/.test(friendId)) {
                alert('Invalid friend ID format.');
                return;
            }            if (friends.includes(friendId)) {
                removeFriend(friendId);
                const friendLinks = document.querySelectorAll('header nav menu li a[href="#"]');
                const friendBtn = Array.from(friendLinks).find(link => !link.textContent.toLowerCase().includes('friends'));
                if (friendBtn && location.pathname.includes(`/${friendId.slice(1)}`)) {
                     friendBtn.textContent = 'add a friend';
                }
                return;
            }


            if (!friendsCache[friendId] || !friendsCache[friendId].username) {
                const usersData = await batchFetchUsersById([friendId]);
                if (usersData && usersData.length > 0) {
                    friendsCache[friendId] = { id: usersData[0].id, username: usersData[0].username, lastUpdate: Date.now() };
                } else {
                    alert(`User with ID ${friendId} not found or error fetching.`);
                    return;
                }
            }

            const updatedFriends = [...friends, friendId];
            updateFriends(updatedFriends, friendsCache);

            currentPage = Math.ceil(friends.length / friendsPerPage);
            if (document.querySelector('.friends-container')?.style.display === 'block') {                 displayFriendsList();
                 updatePagination();
            }
            const friendLinks = document.querySelectorAll('header nav menu li a[href="#"]');
            const friendBtn = Array.from(friendLinks).find(link => !link.textContent.toLowerCase().includes('friends'));
            if (friendBtn && location.pathname.includes(`/${friendId.slice(1)}`)) {
                 friendBtn.textContent = 'remove the friend';
            }
        }function removeFriend(friendIdToRemove) {
            const updatedFriends = friends.filter(fId => fId !== friendIdToRemove);

            // Remove the friend's data from cache
            const updatedCache = { ...friendsCache };
            delete updatedCache[friendIdToRemove];

            updateFriends(updatedFriends, updatedCache);

            const totalPages = Math.ceil(updatedFriends.length / friendsPerPage);
            if (currentPage > totalPages) {
                currentPage = Math.max(1, totalPages);
            }
            if (document.querySelector('.friends-container')?.style.display === 'block') {
                displayFriendsList();
                updatePagination();
            }
        }

        const friendsLink = document.querySelector('header nav menu li a[href="#"]');
        if (friendsLink) {
            friendsLink.addEventListener('click', async (e) => {
                e.preventDefault();
                const container = document.querySelector('.friends-container');
                const isOpen = sessionStorage.getItem('vndb_friends_container_open') === 'true';

                if (isOpen) {
                    container.style.display = 'none';
                    sessionStorage.setItem('vndb_friends_container_open', 'false');
                } else {
                    showContainer();
                }
            });

            friendsLink.addEventListener('mouseover', checkAndRefreshCache);
        }

        document.getElementById('closeFriends').addEventListener('click', () => {
            const container = document.querySelector('.friends-container');
            container.style.display = 'none';
            sessionStorage.setItem('vndb_friends_container_open', 'false');
        });        document.getElementById('addFriend').addEventListener('click', () => {
            const input = document.getElementById('newFriend');
            const username = input.value.trim();
            addFriend(username);
            input.value = '';
        });

        document.getElementById('newFriend').addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                const input = document.getElementById('newFriend');
                const username = input.value.trim();
                addFriend(username);
                input.value = '';
            }
        });

        const toggleButton = document.getElementById('toggleSettings');
        toggleButton.addEventListener('click', () => {
            const isVisible = settingsPanel.style.display === 'block';
            settingsPanel.style.display = isVisible ? 'none' : 'block';
            toggleButton.textContent = isVisible ? 'Show Settings' : 'Hide Settings';
        });

        const resetButtons = document.querySelectorAll('.resetButton');
        resetButtons.forEach(button => {
            button.addEventListener('click', function() {
                resetSetting(this.dataset.setting);
            });
        });

        async function preloadFriendData(specificIdsToLoad = null) {
            const idsToFetch = [];
            const now = Date.now();
            const friendsToIterate = specificIdsToLoad || friends;

            for (const friendId of friendsToIterate) {
                if (!/^u\d+$/.test(friendId)) {
                    console.warn(`Invalid friend ID format in preload: ${friendId}. Skipping.`);
                    continue;
                }
                const cachedData = friendsCache[friendId];
                if (!cachedData || !cachedData.username || !cachedData.lastUpdate ||
                    (now - cachedData.lastUpdate) > 24 * 60 * 60 * 1000) {
                    idsToFetch.push(friendId);
                }
            }

            if (idsToFetch.length > 0) {
                console.log("Preloading/refreshing friend data for IDs:", idsToFetch);
                const fetchedUsers = await batchFetchUsersById(idsToFetch);
                let cacheUpdated = false;
                fetchedUsers.forEach(userData => {
                    if (userData && userData.id && userData.username) {
                        friendsCache[userData.id] = { id: userData.id, username: userData.username, lastUpdate: Date.now() };
                        cacheUpdated = true;
                    }
                });
                if (cacheUpdated) {
                    GM_setValue('vndb_friends_cache', friendsCache);
                    localStorage.setItem('vndb_friends_cache', JSON.stringify(friendsCache));
                    if (document.querySelector('.friends-container')?.style.display === 'block') {
                        displayFriendsList();
                    }
                }
            }
        }
        preloadFriendData();


        document.addEventListener('visibilitychange', () => {
            if (!document.hidden && container.style.display === 'block') {
                forceStyleUpdate();
            }
        });

        window.addEventListener('resize', () => {
            const container = document.querySelector('.friends-container');
            if (!container) return;

            const isOpen = sessionStorage.getItem('vndb_friends_container_open') === 'true';
            if (!isOpen) return;

            if (window.innerWidth >= 300 && window.innerHeight >= 200) {
                if (container.style.display === 'none') {
                    container.style.display = 'block';
                    adjustContainerPosition();
                }
            } else {
                container.style.display = 'none';
            }
        });

        container.addEventListener('animationend', forceStyleUpdate);
        container.addEventListener('transitionend', forceStyleUpdate);

        const containerObserver = new MutationObserver(() => {
            if (container.style.display === 'block') {
                forceStyleUpdate();
            }
        });

        containerObserver.observe(container, {
            attributes: true,
            childList: true,
            subtree: true,
            characterData: true
        });

        function syncColorInputs(colorId, hexId) {
            const colorInput = document.getElementById(colorId);
            const hexInput = document.getElementById(hexId);

            colorInput.addEventListener('input', (e) => {
                hexInput.value = e.target.value;
                settings[colorId] = e.target.value;
                GM_setValue('vndb_friends_settings', settings);
                forceStyleUpdate();
            });

            hexInput.addEventListener('input', (e) => {
                const hex = e.target.value;
                if (/^#[0-9A-Fa-f]{6}$/.test(hex)) {
                    colorInput.value = hex;
                    settings[colorId] = hex;
                    GM_setValue('vndb_friends_settings', settings);
                    forceStyleUpdate();
                }
            });
        }

        function initializeColorInputs() {
            const colorPairs = [
                ['titleColor', 'titleColorHex'],
                ['textColor', 'textColorHex'],
                ['buttonTextColor', 'buttonTextColorHex'],
                ['backgroundColor', 'backgroundColorHex'],
                ['buttonBackgroundColor', 'buttonBackgroundColorHex'],
                ['borderColor', 'borderColorHex'],
                ['separatorColor', 'separatorColorHex']
            ];

            colorPairs.forEach(([colorId, hexId]) => {
                const colorInput = document.getElementById(colorId);
                const hexInput = document.getElementById(hexId);

                if (settings[colorId]) {
                    colorInput.value = settings[colorId];
                    hexInput.value = settings[colorId];
                }

                syncColorInputs(colorId, hexId);
            });

            const numericInputs = [
                'fontSize',
                'buttonFontSize',
                'tabFontSize',
                'cacheDuration',
                'vnVoteCacheDuration',
                'gamesPerFriend',
                'maxActivities'
            ];
            numericInputs.forEach(settingId => {
                const input = document.getElementById(settingId);
                if (input && settings[settingId] !== null && settings[settingId] !== undefined) {
                    input.value = settings[settingId];
                } else if (input && settingId === 'vnVoteCacheDuration' && (settings[settingId] === null || settings[settingId] === undefined)) {
                    settings[settingId] = 5;
                    input.value = 5;
                    GM_setValue('vndb_friends_settings', settings);
                }


                input.addEventListener('change', function() {
                    settings[settingId] = parseInt(this.value) || (settingId === 'vnVoteCacheDuration' ? 5 : null);
                    GM_setValue('vndb_friends_settings', settings);
                    forceStyleUpdate();

                    if (settingId === 'cacheDuration' ||
                        settingId === 'gamesPerFriend' ||
                        settingId === 'maxActivities') {
                        activityCache.timestamp = 0;
                        localStorage.removeItem('vndb_activity_cache');
                        if (document.querySelector('.tab-button[data-tab="activityFeed"]').classList.contains('active')) {
                            updateActivityFeed();
                        }
                    }
                });
            });

            const friendsVotesToggle = document.getElementById('friendsVotesToggle');
            if (friendsVotesToggle) {
                if (settings.friendsVotesEnabled === undefined) {
                    settings.friendsVotesEnabled = true;
                    GM_setValue('vndb_friends_settings', settings);
                }
                friendsVotesToggle.checked = settings.friendsVotesEnabled;
                friendsVotesToggle.addEventListener('change', function() {
                    settings.friendsVotesEnabled = this.checked;
                    GM_setValue('vndb_friends_settings', settings);
                });
            }

            const opacityInput = document.getElementById('opacity');
            const opacityValue = document.getElementById('opacityValue');
            if (settings.opacity !== null) {
                opacityInput.value = settings.opacity * 100;
                opacityValue.textContent = Math.round(settings.opacity * 100);
            } else {
                opacityInput.value = 70;
                opacityValue.textContent = '70';
                settings.opacity = 0.70;
                GM_setValue('vndb_friends_settings', settings);
            }

            opacityInput.addEventListener('input', function() {
                opacityValue.textContent = this.value;
                settings.opacity = this.value / 100;
                GM_setValue('vndb_friends_settings', settings);
                forceStyleUpdate();
            });
        }

        const importExportHTML = `
            <div class="settings-group" style="margin-top: 20px;">
                <label>Backup:</label>
                <div style="display: flex; gap: 5px; flex-wrap: wrap;">
                    <button id="exportData">Export All</button>
                    <button id="importData">Import</button>
                </div>
            </div>
            <div id="importOptions" style="display: none; margin-top: 10px;">
                <div style="margin-bottom: 10px;">
                    <input type="file" id="importFile" accept=".json" style="display: none;">
                    <label>Import options:</label>
                    <div style="margin-top: 5px;">
                        <label style="font-weight: normal;">
                            <input type="checkbox" id="importFriends" checked> Friends List
                        </label>
                        <label style="font-weight: normal; margin-left: 10px;">
                            <input type="checkbox" id="importSettings" checked> Settings
                        </label>
                    </div>
                    <div style="margin-top: 10px;">
                        <button id="confirmImport">Confirm Import</button>
                        <button id="cancelImport">Cancel</button>
                    </div>
                </div>
            </div>
        `;

        function setupImportExport() {
            const exportButton = document.getElementById('exportData');
            const importButton = document.getElementById('importData');
            const importOptions = document.getElementById('importOptions');
            const importFile = document.getElementById('importFile');
            const confirmImport = document.getElementById('confirmImport');
            const cancelImport = document.getElementById('cancelImport');
            const importFriendsCheck = document.getElementById('importFriends');
            const importSettingsCheck = document.getElementById('importSettings');            exportButton.addEventListener('click', () => {
                // Only export cache data for current friends
                const filteredCache = {};
                friends.forEach(friendId => {
                    if (friendsCache[friendId]) {
                        filteredCache[friendId] = friendsCache[friendId];
                    }
                });

                const exportData = {
                    friends: friends,
                    friendsCache: filteredCache,
                    settings: settings
                };

                const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = `vndb_friends_backup_${new Date().toISOString().split('T')[0]}.json`;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
            });

            importButton.addEventListener('click', () => {
                importOptions.style.display = 'block';
                importButton.style.display = 'none';
            });

            cancelImport.addEventListener('click', () => {
                importOptions.style.display = 'none';
                importButton.style.display = 'block';
                importFile.value = '';
            });

            confirmImport.addEventListener('click', () => {
                importFile.click();
            });

            importFile.addEventListener('change', (e) => {
                const file = e.target.files[0];
                if (!file) return;

                const reader = new FileReader();
                reader.onload = (event) => {
                    try {
                        const importData = JSON.parse(event.target.result);

                        if (importFriendsCheck.checked) {
                            friends = importData.friends || [];
                            friendsCache = importData.friendsCache || {};
                            const usernamesToResolve = [];
                            const newFriendsList = [];
                            friends.forEach(f => {
                                if (/^u\d+$/.test(f)) {
                                    if (!newFriendsList.includes(f)) newFriendsList.push(f);
                                } else {
                                    const cachedUser = Object.values(friendsCache).find(u => u.username === f);
                                    if (cachedUser && cachedUser.id) {
                                         if (!newFriendsList.includes(cachedUser.id)) newFriendsList.push(cachedUser.id);
                                    } else {
                                        usernamesToResolve.push(f);
                                    }
                                }
                            });
                            friends = newFriendsList;

                            const newFriendsCache = {};
                            Object.values(friendsCache).forEach(user => {
                                if (user && user.id && /^u\d+$/.test(user.id)) {
                                    newFriendsCache[user.id] = {
                                        id: user.id,
                                        username: user.username || user.id,
                                        lastUpdate: user.lastUpdate || Date.now()
                                    };
                                }
                            });
                            friendsCache = newFriendsCache;

                            if (usernamesToResolve.length > 0) {
                                batchFetchUsersByUsername(usernamesToResolve).then(resolvedUsers => {
                                    resolvedUsers.forEach(u => {
                                        if (u && u.id) {
                                            if (!friends.includes(u.id)) friends.push(u.id);
                                            friendsCache[u.id] = { id: u.id, username: u.username, lastUpdate: Date.now() };
                                        }
                                    });
                                    GM_setValue('vndb_friends', friends);
                                    GM_setValue('vndb_friends_cache', friendsCache);
                                    displayFriendsList();
                                });
                            } else {
                                GM_setValue('vndb_friends', friends);
                                GM_setValue('vndb_friends_cache', friendsCache);
                                displayFriendsList();
                            }
                        }

            if (importSettingsCheck.checked && importData.settings) {
                            const newSettings = {
                                textColor: null,
                                buttonTextColor: null,
                                backgroundColor: null,
                                buttonBackgroundColor: null,
                                titleColor: null,
                                borderColor: null,
                                separatorColor: null,
                                fontSize: 17,
                                buttonFontSize: 16,
                                tabFontSize: 18,
                                opacity: null,
                                cacheDuration: 3,
                                gamesPerFriend: 5,
                                maxActivities: 51,
                                friendsVotesEnabled: true,
                                vnVoteCacheDuration: 5,
                                ...importData.settings
                            };
                            settings = newSettings;
                            GM_setValue('vndb_friends_settings', settings);
                            initializeColorInputs();
                        }

                        if (localStorage.getItem('vndb_friends_active_tab') === 'activityFeed') {
                            activityCache.timestamp = 0;
                            localStorage.removeItem('vndb_activity_cache');
                            updateActivityFeed();
                        }
                        alert('Import completed successfully! Data is being processed.');
                    } catch (error) {
                        alert('Error importing data. Please check the file format.');
                        console.error('Import error:', error);
                    }
                };
                reader.readAsText(file);
            });

            function updateImportButton() {
                confirmImport.disabled = !importFriendsCheck.checked && !importSettingsCheck.checked;
            }

            importFriendsCheck.addEventListener('change', updateImportButton);
            importSettingsCheck.addEventListener('change', updateImportButton);
        }

        function initializeImportExport() {
            const settingsPanel = container.querySelector('.friends-settings');
            const existingSection = settingsPanel.querySelector('#importExportSection');
            if (existingSection) {
                existingSection.remove();
            }

            const importExportDiv = document.createElement('div');
            importExportDiv.id = 'importExportSection';
            importExportDiv.innerHTML = importExportHTML;
            settingsPanel.appendChild(importExportDiv);
            setupImportExport();
        }

        let isContainerOpen = sessionStorage.getItem('vndb_friends_container_open') === 'true';
        if (isContainerOpen && editLink) {
            container.style.display = 'block';
            initializeColorInputs();
            initializeImportExport();
            forceStyleUpdate();
            displayFriendsList();
        }
        let activityCache;
        try {
            const storedCache = localStorage.getItem('vndb_activity_cache') || '';
            activityCache = storedCache ? JSON.parse(storedCache) : { timestamp: 0, data: [] };
        } catch (e) {
            console.error('Error parsing vndb_activity_cache, resetting cache:', e);
            activityCache = { timestamp: 0, data: [] };
            localStorage.setItem('vndb_activity_cache', JSON.stringify(activityCache));
        }

        window.addEventListener('storage', (e) => {
            if (e.key === 'vndb_activity_cache') {
                try {
                    const newCache = e.newValue ? JSON.parse(e.newValue) : { timestamp: 0, data: [] };
                    activityCache = newCache;
                } catch (e) {
                    console.error('Error parsing updated vndb_activity_cache:', e);
                    activityCache = { timestamp: 0, data: [] };
                }
            }
        });

        async function fetchFriendActivity(friendId) {
            try {
                const userData = friendsCache[friendId];
                if (!userData || !userData.id) {
                    console.error(`No cached data found for user ID ${friendId} for activity feed.`);
                    return [];
                }

                const response = await new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method: 'POST',
                        url: 'https://api.vndb.org/kana/ulist',
                        headers: { 'Content-Type': 'application/json' },
                        data: JSON.stringify({
                            "user": userData.id,
                            "fields": "id, vote, voted, vn.title",
                            "filters": ["label", "=", 7],
                            "sort": "voted",
                            "reverse": true,
                            "results": settings.gamesPerFriend || 5
                        }),
                        onload: function(response) {
                            logApiRequestCount('user');
                            if (response.status === 200) {
                                try {
                                    const data = JSON.parse(response.responseText);
                                    if (data && Array.isArray(data.results)) {
                                        resolve(data);
                                    } else {
                                        console.error('Invalid API response structure for activity:', data);
                                        resolve({ results: [] });
                                    }
                                } catch (e) {
                                    console.error('JSON parse error for activity:', e);
                                    resolve({ results: [] });
                                }
                            } else {
                                console.error('API error for activity:', response.status, response.responseText);
                                resolve({ results: [] });
                            }
                        },
                        onerror: function(error) {
                            logApiRequestCount('user');
                            console.error('Request error for activity:', error);
                            resolve({ results: [] });
                        }
                    });
                });

                if (!response.results) {
                    return [];
                }

                return response.results.map(item => ({
                    friendId: friendId,
                    username: userData.username,
                    vnId: item.id,
                    vnTitle: item.vn.title,
                    vote: item.vote / 10,
                    voted: item.voted
                }));
            } catch (error) {
                console.error(`Error fetching activity for ${friendId} (${userData ? userData.username : 'N/A'}):`, error);
                return [];
            }
        }


        function preloadActivityData() {
            const now = Date.now();
            const cacheDurationMs = (settings.cacheDuration || 3) * 60 * 1000;
            if (!activityCache.data || activityCache.data.length === 0 || now - activityCache.timestamp >= cacheDurationMs) {
                updateActivityFeed();
            }
        }

        async function updateActivityFeed() {
            if (isUpdatingActivity && currentRequestId !== null) {
                console.warn("Activity update aborted as one is already in progress or currentRequestId handling needs review for POST.");
                return;
            }

            isUpdatingActivity = true;
            const now = Date.now();
            const cacheDurationMs = (settings.cacheDuration || 3) * 60 * 1000;
            const activityFeed = document.getElementById('activityFeed');

            if (activityCache.data && activityCache.data.length > 0 && now - activityCache.timestamp < cacheDurationMs) {
                displayActivityFeed(activityCache.data);
                if (activityTabClicked) {
                    const cacheMsg = document.createElement('div');
                    cacheMsg.style.textAlign = 'center';
                    cacheMsg.style.opacity = '0.7';
                    cacheMsg.style.fontSize = '0.8em';
                    cacheMsg.style.paddingTop = '3px';
                    const timeLeft = Math.round((cacheDurationMs - (now - activityCache.timestamp)) / 1000);
                    cacheMsg.textContent = `Loaded from cache (expires in ${timeLeft}s)`;

                    cacheMsg.style.visibility = 'hidden';
                    activityFeed.insertAdjacentElement('afterbegin', cacheMsg);

                    const targetHeight = 20;
                    let fontSizePx = parseFloat(window.getComputedStyle(cacheMsg).fontSize);
                    while (cacheMsg.offsetHeight > targetHeight && fontSizePx > 10) {
                        fontSizePx -= 1;
                        cacheMsg.style.fontSize = fontSizePx + "px";
                    }
                    cacheMsg.style.visibility = 'visible';

                    activityFeed.scrollTop = 4;
                    setTimeout(() => {
                        cacheMsg.remove();
                    }, 1500);
                } else {
                    let savedPos = sessionStorage.getItem('vndb_activity_scroll');
                    activityFeed.scrollTop = savedPos ? parseInt(savedPos, 10) : 4;
                }
                isUpdatingActivity = false;
                return;
            }

            if (friends.length > 200) {
                activityFeed.innerHTML = '<div class="error">Too many friends to fetch activity (200 max).<br>Please reduce your friends list or increase cache duration in settings.</div>';
                isUpdatingActivity = false;
                return;
            }

            activityFeed.innerHTML = '<div class="loading">Fetching new activity data...</div>';

            try {
                const missingUsernameIds = friends.filter(fid => !friendsCache[fid] || !friendsCache[fid].username);
                if (missingUsernameIds.length > 0) {
                    await preloadFriendData(missingUsernameIds);
                }

                const activityPromises = friends.map(friendId => fetchFriendActivity(friendId));
                const activityResults = await Promise.all(activityPromises);
                const activities = activityResults.flat();


                activities.sort((a, b) => b.voted - a.voted);
                const maxActivities = settings.maxActivities || 51;
                const limitedActivities = activities.slice(0, maxActivities);

                activityCache = { timestamp: now, data: limitedActivities };
                localStorage.setItem('vndb_activity_cache', JSON.stringify(activityCache));

                displayActivityFeed(limitedActivities);
                if (activityTabClicked) {
                    activityFeed.scrollTop = 4;
                    activityTabClicked = false;
                } else {
                    let savedPos = sessionStorage.getItem('vndb_activity_scroll');
                    activityFeed.scrollTop = savedPos ? parseInt(savedPos, 10) : 4;
                }
            } catch (error) {
                console.error('Error updating activity feed:', error);
                activityFeed.innerHTML = '<div class="error">Error loading activity feed</div>';
            } finally {
                isUpdatingActivity = false;
                currentRequestId = null;
            }
        }

        function displayActivityFeed(activities) {
            const activityFeed = document.getElementById('activityFeed');
            activityFeed.innerHTML = '';

            if (!activities || activities.length === 0) {
                activityFeed.innerHTML = '<div class="no-activity">No recent activity</div>';
                return;
            }

            const maxActivities = settings.maxActivities || 51;
            const limitedActivities = activities.slice(0, maxActivities);

            limitedActivities.forEach(activity => {
                if (!activity.voted || !activity.vnTitle) return;

                const date = new Date(activity.voted * 1000);
                const formattedDate = `${date.getDate().toString().padStart(2, '0')}/${(date.getMonth() + 1).toString().padStart(2, '0')}/${date.getFullYear()}`;

                const activityItem = document.createElement('div');
                activityItem.className = 'activity-item';

                const friendUsername = activity.username || activity.friendId;

                activityItem.innerHTML = `
                    <div>
                        <strong><a href="/${activity.friendId}" class="friend-link">${friendUsername}</a></strong> rated
                        <a href="/v${activity.vnId.toString().replace('v', '')}" class="friend-link vn-link">${activity.vnTitle}</a>
                        <strong>${activity.vote}</strong>
                    </div>
                    <div class="activity-date">${formattedDate}</div>
                `;
                activityFeed.appendChild(activityItem);
            });

            const vnLinks = activityFeed.querySelectorAll('a.vn-link');
            vnLinks.forEach(link => {
                link.addEventListener('mouseenter', function() {
                    handleFriendsMouseOver.call(this);
                });
                link.addEventListener('mouseleave', function() {
                    handleFriendsMouseLeave.call(this);
                });
            });

            adjustContainerPosition();
            window.addEventListener('scroll', () => {
                if ($('#friendsPopover').css('display') === 'block') {
                    $('#friendsPopover').friendsCenter();
                }
            }, { passive: true });
        }

        let activeTab = localStorage.getItem('vndb_friends_active_tab') || 'friendsList';
        document.querySelectorAll('.tab-button').forEach(button => {
            button.addEventListener('click', () => {
                document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));
                document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));

                button.classList.add('active');
                const tabId = button.dataset.tab;

                document.querySelectorAll(`.tab-content[data-tab="${tabId}"], #${tabId}`).forEach(content => {
                    content.classList.add('active');
                });

                localStorage.setItem('vndb_friends_active_tab', tabId);
                activeTab = tabId;

                if (tabId === 'activityFeed') {
                    updateActivityFeed();
                }
            });
        });

        if (isContainerOpen && editLink) {
            container.style.display = 'block';
            initializeColorInputs();
            initializeImportExport();
            forceStyleUpdate();

            document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));
            document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));

            const activeTabButton = document.querySelector(`.tab-button[data-tab="${activeTab}"]`);
            const activeTabContent = document.getElementById(activeTab);

            if (activeTabButton && activeTabContent) {
                activeTabButton.classList.add('active');
                activeTabContent.classList.add('active');
                document.querySelectorAll(`.tab-content[data-tab="${activeTab}"]`).forEach(content => {
                    content.classList.add('active');
                });

                if (activeTab === 'activityFeed') {
                    updateActivityFeed();
                } else {
                    displayFriendsList();
                }
            }
        }

        function adjustContainerPosition() {
            requestAnimationFrame(() => {
                const container = document.querySelector('.friends-container');
                if (!container) return;

                container.style.maxWidth = '90vw';
                container.style.maxHeight = '80vh';
                container.style.top = '50%';
                container.style.left = '50%';
                container.style.transform = 'translate(-50%, -50%)';

                const isOpen = sessionStorage.getItem('vndb_friends_container_open') === 'true';
                if (!isOpen || window.innerWidth < 300 || window.innerHeight < 200) {
                    container.style.display = 'none';
                    return;
                }

                container.style.display = 'block';

                const viewportHeight = window.innerHeight;
                const containerHeight = container.offsetHeight;

                if (containerHeight > viewportHeight * 0.9) {
                    container.style.maxHeight = `${viewportHeight * 0.9}px`;
                    container.style.top = `50%`;
                    container.style.transform = `translate(-50%, -50%)`;
                }
            });
        }

        const settingsObserver = new MutationObserver(() => {
            requestAnimationFrame(() => {
                const container = document.querySelector('.friends-container');
                if (container.style.display === 'block') {
                    adjustContainerPosition();
                }
            });
        });
        if (settingsPanel) {
            settingsObserver.observe(settingsPanel, { attributes: true, attributeFilter: ['style'] });
        }


        function showContainer() {
            const editLink = document.querySelector('header nav menu li a[href$="/edit"]');
            const container = document.querySelector('.friends-container');

            if (!editLink || !container) {
                sessionStorage.setItem('vndb_friends_container_open', 'false');
                if (container) container.style.display = 'none';
                return;
            }

            if (window.innerWidth < 300 || window.innerHeight < 200) {
                alert('The viewport is too small to display the friends list. Please resize your browser window.');
                sessionStorage.setItem('vndb_friends_container_open', 'false');
                container.style.display = 'none';
                return;
            }

            const now = Date.now();
            const lastRefresh = GM_getValue('vndb_friends_last_refresh', 0);
            if (now - lastRefresh > 86400000) {
                preloadFriendData().then(() => GM_setValue('vndb_friends_last_refresh', now));
            }


            sessionStorage.setItem('vndb_friends_container_open', 'true');
            container.style.display = 'block';

            requestAnimationFrame(() => {
                adjustContainerPosition();
                setTimeout(adjustContainerPosition, 100);
            });

            initializeColorInputs();
            initializeImportExport();
            forceStyleUpdate();
            displayFriendsList();
            preloadActivityData();
        }

        const debouncedAdjustContainerPosition = debounce(() => {
            const isOpen = sessionStorage.getItem('vndb_friends_container_open') === 'true';
            if (isOpen) {
                adjustContainerPosition();
                if (window.innerWidth >= 300 && window.innerHeight >= 200) {
                    showContainer();
                }
            }
        }, 100);

        window.addEventListener('resize', debouncedAdjustContainerPosition);
        window.addEventListener('scroll', debouncedAdjustContainerPosition);

        let previewTimeoutId;
        let currentPreviewAjax = null;
        let $popover = $('#friendsPopover');
        if (!$popover.length) {
             $popover = $('<div id="friendsPopover"></div>').css({
                position: 'absolute',
                zIndex: '1001',
                boxShadow: '0px 0px 5px black',
                display: 'none'
            }).appendTo('body');
        }


        jQuery.fn.friendsCenter = function () {
            const windowHeight = $(window).height();
            const boxHeight = $(this).outerHeight();
            const scrollOffset = $(window).scrollTop();
            const hoveredLink = $('.activity-item a.vn-link:hover').get(0);

            if (!hoveredLink || !boxHeight) return this;

            const rect = hoveredLink.getBoundingClientRect();
            const leftoffset = rect.left;
            const topoffset = rect.top;
            let newTopOffset;

            if (topoffset - boxHeight / 2 < 10) {
                newTopOffset = 10;
            } else if (topoffset + boxHeight / 2 > windowHeight - 10) {
                newTopOffset = windowHeight - boxHeight - 10;
            } else {
                newTopOffset = topoffset - boxHeight / 2;
            }

            this.css("top", newTopOffset + scrollOffset);
            this.css("left", Math.max(0, leftoffset - $(this).outerWidth() - 25));
            return this;
        };

        let isScrollingActivityFeed = false;
        let scrollTimeoutId = null;

        const debouncedSaveScrollPosition = debounce(function() {
            const activityFeed = document.getElementById('activityFeed');
            if (activityFeed && activityFeed.offsetParent !== null) {
                 sessionStorage.setItem('vndb_activity_scroll', activityFeed.scrollTop);
            }
        }, 250);

        const activityFeedElement = document.getElementById('activityFeed');
        if (activityFeedElement) {
             activityFeedElement.addEventListener('scroll', function() {
                isScrollingActivityFeed = true;
                clearTimeout(scrollTimeoutId);
                clearTimeout(previewTimeoutId);
                if (currentPreviewAjax) {
                    currentPreviewAjax.abort();
                    currentPreviewAjax = null;
                }
                $popover.hide();

                scrollTimeoutId = setTimeout(() => {
                    isScrollingActivityFeed = false;
                }, 150);

                debouncedSaveScrollPosition();
            }, { passive: true });
        } else {
             console.warn("Could not find #activityFeed to attach debounced scroll listener.");
        }


        function handleFriendsMouseOver() {
            if (isScrollingActivityFeed) {
                return;
            }

            const activeTab = localStorage.getItem('vndb_friends_active_tab');
            if (activeTab !== 'activityFeed') return;

            const vnId = this.getAttribute('href');
            if (!vnId) return;

            const pagelink = 'https://vndb.org' + vnId;
            $popover.data('currentLink', pagelink);

            clearTimeout(previewTimeoutId);
            if (currentPreviewAjax) {
                 currentPreviewAjax.abort();
                 currentPreviewAjax = null;
            }

            previewTimeoutId = setTimeout(() => {
                if ($popover.data('currentLink') !== pagelink) return;

                const cachedImage = GM_getValue(pagelink);
                if (cachedImage) {
                    $popover.empty();
                    const $img = $('<img>').attr('src', cachedImage);

                    $img.off('load error').on('load', function() {
                        if (this.height === 0) {
                            GM_deleteValue(pagelink);
                            $popover.hide();
                        } else {
                            if ($popover.data('currentLink') === pagelink) {
                                $popover.show().friendsCenter();
                            } else {
                                $popover.hide();
                            }
                        }
                    }).on('error', function() {
                        console.warn("Failed to load cached image:", cachedImage);
                        GM_deleteValue(pagelink);
                        $popover.hide();
                    });
                    $popover.append($img);

                } else {
                    currentPreviewAjax = $.ajax({
                        url: pagelink,
                        dataType: 'text',
                        success: function (data) {
                            if ($popover.data('currentLink') !== pagelink) return;
                            currentPreviewAjax = null;

                            const parser = new DOMParser();
                            const dataDOC = parser.parseFromString(data, 'text/html');
                            const imagelink = dataDOC.querySelector(".vnimg img")?.src;
                            if (!imagelink) {
                                $popover.hide();
                                return;
                            }

                            $popover.empty();
                            const $img = $('<img>').attr('src', imagelink);

                            $img.off('load error').on('load', function() {
                                if (this.height === 0) {
                                    $popover.hide();
                                } else {
                                    if ($popover.data('currentLink') === pagelink) {
                                        GM_setValue(pagelink, imagelink);
                                        $popover.show().friendsCenter();
                                    } else {
                                         $popover.hide();
                                    }
                                }
                            }).on('error', function() {
                                console.warn("Failed to load image:", imagelink);
                                $popover.hide();
                            });
                             $popover.append($img);
                        },
                        error: function(jqXHR, textStatus) {
                            if (textStatus !== 'abort') {
                                console.error("AJAX error fetching preview:", textStatus);
                                $popover.hide();
                            }
                            if ($popover.data('currentLink') === pagelink) {
                                currentPreviewAjax = null;
                            }
                        }
                    });
                }
            }, 250);
        }

        function handleFriendsMouseLeave() {
            clearTimeout(previewTimeoutId);
             if (currentPreviewAjax) {
                 currentPreviewAjax.abort();
                 currentPreviewAjax = null;
             }
            $popover.removeData('currentLink').hide();
        }

        const pageObserver = new MutationObserver((mutations) => {
            const container = document.querySelector('.friends-container');
            if (!container || container.style.display !== 'block') return;

            for (const mutation of mutations) {
                // Check if the mutation target is one of our UI elements or their children.
                if (mutation.target.closest('.friends-container, #friendsPopover')) {
                    continue; // Ignore mutations inside our UI.
                }

                // Check if a node being added is one of our UI elements.
                let isInternalNodeChange = false;
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === 1 && (node.id === 'friendsPopover' || node.classList.contains('friends-container'))) {
                        isInternalNodeChange = true;
                        break;
                    }
                }
                if (isInternalNodeChange) {
                    continue; // Ignore the addition of our UI elements to the DOM.
                }

                // If we reach here, it's a genuine page change.
                adjustContainerPosition();
                return; // We found a valid trigger, so we can stop checking.
            }
        });

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

        function checkAndRefreshCache() {
            const now = Date.now();
            const cacheDurationMs = (settings.cacheDuration || 3) * 60 * 1000;
            if (now - activityCache.timestamp >= cacheDurationMs) {
                updateActivityFeed();
            }
        }

        const reloadButton = document.getElementById('reloadActivity');
        reloadButton.addEventListener('click', async () => {
            reloadButton.disabled = true;
            clearTimeout(reloadTimeout);
            reloadTimeout = setTimeout(() => {
                activityCache.timestamp = 0;
                localStorage.removeItem('vndb_activity_cache');
                updateActivityFeed().finally(() => {
                    reloadButton.disabled = false;
                });
            }, 300);
        });

        if (!editLink) {
            const menuObserver = new MutationObserver((mutations) => {
                const currentEditLink = document.querySelector('header nav menu li a[href$="/edit"]');
                if (currentEditLink) {
                    editLink = currentEditLink;
                    const isOpen = sessionStorage.getItem('vndb_friends_container_open') === 'true';
                    if (isOpen) safeShowContainer();
                    menuObserver.disconnect();
                }
            });
            menuObserver.observe(document.body, { childList: true, subtree: true });
        }

        function validateContainerState() {
            const isValidState = () => {
                const editLinkExists = !!document.querySelector('header nav menu li a[href$="/edit"]');
                const containerExists = !!document.querySelector('.friends-container');
                const storedState = sessionStorage.getItem('vndb_friends_container_open') === 'true';
                return editLinkExists && containerExists && storedState;
            };

            if (!isValidState()) {
                sessionStorage.setItem('vndb_friends_container_open', 'false');
                const container = document.querySelector('.friends-container');
                if (container) container.style.display = 'none';
            }
        }

        setInterval(validateContainerState, 2000);

        function ensureContainerExists() {
            if (!document.querySelector('.friends-container')) {
                document.body.appendChild(friendsContainer);
                console.warn('Recreated missing friends container');
            }
            if (!document.getElementById('friendsPopover')) {
                 $popover = $('<div id="friendsPopover"></div>').css({
                    position: 'absolute',
                    zIndex: '1001',
                    boxShadow: '0px 0px 5px black',
                    display: 'none'
                }).appendTo('body');
                console.warn('Recreated missing friends popover');
            }
        }

        setInterval(ensureContainerExists, 5000);

        let retryCount = 0;
        function showContainerWithRetry() {
            if (retryCount > 3) return;
            try {
                showContainer();
                retryCount = 0;
            } catch (error) {
                console.error('Container show error:', error);
                retryCount++;
                setTimeout(showContainerWithRetry, 500 * retryCount);
            }
        }

        function safeShowContainer() {
            try {
                showContainer();
            } catch (error) {
                console.error('Error showing container:', error);
                sessionStorage.setItem('vndb_friends_container_open', 'false');
                ensureContainerExists();
                setTimeout(() => {
                    const fc = document.querySelector('.friends-container');
                    if (fc) fc.style.display = 'none';
                }, 100);
            }
        }

        const visibilityEvents = ['pageshow', 'focus', 'hashchange'];
        visibilityEvents.forEach(event => {
            window.addEventListener(event, () => {
                setTimeout(handleContainerVisibility, 100);
            });
        });

        const originalPushState = history.pushState;
        history.pushState = function(...args) {
            originalPushState.apply(this, args);
            setTimeout(handleContainerVisibility, 50);
        };

        const originalReplaceState = history.replaceState;
        history.replaceState = function(...args) {
            originalReplaceState.apply(this, args);
            setTimeout(handleContainerVisibility, 50);
        };

        window.addEventListener('pageshow', function(event) {
            if (event.persisted) {
                editLink = document.querySelector('header nav menu li a[href$="/edit"]');
                setTimeout(() => {
                    handleContainerVisibility();
                    adjustContainerPosition();
                    initializeColorInputs();
                    initializeImportExport();
                    displayFriendsList();
                    forceStyleUpdate();
                    if (sessionStorage.getItem('vndb_friends_container_open') === 'true') {
                        showContainerWithRetry();
                    }
                }, 150);
            }
        });

        function handleContainerVisibility() {
            const currentEditLink = document.querySelector('header nav menu li a[href$="/edit"]');
            const containerElement = document.querySelector('.friends-container');
            const storedState = sessionStorage.getItem('vndb_friends_container_open') === 'true';

            const shouldBeVisible = () => {
                if (!currentEditLink || !containerElement) return false;
                if (window.innerWidth < 300 || window.innerHeight < 200) return false;
                return storedState;
            };

            if (shouldBeVisible() && containerElement.style.display !== 'block') {
                showContainerWithRetry();
            } else if (!shouldBeVisible() && containerElement && containerElement.style.display === 'block') {
                containerElement.style.display = 'none';
            }
        }

        setTimeout(handleContainerVisibility, 500);

    })();
})();