From 613632f3a8fccb998147e46d0e751ca4afc66544 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sun, 14 Dec 2025 16:39:17 -0800 Subject: Fixes --- Dockerfile | 2 +- esbuild.config.js | 60 +++++++ package-lock.json | 506 +++++++++++++++++++++++++++++++++++++---------------- package.json | 6 +- rolldown.config.js | 36 ---- src/js/oneko.ts | 277 ----------------------------- src/js/script.ts | 78 --------- src/ts/oneko.ts | 277 +++++++++++++++++++++++++++++ src/ts/script.ts | 78 +++++++++ 9 files changed, 777 insertions(+), 543 deletions(-) create mode 100644 esbuild.config.js delete mode 100644 rolldown.config.js delete mode 100644 src/js/oneko.ts delete mode 100644 src/js/script.ts create mode 100644 src/ts/oneko.ts create mode 100644 src/ts/script.ts diff --git a/Dockerfile b/Dockerfile index 18c3134..26c8c2a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ COPY package*.json ./ RUN npm ci COPY src/ ./src/ -COPY rolldown.config.js ./ +COPY esbuild.config.js ./ RUN npm run build FROM nginx:alpine as adelie diff --git a/esbuild.config.js b/esbuild.config.js new file mode 100644 index 0000000..1a7e09b --- /dev/null +++ b/esbuild.config.js @@ -0,0 +1,60 @@ +import esbuild from 'esbuild'; +import fs from 'fs-extra'; + +const production = process.env.NODE_ENV === 'production'; + +async function buildJS() { + await esbuild.build({ + entryPoints: ['src/ts/script.ts'], + bundle: true, + minify: production, + sourcemap: true, + target: 'es2020', + outfile: 'dist/bundle.js', + }); +} + +async function buildCSS() { + await esbuild.build({ + entryPoints: ['src/css/style.css'], + bundle: true, + minify: production, + sourcemap: true, + loader: { + '.css': 'css', + '.woff2': 'file', + '.png': 'file', + '.svg': 'file', + }, + outfile: 'dist/bundle.css', + }); +} + +async function copyAssets() { + await fs.copy('src/assets', 'dist', { + overwrite: true, + }); +} + +async function processHTML() { + let html = await fs.readFile('src/index.html', 'utf8'); + html = html.replace(/ASSET_BASE_PLACEHOLDER/g, ''); + await fs.writeFile('dist/index.html', html); +} + +async function clean() { + await fs.remove('dist'); + await fs.ensureDir('dist'); +} + +async function build() { + try { + await clean(); + await Promise.all([buildJS(), buildCSS(), copyAssets()]); + await processHTML(); + } catch (err) { + process.exit(1); + } +} + +build(); diff --git a/package-lock.json b/package-lock.json index 5791614..1c771dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,9 +13,9 @@ "devDependencies": { "@biomejs/biome": "^1.9.4", "@types/node": "^22.10.2", + "esbuild": "^0.25.5", "fs-extra": "^11.2.0", "http-server": "^14.1.1", - "rolldown": "^0.12.1", "typescript": "^5.9.3" } }, @@ -183,57 +183,78 @@ "node": ">=14.21.3" } }, - "node_modules/@emnapi/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", - "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" + "os": [ + "aix" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-0.12.2.tgz", - "integrity": "sha512-Y3Ajye63Z5KymGUwTLaK7Q6YMvycXqNiXtosecgVzjAwMITCmXdzgnWgzzx5UlWHMrDYL4m5zIeXGB5slLwoMA==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -242,12 +263,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-0.12.2.tgz", - "integrity": "sha512-ZcVuVFEFBXhp00TUNn+EDYs7SGGLQCznvCeuW1XkM8EC2/LfewU/o4WuJK7CBC4iCSktuFGpw7+zLe8D6iinzg==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -256,12 +280,32 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-0.12.2.tgz", - "integrity": "sha512-4fjuQHpm3q/Ly4fcqb8Qn49OQc2EQR2scUbQaOzXr7mIn9Zy8NfdRrsVG4/wpYvihIlTEtVx+ku0IZwcUzzZGg==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -270,12 +314,15 @@ "optional": true, "os": [ "freebsd" - ] + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.12.2.tgz", - "integrity": "sha512-MGEDaYLzTQ1kpvt13PzOwnd6O668S1mPM/vgi4O9vCfqJNTXZX8SeAg8Z2dQZbMSUyFDBVKGkz23GRTHqkGIsQ==", + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -284,12 +331,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.12.2.tgz", - "integrity": "sha512-8uiaMe39twyIAw0do1Gc3O2SpQmyL1A/BucFncEB8eU5jtb3BWIM/X+F+eKDU6c+XZ+S1T025dhR1cGg8y20Xw==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -298,40 +348,117 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.12.2.tgz", - "integrity": "sha512-GOFSKaMJueaSSODcqI+0Hu79buHYtGV7h2tydIDkSDD20mQuvwOF7jIVd27yNdlXWS9wLObwEu3BNgHmIj1M6A==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ - "arm64" + "ia32" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.12.2.tgz", - "integrity": "sha512-+hlRURUSiVP0+PFtjoTxUsiy/2NQpbf3DyUyMyl8Nv5+1BxjB6452VY1iFI+RzG4iLNJslcVcI6d6lJQ5zZYfw==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ - "x64" + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-0.12.2.tgz", - "integrity": "sha512-9WKIaQSZZ0i9F5msW4I5kEj6ov+TZLteuTqCzI7nYWDBDm7m/hYkOkdIBf41GC8iKFsgVIQO0kRVAT966U8RxQ==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], @@ -340,29 +467,49 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-0.12.2.tgz", - "integrity": "sha512-KY7bNrR3Jk6O0ne8LAaAnq9yI6xuKwhr/L2d1lBwrraCCyLHk0UEv4g6PfJkyUx6GfN0gVYuSxFgo9OggycldA==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", "cpu": [ - "wasm32" + "arm64" ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.4" - }, + "os": [ + "netbsd" + ], "engines": { - "node": ">=14.21.3" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.12.2.tgz", - "integrity": "sha512-XYBXifPk5iNcPUTyV/yB4tlj5nI+fYoe/8CLHjyUG8GS0l8rCD+jKaAv3Jvz2T2erlkjEPqJXmzhUfBscOUo6g==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", "cpu": [ "arm64" ], @@ -370,46 +517,112 @@ "license": "MIT", "optional": true, "os": [ - "win32" - ] + "openbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rolldown/binding-win32-ia32-msvc": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.12.2.tgz", - "integrity": "sha512-SV9LqvEc0d4gCLbkezH+UJ2uj2pTw3mwSKhFJEm6ir1lb05W6y9++m67NRs17fhudkFAz8iKlyPxKtZBKzAKOw==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ - "ia32" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" - ] + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.12.2.tgz", - "integrity": "sha512-XnRqHx182tyk1M12OMXqLajIj9DZrOUEKSPYrQUSaMCmHHAhULYP6ki240CV16PBWZW4Q1pwQ7YVKYFevRtvKg==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, "node_modules/@types/node": { @@ -602,6 +815,48 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -951,33 +1206,6 @@ "dev": true, "license": "MIT" }, - "node_modules/rolldown": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-0.12.2.tgz", - "integrity": "sha512-YJYKiYt2O9XytiQ3Na4Kk29avfIXhvK7udB3wAaVaF4kiSsFKE1167tElO/0eD6tjfJXCvwNxwsyYkBJRtsLmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "zod": "^3.23.8" - }, - "bin": { - "rolldown": "bin/cli.js" - }, - "optionalDependencies": { - "@rolldown/binding-darwin-arm64": "0.12.2", - "@rolldown/binding-darwin-x64": "0.12.2", - "@rolldown/binding-freebsd-x64": "0.12.2", - "@rolldown/binding-linux-arm-gnueabihf": "0.12.2", - "@rolldown/binding-linux-arm64-gnu": "0.12.2", - "@rolldown/binding-linux-arm64-musl": "0.12.2", - "@rolldown/binding-linux-x64-gnu": "0.12.2", - "@rolldown/binding-linux-x64-musl": "0.12.2", - "@rolldown/binding-wasm32-wasi": "0.12.2", - "@rolldown/binding-win32-arm64-msvc": "0.12.2", - "@rolldown/binding-win32-ia32-msvc": "0.12.2", - "@rolldown/binding-win32-x64-msvc": "0.12.2" - } - }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -1088,14 +1316,6 @@ "node": ">=8" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -1158,16 +1378,6 @@ "engines": { "node": ">=12" } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } } } diff --git a/package.json b/package.json index e8f5dac..08a6855 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "type": "module", "private": true, "scripts": { - "build": "NODE_ENV=production node rolldown.config.js", - "dev": "node rolldown.config.js", + "build": "NODE_ENV=production node esbuild.config.js", + "dev": "node esbuild.config.js", "clean": "rm -rf dist", "serve": "npx http-server dist -p 8080 -o", "dev:serve": "npm run dev && npm run serve", @@ -20,9 +20,9 @@ "devDependencies": { "@biomejs/biome": "^1.9.4", "@types/node": "^22.10.2", + "esbuild": "^0.25.5", "fs-extra": "^11.2.0", "http-server": "^14.1.1", - "rolldown": "^0.12.1", "typescript": "^5.9.3" } } diff --git a/rolldown.config.js b/rolldown.config.js deleted file mode 100644 index 6cc7765..0000000 --- a/rolldown.config.js +++ /dev/null @@ -1,36 +0,0 @@ -import { rolldown } from 'rolldown'; -import fs from 'fs-extra'; -import path from 'path'; - -const production = process.env.NODE_ENV === 'production'; - -async function build() { - try { - await fs.remove('dist'); - await fs.ensureDir('dist'); - - const jsBundle = await rolldown({ - input: 'src/js/script.ts', - output: { - file: 'dist/bundle.js', - format: 'es', - sourcemap: !production, - }, - }); - - await jsBundle.write(); - - await fs.copy('src/css/style.css', 'dist/bundle.css'); - await fs.copy('src/assets', 'dist', { - overwrite: true, - }); - - let html = await fs.readFile('src/index.html', 'utf8'); - await fs.writeFile('dist/index.html', html); - } catch (err) { - console.error('Build failed:', err); - process.exit(1); - } -} - -build(); diff --git a/src/js/oneko.ts b/src/js/oneko.ts deleted file mode 100644 index 236c7cb..0000000 --- a/src/js/oneko.ts +++ /dev/null @@ -1,277 +0,0 @@ -// oneko.js: https://github.com/adryd325/oneko.js - -export function initOneko(): void { - 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 persistPosition = true; - - let nekoPosX = 32; - let nekoPosY = 32; - - let mousePosX = 0; - let mousePosY = 0; - - let frameCount = 0; - let idleTime = 0; - let idleAnimation: string | null = null; - let idleAnimationFrame = 0; - - const nekoSpeed = 10; - const spriteSets: 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], - ], - }; - - function init(): void { - const assetBase = window.ASSET_BASE || ''; - let nekoFile = `${assetBase}/oneko/oneko.gif`; - const curScript = document.currentScript as HTMLScriptElement; - if (curScript?.dataset.cat) { - nekoFile = curScript.dataset.cat; - } - if (curScript?.dataset.persistPosition) { - if (curScript.dataset.persistPosition === '') { - persistPosition = true; - } else { - persistPosition = JSON.parse(curScript.dataset.persistPosition.toLowerCase()); - } - } - - if (persistPosition) { - const storedNekoStr = window.localStorage.getItem('oneko'); - const storedNeko = storedNekoStr ? JSON.parse(storedNekoStr) : null; - if (storedNeko !== null) { - nekoPosX = storedNeko.nekoPosX; - nekoPosY = storedNeko.nekoPosY; - mousePosX = storedNeko.mousePosX; - mousePosY = storedNeko.mousePosY; - frameCount = storedNeko.frameCount; - idleTime = storedNeko.idleTime; - idleAnimation = storedNeko.idleAnimation; - idleAnimationFrame = storedNeko.idleAnimationFrame; - nekoEl.style.backgroundPosition = storedNeko.bgPos; - } - } - - 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 = '2147483647'; - - nekoEl.style.backgroundImage = `url(${nekoFile})`; - - document.body.appendChild(nekoEl); - - document.addEventListener('mousemove', (event: MouseEvent) => { - mousePosX = event.clientX; - mousePosY = event.clientY; - }); - - if (persistPosition) { - window.addEventListener('beforeunload', () => { - window.localStorage.setItem( - 'oneko', - JSON.stringify({ - nekoPosX: nekoPosX, - nekoPosY: nekoPosY, - mousePosX: mousePosX, - mousePosY: mousePosY, - frameCount: frameCount, - idleTime: idleTime, - idleAnimation: idleAnimation, - idleAnimationFrame: idleAnimationFrame, - bgPos: nekoEl.style.backgroundPosition, - }) - ); - }); - } - - window.requestAnimationFrame(onAnimationFrame); - } - - let lastFrameTimestamp: number | undefined; - - function onAnimationFrame(timestamp: number): void { - if (!nekoEl.isConnected) { - return; - } - if (!lastFrameTimestamp) { - lastFrameTimestamp = timestamp; - } - if (lastFrameTimestamp && timestamp - lastFrameTimestamp > 100) { - lastFrameTimestamp = timestamp; - frame(); - } - window.requestAnimationFrame(onAnimationFrame); - } - - function setSprite(name: string, frame: number): void { - const sprite = spriteSets[name][frame % spriteSets[name].length]; - nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`; - } - - function resetIdleAnimation(): void { - idleAnimation = null; - idleAnimationFrame = 0; - } - - function idle(): void { - idleTime += 1; - - // every ~ 20 seconds - if (idleTime > 10 && Math.floor(Math.random() * 200) === 0 && idleAnimation == null) { - const avalibleIdleAnimations: string[] = ['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(): void { - 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(); -} diff --git a/src/js/script.ts b/src/js/script.ts deleted file mode 100644 index e0b0b85..0000000 --- a/src/js/script.ts +++ /dev/null @@ -1,78 +0,0 @@ -import Prism from 'prismjs'; -import 'prismjs/components/prism-javascript'; -import 'prismjs/components/prism-css'; -import 'prismjs/components/prism-markup'; -import { initOneko } from './oneko'; - -(() => { - const toggleButton = document.getElementById('theme-toggle') as HTMLInputElement; - const html = document.documentElement; - - const sessionTheme = sessionStorage.getItem('theme'); - const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - - const initialTheme = sessionTheme || (systemPrefersDark ? 'dark' : 'light'); - - if (initialTheme === 'dark') { - html.setAttribute('data-theme', 'dark'); - toggleButton.checked = true; - } - - toggleButton.addEventListener('change', () => { - const theme = html.getAttribute('data-theme'); - - if (theme === 'dark') { - html.removeAttribute('data-theme'); - sessionStorage.setItem('theme', 'light'); - toggleButton.checked = false; - } else { - html.setAttribute('data-theme', 'dark'); - sessionStorage.setItem('theme', 'dark'); - toggleButton.checked = true; - } - }); -})(); - -(() => { - const colors = ['#ff69b4', '#b19cd9', '#8b6f47', '#ff85c0', '#c4b5fd', '#d4a574']; - const shapes = ['❀', '✿', '✽', '✾', '✻', '❊', '❋', '✼']; - - document.addEventListener('mousemove', (e: MouseEvent) => { - createParticle(e.clientX, e.clientY); - }); - - const createParticle = (x: number, y: number) => { - const particle = document.createElement('div'); - particle.className = 'fairy-dust'; - - const shape = shapes[Math.floor(Math.random() * shapes.length)]; - const size = Math.random() * 8 + 6; - const color = colors[Math.floor(Math.random() * colors.length)]; - const offsetX = (Math.random() - 0.5) * 20; - const offsetY = (Math.random() - 0.5) * 20; - const rotation = Math.random() * 360; - - particle.textContent = shape; - particle.style.cssText = ` - position: fixed; - left: ${x + offsetX}px; - top: ${y + offsetY}px; - font-size: ${size}px; - color: ${color}; - opacity: 0.4; - pointer-events: none; - z-index: 9001; /* it's over 9000 */ - line-height: 1; - transform: rotate(${rotation}deg); - animation: fairy-float 0.8s ease-out forwards; - `; - - document.body.appendChild(particle); - setTimeout(() => particle.remove(), 800); - }; -})(); - -document.addEventListener('DOMContentLoaded', () => { - Prism.highlightAll(); - initOneko(); -}); diff --git a/src/ts/oneko.ts b/src/ts/oneko.ts new file mode 100644 index 0000000..236c7cb --- /dev/null +++ b/src/ts/oneko.ts @@ -0,0 +1,277 @@ +// oneko.js: https://github.com/adryd325/oneko.js + +export function initOneko(): void { + 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 persistPosition = true; + + let nekoPosX = 32; + let nekoPosY = 32; + + let mousePosX = 0; + let mousePosY = 0; + + let frameCount = 0; + let idleTime = 0; + let idleAnimation: string | null = null; + let idleAnimationFrame = 0; + + const nekoSpeed = 10; + const spriteSets: 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], + ], + }; + + function init(): void { + const assetBase = window.ASSET_BASE || ''; + let nekoFile = `${assetBase}/oneko/oneko.gif`; + const curScript = document.currentScript as HTMLScriptElement; + if (curScript?.dataset.cat) { + nekoFile = curScript.dataset.cat; + } + if (curScript?.dataset.persistPosition) { + if (curScript.dataset.persistPosition === '') { + persistPosition = true; + } else { + persistPosition = JSON.parse(curScript.dataset.persistPosition.toLowerCase()); + } + } + + if (persistPosition) { + const storedNekoStr = window.localStorage.getItem('oneko'); + const storedNeko = storedNekoStr ? JSON.parse(storedNekoStr) : null; + if (storedNeko !== null) { + nekoPosX = storedNeko.nekoPosX; + nekoPosY = storedNeko.nekoPosY; + mousePosX = storedNeko.mousePosX; + mousePosY = storedNeko.mousePosY; + frameCount = storedNeko.frameCount; + idleTime = storedNeko.idleTime; + idleAnimation = storedNeko.idleAnimation; + idleAnimationFrame = storedNeko.idleAnimationFrame; + nekoEl.style.backgroundPosition = storedNeko.bgPos; + } + } + + 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 = '2147483647'; + + nekoEl.style.backgroundImage = `url(${nekoFile})`; + + document.body.appendChild(nekoEl); + + document.addEventListener('mousemove', (event: MouseEvent) => { + mousePosX = event.clientX; + mousePosY = event.clientY; + }); + + if (persistPosition) { + window.addEventListener('beforeunload', () => { + window.localStorage.setItem( + 'oneko', + JSON.stringify({ + nekoPosX: nekoPosX, + nekoPosY: nekoPosY, + mousePosX: mousePosX, + mousePosY: mousePosY, + frameCount: frameCount, + idleTime: idleTime, + idleAnimation: idleAnimation, + idleAnimationFrame: idleAnimationFrame, + bgPos: nekoEl.style.backgroundPosition, + }) + ); + }); + } + + window.requestAnimationFrame(onAnimationFrame); + } + + let lastFrameTimestamp: number | undefined; + + function onAnimationFrame(timestamp: number): void { + if (!nekoEl.isConnected) { + return; + } + if (!lastFrameTimestamp) { + lastFrameTimestamp = timestamp; + } + if (lastFrameTimestamp && timestamp - lastFrameTimestamp > 100) { + lastFrameTimestamp = timestamp; + frame(); + } + window.requestAnimationFrame(onAnimationFrame); + } + + function setSprite(name: string, frame: number): void { + const sprite = spriteSets[name][frame % spriteSets[name].length]; + nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`; + } + + function resetIdleAnimation(): void { + idleAnimation = null; + idleAnimationFrame = 0; + } + + function idle(): void { + idleTime += 1; + + // every ~ 20 seconds + if (idleTime > 10 && Math.floor(Math.random() * 200) === 0 && idleAnimation == null) { + const avalibleIdleAnimations: string[] = ['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(): void { + 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(); +} diff --git a/src/ts/script.ts b/src/ts/script.ts new file mode 100644 index 0000000..e0b0b85 --- /dev/null +++ b/src/ts/script.ts @@ -0,0 +1,78 @@ +import Prism from 'prismjs'; +import 'prismjs/components/prism-javascript'; +import 'prismjs/components/prism-css'; +import 'prismjs/components/prism-markup'; +import { initOneko } from './oneko'; + +(() => { + const toggleButton = document.getElementById('theme-toggle') as HTMLInputElement; + const html = document.documentElement; + + const sessionTheme = sessionStorage.getItem('theme'); + const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + + const initialTheme = sessionTheme || (systemPrefersDark ? 'dark' : 'light'); + + if (initialTheme === 'dark') { + html.setAttribute('data-theme', 'dark'); + toggleButton.checked = true; + } + + toggleButton.addEventListener('change', () => { + const theme = html.getAttribute('data-theme'); + + if (theme === 'dark') { + html.removeAttribute('data-theme'); + sessionStorage.setItem('theme', 'light'); + toggleButton.checked = false; + } else { + html.setAttribute('data-theme', 'dark'); + sessionStorage.setItem('theme', 'dark'); + toggleButton.checked = true; + } + }); +})(); + +(() => { + const colors = ['#ff69b4', '#b19cd9', '#8b6f47', '#ff85c0', '#c4b5fd', '#d4a574']; + const shapes = ['❀', '✿', '✽', '✾', '✻', '❊', '❋', '✼']; + + document.addEventListener('mousemove', (e: MouseEvent) => { + createParticle(e.clientX, e.clientY); + }); + + const createParticle = (x: number, y: number) => { + const particle = document.createElement('div'); + particle.className = 'fairy-dust'; + + const shape = shapes[Math.floor(Math.random() * shapes.length)]; + const size = Math.random() * 8 + 6; + const color = colors[Math.floor(Math.random() * colors.length)]; + const offsetX = (Math.random() - 0.5) * 20; + const offsetY = (Math.random() - 0.5) * 20; + const rotation = Math.random() * 360; + + particle.textContent = shape; + particle.style.cssText = ` + position: fixed; + left: ${x + offsetX}px; + top: ${y + offsetY}px; + font-size: ${size}px; + color: ${color}; + opacity: 0.4; + pointer-events: none; + z-index: 9001; /* it's over 9000 */ + line-height: 1; + transform: rotate(${rotation}deg); + animation: fairy-float 0.8s ease-out forwards; + `; + + document.body.appendChild(particle); + setTimeout(() => particle.remove(), 800); + }; +})(); + +document.addEventListener('DOMContentLoaded', () => { + Prism.highlightAll(); + initOneko(); +}); -- cgit v1.2.3-70-g09d2