From 260bb91fd688e7ed06caba1247b1b5d9c7d25ac8 Mon Sep 17 00:00:00 2001
From: sophie
Date: Wed, 24 Jul 2024 19:21:07 +0300
Subject: [PATCH] oneko.js TSified
---
website/assets/oneko.webp | Bin 0 -> 2894 bytes
website/assets/style.css | 3 +
website/index.html | 5 +-
website/scripts/oneko.ts | 243 ++++++++++++++++++++++++++++++++++++
website/templates/head.html | 2 +-
5 files changed, 250 insertions(+), 3 deletions(-)
create mode 100644 website/assets/oneko.webp
create mode 100644 website/scripts/oneko.ts
diff --git a/website/assets/oneko.webp b/website/assets/oneko.webp
new file mode 100644
index 0000000000000000000000000000000000000000..7724bd481129246cbbce3ac2553c8d944300e1e1
GIT binary patch
literal 2894
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
☹️☹️☹️.ovh
I'm Latvian, 17. My name's Sophie meows a lot. I love listening to music.
-
+
I love to play games with peeps! My favorite games recently have been Minecraft and Stardew Valley! DM me
- if you wanna play with me ^w^
+ if you wanna play with me ^w^
+
DNI:
diff --git a/website/scripts/oneko.ts b/website/scripts/oneko.ts
new file mode 100644
index 0000000..3883950
--- /dev/null
+++ b/website/scripts/oneko.ts
@@ -0,0 +1,243 @@
+class Neko {
+ element: HTMLDivElement;
+
+ readonly nekoSpeed = 10;
+ readonly spriteSheets: Record = {
+ 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],
+ ],
+ };
+ private nekoPosX = 32;
+ private nekoPosY = 32;
+
+ private mousePosX = 0;
+ private mousePosY = 0;
+
+ private frameCount = 0;
+ private idleTime = 0;
+ private idleAnimation?: string;
+ private idleAnimationFrame = 0;
+ private lastFrameTimestamp?: number;
+ private file: string;
+
+ constructor(file: string = "assets/oneko.webp") {
+ this.file = file;
+
+ this.element = document.createElement("div");
+
+ this.element.id = "oneko";
+ this.element.ariaHidden = "true";
+ this.element.style.width = "32px";
+ this.element.style.height = "32px";
+ this.element.style.position = "fixed";
+ this.element.style.pointerEvents = "none";
+ this.element.style.imageRendering = "pixelated";
+ this.element.style.left = `${this.nekoPosX - 16}px`;
+ this.element.style.top = `${this.nekoPosY - 16}px`;
+ this.element.style.zIndex = "2147483647";
+
+ this.element.style.backgroundImage = `url(${this.file})`;
+ let element = document.body;
+
+ if (!element) {
+ throw new Error("no body exists");
+ }
+
+ element.appendChild(this.element);
+ document.addEventListener("mousemove", (event) => {
+ this.mousePosX = event.clientX;
+ this.mousePosY = event.clientY;
+ });
+
+ window.requestAnimationFrame((t) => this.onAnimationFrame(t));
+ }
+ private onAnimationFrame(timestamp: number) {
+ // Stops execution if the neko element is removed from DOM
+ if (!this.element.isConnected) {
+ return;
+ }
+ if (!this.lastFrameTimestamp) {
+ this.lastFrameTimestamp = timestamp;
+ }
+ if (timestamp - this.lastFrameTimestamp > 100) {
+ this.lastFrameTimestamp = timestamp;
+ this.frame();
+ }
+ window.requestAnimationFrame((t) => this.onAnimationFrame(t));
+ }
+
+ setSprite(name: string, frame: number) {
+ const sprite =
+ this.spriteSheets[name][frame % this.spriteSheets[name].length];
+ this.element.style.backgroundPosition = `${sprite[0] * 32}px ${
+ sprite[1] * 32
+ }px`;
+ }
+
+ resetidleAnimation() {
+ this.idleAnimation = undefined;
+ this.idleAnimationFrame = 0;
+ }
+
+ idle() {
+ this.idleTime += 1;
+
+ // every ~ 20 seconds
+ if (
+ this.idleTime > 10 &&
+ Math.floor(Math.random() * 200) == 0 &&
+ this.idleAnimation == null
+ ) {
+ let availableidleAnimations = ["sleeping", "scratchSelf"];
+ if (this.nekoPosX < 32) {
+ availableidleAnimations.push("scratchWallW");
+ }
+ if (this.nekoPosY < 32) {
+ availableidleAnimations.push("scratchWallN");
+ }
+ if (this.nekoPosX > window.innerWidth - 32) {
+ availableidleAnimations.push("scratchWallE");
+ }
+ if (this.nekoPosY > window.innerHeight - 32) {
+ availableidleAnimations.push("scratchWallS");
+ }
+ this.idleAnimation =
+ availableidleAnimations[
+ Math.floor(Math.random() * availableidleAnimations.length)
+ ];
+ }
+
+ switch (this.idleAnimation) {
+ case "sleeping":
+ if (this.idleAnimationFrame < 8) {
+ this.setSprite("tired", 0);
+ break;
+ }
+ this.setSprite("sleeping", Math.floor(this.idleAnimationFrame / 4));
+ if (this.idleAnimationFrame > 192) {
+ this.resetidleAnimation();
+ }
+ break;
+ case "scratchWallN":
+ case "scratchWallS":
+ case "scratchWallE":
+ case "scratchWallW":
+ case "scratchSelf":
+ this.setSprite(this.idleAnimation, this.idleAnimationFrame);
+ if (this.idleAnimationFrame > 9) {
+ this.resetidleAnimation();
+ }
+ break;
+ default:
+ this.setSprite("idle", 0);
+ return;
+ }
+ this.idleAnimationFrame += 1;
+ }
+
+ frame() {
+ this.frameCount += 1;
+ const diffX = this.nekoPosX - this.mousePosX;
+ const diffY = this.nekoPosY - this.mousePosY;
+ const distance = Math.sqrt(diffX ** 2 + diffY ** 2);
+
+ if (distance < this.nekoSpeed || distance < 48) {
+ this.idle();
+ return;
+ }
+
+ this.idleAnimation = undefined;
+ this.idleAnimationFrame = 0;
+
+ if (this.idleTime > 1) {
+ this.setSprite("alert", 0);
+ // count down after being alerted before moving
+ this.idleTime = Math.min(this.idleTime, 7);
+ this.idleTime -= 1;
+ return;
+ }
+
+ let direction: string;
+ direction = diffY / distance > 0.5 ? "N" : "";
+ direction += diffY / distance < -0.5 ? "S" : "";
+ direction += diffX / distance > 0.5 ? "W" : "";
+ direction += diffX / distance < -0.5 ? "E" : "";
+ this.setSprite(direction, this.frameCount);
+
+ this.nekoPosX -= (diffX / distance) * this.nekoSpeed;
+ this.nekoPosY -= (diffY / distance) * this.nekoSpeed;
+
+ this.nekoPosX = Math.min(
+ Math.max(16, this.nekoPosX),
+ window.innerWidth - 16
+ );
+ this.nekoPosY = Math.min(
+ Math.max(16, this.nekoPosY),
+ window.innerHeight - 16
+ );
+
+ this.element.style.left = `${this.nekoPosX - 16}px`;
+ this.element.style.top = `${this.nekoPosY - 16}px`;
+ }
+}
+
+window.addEventListener("load", () => {
+ new Neko("/assets/oneko.webp");
+});
diff --git a/website/templates/head.html b/website/templates/head.html
index 71b262d..6c57871 100644
--- a/website/templates/head.html
+++ b/website/templates/head.html
@@ -23,4 +23,4 @@
-
\ No newline at end of file
+
\ No newline at end of file