Virtual Pet - Cursor-Friendly

A virtual creature that follows your cursor, that you've nurtured, raised, and played with.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Virtual Pet - Cursor-Friendly
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  A virtual creature that follows your cursor, that you've nurtured, raised, and played with.
// @author       Mustafa Hakan
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_notification
// @grant        GM_addStyle
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // Wait for body to be available
    function waitForBody(callback) {
        if (document.body) {
            callback();
        } else {
            setTimeout(() => waitForBody(callback), 100);
        }
    }

    // Creature data
    const PET = {
        name: GM_getValue('pet_name', 'Minik'),
        type: GM_getValue('pet_type', 'cat'),
        level: GM_getValue('pet_level', 1),
        xp: GM_getValue('pet_xp', 0),
        hunger: GM_getValue('pet_hunger', 100),
        happiness: GM_getValue('pet_happiness', 100),
        energy: GM_getValue('pet_energy', 100),
        coins: GM_getValue('pet_coins', 0),
        accessories: JSON.parse(GM_getValue('pet_acc', '[]')),
        color: GM_getValue('pet_color', '#ff6b35'),
        born: GM_getValue('pet_born', Date.now()),
        lastFed: GM_getValue('pet_lastFed', Date.now()),
        lastPlayed: GM_getValue('pet_lastPlayed', Date.now())
    };

    // Creature types
    const TYPES = {
        cat: { name: 'Cat', emoji: '🐱', sound: 'Meow!', foods: ['🐟','🥛','🐭'], toys: ['🧶','🎾','📦'] },
        dog: { name: 'Dog', emoji: '🐶', sound: 'Woof!', foods: ['🦴','🥩','🍖'], toys: ['🦴','🎾','🥏'] },
        dragon: { name: 'Dragon', emoji: '🐉', sound: 'Roar!', foods: ['🔥','🍖','💎'], toys: ['🏰','⚔️','💍'] },
        bunny: { name: 'Bunny', emoji: '🐰', sound: 'Hop!', foods: ['🥕','🥬','🍎'], toys: ['🏀','🪁','🎪'] },
        panda: { name: 'Panda', emoji: '🐼', sound: 'Purr!', foods: ['🎋','🍎','🍯'], toys: ['🪵','🧸','🏀'] },
        fox: { name: 'Fox', emoji: '🦊', sound: 'Yip!', foods: ['🍇','🧀','🥚'], toys: ['🪶','🎯','🧩'] }
    };

    const petType = TYPES[PET.type] || TYPES.cat;
    let petEl, statsEl, menuEl;
    let x = window.innerWidth / 2, y = window.innerHeight / 2;
    let targetX = x, targetY = y;
    let velX = 0, velY = 0;
    let animFrame;
    let state = 'idle';
    let stateTimer = 0;
    let particles = [];
    let mouseX = x, mouseY = y;

    function save() {
        try {
            GM_setValue('pet_name', PET.name);
            GM_setValue('pet_type', PET.type);
            GM_setValue('pet_level', PET.level);
            GM_setValue('pet_xp', PET.xp);
            GM_setValue('pet_hunger', PET.hunger);
            GM_setValue('pet_happiness', PET.happiness);
            GM_setValue('pet_energy', PET.energy);
            GM_setValue('pet_coins', PET.coins);
            GM_setValue('pet_acc', JSON.stringify(PET.accessories));
            GM_setValue('pet_color', PET.color);
            GM_setValue('pet_lastFed', PET.lastFed);
            GM_setValue('pet_lastPlayed', PET.lastPlayed);
        } catch(e) {
            console.log('Pet save error:', e);
        }
    }

    function addXP(amount) {
        PET.xp += amount;
        if (PET.xp >= PET.level * 100) {
            PET.xp = 0;
            PET.level++;
            PET.coins += PET.level * 10;
            try {
                GM_notification({ text: `🎉 ${PET.name} reached level ${PET.level}! +${PET.level*10} 💰`, timeout: 3000 });
            } catch(e) {}
            spawnParticles('levelup');
        }
        save();
    }

    function spawnParticles(type) {
        const emojis = {
            eat: ['✨','💫','🌟'],
            play: ['🎈','💝','💖'],
            levelup: ['🎉','🎊','⭐','🌟','💫'],
            sleep: ['💤','💤','💤'],
            love: ['💕','💗','💖']
        };
        const list = emojis[type] || ['✨'];
        for (let i = 0; i < 8; i++) {
            particles.push({
                emoji: list[Math.floor(Math.random() * list.length)],
                x: x, y: y - 20,
                vx: (Math.random() - 0.5) * 4,
                vy: -Math.random() * 4 - 2,
                life: 1,
                size: 16 + Math.random() * 16
            });
        }
    }

    function feedPet() {
        if (PET.hunger >= 100) { showMsg('I\'m full! 😊'); return; }
        if (Date.now() - PET.lastFed < 5000) { showMsg('Not hungry yet! ⏳'); return; }
        PET.hunger = Math.min(100, PET.hunger + 20);
        PET.lastFed = Date.now();
        PET.coins = Math.max(0, PET.coins - 5);
        addXP(10);
        state = 'eating';
        stateTimer = 30;
        spawnParticles('eat');
        showMsg('Yummy! 😋');
        save();
    }

    function playPet() {
        if (PET.energy < 20) { showMsg('Too tired... 😴'); return; }
        if (Date.now() - PET.lastPlayed < 3000) { showMsg('Need a break! ⏳'); return; }
        PET.happiness = Math.min(100, PET.happiness + 20);
        PET.energy = Math.max(0, PET.energy - 20);
        PET.hunger = Math.max(0, PET.hunger - 10);
        PET.lastPlayed = Date.now();
        PET.coins += Math.floor(Math.random() * 10) + 1;
        addXP(15);
        state = 'playing';
        stateTimer = 40;
        spawnParticles('play');
        if (Math.random() < 0.3) {
            PET.coins += 5;
            showMsg('💰 Found treasure! +5 coin');
        } else {
            showMsg('Yay! 🎾');
        }
        save();
    }

    function sleepPet() {
        if (PET.energy > 80) { showMsg('Not sleepy! 😄'); return; }
        state = 'sleeping';
        stateTimer = 60;
        PET.energy = Math.min(100, PET.energy + 30);
        PET.happiness = Math.min(100, PET.happiness + 5);
        spawnParticles('sleep');
        showMsg('Zzz... 😴');
        save();
    }

    function showMsg(text) {
        if (!petEl) return;
        const msg = document.createElement('div');
        msg.textContent = text;
        msg.style.cssText = `
            position: fixed; left: ${x}px; top: ${y - 50}px;
            background: rgba(0,0,0,0.85); color: #fff; padding: 8px 16px;
            border-radius: 20px; font: 13px system-ui; pointer-events: none;
            z-index: 2147483648; animation: petMsg 2s forwards; white-space: nowrap;
        `;
        document.body.appendChild(msg);
        setTimeout(() => { if (msg.parentNode) msg.remove(); }, 2000);
    }

    function changeAccessory() {
        const all = ['🎩','👑','🎀','🕶️','🧢','💍','📿','🎒','🧣','👒','🪖','🎭'];
        const available = all.filter(a => !PET.accessories.includes(a));
        if (available.length === 0) { showMsg('Collected them all! 🏆'); return; }
        const chosen = available[Math.floor(Math.random() * available.length)];
        PET.accessories.push(chosen);
        PET.coins = Math.max(0, PET.coins - 10);
        spawnParticles('love');
        showMsg(`New accessory: ${chosen}!`);
        save();
    }

    function createPet() {
        if (document.getElementById('virtual-pet')) return;
        petEl = document.createElement('div');
        petEl.id = 'virtual-pet';
        petEl.style.cssText = `
            position: fixed; left: ${x}px; top: ${y}px; z-index: 2147483645;
            font-size: ${30 + PET.level * 3}px; cursor: pointer; pointer-events: all;
            transition: none; user-select: none; transform: translate(-50%, -50%);
            filter: drop-shadow(0 5px 15px rgba(0,0,0,0.5));
        `;
        updatePetAppearance();
        document.body.appendChild(petEl);

        petEl.addEventListener('click', (e) => {
            e.stopPropagation();
            if (state === 'sleeping') { showMsg('Shh sleeping... 😴'); return; }
            PET.happiness = Math.min(100, PET.happiness + 5);
            spawnParticles('love');
            if (Math.random() < 0.2) PET.coins++;
            save();
        });

        petEl.addEventListener('dblclick', (e) => {
            e.stopPropagation();
            toggleMenu();
        });
    }

    function updatePetAppearance() {
        if (!petEl) return;
        let display = petType.emoji;
        if (PET.accessories.length > 0) {
            display += PET.accessories[PET.accessories.length - 1];
        }
        if (state === 'sleeping') display += '💤';
        petEl.textContent = display;
        petEl.style.fontSize = (30 + PET.level * 3) + 'px';
        petEl.style.filter = `drop-shadow(0 5px 15px rgba(0,0,0,0.5)) drop-shadow(0 0 ${5+PET.level*2}px ${PET.color})`;
    }

    function createStats() {
        if (document.getElementById('pet-stats')) return;
        statsEl = document.createElement('div');
        statsEl.id = 'pet-stats';
        statsEl.style.cssText = `
            position: fixed; bottom: 20px; left: 20px; background: rgba(15,15,20,0.92);
            border: 1px solid rgba(255,255,255,0.15); border-radius: 16px; padding: 15px;
            z-index: 2147483645; font: 12px system-ui; color: #ccc; min-width: 200px;
            backdrop-filter: blur(20px); box-shadow: 0 10px 30px rgba(0,0,0,0.6);
        `;
        updateStats();
        document.body.appendChild(statsEl);
    }

    function updateStats() {
        if (!statsEl) return;
        statsEl.innerHTML = `
            <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
                <span style="font-size:24px;">${petType.emoji}</span>
                <div>
                    <div style="color:#fff;font-weight:700;">${PET.name}</div>
                    <div style="color:#888;font-size:10px;">${petType.name} • Lv ${PET.level}</div>
                </div>
            </div>
            ${bar('🍗', 'Hunger', PET.hunger, '#f97316')}
            ${bar('😊', 'Happiness', PET.happiness, '#f472b6')}
            ${bar('⚡', 'Energy', PET.energy, '#4ade80')}
            <div style="display:flex;justify-content:space-between;margin-top:8px;">
                <span>⭐ XP: ${PET.xp}/${PET.level*100}</span>
                <span>💰 ${PET.coins}</span>
            </div>
            <div style="display:flex;gap:4px;margin-top:8px;">
                <button class="pet-feed-btn" style="flex:1;padding:8px;border-radius:8px;border:1px solid #f97316;background:#f9731622;color:#f97316;cursor:pointer;font-size:16px;" title="Feed (-5💰)">🍗</button>
                <button class="pet-play-btn" style="flex:1;padding:8px;border-radius:8px;border:1px solid #f472b6;background:#f472b622;color:#f472b6;cursor:pointer;font-size:16px;" title="Play">🎾</button>
                <button class="pet-sleep-btn" style="flex:1;padding:8px;border-radius:8px;border:1px solid #4ade80;background:#4ade8022;color:#4ade80;cursor:pointer;font-size:16px;" title="Sleep">💤</button>
                <button class="pet-acc-btn" style="flex:1;padding:8px;border-radius:8px;border:1px solid #a78bfa;background:#a78bfa22;color:#a78bfa;cursor:pointer;font-size:16px;" title="Accessory (-10💰)">🎀</button>
            </div>
            <div style="color:#666;font-size:9px;text-align:center;margin-top:6px;">Click: Pet • Double-click: Menu</div>
        `;

        // Add event listeners directly
        const feedBtn = statsEl.querySelector('.pet-feed-btn');
        const playBtn = statsEl.querySelector('.pet-play-btn');
        const sleepBtn = statsEl.querySelector('.pet-sleep-btn');
        const accBtn = statsEl.querySelector('.pet-acc-btn');

        if (feedBtn) feedBtn.onclick = feedPet;
        if (playBtn) playBtn.onclick = playPet;
        if (sleepBtn) sleepBtn.onclick = sleepPet;
        if (accBtn) accBtn.onclick = changeAccessory;
    }

    function bar(icon, label, value, color) {
        return `<div style="margin:4px 0;"><span style="font-size:10px;">${icon} ${label}</span><div style="height:6px;background:#333;border-radius:3px;margin-top:2px;"><div style="height:100%;width:${value}%;background:${color};border-radius:3px;transition:width 0.5s;"></div></div></div>`;
    }

    function createMenu() {
        if (menuEl) { menuEl.remove(); menuEl = null; }
        menuEl = document.createElement('div');
        menuEl.id = 'pet-menu';
        menuEl.style.cssText = `
            position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
            background: rgba(15,15,20,0.95); border: 1px solid rgba(255,255,255,0.2);
            border-radius: 20px; padding: 25px; z-index: 2147483649; font: 14px system-ui;
            color: #fff; min-width: 280px; backdrop-filter: blur(30px);
            box-shadow: 0 20px 60px rgba(0,0,0,0.8);
        `;

        const menuHTML = `
            <div style="font-weight:700;font-size:18px;margin-bottom:15px;text-align:center;">🐾 ${PET.name} Menu</div>
            <div style="display:flex;flex-direction:column;gap:8px;">
                <button id="pet-btn-name" style="padding:12px;border-radius:10px;border:1px solid #444;background:#222;color:#fff;cursor:pointer;">✏️ Change Name</button>
                <button id="pet-btn-type" style="padding:12px;border-radius:10px;border:1px solid #444;background:#222;color:#fff;cursor:pointer;">🔄 Change Type</button>
                <button id="pet-btn-color" style="padding:12px;border-radius:10px;border:1px solid #444;background:#222;color:#fff;cursor:pointer;">🎨 Change Color</button>
                <button id="pet-btn-stats" style="padding:12px;border-radius:10px;border:1px solid #444;background:#222;color:#fff;cursor:pointer;">📊 Detailed Stats</button>
            </div>
            <button id="pet-btn-close" style="margin-top:15px;width:100%;padding:10px;border-radius:10px;border:1px solid #444;background:transparent;color:#888;cursor:pointer;">Close</button>
        `;
        menuEl.innerHTML = menuHTML;
        document.body.appendChild(menuEl);

        document.getElementById('pet-btn-name').onclick = () => {
            const name = prompt('New name:', PET.name);
            if (name && name.trim()) { 
                PET.name = name.trim(); 
                save(); 
                updateStats(); 
                updatePetAppearance(); 
                showMsg(`My name is now ${PET.name}! 🎉`); 
            }
        };

        document.getElementById('pet-btn-type').onclick = () => {
            const types = Object.keys(TYPES);
            const current = types.indexOf(PET.type);
            PET.type = types[(current + 1) % types.length];
            save(); 
            updateStats(); 
            updatePetAppearance();
            showMsg(`Now I'm a ${TYPES[PET.type].name}! ${TYPES[PET.type].emoji}`);
        };

        document.getElementById('pet-btn-color').onclick = () => {
            const color = prompt('Color (hex):', PET.color);
            if (color) { 
                PET.color = color; 
                save(); 
                updatePetAppearance(); 
                showMsg('My color changed! ✨'); 
            }
        };

        document.getElementById('pet-btn-stats').onclick = () => {
            const age = Math.floor((Date.now() - PET.born) / 86400000);
            const acc = PET.accessories.length > 0 ? PET.accessories.join(' ') : 'None';
            alert(`${PET.name} - ${petType.name} Lv ${PET.level}\n\n🍗 Hunger: ${PET.hunger}%\n😊 Happiness: ${PET.happiness}%\n⚡ Energy: ${PET.energy}%\n⭐ XP: ${PET.xp}/${PET.level*100}\n💰 Coin: ${PET.coins}\n🎀 Accessories: ${acc}\n📅 Age: ${age} days`);
        };

        document.getElementById('pet-btn-close').onclick = () => {
            if (menuEl) { menuEl.remove(); menuEl = null; }
        };

        // Hover effects
        ['pet-btn-name','pet-btn-type','pet-btn-color','pet-btn-stats'].forEach(id => {
            const btn = document.getElementById(id);
            if (btn) {
                btn.onmouseover = () => { btn.style.background = '#333'; };
                btn.onmouseout = () => { btn.style.background = '#222'; };
            }
        });
    }

    function toggleMenu() {
        if (document.getElementById('pet-menu')) {
            const existing = document.getElementById('pet-menu');
            existing.remove();
            menuEl = null;
        } else {
            createMenu();
        }
    }

    function updatePet() {
        if (!petEl) return;

        const dx = mouseX - x;
        const dy = mouseY - y;
        const dist = Math.sqrt(dx * dx + dy * dy);

        if (state === 'sleeping') {
            velX *= 0.9;
            velY *= 0.9;
        } else if (dist > 30) {
            const speed = 0.08;
            velX += dx * speed;
            velY += dy * speed;
        } else {
            velX *= 0.95;
            velY *= 0.95;
            if (dist < 5 && Math.random() < 0.01) {
                spawnParticles('love');
            }
        }

        const maxSpeed = state === 'playing' ? 12 : 8;
        const spd = Math.sqrt(velX * velX + velY * velY);
        if (spd > maxSpeed) {
            velX = velX / spd * maxSpeed;
            velY = velY / spd * maxSpeed;
        }

        x += velX;
        y += velY;

        x = Math.max(20, Math.min(window.innerWidth - 20, x));
        y = Math.max(20, Math.min(window.innerHeight - 20, y));

        if (stateTimer > 0) {
            stateTimer--;
        } else if (state !== 'idle') {
            state = 'idle';
        }

        petEl.style.left = x + 'px';
        petEl.style.top = y + 'px';

        particles = particles.filter(p => {
            p.x += p.vx;
            p.y += p.vy;
            p.vy += 0.1;
            p.life -= 0.03;
            return p.life > 0;
        });

        updatePetAppearance();
        updateStats();
        renderParticles();

        if (Math.random() < 0.001) {
            PET.hunger = Math.max(0, PET.hunger - 1);
            PET.happiness = Math.max(0, PET.happiness - 0.5);
            PET.energy = Math.min(100, PET.energy + 0.5);
            save();
        }

        animFrame = requestAnimationFrame(updatePet);
    }

    let particlesCanvas, particlesCtx;
    function renderParticles() {
        if (!particlesCanvas) {
            particlesCanvas = document.createElement('canvas');
            particlesCanvas.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:2147483646;';
            document.body.appendChild(particlesCanvas);
            particlesCanvas.width = window.innerWidth;
            particlesCanvas.height = window.innerHeight;
            particlesCtx = particlesCanvas.getContext('2d');
        }
        if (!particlesCtx) return;
        particlesCtx.clearRect(0, 0, particlesCanvas.width, particlesCanvas.height);
        particles.forEach(p => {
            particlesCtx.globalAlpha = p.life;
            particlesCtx.font = p.size + 'px serif';
            particlesCtx.fillText(p.emoji, p.x, p.y);
        });
        particlesCtx.globalAlpha = 1;
    }

    function init() {
        GM_addStyle(`
            @keyframes petMsg { 
                0% { opacity: 0; transform: translateY(10px); } 
                20% { opacity: 1; transform: translateY(0); } 
                80% { opacity: 1; } 
                100% { opacity: 0; transform: translateY(-30px); } 
            }
        `);

        document.addEventListener('mousemove', (e) => {
            mouseX = e.clientX;
            mouseY = e.clientY;
        });

        document.addEventListener('click', (e) => {
            if (e.target.closest('#pet-menu') || e.target.closest('#virtual-pet') || e.target.closest('#pet-stats')) return;
            if (document.getElementById('pet-menu')) {
                document.getElementById('pet-menu').remove();
                menuEl = null;
            }
        });

        window.addEventListener('resize', () => {
            if (particlesCanvas) {
                particlesCanvas.width = window.innerWidth;
                particlesCanvas.height = window.innerHeight;
            }
        });

        createPet();
        createStats();
        updatePet();

        try {
            GM_notification({ text: `🐾 ${PET.name} is here! Click to pet, double-click for menu.`, timeout: 3000 });
        } catch(e) {}

        console.log('🐾 Virtual Pet loaded!');
    }

    waitForBody(init);
})();