restructured everything
130
.gitignore
vendored
Normal file
|
@ -0,0 +1,130 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
60
eslint.config.mjs
Normal file
|
@ -0,0 +1,60 @@
|
|||
import pluginJs from "@eslint/js";
|
||||
import stylisticJs from "@stylistic/eslint-plugin-js";
|
||||
import globals from "globals";
|
||||
|
||||
export default [
|
||||
{files: ["**/*.{js,mjs,cjs,ts}"],},
|
||||
{languageOptions: { globals: globals.node }},
|
||||
pluginJs.configs.recommended,
|
||||
{
|
||||
ignores: ["dist/*", "**/jquery.js"]
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
"@stylistic/js": stylisticJs,
|
||||
},
|
||||
rules: {
|
||||
"yoda": "error",
|
||||
"eqeqeq": ["error", "always", { "null": "ignore" }],
|
||||
"prefer-destructuring": ["error", {
|
||||
"VariableDeclarator": { "array": false, "object": true },
|
||||
"AssignmentExpression": { "array": false, "object": false }
|
||||
}],
|
||||
"operator-assignment": ["error", "always"],
|
||||
"no-useless-computed-key": "error",
|
||||
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
|
||||
"no-invalid-regexp": "error",
|
||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||
"no-duplicate-imports": "error",
|
||||
"dot-notation": "error",
|
||||
"no-fallthrough": "error",
|
||||
"for-direction": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"no-cond-assign": "error",
|
||||
"no-dupe-else-if": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-loss-of-precision": "error",
|
||||
"no-misleading-character-class": "error",
|
||||
"no-prototype-builtins": "error",
|
||||
"no-regex-spaces": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-unsafe-optional-chaining": "error",
|
||||
"no-useless-backreference": "error",
|
||||
"use-isnan": "error",
|
||||
"prefer-const": "error",
|
||||
"prefer-spread": "error",
|
||||
"semi": [2, "always"],
|
||||
"@stylistic/js/indent": ["error", 4],
|
||||
"@stylistic/js/quotes": [2, "double", { "avoidEscape": true }]
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["public/**/*.js"],
|
||||
rules: {
|
||||
"no-undef": "off", // due to being separate files eslint goes insane
|
||||
"@typescript-eslint/no-unused-vars": ["off"] // noone cares lol
|
||||
}
|
||||
}
|
||||
];
|
65
index.js
|
@ -1,65 +0,0 @@
|
|||
let shouldLog = false;
|
||||
function log(content) {
|
||||
if (shouldLog) console.log(content);
|
||||
}
|
||||
function onUpdate(data) {
|
||||
// set status
|
||||
log(data)
|
||||
const pfp = document.getElementById("pfp");
|
||||
switch(data["discord_status"]) {
|
||||
case "online":
|
||||
pfp.style.borderColor = "var(--online-color)";
|
||||
break;
|
||||
case "idle":
|
||||
pfp.style.borderColor = "var(--idle-color)";
|
||||
break;
|
||||
case "dnd":
|
||||
pfp.style.borderColor = "var(--dnd-color)";
|
||||
break;
|
||||
case "offline":
|
||||
pfp.style.borderColor = "var(--offline-color)";
|
||||
break;
|
||||
}
|
||||
// set presence
|
||||
log(data["activities"])
|
||||
let listening = false
|
||||
let content = ""
|
||||
data["activities"].forEach(presence => {
|
||||
if(presence["application_id"] == "463151177836658699" && presence["assets"]["small_text"] != "Paused") { // premid
|
||||
listening = true
|
||||
artist = presence["state"].substring(0, presence["state"].indexOf(" -"))
|
||||
if (artist == "") {
|
||||
artist = presence["state"]
|
||||
}
|
||||
content = `Listening to ${presence["details"]} - ${artist}`
|
||||
}
|
||||
if(presence["application_id"] == "1108588077900898414") { // vencord lastfm
|
||||
listening = true
|
||||
content = `Listening to ${presence["details"]} - ${presence["state"]}`
|
||||
}
|
||||
if(presence["application_id"] == "1054951789318909972") { // vendetta lastfm
|
||||
listening = true
|
||||
content = `Listening to ${presence["details"]} - ${presence["state"]}`
|
||||
}
|
||||
if(presence["id"] == "spotify:1") { // built in spotify hooluy shit normal presence)
|
||||
listening = true
|
||||
content = `Listening to ${presence["details"]} - ${presence["state"]}`
|
||||
}
|
||||
if(presence["type"] == 0 && presence["application_id"] != "463151177836658699" && presence["application_id"] != "1108588077900898414") { // generic playing status that isn't vencord lastfm or premid
|
||||
listening = false
|
||||
content = `Playing ${presence["name"]}`
|
||||
}
|
||||
|
||||
});
|
||||
document.getElementById("presence").style.display = content == "" ? "none": "flex"
|
||||
document.getElementById("presence-content").innerText = content
|
||||
document.getElementById("presence-icon").src = listening ? "music.ico" : "game.ico"
|
||||
}
|
||||
LanyardWrapper.connectWebSocket("886685857560539176", onUpdate)
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
function showCredits() {
|
||||
document.getElementById("credits").style.display = "block"
|
||||
document.getElementById("credits-button").style.display = "none"
|
||||
}
|
239
oneko.js
|
@ -1,239 +0,0 @@
|
|||
// 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 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]],
|
||||
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 = `${nekoPosX - 16}px`;
|
||||
nekoEl.style.top = `${nekoPosY - 16}px`;
|
||||
nekoEl.style.zIndex = Number.MAX_VALUE;
|
||||
|
||||
let nekoFile = "./oneko.gif"
|
||||
const curScript = document.currentScript
|
||||
if (curScript && curScript.dataset.cat) {
|
||||
nekoFile = curScript.dataset.cat
|
||||
}
|
||||
nekoEl.style.backgroundImage = `url(${nekoFile})`;
|
||||
|
||||
document.body.appendChild(nekoEl);
|
||||
|
||||
document.addEventListener("mousemove", function (event) {
|
||||
mousePosX = event.clientX;
|
||||
mousePosY = event.clientY;
|
||||
});
|
||||
|
||||
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 idle() {
|
||||
idleTime += 1;
|
||||
|
||||
// every ~ 20 seconds
|
||||
if (
|
||||
idleTime > 10 &&
|
||||
Math.floor(Math.random() * 200) == 0 &&
|
||||
idleAnimation == null
|
||||
) {
|
||||
let 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;
|
||||
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;
|
||||
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();
|
||||
})();
|
22
package.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "website",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "nin0dev",
|
||||
"license": "All Rights Reserved",
|
||||
"dependencies": {
|
||||
"@stylistic/eslint-plugin-js": "^2.4.0",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"express": "^4.19.2",
|
||||
"nodemon": "^3.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.8.0",
|
||||
"eslint": "9.x",
|
||||
"globals": "^15.8.0"
|
||||
}
|
||||
}
|
1455
pnpm-lock.yaml
Normal file
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
66
public/js/index.js
Normal file
|
@ -0,0 +1,66 @@
|
|||
const shouldLog = false;
|
||||
function log(content) {
|
||||
if (shouldLog) console.log(content);
|
||||
}
|
||||
function onUpdate(data) {
|
||||
// set status
|
||||
log(data);
|
||||
const pfp = document.getElementById("pfp");
|
||||
switch(data.discord_status) {
|
||||
case "online":
|
||||
pfp.style.borderColor = "var(--online-color)";
|
||||
break;
|
||||
case "idle":
|
||||
pfp.style.borderColor = "var(--idle-color)";
|
||||
break;
|
||||
case "dnd":
|
||||
pfp.style.borderColor = "var(--dnd-color)";
|
||||
break;
|
||||
case "offline":
|
||||
pfp.style.borderColor = "var(--offline-color)";
|
||||
break;
|
||||
}
|
||||
// set presence
|
||||
log(data.activities);
|
||||
let listening = false;
|
||||
let content = "";
|
||||
data.activities.forEach(presence => {
|
||||
if(presence.application_id === "463151177836658699" && presence.assets.small_text != "Paused") { // premid
|
||||
listening = true;
|
||||
artist = presence.state.substring(0, presence.state.indexOf(" -"));
|
||||
if (artist === "") {
|
||||
artist = presence.state;
|
||||
}
|
||||
content = `Listening to ${presence.details} - ${artist}`;
|
||||
}
|
||||
if(presence.application_id === "1108588077900898414") { // vencord lastfm
|
||||
listening = true;
|
||||
content = `Listening to ${presence.details} - ${presence.state}`;
|
||||
}
|
||||
if(presence.application_id === "1054951789318909972") { // vendetta lastfm
|
||||
listening = true;
|
||||
content = `Listening to ${presence.details} - ${presence.state}`;
|
||||
}
|
||||
if(presence.id === "spotify:1") { // built in spotify hooluy shit normal presence)
|
||||
listening = true;
|
||||
content = `Listening to ${presence.details} - ${presence.state}`;
|
||||
}
|
||||
if(presence.type === 0 && presence.application_id !== "463151177836658699" && presence.application_id !== "1108588077900898414") { // generic playing status that isn't vencord lastfm or premid
|
||||
listening = false;
|
||||
content = `Playing ${presence.name}`;
|
||||
}
|
||||
|
||||
});
|
||||
document.getElementById("presence").style.display = content === "" ? "none": "flex";
|
||||
document.getElementById("presence-content").innerText = content;
|
||||
document.getElementById("presence-icon").src = listening ? "music.ico" : "game.ico";
|
||||
}
|
||||
LanyardWrapper.connectWebSocket("886685857560539176", onUpdate)
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function showCredits() {
|
||||
document.getElementById("credits").style.display = "block";
|
||||
document.getElementById("credits-button").style.display = "none";
|
||||
}
|
239
public/js/oneko.js
Normal file
|
@ -0,0 +1,239 @@
|
|||
// 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 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]],
|
||||
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 = `${nekoPosX - 16}px`;
|
||||
nekoEl.style.top = `${nekoPosY - 16}px`;
|
||||
nekoEl.style.zIndex = Number.MAX_VALUE;
|
||||
|
||||
let nekoFile = "./oneko.gif";
|
||||
const curScript = document.currentScript;
|
||||
if (curScript && curScript.dataset.cat) {
|
||||
nekoFile = curScript.dataset.cat;
|
||||
}
|
||||
nekoEl.style.backgroundImage = `url(${nekoFile})`;
|
||||
|
||||
document.body.appendChild(nekoEl);
|
||||
|
||||
document.addEventListener("mousemove", function (event) {
|
||||
mousePosX = event.clientX;
|
||||
mousePosY = event.clientY;
|
||||
});
|
||||
|
||||
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 idle() {
|
||||
idleTime += 1;
|
||||
|
||||
// every ~ 20 seconds
|
||||
if (
|
||||
idleTime > 10 &&
|
||||
Math.floor(Math.random() * 200) === 0 &&
|
||||
idleAnimation == null
|
||||
) {
|
||||
const 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;
|
||||
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;
|
||||
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();
|
||||
})();
|
11
server.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
var express = require("express");
|
||||
var app = express();
|
||||
|
||||
app.set("view engine", "ejs");
|
||||
|
||||
app.get("/", function(req, res) {
|
||||
res.render("pages/index");
|
||||
});
|
||||
|
||||
app.listen(8080);
|
||||
console.log("Server is listening on port 8080");
|
BIN
wallpaper.jpg
Before Width: | Height: | Size: 32 KiB |