pandatype

Scripts for pandatype

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Advertisement:

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

Advertisement:

// ==UserScript==
// @name pandatype
// @namespace http://tampermonkey.net/
// @version 0.72
// @description Scripts for pandatype
// @author kryptonite
// @match https://pandatype.org/*
// @match https://www.pandatype.org/*
// @grant GM_addStyle
// @run-at document-start
// ==/UserScript==

(function() {
        'use strict';

        // 1. Base CSS Style Sheet
        const glassThemeCSS=`:root {
            --glass-bg: rgba(0, 0, 0, .18);
            --glass-border: rgba(255, 255, 255, .08);
            --glass-hover: rgba(255, 159, 47, .35);
            --glass-radius: 24px;
            --blur: 14px;
            --shadow: 0 10px 30px rgba(0, 0, 0, .35);
            --text: #fff;
        }

        html,
        body {
            overscroll-behavior: none;
        }

        body {
            color: var(--text);
            font: 20px "Trebuchet MS", sans-serif;
            text-align: center;
            overflow-x: hidden;
            background: center / cover fixed no-repeat;
            scrollbar-width: none;
            -ms-overflow-style: none;
        }

        body::-webkit-scrollbar {
            display: none;
        }

        a {
            color: inherit;
            text-decoration: none;
        }

        hr {
            margin: 48px;
            border: 0;
            border-top: 2px solid rgba(255, 255, 255, .15);
        }

        /* Glass Components */

        nav,
        .navbar,
        .blurrer {
            background: var(--glass-bg);
            border: 1px solid var(--glass-border);
            border-radius: var(--glass-radius);
            backdrop-filter: blur(var(--blur));
            -webkit-backdrop-filter: blur(var(--blur));
            box-shadow: var(--shadow);
            transition: .25s ease;
        }

        .fancy,
        nav a {
            display: inline-block;
            padding: 10px 14px;
            border-radius: 16px;
            cursor: pointer;
            transition: .2s ease;
        }

        .fancy:hover,
        nav a:hover {
            background: var(--glass-hover);
            text-shadow: 0 0 8px rgba(255, 255, 255, .4);
        }

        /* Background Panel */

        #bg-uploader-panel {
            position: fixed;
            bottom: 20px;
            left: 20px;
            z-index: 999999;
            display: flex;
            align-items: center;
            font: 13px sans-serif;
        }

        #bg-uploader-gear {
            width: 36px;
            height: 36px;
            display: grid;
            place-items: center;
            border-radius: 50%;
            cursor: pointer;
            background: rgba(0, 0, 0, .6);
            backdrop-filter: blur(10px);
            box-shadow: var(--shadow);
            transition: transform .3s;
        }

        #bg-uploader-panel:hover #bg-uploader-gear {
            transform: rotate(90deg);
        }

        #bg-uploader-menu {
            display: none;
            flex-direction: column;
            gap: 10px;
            width: 210px;
            margin-left: 10px;
            padding: 14px;
            border-radius: 16px;
            background: rgba(0, 0, 0, .8);
            border: 1px solid var(--glass-border);
            backdrop-filter: blur(var(--blur));
            box-shadow: var(--shadow);
        }

        #bg-uploader-panel:hover #bg-uploader-menu {
            display: flex;
        }

        body.pt-tape #passageBox {
            overflow: hidden !important;
            height: 100px !important;
        }

        body.pt-tape #passageInner {
            white-space: nowrap !important;
            display: inline-block !important;
            width: max-content !important;
            font-size: 27px !important;
            padding: 20px !important;
            will-change: transform;
        }

        body.pt-tape .ch {
            display: inline-block !important;
        }

        body.pt-tape .ch.space {
            width: .6em !important;
        }

        .bg-btn {
            padding: 7px;
            border: 0;
            border-radius: 8px;
            cursor: pointer;
            color: #fff;
            background: rgba(255, 255, 255, .15);
            transition: background .2s;
        }

        .bg-btn:hover {
            background: var(--glass-hover);
        }

        .discord-btn {
            display: block;
            background: rgba(88, 101, 242, .25);
            border: 1px dashed rgba(88, 101, 242, .45);
        }

        .discord-btn:hover {
            background: rgba(88, 101, 242, .5);
        }

        /* Toggle */

        .switch-container {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-top: 8px;
            padding-top: 8px;
            border-top: 1px solid rgba(255, 255, 255, .1);
        }

        .switch-label-block {
            display: flex;
            flex-direction: column;
        }

        .credits-tag {
            font-size: 9px;
            color: rgba(255, 255, 255, .5);
        }

        .toggle-switch {
            position: relative;
            width: 34px;
            height: 20px;
        }

        .toggle-switch input {
            display: none;
        }

        .slider {
            position: absolute;
            inset: 0;
            cursor: pointer;
            border-radius: 20px;
            background: rgba(255, 255, 255, .2);
            transition: .25s;
        }

        .slider::before {
            content: "";
            position: absolute;
            left: 3px;
            bottom: 3px;
            width: 14px;
            height: 14px;
            border-radius: 50%;
            background: #fff;
            transition: .25s;
        }

        input:checked + .slider {
            background: #ff9f2f;
        }

        input:checked + .slider::before {
            transform: translateX(14px);
        }

        `;

        if (typeof GM_addStyle !=='undefined') {
            GM_addStyle(glassThemeCSS);
        }

        else {
            const style=document.createElement('style');
            style.textContent=glassThemeCSS;
            document.head.appendChild(style);
        }

        // 2. Auto-Reloader Script Engine

        const DELAY_MS=0.369;

        let autoReloadEnabled=localStorage.getItem("pt_auto_reload") !=="false";

        let tapeModeEnabled=localStorage.getItem("pt_tape_mode") !=="false";

        let tapeRAF=0;
        let tapeOffset=0;
        let currentPassage="";
        let handledButton=null;

        let passageBox=null;
        let passageInner=null;
        let lastTransform="";

        function getPassageElements() {
            const box=document.getElementById("passageBox");
            const inner=document.getElementById("passageInner");

            if (box !==passageBox) {
                passageBox=box;
            }

            if (inner !==passageInner) {
                passageInner=inner;
            }
        }

        function setupPassage() {

            getPassageElements();

            if ( !passageInner) {
                return;
            }

            const text=passageInner.textContent;

            if (text===currentPassage) {
                return;
            }

            currentPassage=text;

            passageInner.querySelectorAll(".ch").forEach(ch=> {

                    if (ch.textContent===" " || ch.textContent==="") {

                        ch.classList.add("space");
                        ch.innerHTML=" ";

                    }

                });

        }

        function updateTape() {

            const box=document.getElementById("passageBox");
            const inner=document.getElementById("passageInner");
            const current=document.querySelector(".ch.current");

            if (!box || !inner || !current) return;

            const target=box.clientWidth * 0.48 -
                  current.offsetLeft -
                  current.clientWidth / 2;

            const delta = target - tapeOffset;

            if (Math.abs(delta) < 0.01) return;

            tapeOffset+=delta * 0.15;

            inner.style.transform=`translateX(${tapeOffset}px)`;
        }

        function tapeLoop() {
            if ( !tapeModeEnabled) {
                tapeRAF=0; return;
            }

            setupPassage();
            updateTape(); tapeRAF=requestAnimationFrame(tapeLoop);
        }

        function startTape() {
            getPassageElements(); document.body.classList.add("pt-tape");

            currentPassage=""; tapeOffset=0; lastTransform=""; setupPassage(); const current=document.querySelector(".ch.current"); if (passageBox && passageInner && current) {
                tapeOffset=passageBox.clientWidth * 0.42 - current.offsetLeft - current.clientWidth / 2; lastTransform=`translateX(${tapeOffset}px)`;
            passageInner.style.transform=lastTransform;
        }

        if ( !tapeRAF) {
            tapeLoop();
        }
    }

    function stopTape() {
        document.body.classList.remove("pt-tape"); cancelAnimationFrame(tapeRAF);

        tapeRAF=0; tapeOffset=0; currentPassage=""; lastTransform=""; if (passageInner) {
            passageInner.style.transform="";
        }
    }

    const observer=new MutationObserver(()=> {

            if ( !autoReloadEnabled) {
                return;
            }

            const btn=document.getElementById("raceAgainBtn");

            if (btn===handledButton) {
                return;
            }

            handledButton=btn;

            if ( !btn) {
                return;
            }

            requestAnimationFrame(()=> {

                    if (btn.isConnected && !btn.disabled) {
                        btn.click();
                    }

                });

        });

    // 3. Background State & Control Panel UI Injection
    function initThemeEngine() {
        const savedBg=localStorage.getItem('pt_custom_bg');

        if (savedBg) {
            document.body.style.backgroundImage=`url("${savedBg}")`;
        }

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

    if (tapeModeEnabled) {
        startTape();
    }

    if (document.getElementById('bg-uploader-panel')) return;

    const panel=document.createElement('div');
    panel.id='bg-uploader-panel';

    const gear=document.createElement('div');
    gear.id='bg-uploader-gear';

    gear.innerHTML=` <svg xmlns="http://www.w3.org/2000/svg"
    width="22"
    height="22"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    stroke-linejoin="round" > <circle cx="12" cy="12" r="3" /> <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0
 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9A1.65 1.65 0 0 0 10 3.09V3a2 2 0 1 1 4 0v.09A1.65 1.65 0 0 0 15 4.6h.08a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9v.08A1.65 1.65 0 0 0 20.91 10H21a2 2 0 1 1 0 4h-.09A1.65 1.65 0 0 0 19.4 15z"
 /> < /svg>`;

    const menu=document.createElement('div');
    menu.id='bg-uploader-menu';

    const label=document.createElement('span');
    label.style.fontWeight='bold';
    label.style.color='#ff9f2f';

    // File Uploader Elements (Images)
    const fileInput=document.createElement('input');
    fileInput.type='file';
    fileInput.accept='.png, .jpg, .jpeg';
    fileInput.style.display='none';

    const uploadBtn=document.createElement('button');
    uploadBtn.className='bg-btn';
    uploadBtn.textContent='📁 Upload Background';
    uploadBtn.onclick=()=> fileInput.click();

    // Remove Background button
    const resetBtn=document.createElement('button');
    resetBtn.className='bg-btn';
    resetBtn.style.background='rgba(239, 68, 68, 0.25)';
    resetBtn.textContent='❌ Remove Background';

    resetBtn.onclick=()=> {
        localStorage.removeItem('pt_custom_bg');
        document.body.style.backgroundImage='none';
    }

    ;

    // File loading pipeline logic
    const processFile=(file)=> {
        if ( !file) return;
        const reader=new FileReader();

        reader.onload=(event)=> {
            const base64Data=event.target.result;

            try {
                localStorage.setItem('pt_custom_bg', base64Data);
                document.body.style.backgroundImage=`url("${base64Data}")`;
            }

            catch (error) {
                alert("This file is too large for storage.");
            }
        }

        ;
        reader.readAsDataURL(file);
    }

    ;

    fileInput.onchange=(e)=> processFile(e.target.files[0]);

    // Auto Reloader Toggle Element
    const switchContainer=document.createElement('div');
    switchContainer.className='switch-container';

    const labelBlock=document.createElement('div');
    labelBlock.className='switch-label-block';

    const switchText=document.createElement('span');
    switchText.style.fontWeight='bold';
    switchText.style.color='#ffffff';
    switchText.textContent='Reloader';
    labelBlock.appendChild(switchText);

    const toggleLabel=document.createElement('label');
    toggleLabel.className='toggle-switch';

    const toggleInput=document.createElement('input');
    toggleInput.type='checkbox';
    toggleInput.checked=autoReloadEnabled;

    toggleInput.addEventListener('change', e=> {

            autoReloadEnabled=e.target.checked;

            localStorage.setItem('pt_auto_reload',
                String(autoReloadEnabled));

            console.log(`[PandaTree] Auto Reload $ {
                    autoReloadEnabled ? "Enabled" : "Disabled"
                }

                `);

        });

    const sliderSpan=document.createElement('span');
    sliderSpan.className='slider';

    toggleLabel.appendChild(toggleInput);
    toggleLabel.appendChild(sliderSpan);
    switchContainer.appendChild(labelBlock);
    switchContainer.appendChild(toggleLabel);

    // Assembly
    menu.appendChild(label);
    menu.appendChild(uploadBtn);
    menu.appendChild(resetBtn);
    menu.appendChild(switchContainer);

    // Tape Mode Toggle
    const tapeContainer=document.createElement('div');
    tapeContainer.className='switch-container';

    const tapeLabelBlock=document.createElement('div');
    tapeLabelBlock.className='switch-label-block';

    const tapeText=document.createElement('span');
    tapeText.style.fontWeight='bold';
    tapeText.style.color='#ffffff';
    tapeText.textContent='Tape Mode';

    tapeLabelBlock.appendChild(tapeText);

    const tapeToggleLabel=document.createElement('label');
    tapeToggleLabel.className='toggle-switch';

    const tapeToggleInput=document.createElement('input');
    tapeToggleInput.type='checkbox';
    tapeToggleInput.checked=tapeModeEnabled;

    tapeToggleInput.addEventListener("change", e=> {

            tapeModeEnabled=e.target.checked;

            localStorage.setItem("pt_tape_mode",
                tapeModeEnabled);

            if (tapeModeEnabled) {
                startTape();
            }

            else {
                stopTape();
            }

        });

    const tapeSlider=document.createElement('span');
    tapeSlider.className='slider';

    tapeToggleLabel.appendChild(tapeToggleInput);
    tapeToggleLabel.appendChild(tapeSlider);

    tapeContainer.appendChild(tapeLabelBlock);
    tapeContainer.appendChild(tapeToggleLabel);

    menu.appendChild(tapeContainer);

    panel.appendChild(gear);
    panel.appendChild(menu);

    document.body.appendChild(panel);
}

if (document.readyState==='loading') {
    document.addEventListener('DOMContentLoaded', initThemeEngine);
}

else {
    initThemeEngine();
}
})();