diff --git a/src/routes/(main)/+layout.svelte b/src/routes/(main)/+layout.svelte
index 1de711e..522245b 100644
--- a/src/routes/(main)/+layout.svelte
+++ b/src/routes/(main)/+layout.svelte
@@ -4,6 +4,7 @@
import type { LayoutData } from '../$types'
import { browser } from '$app/environment'
import { writable } from 'svelte/store'
+ import { onMount } from 'svelte'
export let data: LayoutData
@@ -56,6 +57,7 @@
}
}
+ const pageRendered = writable(false)
if (browser) {
const initialTheme = localStorage.getItem('theme') ?? 'dark'
let globalTheme = writable(initialTheme)
@@ -76,6 +78,24 @@
// update copyright year from local storage
const storedCopyrightYear = localStorage.getItem('copyrightYear')
if (storedCopyrightYear) copyrightYear = Number(storedCopyrightYear)
+
+ // neko persistence
+ const persistNeko = localStorage.getItem('neko-persist')
+ if (persistNeko === 'true') {
+ ;(async () => {
+ const { pageRendered: nekoPageRendered } = await import('../neko/oneko')
+ import('../neko/oneko.css')
+
+ // our neko script needs to know when the page is rendered, which may or may not have already happened
+ nekoPageRendered.set($pageRendered)
+ pageRendered.subscribe((v) => {
+ nekoPageRendered.set(v)
+ })
+ })()
+ }
+ onMount(() => {
+ $pageRendered = true
+ })
}
diff --git a/src/routes/neko/+page.svelte b/src/routes/neko/+page.svelte
new file mode 100644
index 0000000..e44a233
--- /dev/null
+++ b/src/routes/neko/+page.svelte
@@ -0,0 +1,177 @@
+
+
+
+ cat config page
+
+
+
+
@@ -163,16 +170,22 @@
@@ -537,7 +550,7 @@
.neko-status-title-container h3 {
display: inline-block;
}
- #oneko {
+ .oneko {
display: inline-block;
}
diff --git a/src/routes/retro/app.css b/src/routes/retro/app.css
index 004c09d..b4e8c75 100644
--- a/src/routes/retro/app.css
+++ b/src/routes/retro/app.css
@@ -51,18 +51,6 @@ h2 {
box-sizing: border-box;
}
-#oneko {
- width: 32px;
- height: 32px;
- image-rendering: pixelated;
- z-index: 2147483647;
- position: absolute;
- cursor: pointer;
-
- /* default idle sprite */
- background-position: -96px -96px;
-}
-
#main-title:after {
content: 'silly edition';
display: block;
diff --git a/src/routes/retro/oneko.ts b/src/routes/retro/oneko.ts
deleted file mode 100644
index e03c78e..0000000
--- a/src/routes/retro/oneko.ts
+++ /dev/null
@@ -1,226 +0,0 @@
-// based on code written by adryd, ty <3
-// https://github.com/adryd325/oneko.js/blob/main/oneko.js
-
-export function initNeko(nekoEl: HTMLDivElement, updateSpriteCallback: (name: string) => void) {
- // set our pos based on where the element is on the page
- let nekoPosX = nekoEl.offsetLeft + 16
- let nekoPosY = nekoEl.offsetTop + 16
-
- let mousePosX = nekoEl.offsetLeft
- let mousePosY = nekoEl.offsetTop
-
- let frameCount = 0
- let idleTime = 0
- let idleAnimation: string | null = 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 startFollowingMouse() {
- nekoPosX = nekoEl.offsetLeft - window.scrollX + 16
- nekoPosY = nekoEl.offsetTop - window.scrollY + 16
- mousePosX = nekoPosX
- mousePosY = nekoPosY
-
- nekoEl.style.position = 'fixed'
- nekoEl.style.pointerEvents = 'none'
- nekoEl.style.left = `${nekoPosX - 16}px`
- nekoEl.style.top = `${nekoPosY - 16}px`
- nekoEl.style.zIndex = Number.MAX_VALUE.toString()
-
- document.addEventListener('mousemove', function (event) {
- mousePosX = event.clientX
- mousePosY = event.clientY
- })
-
- // move to body so it persists on page changes
- document.body.appendChild(nekoEl)
- }
-
- function init() {
- requestAnimationFrame(onAnimationFrame)
- }
-
- let lastFrameTimestamp: undefined | number
-
- function onAnimationFrame(timestamp: number) {
- // 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()
- }
- requestAnimationFrame(onAnimationFrame)
- }
-
- function setSprite(name: keyof typeof spriteSets, frame: number) {
- const sprite = spriteSets[name][frame % spriteSets[name].length]
- nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`
-
- updateSpriteCallback(name)
- }
-
- 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: 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' : ''
- setSprite(direction as any, 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()
- return startFollowingMouse
-}