pandatype

Scripts for pandatype

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

Advertisement:

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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