From ae2ce608935993583304a0e1e6a301bf69ccc75b Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sun, 29 Jun 2025 02:52:42 +0900 Subject: [PATCH] static: replace error page Also add cat to error page. --- static/404.html | 15 ++--- static/js/oneko.js | 165 +++++++++++++++++++++++++++++++++++++++++++++ static/oneko.gif | Bin 0 -> 3316 bytes 3 files changed, 172 insertions(+), 8 deletions(-) create mode 100644 static/js/oneko.js create mode 100644 static/oneko.gif diff --git a/static/404.html b/static/404.html index c9cbca36..f3de5a77 100644 --- a/static/404.html +++ b/static/404.html @@ -2,22 +2,20 @@ - Page not found | GrapheneOS + Page not found | Hakurei - - - + - - + + @@ -25,7 +23,8 @@ [[css|/main.css]] - + + [[js|/js/oneko.js]] {% include "header.html" %} @@ -33,7 +32,7 @@

Page not found

The requested page does not exist. If you think this is a mistake, please - report an issue.

+ report an issue.

{% include "footer.html" %} diff --git a/static/js/oneko.js b/static/js/oneko.js new file mode 100644 index 00000000..2f01bbcf --- /dev/null +++ b/static/js/oneko.js @@ -0,0 +1,165 @@ +(function oneko() { + const nekoEl = document.createElement("div"); + let nekoPosX = 32; + let nekoPosY = 32; + let mousePosX = 0; + let mousePosY = 0; + let frameCount = 0; + let idleTime = 0; + let idleAnimation = null; + let idleAnimationFrame = 0; + const nekoSpeed = 10; + const spriteSets = { + idle: [[-3, -3]], + alert: [[-7, -3]], + scratch: [ + [-5, 0], + [-6, 0], + [-7, 0], + ], + 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 create() { + nekoEl.id = "oneko"; + nekoEl.style.width = "32px"; + nekoEl.style.height = "32px"; + nekoEl.style.position = "fixed"; + nekoEl.style.pointerEvents = "none"; + nekoEl.style.backgroundImage = "url('/oneko.gif')"; + nekoEl.style.imageRendering = "pixelated"; + nekoEl.style.left = "16px"; + nekoEl.style.top = "16px"; + + document.body.appendChild(nekoEl); + + document.onmousemove = (event) => { + mousePosX = event.clientX; + mousePosY = event.clientY; + }; + + window.onekoInterval = setInterval(frame, 100); + } + + 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 idle() { + idleTime += 1; + + // every ~ 20 seconds + if ( + idleTime > 10 && + Math.floor(Math.random() * 200) == 0 && + idleAnimation == null + ) { + idleAnimation = ["sleeping", "scratch"][ + Math.floor(Math.random() * 2) + ]; + } + + switch (idleAnimation) { + case "sleeping": + if (idleAnimationFrame < 8) { + setSprite("tired", 0); + break; + } + setSprite("sleeping", Math.floor(idleAnimationFrame / 4)); + if (idleAnimationFrame > 192) { + resetIdleAnimation(); + } + break; + case "scratch": + setSprite("scratch", idleAnimationFrame); + if (idleAnimationFrame > 9) { + resetIdleAnimation(); + } + break; + default: + setSprite("idle", 0); + return; + } + idleAnimationFrame += 1; + } + + function frame() { + frameCount += 1; + const diffX = nekoPosX - mousePosX; + const diffY = nekoPosY - mousePosY; + const distance = Math.sqrt(diffX ** 2 + 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; + } + + let 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; + + nekoEl.style.left = `${nekoPosX - 16}px`; + nekoEl.style.top = `${nekoPosY - 16}px`; + } + + create(); +})(); diff --git a/static/oneko.gif b/static/oneko.gif new file mode 100644 index 0000000000000000000000000000000000000000..a009c2cc19c96b001ac76e96f27e5fa1a9e56577 GIT binary patch literal 3316 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 F1^`&`O8o!; literal 0 HcmV?d00001