pandatype

Scripts for pandatype

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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