oneko, i guess.
This commit is contained in:
parent
6c477b7343
commit
0d17c1f0b8
@ -27,7 +27,9 @@
|
|||||||
"styles": [
|
"styles": [
|
||||||
"src/assets/style.css"
|
"src/assets/style.css"
|
||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": [
|
||||||
|
"src/assets/oneko.js"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
|
|||||||
@ -19,11 +19,11 @@
|
|||||||
.upload-button {
|
.upload-button {
|
||||||
background-color: #0c090a;
|
background-color: #0c090a;
|
||||||
font-family: monospace, monospace;
|
font-family: monospace, monospace;
|
||||||
border: 2px dotted #CCCCCC;
|
border: 2px dotted #d2d6de;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
color: white
|
color: #d2d6de;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-button:hover {
|
.upload-button:hover {
|
||||||
|
|||||||
BIN
nise-frontend/src/assets/oneko.gif
Normal file
BIN
nise-frontend/src/assets/oneko.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
293
nise-frontend/src/assets/oneko.js
Normal file
293
nise-frontend/src/assets/oneko.js
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
// oneko.js: https://github.com/adryd325/oneko.js
|
||||||
|
|
||||||
|
(function oneko() {
|
||||||
|
const isReducedMotion =
|
||||||
|
window.matchMedia(`(prefers-reduced-motion: reduce)`) === true ||
|
||||||
|
window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true;
|
||||||
|
|
||||||
|
if (isReducedMotion) return;
|
||||||
|
|
||||||
|
const nekoEl = document.createElement("div");
|
||||||
|
|
||||||
|
let nekoPosX = 32;
|
||||||
|
let nekoPosY = 32;
|
||||||
|
|
||||||
|
let destinationPosX = 0;
|
||||||
|
let destinationPosY = 0;
|
||||||
|
|
||||||
|
loadNekoLocation();
|
||||||
|
|
||||||
|
let frameCount = 0;
|
||||||
|
let idleTime = 0;
|
||||||
|
let idleAnimation = null;
|
||||||
|
let idleAnimationFrame = 0;
|
||||||
|
|
||||||
|
const nekoSpeed = 8;
|
||||||
|
const 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.cursor = "pointer";
|
||||||
|
nekoEl.style.imageRendering = "pixelated";
|
||||||
|
nekoEl.style.left = `${nekoPosX - 16}px`;
|
||||||
|
nekoEl.style.top = `${nekoPosY - 16}px`;
|
||||||
|
nekoEl.style.zIndex = Number.MAX_VALUE;
|
||||||
|
|
||||||
|
let nekoFile = "./assets/oneko.gif"
|
||||||
|
const curScript = document.currentScript
|
||||||
|
if (curScript && curScript.dataset.cat) {
|
||||||
|
nekoFile = curScript.dataset.cat
|
||||||
|
}
|
||||||
|
nekoEl.style.backgroundImage = `url(${nekoFile})`;
|
||||||
|
|
||||||
|
// Add click on nekoEl event
|
||||||
|
nekoEl.addEventListener("click", function () {
|
||||||
|
pickNewDestination();
|
||||||
|
idleTime = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(nekoEl);
|
||||||
|
|
||||||
|
// document.addEventListener("mousemove", function (event) {
|
||||||
|
// mousePosX = event.clientX;
|
||||||
|
// mousePosY = event.clientY;
|
||||||
|
// });
|
||||||
|
|
||||||
|
setSprite("alert", 0);
|
||||||
|
|
||||||
|
window.requestAnimationFrame(onAnimationFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
let 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) {
|
||||||
|
const sprite = spriteSets[name][frame % spriteSets[name].length];
|
||||||
|
nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetIdleAnimation() {
|
||||||
|
idleAnimation = null;
|
||||||
|
idleAnimationFrame = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickNewDestination() {
|
||||||
|
const margin = 50;
|
||||||
|
const angle = Math.random() * Math.PI * 2;
|
||||||
|
|
||||||
|
const distance = Math.random() * 850;
|
||||||
|
|
||||||
|
let newPosX = nekoPosX + Math.cos(angle) * distance;
|
||||||
|
let newPosY = nekoPosY + Math.sin(angle) * distance;
|
||||||
|
|
||||||
|
newPosX = Math.max(margin, Math.min(newPosX, window.innerWidth - margin));
|
||||||
|
newPosY = Math.max(margin, Math.min(newPosY, window.innerHeight - margin));
|
||||||
|
|
||||||
|
destinationPosX = newPosX;
|
||||||
|
destinationPosY = newPosY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function idle() {
|
||||||
|
idleTime += 1;
|
||||||
|
|
||||||
|
if(idleTime > 720) {
|
||||||
|
pickNewDestination();
|
||||||
|
idleTime = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
idleTime > 2 &&
|
||||||
|
Math.floor(Math.random() * 5) === 0 &&
|
||||||
|
idleAnimation == null
|
||||||
|
) {
|
||||||
|
let avaliableIdleAnimations = ["sleeping", "scratchSelf"];
|
||||||
|
if (nekoPosX < 32) {
|
||||||
|
avaliableIdleAnimations.push("scratchWallW");
|
||||||
|
}
|
||||||
|
if (nekoPosY < 32) {
|
||||||
|
avaliableIdleAnimations.push("scratchWallN");
|
||||||
|
}
|
||||||
|
if (nekoPosX > window.innerWidth - 32) {
|
||||||
|
avaliableIdleAnimations.push("scratchWallE");
|
||||||
|
}
|
||||||
|
if (nekoPosY > window.innerHeight - 32) {
|
||||||
|
avaliableIdleAnimations.push("scratchWallS");
|
||||||
|
}
|
||||||
|
idleAnimation =
|
||||||
|
avaliableIdleAnimations[
|
||||||
|
Math.floor(Math.random() * avaliableIdleAnimations.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 loadNekoLocation() {
|
||||||
|
const nekoLocation = localStorage.getItem("nekoLocation")
|
||||||
|
if (nekoLocation) {
|
||||||
|
const { x, y } = JSON.parse(nekoLocation)
|
||||||
|
nekoPosX = x
|
||||||
|
nekoPosY = y
|
||||||
|
|
||||||
|
destinationPosX = x
|
||||||
|
destinationPosY = y
|
||||||
|
} else {
|
||||||
|
pickNewDestination();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistNekoLocation() {
|
||||||
|
const nekoLocation = {
|
||||||
|
x: nekoPosX,
|
||||||
|
y: nekoPosY
|
||||||
|
}
|
||||||
|
localStorage.setItem("nekoLocation", JSON.stringify(nekoLocation))
|
||||||
|
}
|
||||||
|
|
||||||
|
function frame() {
|
||||||
|
frameCount += 1;
|
||||||
|
const diffX = nekoPosX - destinationPosX;
|
||||||
|
const diffY = nekoPosY - destinationPosY;
|
||||||
|
const distance = Math.sqrt(diffX ** 2 + diffY ** 2);
|
||||||
|
|
||||||
|
if (distance < nekoSpeed || distance < 48) {
|
||||||
|
idle();
|
||||||
|
persistNekoLocation();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
let 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 = `${nekoPosX - 16}px`;
|
||||||
|
nekoEl.style.top = `${nekoPosY - 16}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
})();
|
||||||
Loading…
Reference in New Issue
Block a user