diff --git a/pets.html b/pets.html index 2026576..ea8bb48 100644 --- a/pets.html +++ b/pets.html @@ -245,6 +245,29 @@ 100% { transform: translate(-50%, -44px) scale(0.85); opacity: 0; } } + /* !pet @user — a cartoon hand bobs above the target's head, rubbing it. */ + .pet-rub { + position: absolute; + top: -22px; left: 50%; + width: 36px; height: 36px; + margin-left: -18px; + pointer-events: none; + opacity: 0; + will-change: transform; + filter: drop-shadow(0 2px 3px rgba(0,0,0,0.45)); + } + .pet.petted .pet-rub { animation: pet-rub 1.8s ease-in-out forwards; } + @keyframes pet-rub { + 0% { opacity: 0; transform: translateY(8px) rotate(-12deg); } + 10% { opacity: 1; transform: translateY(2px) rotate(-12deg); } + 25% { transform: translateY(-4px) rotate(8deg); } + 40% { transform: translateY(2px) rotate(-12deg); } + 55% { transform: translateY(-4px) rotate(8deg); } + 70% { transform: translateY(2px) rotate(-12deg); } + 85% { transform: translateY(-4px) rotate(8deg); opacity: 1; } + 100% { transform: translateY(-12px) rotate(0deg); opacity: 0; } + } + .pet.spawning { animation: spawn-in 0.6s ease-out; } @keyframes spawn-in { 0% { opacity: 0; } @@ -655,6 +678,19 @@ const HEART_SVG = ` fill="#ff5577" stroke="#fff" stroke-width="1.5"/> `; +// Cartoon hand for !pet — palm-down so it reads as patting a head from above. +const HAND_SVG = ` + + + + + + + + + + +`; const FART_GREENS = ['#9bbf2f', '#aacc3a', '#b8d650', '#8eb02a', '#7ea020', '#c4dc66', '#a3c544', '#88a52a', '#d4e878']; function randomFartSvg() { @@ -838,6 +874,10 @@ class Pet { this.fartEl = document.createElement('div'); this.fartEl.className = 'fart'; + this.rubEl = document.createElement('div'); + this.rubEl.className = 'pet-rub'; + this.rubEl.innerHTML = HAND_SVG; + this.labelEl = document.createElement('div'); this.labelEl.className = 'pet-label'; @@ -845,6 +885,7 @@ class Pet { this.el.appendChild(this.zzzEl); this.el.appendChild(this.heartEl); this.el.appendChild(this.fartEl); + this.el.appendChild(this.rubEl); this.el.appendChild(this.labelEl); this.setHandle(handle); @@ -1108,6 +1149,16 @@ class Pet { }, 4500); } + // !pet @target — show a cartoon hand patting the pet for ~1.8s, plus a heart. + petPat() { + this.el.classList.remove('petted'); + void this.el.offsetWidth; + this.el.classList.add('petted'); + this.pulseHeart(); + setTimeout(() => this.el.classList.remove('petted'), 1900); + this.lastMessage = performance.now(); + } + pulseHeart() { this.el.classList.remove('happy', 'idle', 'sleep'); void this.el.offsetWidth; @@ -1735,7 +1786,7 @@ async function handleChatMessage(authorDid, record) { if (/!(clearall|factoryreset)\b/i.test(text)) triggerClearAll(authorDid); return; } - const ALL_COMMAND_RE = /(!party|!snow|!rain|!confetti|!hearts?|!(?:quake|earthquake|shake)|!shrink(?:ray)?|!grow(?:ray)?|barrel\s*roll)\b/i; + const ALL_COMMAND_RE = /(!party|!snow|!rain|!confetti|!hearts?|!(?:quake|earthquake|shake)|!shrink(?:ray)?|!grow(?:ray)?|!pet\b|barrel\s*roll)\b/i; const looksLikeCommand = ALL_COMMAND_RE.test(text) || /\b(left|right|up|down|fast|faster|slow|slower|jump|dance|fart|wide|narrow|tall|short|stretch|hop|bulge|bulgy|fisheye|unbulge|flatten|debulge|unfish|blur|unblur)/i.test(text) || /\b(links|rechts|hoch|runter|größer|grösser|kleiner|schneller|langsamer|spring|tanz|scheet|scheten|furz|breit|schmal|kurz|lang)/i.test(text) @@ -1752,6 +1803,19 @@ async function handleChatMessage(authorDid, record) { commandCooldownUntil.set(authorDid, now + COMMAND_COOLDOWN_MS); } + // !pet @target — friendly pat on the head, no badge required. + const petMatch = text.match(/!pet\s+@?([\w\.\-]+)/i); + if (petMatch) { + const target = petMatch[1].toLowerCase(); + for (const p of pets.values()) { + const h = (p.handle || '').toLowerCase(); + if (h === target || h.split('.')[0] === target || h.startsWith(target + '.')) { + p.petPat(); + break; + } + } + } + // !shrink @target / !grow @target — affect another chatter's pet by handle. // Mod-only (badge-gated) per streamer: cross-user commands need mod perms. const shrinkRayMatch = text.match(/!(shrink|shrinkray|grow|growray)\s+@?([\w\.\-]+)/i);