VNDB Friends List

Add friends list and friend votes display for VNDB

// ==UserScript==
// @name         VNDB Friends List
// @namespace    http://tampermonkey.net/
// @version      1.69.8
// @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
// @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 friendBtn = document.querySelector('header nav menu li a[href="#"]:not(:contains("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 friendBtn = document.querySelector('header nav menu li a[href="#"]:not(:contains("friends"))');
                    if (friendBtn) {
                        friendBtn.textContent = friends.includes(friendId) ? 'remove the friend' : 'add a friend';
                    }
                }
            } catch (e) {
                console.error(e);
            }
        }
    });

    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 friendsCache = GM_getValue('vndb_friends_cache', {});
            if (Object.keys(friendsCache).length === 0) {
                try {
                    friendsCache = JSON.parse(localStorage.getItem('vndb_friends_cache') || '{}');
                } catch (e) {
                    console.error('Error reading cache from localStorage:', e);
                }
            }
            if (Object.keys(friendsCache).length > 0) {
                friends = Object.keys(friendsCache).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];
        }
    }
    GM_setValue('vndb_friends_cache', friendsCache);
    localStorage.setItem('vndb_friends_cache', JSON.stringify(friendsCache));    (function() {
        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
        });

        if (!settings.friendsVotesEnabled) {
            console.log("VNDB Friend Votes: Disabled by settings");
            return;
        }

        const vnId = 'v' + vnPageMatch[1];
        console.log(`VNDB Friend Votes: Loading for ${vnId}`);

        if (friends.length === 0) {
            console.log('VNDB Friend Votes: No friends found in any storage');
            return;
        }
        console.log(`VNDB Friend Votes: Found ${friends.length} friends`);
        processFriends(friends).catch(err => {
            console.error('VNDB Friend Votes: Error processing friends:', err);
        });

        async function processFriends(friendsList) {
            function fetchFriendVote(userId) {
                return new Promise(resolve => {
                    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) {
                            try {
                                const data = JSON.parse(resp.responseText);
                                if (data.results && data.results.length > 0 && data.results[0].vote != null) {
                                    resolve({ userId, vote: data.results[0].vote });
                                } else {
                                    resolve(null);
                                }
                            } catch (e) {
                                console.error(`Error processing response for ${userId}:`, e);
                                resolve(null);
                            }
                        },
                        onerror() {
                            console.error(`Request failed for ${userId}`);
                            resolve(null);
                        }
                    });
                });
            }

            async function ensureUsernames(votes) {
                let cache = GM_getValue('vndb_friends_cache', {});
                if (Object.keys(cache).length === 0) {
                    try {
                        const localCache = JSON.parse(localStorage.getItem('vndb_friends_cache') || '{}');
                        if (Object.keys(localCache).length > 0) {
                            cache = localCache;
                        }
                    } catch (e) {
                        console.error('VNDB Friend Votes: Error reading cache from localStorage:', e);
                    }
                }
                const missing = votes.filter(v => !cache[v.userId] || !cache[v.userId].username);

                if (missing.length > 0) {
                    console.log(`VNDB Friend Votes: Fetching ${missing.length} missing usernames`);
                    await Promise.all(missing.map(v =>
                        new Promise(resolve => {
                            GM_xmlhttpRequest({
                                method: 'GET',
                                url: `https://api.vndb.org/kana/user?q=${v.userId}&fields=username`,
                                headers: { 'Content-Type': 'application/json' },
                                onload(r) {
                                    try {
                                        const data = JSON.parse(r.responseText);
                                        if (data.results && data.results.length > 0) {
                                            cache[v.userId] = { username: data.results[0].username };
                                        } else {
                                            cache[v.userId] = { username: v.userId };
                                        }
                                    } catch (e) {
                                        console.error(`Error fetching username for ${v.userId}:`, e);
                                        cache[v.userId] = { username: v.userId };
                                    }
                                    resolve();
                                },
                                onerror() {
                                    console.error(`Username request failed for ${v.userId}`);
                                    cache[v.userId] = { username: v.userId };
                                    resolve();
                                }
                            });
                        })
                    ));
                    GM_setValue('vndb_friends_cache', cache);
                    friendsCache = cache;
                }
                return votes.map(v => ({
                    userId: v.userId,
                    username: cache[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(friendVotes) {
                const data = await ensureUsernames(friendVotes);
                data.sort((a, b) => b.vote - a.vote);

                const statsContainer = document.querySelector('.votestats');
                if (!statsContainer) {
                    console.error('VNDB Friend Votes: 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: 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 oldTable = wrapper.querySelector('table.friends-votes-table');
                if (oldTable) oldTable.remove();
                const oldBubbleSection = wrapper.querySelector('.friends-votes-section');
                if (oldBubbleSection) oldBubbleSection.remove();
                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: 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: UI rendered successfully (Centered Tag Layout, After Stats)');
            }

            console.log('VNDB Friend Votes: Starting API requests');
            Promise.all(friendsList.map(fetchFriendVote))
                .then(results => results.filter(Boolean))
                .then(friendVotes => {
                    if (friendVotes.length === 0) {
                        console.log('VNDB Friend Votes: No friend votes found for this VN');
                        return;
                    }
                    console.log(`VNDB Friend Votes: Found ${friendVotes.length} friend votes`);
                    return renderFriendVotes(friendVotes);
                })
                .catch(err => {
                    console.error('VNDB Friend Votes: Error:', err);
                });
        }

        GM_addValueChangeListener('vndb_friends', () => {
            console.log('VNDB Friend Votes: Friends list changed, reloading page');
            location.reload();
        });

        GM_addValueChangeListener('vndb_friends_cache', () => {
            console.log('VNDB Friend Votes: Friends cache changed, reloading page');
            location.reload();
        });
    })();

    (function() {
        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;
                displayFriendsList();
            }
        });
        GM_addValueChangeListener('vndb_friends_cache', (name, old_value, new_value, isRemote) => {
            if (isRemote) {
                friendsCache = new_value;
                displayFriendsList();
            }
        });

        (async function migrateIfNeeded() {
            if (friends.some(f => !/^u\d+$/.test(f))) {
                async function migrateOldFriends() {
                    const newFriends = [];
                    for (const friend of friends) {
                        if (/^u\d+$/.test(friend)) {
                            newFriends.push(friend);
                        } else {
                            const userData = await fetchFriendData(friend);
                            if (userData && userData.id) {
                                newFriends.push(userData.id);
                                friendsCache[userData.id] = userData;
                            } else {
                                console.warn(`Warning: Could not fetch data for friend "${friend}". Entry skipped.`);
                            }
                        }
                    }
                    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));
                    displayFriendsList();
                }
                await migrateOldFriends();
            }
        })();

        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
        });

        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;
                }
                #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>Cache Duration:</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>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') {
                requestAnimationFrame(forceStyleUpdate);
            }
        });

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

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

        function resetSetting(setting) {
            switch(setting) {
                case 'cacheDuration':
                    settings[setting] = 3;
                    const cacheDurationInput = document.getElementById('cacheDuration');
                    if (cacheDurationInput) {
                        cacheDurationInput.value = 3;
                        activityCache.timestamp = 0;
                        localStorage.removeItem('vndb_activity_cache');
                        if (document.querySelector('.tab-button[data-tab="activityFeed"]').classList.contains('active')) {
                            updateActivityFeed();
                        }
                    }
                    break;
                case 'gamesPerFriend':
                    settings[setting] = 5;
                    const gamesPerFriendInput = document.getElementById('gamesPerFriend');
                    if (gamesPerFriendInput) gamesPerFriendInput.value = 5;
                    break;
                case 'maxActivities':
                    settings[setting] = 51;
                    const maxActivitiesInput = document.getElementById('maxActivities');
                    if (maxActivitiesInput) maxActivitiesInput.value = 51;
                    break;
                case 'fontSize':
                    settings[setting] = 17;
                    const fontSizeInput = document.getElementById('fontSize');
                    if (fontSizeInput) fontSizeInput.value = 17;
                    break;
                case 'buttonFontSize':
                    settings[setting] = 16;
                    const buttonFontSizeInput = document.getElementById('buttonFontSize');
                    if (buttonFontSizeInput) buttonFontSizeInput.value = 16;
                    break;
                case 'tabFontSize':
                    settings[setting] = 18;
                    const tabFontSizeInput = document.getElementById('tabFontSize');
                    if (tabFontSizeInput) tabFontSizeInput.value = 18;
                    break;
                case 'opacity':
                    settings[setting] = 0.70;
                    const opacityInput = document.getElementById('opacity');
                    const opacityValue = document.getElementById('opacityValue');
                    if (opacityInput) {
                        opacityInput.value = 70;
                        opacityValue.textContent = '70';
                    }
                    break;
                default:
                    settings[setting] = null;
                    const colorInput = document.getElementById(setting);
                    const hexInput = document.getElementById(setting + 'Hex');
                    if (colorInput && hexInput) {
                        colorInput.value = '#000000';
                        hexInput.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');
        if (!editLink && !isNotifiesPage) {
            const friendLi = document.createElement('li');
            const friendBtn = document.createElement('a');
            friendBtn.href = "#";
            const friendId = 'u' + userId;
            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 friend of currentFriends) {
                const userData = friendsCache[friend];
                if (userData) {
                    const friendDiv = document.createElement('div');
                    friendDiv.style.margin = '5px 0';
                    friendDiv.innerHTML = `
                        <a href="/u${userData.id.slice(1)}" class="friend-link">${userData.username}</a>
                        <button class="removeFriend" data-username="${friend}" style="margin-left: 10px;">Remove</button>
                    `;
                    friendsList.appendChild(friendDiv);
                }
            }

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

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

            updatePagination();
        }

        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;
            }
        }
        function updateFriends(newFriends, newCache) {
            friends = newFriends;
            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 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();
                //alert('Friend added!');
            } else {
                alert('User not found!');
            }
        }        function removeFriend(username) {
            const updatedFriends = friends.filter(f => f !== username);
            const updatedCache = {...friendsCache};
            delete updatedCache[username];

            updateFriends(updatedFriends, updatedCache);

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

            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() {
            for (const friend of friends) {
                if (!friendsCache[friend]) {
                    await fetchFriendData(friend);
                }
            }
        })();

        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',
                'gamesPerFriend',
                'maxActivities'
            ];            numericInputs.forEach(settingId => {
                const input = document.getElementById(settingId);
                if (input && settings[settingId] !== null) {
                    input.value = settings[settingId];
                }

                input.addEventListener('change', function() {
                    settings[settingId] = parseInt(this.value) || 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', () => {
                const exportData = {
                    friends: friends,
                    friendsCache: friendsCache,
                    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 || {};
                            friends = friends.map(f => /^u\d+$/.test(f)
                                ? f
                                : (friendsCache[f] && friendsCache[f].id ? friendsCache[f].id : f));
                            for (const key in friendsCache) {
                                if (!friendsCache[key].hasOwnProperty('lastUpdate') || !friendsCache[key].lastUpdate) {
                                    friendsCache[key].lastUpdate = Date.now();
                                }
                            }
                            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];
                                }
                            }
                            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,
                                ...importData.settings
                            };
                            settings = newSettings;
                            GM_setValue('vndb_friends_settings', settings);
                            initializeColorInputs();
                        }

                        if (localStorage.getItem('vndb_friends_active_tab') === 'activityFeed') {
                            updateActivityFeed();
                        }
                        alert('Import completed successfully!');
                    } 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(username) {
            try {
                const userData = friendsCache[username];
                if (!userData || !userData.id) {
                    console.error(`No cached data found for user ${username}`);
                    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) {
                            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:', data);
                                        resolve({ results: [] });
                                    }
                                } catch (e) {
                                    console.error('JSON parse error:', e);
                                    resolve({ results: [] });
                                }
                            } else {
                                console.error('API error:', response.responseText);
                                resolve({ results: [] });
                            }
                        },
                        onerror: function(error) {
                            console.error('Request error:', error);
                            resolve({ results: [] });
                        }
                    });
                });

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

                return response.results.map(item => ({
                    username,
                    vnId: item.id,
                    vnTitle: item.vn.title,
                    vote: item.vote / 10,
                    voted: item.voted
                }));
            } catch (error) {
                console.error(`Error fetching activity for ${username}:`, 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) {
                GM_xmlhttpRequest.abort(currentRequestId);
                currentRequestId = null;
            }

            isUpdatingActivity = true;
            const now = Date.now();
            const cacheDurationMs = (settings.cacheDuration || 3) * 60 * 1000;            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 requests = friends.map(friend => new Promise((resolve) => {
                    const requestId = GM_xmlhttpRequest({
                        method: 'POST',
                        url: 'https://api.vndb.org/kana/ulist',
                        headers: { 'Content-Type': 'application/json' },
                        data: JSON.stringify({
                            "user": friendsCache[friend] && friendsCache[friend].id,
                            "fields": "id, vote, voted, vn.title",
                            "filters": ["label", "=", 7],
                            "sort": "voted",
                            "reverse": true,
                            "results": settings.gamesPerFriend || 5
                        }),
                        onload: function(response) {
                            try {
                                const data = JSON.parse(response.responseText);
                                if (data && Array.isArray(data.results)) {
                                    resolve(data.results.map(item => ({
                                        username: friend,
                                        vnId: item.id,
                                        vnTitle: item.vn.title,
                                        vote: item.vote / 10,
                                        voted: item.voted
                                    })));
                                } else {
                                    resolve([]);
                                }
                            } catch(e) {
                                resolve([]);
                            }
                        },
                        onerror: function(err) {
                            resolve([]);
                        }
                    });
                    currentRequestId = requestId;
                }));
                const activityResults = await Promise.all(requests);
                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 userData = friendsCache[activity.username];
                const userId = userData ? userData.id.slice(1) : '';

                activityItem.innerHTML = `
                    <div>
                        <strong><a href="/u${userId}" class="friend-link">${userData ? userData.username : activity.username}</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();
                }
            });
        }

        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();
                }
            });
        });

        settingsObserver.observe(document.querySelector('.friends-settings'), { 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) {
                Promise.all(friends.map(friend => fetchFriendData(friend)))
                    .then(() => GM_setValue('vndb_friends_last_refresh', now))
                    .catch(console.error);
            }

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

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

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

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

        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 timeoutId;
        $('body').append('<div id="friendsPopover"></div>');
        $('#friendsPopover').css({
            position: 'absolute',
            zIndex: '1001',
            boxShadow: '0px 0px 5px black',
            display: 'none'
        });

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

            if (!hoveredLink) 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;
        };

        function handleFriendsMouseOver() {
            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;

            timeoutId = setTimeout(() => {
                if (GM_getValue(pagelink)) {
                    const retrievedLink = GM_getValue(pagelink);
                    $('#friendsPopover').empty().append('<img src="' + retrievedLink + '"></img>');
                    $('#friendsPopover img').on('load', function() {
                        if (this.height === 0) {
                            GM_deleteValue(pagelink);
                        } else {
                            $('#friendsPopover').friendsCenter().css('display', 'block');
                        }
                    });
                } else {
                    $.ajax({
                        url: pagelink,
                        dataType: 'text',
                        success: function (data) {
                            const parser = new DOMParser();
                            const dataDOC = parser.parseFromString(data, 'text/html');
                            const imagelink = dataDOC.querySelector(".vnimg img").src;
                            if (!imagelink) return;

                            const img = new Image();
                            img.onload = function() {
                                const currentTab = localStorage.getItem('vndb_friends_active_tab');
                                if (currentTab !== 'activityFeed') return;

                                if (this.height === 0) return;
                                $('#friendsPopover').empty().append(this).friendsCenter().css('display', 'block');
                                GM_setValue(pagelink, imagelink);
                            };
                            img.src = imagelink;
                        }
                    });
                }
            }, 250);
        }

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

            clearTimeout(timeoutId);
            $('#friendsPopover').css('display', 'none');
        }

        const pageObserver = new MutationObserver(() => {
            if (document.querySelector('.friends-container').style.display === 'block') {
                adjustContainerPosition();
            }
        });

        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;
                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');
            }
        }

        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(() => {
                    document.querySelector('.friends-container').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 editLink = document.querySelector('header nav menu li a[href$="/edit"]');
            const container = document.querySelector('.friends-container');
            const storedState = sessionStorage.getItem('vndb_friends_container_open') === 'true';

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

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

        setTimeout(handleContainerVisibility, 500);

        document.getElementById('activityFeed').addEventListener('scroll', function () {
            sessionStorage.setItem('vndb_activity_scroll', this.scrollTop);
        });
    })();
})();