Lichess Predator Vision Overlay

Predator vision thermal overlay with heat shimmer, triangular reticle, numeric stream, and draggable anchored toggle button.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         Lichess Predator Vision Overlay
// @namespace    http://tampermonkey.net/
// @version      1.6
// @description  Predator vision thermal overlay with heat shimmer, triangular reticle, numeric stream, and draggable anchored toggle button.
// @match        https://lichess.org/
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Load saved position or default
    let savedPos = localStorage.getItem('predatorVisionBtnPos');
    let pos = savedPos ? JSON.parse(savedPos) : null;

    // Default: under Terminator Vision button (shift 50px down)
    const defaultTop = pos ? pos.top : 156;
    const defaultRight = pos ? null : 154;

    // Create toggle button
    const btn = document.createElement('button');
    btn.textContent = 'Predator Vision';
    Object.assign(btn.style, {
        position: 'fixed',
        top: `${defaultTop}px`,
        right: defaultRight !== null ? `${defaultRight}px` : '',
        left: pos ? `${pos.left}px` : '',
        zIndex: 999999,
        padding: '8px 14px',
        background: '#004400',
        color: 'lime',
        border: 'none',
        borderRadius: '5px',
        cursor: 'pointer',
        fontWeight: 'bold',
        fontFamily: 'Consolas, monospace',
        userSelect: 'none',
        boxShadow: '0 0 8px lime',
        letterSpacing: '0.05em',
        touchAction: 'none',
    });
    btn.addEventListener('mouseenter', () => btn.style.backgroundColor = '#006600');
    btn.addEventListener('mouseleave', () => btn.style.backgroundColor = '#004400');
    document.body.appendChild(btn);

    // Dragging logic
    let dragging = false, dragStartX = 0, dragStartY = 0, btnStartLeft = 0, btnStartTop = 0;
    btn.addEventListener('mousedown', e => {
        dragging = true;
        dragStartX = e.clientX;
        dragStartY = e.clientY;
        const rect = btn.getBoundingClientRect();
        btnStartLeft = rect.left;
        btnStartTop = rect.top;
        e.preventDefault();
    });
    window.addEventListener('mousemove', e => {
        if (!dragging) return;
        let newLeft = btnStartLeft + (e.clientX - dragStartX);
        let newTop = btnStartTop + (e.clientY - dragStartY);
        newLeft = Math.min(window.innerWidth - btn.offsetWidth, Math.max(0, newLeft));
        newTop = Math.min(window.innerHeight - btn.offsetHeight, Math.max(0, newTop));
        btn.style.left = `${newLeft}px`;
        btn.style.top = `${newTop}px`;
        btn.style.right = '';
        localStorage.setItem('predatorVisionBtnPos', JSON.stringify({left: newLeft, top: newTop}));
    });
    window.addEventListener('mouseup', () => dragging = false);

    // Overlay container
    const overlay = document.createElement('div');
    Object.assign(overlay.style, {
        position: 'fixed',
        top: 0,
        left: 0,
        width: '100vw',
        height: '100vh',
        background: 'rgba(0, 50, 0, 0.6)',  // greener overlay
        zIndex: 999998,
        pointerEvents: 'none',
        display: 'none',
        overflow: 'hidden'
    });

    // Heatmap shimmer filter
    overlay.style.backdropFilter = 'contrast(200%) saturate(250%) hue-rotate(90deg)';

    // Shimmer effect
    const shimmer = document.createElement('div');
    Object.assign(shimmer.style, {
        position: 'absolute',
        width: '100%',
        height: '100%',
        background: 'radial-gradient(rgba(0,255,0,0.1), transparent)',
        mixBlendMode: 'screen',
        animation: 'heatShimmer 2s infinite linear',
        opacity: 0.4
    });
    overlay.appendChild(shimmer);

    // Numeric stream box (only numeric box now)
    const numStream = document.createElement('div');
    Object.assign(numStream.style, {
        position: 'fixed',
        top: '300px',
        right: '20px',
        width: '80px',
        height: '170px',  // reduced height to fit lines better
        color: 'lime',
        fontFamily: 'monospace',
        fontWeight: 'bold',
        fontSize: '14px',
        background: 'rgba(0,0,0,0.3)',
        textShadow: '0 0 10px lime',
        overflow: 'hidden',
        pointerEvents: 'none',
        zIndex: 40,
        userSelect: 'none',
        paddingLeft: '4px',  // slight left shift to avoid cutoff
        lineHeight: '1.2',
        letterSpacing: '0.07em',
        borderRadius: '8px 0 0 8px',
    });
    overlay.appendChild(numStream);

    const chars = '0123456789ABCDEF';
    const lineCount = 10;
    const lines = [];
    for(let i=0; i<lineCount; i++){
        const span = document.createElement('span');
        span.textContent = Array.from({length:12}, () => chars[Math.floor(Math.random()*chars.length)]).join('');
        span.style.opacity = Math.random() * 0.5 + 0.5;
        numStream.appendChild(span);
        if (i < lineCount - 1) {
            numStream.appendChild(document.createElement('br'));  // no br after last line
        }
        lines.push(span);
    }

    function animateNumStream(){
        lines.forEach(line => {
            if(Math.random() < 0.05){ // slower updates
                line.textContent = Array.from({length:12}, () => chars[Math.floor(Math.random()*chars.length)]).join('');
            }
        });
        requestAnimationFrame(animateNumStream);
    }

    // Text labels in corners and top center
    function createTextLabel(text, styles){
        const d = document.createElement('div');
        d.textContent = text.toUpperCase();
        Object.assign(d.style, {
            position: 'fixed',
            color: 'lime',
            fontWeight: '900',
            fontFamily: 'Consolas, monospace',
            fontSize: '1.4rem',
            textShadow: '0 0 8px lime',
            userSelect: 'none',
            pointerEvents: 'none',
            letterSpacing: '0.1em',
            ...styles,
        });
        overlay.appendChild(d);
    }
    createTextLabel('PREDATOR MODE ACTIVATED', {top: '20px', left: '50%', transform: 'translateX(-50%)'});
    createTextLabel('THERMAL TARGET LOCKED', {top: '20px', left: '20px'});
    createTextLabel('SCANNING', {top: '20px', right: '20px'});

    // Triangular reticle element creation & animation
    const reticle = document.createElement('div');
    reticle.innerHTML = `
        <svg width="80" height="80" viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg">
            <polygon points="40,5 75,75 5,75" stroke="lime" stroke-width="2" fill="none" />
            <circle cx="40" cy="50" r="5" fill="lime" />
        </svg>
    `;
    Object.assign(reticle.style, {
        position: 'fixed',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%, -50%)',
        pointerEvents: 'none',
        zIndex: 50,
    });
    overlay.appendChild(reticle);

    let reticleTime = 0;
    function animateReticle(){
        reticleTime += 0.015; // speed up slightly for smoothness
        const radius = 80;
        const centerX = window.innerWidth / 2;
        const centerY = window.innerHeight / 2;

        const x = centerX + Math.cos(reticleTime) * radius;
        const y = centerY + Math.sin(reticleTime) * radius;

        reticle.style.left = `${x}px`;
        reticle.style.top = `${y}px`;

        requestAnimationFrame(animateReticle);
    }

    // Create waiting message at bottom center, big green with sharp border and pulsate
    const waitingMsg = document.createElement('div');
    waitingMsg.textContent = 'WAITING FOR GAME...';
    Object.assign(waitingMsg.style, {
        position: 'fixed',
        bottom: '20px',
        left: '50%',
        transform: 'translateX(-50%)',
        color: 'lime',
        fontWeight: '900',
        fontFamily: 'Consolas, monospace',
        fontSize: '5rem',
        letterSpacing: '0.15em',
        textTransform: 'uppercase',
        userSelect: 'none',
        pointerEvents: 'none',
        whiteSpace: 'nowrap',
        zIndex: 1000000,
        textShadow:
          '-2px -2px 0 #000,' +
          '2px -2px 0 #000,' +
          '-2px 2px 0 #000,' +
          '2px 2px 0 #000',
        animation: 'pulseSharp 2.5s ease-in-out infinite',
        display: 'none'  // initially hidden
    });
    document.body.appendChild(waitingMsg);

    // Keyframes for shimmer and pulse plus pulseSharp for waiting message
    const style = document.createElement('style');
    style.textContent = `
        @keyframes pulseGreen {
            0%, 100% { opacity: 1; text-shadow: 0 0 6px lime; }
            50% { opacity: 0.5; text-shadow: 0 0 2px green; }
        }
        @keyframes heatShimmer {
            0% { transform: translateY(0); }
            50% { transform: translateY(-5px); }
            100% { transform: translateY(0); }
        }
        @keyframes pulseSharp {
            0%, 100% {
                opacity: 1;
                text-shadow:
                  -2px -2px 0 #000,
                  2px -2px 0 #000,
                  -2px 2px 0 #000,
                  2px 2px 0 #000;
            }
            50% {
                opacity: 0.5;
                text-shadow:
                  -1px -1px 0 #222,
                  1px -1px 0 #222,
                  -1px 1px 0 #222,
                  1px 1px 0 #222;
            }
        }
    `;
    document.head.appendChild(style);

    document.body.appendChild(overlay);

    // Toggle overlay on button click
    let active = false;
    btn.addEventListener('click', () => {
        active = !active;
        overlay.style.display = active ? 'block' : 'none';
        waitingMsg.style.display = active ? 'block' : 'none';  // Show waiting message only when overlay is ON
        btn.style.backgroundColor = active ? '#006600' : '#004400';
        if (active) {
            animateNumStream();
            animateReticle();
        }
    });

})();