MediaWiki:Gadget-oneko.js
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
// oneko.js
// Copyright © 2022 adryd
// SPDX-License-Identifier: MIT
// https://github.com/adryd325/oneko.js
// compiled to es5 with babel because mediawiki is insane and needs es5
(function oneko() {
var isReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)") === true || window.matchMedia("(prefers-reduced-motion: reduce)").matches === true;
if (isReducedMotion) return;
var nekoEl = document.createElement("div");
var nekoPosX = 32;
var nekoPosY = 32;
var mousePosX = 0;
var mousePosY = 0;
var frameCount = 0;
var idleTime = 0;
var idleAnimation = null;
var idleAnimationFrame = 0;
var nekoSpeed = 10;
var spriteSets = {
idle: [[-3, -3]],
alert: [[-7, -3]],
scratchSelf: [[-5, 0], [-6, 0], [-7, 0]],
scratchWallN: [[0, 0], [0, -1]],
scratchWallS: [[-7, -1], [-6, -2]],
scratchWallE: [[-2, -2], [-2, -3]],
scratchWallW: [[-4, 0], [-4, -1]],
tired: [[-3, -2]],
sleeping: [[-2, 0], [-2, -1]],
N: [[-1, -2], [-1, -3]],
NE: [[0, -2], [0, -3]],
E: [[-3, 0], [-3, -1]],
SE: [[-5, -1], [-5, -2]],
S: [[-6, -3], [-7, -2]],
SW: [[-5, -3], [-6, -1]],
W: [[-4, -2], [-4, -3]],
NW: [[-1, 0], [-1, -1]]
};
function init() {
nekoEl.id = "oneko";
nekoEl.ariaHidden = true;
nekoEl.style.width = "32px";
nekoEl.style.height = "32px";
nekoEl.style.position = "fixed";
nekoEl.style.pointerEvents = "none";
nekoEl.style.imageRendering = "pixelated";
nekoEl.style.left = "".concat(nekoPosX - 16, "px");
nekoEl.style.top = "".concat(nekoPosY - 16, "px");
nekoEl.style.zIndex = Number.MAX_VALUE;
var nekoFile = "https://wiki.vencord.dev/images/8/8c/Oneko.gif";
var curScript = document.currentScript;
if (curScript && curScript.dataset.cat) {
nekoFile = curScript.dataset.cat;
}
nekoEl.style.backgroundImage = "url(".concat(nekoFile, ")");
document.body.appendChild(nekoEl);
document.addEventListener("mousemove", function (event) {
mousePosX = event.clientX;
mousePosY = event.clientY;
});
window.requestAnimationFrame(onAnimationFrame);
}
var lastFrameTimestamp;
function onAnimationFrame(timestamp) {
// Stops execution if the neko element is removed from DOM
if (!nekoEl.isConnected) {
return;
}
if (!lastFrameTimestamp) {
lastFrameTimestamp = timestamp;
}
if (timestamp - lastFrameTimestamp > 100) {
lastFrameTimestamp = timestamp;
frame();
}
window.requestAnimationFrame(onAnimationFrame);
}
function setSprite(name, frame) {
var sprite = spriteSets[name][frame % spriteSets[name].length];
nekoEl.style.backgroundPosition = "".concat(sprite[0] * 32, "px ").concat(sprite[1] * 32, "px");
}
function resetIdleAnimation() {
idleAnimation = null;
idleAnimationFrame = 0;
}
function idle() {
idleTime += 1;
// every ~ 20 seconds
if (idleTime > 10 && Math.floor(Math.random() * 200) == 0 && idleAnimation == null) {
var avalibleIdleAnimations = ["sleeping", "scratchSelf"];
if (nekoPosX < 32) {
avalibleIdleAnimations.push("scratchWallW");
}
if (nekoPosY < 32) {
avalibleIdleAnimations.push("scratchWallN");
}
if (nekoPosX > window.innerWidth - 32) {
avalibleIdleAnimations.push("scratchWallE");
}
if (nekoPosY > window.innerHeight - 32) {
avalibleIdleAnimations.push("scratchWallS");
}
idleAnimation = avalibleIdleAnimations[Math.floor(Math.random() * avalibleIdleAnimations.length)];
}
switch (idleAnimation) {
case "sleeping":
if (idleAnimationFrame < 8) {
setSprite("tired", 0);
break;
}
setSprite("sleeping", Math.floor(idleAnimationFrame / 4));
if (idleAnimationFrame > 192) {
resetIdleAnimation();
}
break;
case "scratchWallN":
case "scratchWallS":
case "scratchWallE":
case "scratchWallW":
case "scratchSelf":
setSprite(idleAnimation, idleAnimationFrame);
if (idleAnimationFrame > 9) {
resetIdleAnimation();
}
break;
default:
setSprite("idle", 0);
return;
}
idleAnimationFrame += 1;
}
function frame() {
frameCount += 1;
var diffX = nekoPosX - mousePosX;
var diffY = nekoPosY - mousePosY;
var distance = Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2));
if (distance < nekoSpeed || distance < 48) {
idle();
return;
}
idleAnimation = null;
idleAnimationFrame = 0;
if (idleTime > 1) {
setSprite("alert", 0);
// count down after being alerted before moving
idleTime = Math.min(idleTime, 7);
idleTime -= 1;
return;
}
var direction;
direction = diffY / distance > 0.5 ? "N" : "";
direction += diffY / distance < -0.5 ? "S" : "";
direction += diffX / distance > 0.5 ? "W" : "";
direction += diffX / distance < -0.5 ? "E" : "";
setSprite(direction, frameCount);
nekoPosX -= diffX / distance * nekoSpeed;
nekoPosY -= diffY / distance * nekoSpeed;
nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16);
nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16);
nekoEl.style.left = "".concat(nekoPosX - 16, "px");
nekoEl.style.top = "".concat(nekoPosY - 16, "px");
}
init();
})();