A virtual creature that follows your cursor, that you've nurtured, raised, and played with.
// ==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);
})();