A New Avatar

Neopets "Favorite Picker" for your avatars

// ==UserScript==
// @name         A New Avatar
// @version      0.9
// @license      GNU GPLv3
// @description  Neopets "Favorite Picker" for your avatars
// @match        https://www.neopets.com/settings/neoboards/
// @icon         https://images.neopets.com/new_shopkeepers/t_1900.gif
// @author       Posterboy
// @namespace    https://youtube.com/@Neo_Posterboy
// @grant        none
// ==/UserScript==

(function () {
    console.log("Avatar Selector: Script started");

    // ==============
    // Styling
    // ==============

    const styles = {
        selectionDiv: {
            position: "fixed",
            top: "50%",
            left: "50%",
            transform: "translate(-50%, -50%)",
            backgroundColor: "#fff",
            borderRadius: "12px",
            padding: "20px",
            boxShadow: "0 4px 20px rgba(0,0,0,0.2)",
            zIndex: 10000,
            display: "grid",
            gridTemplateColumns: "repeat(4, 90px)",
            gridTemplateRows: "repeat(3, 90px)",
            gap: "15px",
            minWidth: "400px",
            userSelect: "none",
        },
        avatarImage: (selected) => ({
            width: "80px",
            height: "80px",
            cursor: "pointer",
            border: selected ? "5px solid #4CAF50" : "5px solid gray",
            borderRadius: "8px",
            boxSizing: "border-box",
        }),
        submitButton: {
            marginTop: "15px",
            cursor: "pointer",
            width: "auto",
            padding: "8px 16px",
            position: "relative",
            left: "50%",
            transform: "translateX(-50%)",
        },
        exitButton: {
            position: "absolute",
            top: "15%",
            right: "10px",
            width: "55px",
            height: "70%",
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            cursor: "pointer",
        }
    };

    // ==============
    // User Interface
    // ==============

    let avatarArray = [];
    let selectionDiv = null;

    const startTime = Date.now();
    const interval = setInterval(() => {
        const avatars = Array.from(document.querySelectorAll("img")).filter(img =>
                                                                            img.src.includes("neopets.com/neoboards/avatars/")
                                                                           );

        if (avatars.length > 0 || Date.now() - startTime > 10000) {
            createInitButton();
            clearInterval(interval);
        }
    }, 500);

    function createInitButton() {
        if (document.getElementById("avatar-init-button")) return;

        const btn = document.createElement("button");
        btn.id = "avatar-init-button";
        btn.textContent = "Avatar Picker";
        btn.className = "settings-button";
        Object.assign(btn.style, {
            maxWidth: "200px",
            marginLeft: "10px",
            width: "100%",
            boxSizing: "border-box"
        });

        btn.addEventListener("click", initializeAvatars);

        const changeAvatarBtn = document.getElementById("avvie_btn");
        if (changeAvatarBtn && changeAvatarBtn.parentNode) {
            const wrapper = document.createElement("div");
            wrapper.style.display = "flex";
            wrapper.style.gap = "10px";
            wrapper.style.alignItems = "flex-start";
            changeAvatarBtn.parentNode.insertBefore(wrapper, changeAvatarBtn);
            wrapper.appendChild(changeAvatarBtn);
            wrapper.appendChild(btn);
        } else {
            document.body.appendChild(btn);
        }
    }

    function showSelectionDiv(batch) {
        removeSelectionDiv();

        selectionDiv = document.createElement("div");
        selectionDiv.className = "togglePopup__2020 movePopup__2020 invDesc";
        Object.assign(selectionDiv.style, {
            display: "block",
            position: "fixed",
            top: "50%",
            left: "50%",
            transform: "translate(-50%, -50%)",
            zIndex: 10000,
            minWidth: "450px"
        });

        const header = document.createElement("div");
        header.className = "popup-header__2020";

        const title = document.createElement("h3");
        title.textContent = "Select Your Favorite(s)";
        header.appendChild(title);

        const exitBtn = document.createElement("div");
        exitBtn.className = "inv-popup-exit button-default__2020 button-red__2020 popup-right-button__2020";
        exitBtn.title = "Close";
        Object.assign(exitBtn.style, styles.exitButton);
        exitBtn.addEventListener("click", removeSelectionDiv);

        const exitX = document.createElement("div");
        exitX.className = "button-x__2020";
        exitBtn.appendChild(exitX);
        header.appendChild(exitBtn);

        selectionDiv.appendChild(header);

        const body = document.createElement("div");
        body.className = "popup-body__2020";
        body.style.maxHeight = "400px";
        body.style.overflowY = "auto";

        const grid = document.createElement("div");
        grid.style.display = "grid";
        grid.style.gridTemplateColumns = "repeat(4, 90px)";
        grid.style.gridTemplateRows = "repeat(3, 90px)";
        grid.style.gap = "15px";
        grid.style.justifyContent = "center";
        grid.style.userSelect = "none";

        const batchState = new Map();
        batch.forEach(a => batchState.set(a.src, false));

        batch.forEach(a => {
            const img = document.createElement("img");
            img.src = `${a.src}?t=${Date.now()}`;
            img.title = "Click to toggle selection. Green = keep, Gray = eliminate";
            Object.assign(img.style, styles.avatarImage(false));

            img.addEventListener("click", () => {
                const current = batchState.get(a.src);
                batchState.set(a.src, !current);
                Object.assign(img.style, styles.avatarImage(!current));
            });

            grid.appendChild(img);
        });

        body.appendChild(grid);

        const submitBtn = document.createElement("button");
        submitBtn.textContent = "Submit";
        submitBtn.className = "button-default__2020 button-yellow__2020";
        Object.assign(submitBtn.style, styles.submitButton);

        submitBtn.addEventListener("click", () => {
            batch.forEach(a => {
                a.tag = batchState.get(a.src) ? "selected" : "eliminated";
            });
            startEliminationRound();
        });

        body.appendChild(submitBtn);
        selectionDiv.appendChild(body);
        document.body.appendChild(selectionDiv);
    }

    function removeSelectionDiv() {
        if (selectionDiv) {
            selectionDiv.remove();
            selectionDiv = null;
        }
    }

    // ==============
    // Background Functions
    // ==============

    function initializeAvatars() {
        const avatars = Array.from(document.querySelectorAll("img")).filter(img =>
                                                                            img.src.includes("neopets.com/neoboards/avatars/")
                                                                           );

        avatarArray = avatars.map(img => ({
            src: img.src,
            tag: "selected"
        }));

        console.log("Avatar Selector: Found", avatarArray.length, "avatars");

        if (avatarArray.length === 0) {
            alert("No avatars found.");
        } else {
            startEliminationRound();
        }
    }

    function getSelectedAvatars() {
        return avatarArray.filter(a => a.tag === "selected");
    }

    function startEliminationRound() {
        const remaining = getSelectedAvatars();
        console.log("Avatar Selector: Starting elimination round, avatars left:", remaining.length);

        if (remaining.length === 1) {
            if (selectionDiv) {
                while (selectionDiv.childNodes.length > 1) {
                    selectionDiv.removeChild(selectionDiv.lastChild);
                }
            } else {
                selectionDiv = document.createElement("div");
                document.body.appendChild(selectionDiv);
            }

            const body = document.createElement("div");
            body.className = "popup-body__2020";
            body.style.textAlign = "center";
            body.style.padding = "20px";

            const avatarImg = document.createElement("img");
            avatarImg.src = remaining[0].src;
            avatarImg.style.width = "80px";
            avatarImg.style.height = "80px";
            const message = document.createElement("p");
            message.textContent = "Congratulations! This is your favorite avatar.";
            message.style.fontWeight = "bold";

            body.appendChild(avatarImg);
            body.appendChild(message);
            selectionDiv.appendChild(body);

            return;
        }

        const batch = pickRandomBatch(remaining);
        showSelectionDiv(batch);
    }

    function pickRandomBatch(selected) {
        if (selected.length <= 12) return selected.slice();

        const shuffled = [...selected];
        for (let i = shuffled.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
        }
        return shuffled.slice(0, 12);
    }
})();