Poki Better Fullscreen

Adds a smart fullscreen button and hides navbar.

// ==UserScript==
// @name         Poki Better Fullscreen
// @namespace    https://poki.com/
// @version      1.0
// @description  Adds a smart fullscreen button and hides navbar.
// @match        https://poki.com/*
// @run-at       document-idle
// @grant        none
// @noframes
// @license      MIT
// @author       giovane2230
// ==/UserScript==

(() => {
    "use strict";

    // ========================
    // UTILITY FUNCTIONS
    // ========================

    // Shortcut for requestAnimationFrame
    const raf = (fn) => requestAnimationFrame(fn);

    // Add an event listener that triggers only once
    const once = (el, type, fn) => el.addEventListener(type, fn, { once: true });

    // ========================
    // SPA URL CHANGE DETECTION
    // ========================

    // Detect URL changes in Single Page Applications (SPA)
    const initUrlChangeHook = () => {
        const originalPush = history.pushState;
        const originalReplace = history.replaceState;
        const triggerEvent = () => window.dispatchEvent(new Event("poki-urlchange"));

        history.pushState = function () {
            originalPush.apply(this, arguments);
            triggerEvent();
        };

        history.replaceState = function () {
            originalReplace.apply(this, arguments);
            triggerEvent();
        };

        window.addEventListener("popstate", triggerEvent);
    };

    // ========================
    // FULLSCREEN BUTTON
    // ========================

    // Find Poki's "report bug" button for reference
    const findReportButton = () => {
        const useEl = document.querySelector(
            "button use[href='#reportIcon'], button use[xlink\\:href='#reportIcon']"
        );
        return useEl ? useEl.closest("button") : null;
    };

    // Check if a fullscreen button already exists
    const fullscreenButtonExists = () =>
        !!document.querySelector(
            "#fullscreen-button, button[aria-label*='ullscreen'], button[aria-label*='inimize']"
        );

    // Create a fullscreen button with native Poki styling
    const createFullscreenButton = () => {
        const btn = document.createElement("button");
        btn.type = "button";
        btn.id = "fullscreen-button";
        btn.className =
            "HPn_GzeLxs8_4nNebuj1 mDTrvHhilj2xlIvo_kXA phlaiC_iad_lookW5__d";
        btn.setAttribute("aria-label", "Fullscreen");
        btn.title = "Toggle Fullscreen";

        // Icon container
        const iconDiv = document.createElement("div");
        iconDiv.className = "tqh57qBcKxMV9EdZQoAb";
        iconDiv.innerHTML = `
            <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" class="AUcJqk5uLaoXL0jqRGuH">
                <use xlink:href="#enterFullscreenIcon" href="#enterFullscreenIcon"></use>
            </svg>
        `;
        btn.append(iconDiv);

        // Text container
        const textDiv = document.createElement("div");
        textDiv.className = "aAJE6r6D5rwwQuTmZqYG";

        const emptySpan = document.createElement("span");
        emptySpan.className = "L6WSODmebiIqJJOEi46E Vlw13G6cUIC6W9LiGC_X";
        textDiv.append(emptySpan);

        const label = document.createElement("span");
        label.className = "L6WSODmebiIqJJOEi46E tz2DEu5qBC9Yd6hJGjoW";
        label.textContent = "Fullscreen";
        textDiv.append(label);

        btn.append(textDiv);

        // Toggle fullscreen on click
        btn.addEventListener("click", () => {
            const elem = document.documentElement;

            if (!document.fullscreenElement) {
                // Request fullscreen
                (elem.requestFullscreen ||
                    elem.webkitRequestFullscreen ||
                    elem.msRequestFullscreen)?.call(elem);
            } else {
                // Exit fullscreen
                (document.exitFullscreen ||
                    document.webkitExitFullscreen ||
                    document.msExitFullscreen)?.call(document);
            }
        });

        return btn;
    };

    // Insert a new node after a reference node
    const insertAfter = (newNode, referenceNode) => {
        referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
    };

    // ========================
    // GAME MAXIMIZATION
    // ========================

    // Hide navbar and other navigation elements only in fullscreen
    const hideNavbarInFullscreen = () => {
        if (!document.fullscreenElement) return;

        const selectors = [
            'nav#nav.qoMYGbBhf9dsbaBGBphh.DJT17TB5hYo14sdLEAwk',
            '.J91n1ymJasoch_FZq89b',
            'nav',
            '[role="navigation"]',
            '.navbar',
            '.navigation',
            '.nav',
            'header nav',
        ];

        selectors.forEach((selector) => {
            try {
                const elements = document.querySelectorAll(selector);
                elements.forEach((el) => {
                    if (!el.hasAttribute("data-hidden-in-fullscreen")) {
                        el.style.cssText =
                            "display: none !important; visibility: hidden !important;";
                        el.setAttribute("data-hidden-in-fullscreen", "true");
                    }
                });
            } catch (e) {
                // Ignore invalid selectors
            }
        });

        // Remove Poki-specific nav completely
        const pokiNav = document.querySelector(
            'nav#nav.qoMYGbBhf9dsbaBGBphh.DJT17TB5hYo14sdLEAwk'
        );
        if (pokiNav && !pokiNav.hasAttribute("data-removed-in-fullscreen")) {
            pokiNav.remove();
            pokiNav.setAttribute("data-removed-in-fullscreen", "true");
        }
    };

    // Restore navbar when leaving fullscreen
    const restoreNavbarFromFullscreen = () => {
        if (document.fullscreenElement) return;

        const hiddenElements = document.querySelectorAll("[data-hidden-in-fullscreen]");
        hiddenElements.forEach((el) => {
            el.style.cssText = "";
            el.removeAttribute("data-hidden-in-fullscreen");
        });
    };

    // ========================
    // FULLSCREEN BUTTON MANAGEMENT
    // ========================

    let localObserver = null;

    // Ensure a fullscreen button exists and is functional
    const ensureFullscreenButton = () => {
        if (fullscreenButtonExists()) return;

        const reportBtn = findReportButton();
        if (!reportBtn) return;

        const btn = createFullscreenButton();
        insertAfter(btn, reportBtn);

        // Observer to keep the button alive if DOM changes
        if (localObserver) localObserver.disconnect();
        localObserver = new MutationObserver(() => {
            if (!fullscreenButtonExists()) {
                localObserver.disconnect();
                Promise.resolve().then(() => raf(ensureFullscreenButton));
            }
        });

        const scope = reportBtn.parentElement || reportBtn;
        localObserver.observe(scope, { childList: true });

        // Remove our button if Poki adds a native fullscreen button
        const removeIfNativeAppears = () => {
            if (
                fullscreenButtonExists() &&
                btn.isConnected &&
                btn !== document.querySelector("#fullscreen-button")
            ) {
                btn.remove();
                localObserver?.disconnect();
            }
        };
        once(document, "fullscreenchange", removeIfNativeAppears);
    };

    // ========================
    // INITIALIZATION
    // ========================

    initUrlChangeHook();

    // Ensure button after DOM is ready
    document.addEventListener("readystatechange", () => raf(ensureFullscreenButton));

    // Handle fullscreen changes
    document.addEventListener(
        "fullscreenchange",
        () => {
            if (document.fullscreenElement) {
                setTimeout(() => hideNavbarInFullscreen(), 100);
            } else {
                setTimeout(() => restoreNavbarFromFullscreen(), 100);
            }
            raf(ensureFullscreenButton);
        },
        { passive: true }
    );

    // Handle SPA route changes
    window.addEventListener(
        "poki-urlchange",
        () => {
            setTimeout(() => raf(ensureFullscreenButton), 100);
        },
        { passive: true }
    );

    // Safety interval to maintain fullscreen button and hide navbar
    setInterval(() => {
        if (!fullscreenButtonExists()) ensureFullscreenButton();
        if (document.fullscreenElement) hideNavbarInFullscreen();
    }, 2000);

    raf(ensureFullscreenButton);

    // ========================
    // ADDITIONAL STYLES
    // ========================

    const style = document.createElement("style");
    style.textContent = `
html:fullscreen, html:-webkit-full-screen, html:-moz-full-screen { overflow:hidden !important; }
html:fullscreen body, html:-webkit-full-screen body, html:-moz-full-screen body { margin:0 !important; padding:0 !important; overflow:hidden !important; }
#fullscreen-button:hover { opacity:0.8 !important; transform:scale(1.05) !important; transition:all 0.2s ease !important; }
#game-player[data-enhanced] { box-sizing: border-box !important; }
html:fullscreen nav, html:-webkit-full-screen nav, html:-moz-full-screen nav,
html:fullscreen [role="navigation"], html:-webkit-full-screen [role="navigation"], html:-moz-full-screen [role="navigation"],
html:fullscreen .navbar, html:-webkit-full-screen .navbar, html:-moz-full-screen .navbar { display:none !important; visibility:hidden !important; }
html:fullscreen [class*="qoMYGbBhf9dsbaBGBphh"], html:-webkit-full-screen [class*="qoMYGbBhf9dsbaBGBphh"], html:-moz-full-screen [class*="qoMYGbBhf9dsbaBGBphh"],
html:fullscreen [class*="DJT17TB5hYo14sdLEAwk"], html:-webkit-full-screen [class*="DJT17TB5hYo14sdLEAwk"], html:-moz-full-screen [class*="DJT17TB5hYo14sdLEAwk"] { display:none !important; visibility:hidden !important; }
`;
    document.head.appendChild(style);

    console.log(
        "betterfullscreen loaded"
    );
})();