From c29f183647be240db1d799f075af8438b68459dd Mon Sep 17 00:00:00 2001 From: Elizabeth Alexander Hunt Date: Thu, 23 Jul 2026 08:20:08 -0700 Subject: yay 2 --- html/assets/coffee-koupen.jpg | Bin 0 -> 96014 bytes html/assets/oneko.gif | Bin 0 -> 12988 bytes html/assets/oneko.js | 279 ++++++++++++++++++++++++++++++++++++++++++ html/index.html | 41 ++++--- 4 files changed, 303 insertions(+), 17 deletions(-) create mode 100644 html/assets/coffee-koupen.jpg create mode 100644 html/assets/oneko.gif create mode 100644 html/assets/oneko.js (limited to 'html') diff --git a/html/assets/coffee-koupen.jpg b/html/assets/coffee-koupen.jpg new file mode 100644 index 0000000..4d5f3a7 Binary files /dev/null and b/html/assets/coffee-koupen.jpg differ diff --git a/html/assets/oneko.gif b/html/assets/oneko.gif new file mode 100644 index 0000000..799c632 Binary files /dev/null and b/html/assets/oneko.gif differ diff --git a/html/assets/oneko.js b/html/assets/oneko.js new file mode 100644 index 0000000..789c2e0 --- /dev/null +++ b/html/assets/oneko.js @@ -0,0 +1,279 @@ +// 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 persistPosition = true; + + 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() { + let nekoFile = "assets/oneko.gif" + const curScript = document.currentScript + if (curScript && curScript.dataset.cat) { + nekoFile = curScript.dataset.cat + } + if (curScript && curScript.dataset.persistPosition) { + if (curScript.dataset.persistPosition === "") { + persistPosition = true; + } else { + persistPosition = JSON.parse(curScript.dataset.persistPosition.toLowerCase()); + } + } + + if (persistPosition) { + let storedNeko = JSON.parse(window.localStorage.getItem("oneko")); + 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", function (event) { + mousePosX = event.clientX; + mousePosY = event.clientY; + }); + + if (persistPosition) { + window.addEventListener("beforeunload", function (event) { + 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; + + 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(); +})(); diff --git a/html/index.html b/html/index.html index 8eee773..5f51937 100644 --- a/html/index.html +++ b/html/index.html @@ -12,6 +12,10 @@ --a: #c4d0c8; --h: #7d978c; } + * { + cursor: url('data:image/svg+xml;utf8,') + 0 0, auto !important; + } body { max-width: 800px; @@ -34,15 +38,15 @@ text-decoration: underline; } .pro { - text-decoration: none; + text-decoration: underline dotted; }

Elizabeth Hunt (she) (ca 2003)

- [me at Li(z) dot coffee (mailto)]· - [code] · + [me at Li(z) dot coffee (mailto)] · + [git hub] · [linkedin] · [instagram] · [twitter] @@ -51,18 +55,20 @@ If you don't like the marquee tag then she will find you and she will blast you to smithereens.
-

She is not particularly exceptional at anything but can functionally do a fair bit of stuff, of which her extremely variable ability perhaps is the only exceptional thing about her.

+

She is not particularly exceptional at anything but can functionally do a fair bit of stuff, of which her extremely variable ability perhaps is the only exceptional thing about her.

+

Koupenchan becomes the color of what it drinks
-

She is a programmer at a Big Ol' Tech™ company in Seattle (unfortunately, probably the one you're thinking of). And will continue to refer to herself in the third person for the remainder of this World Wide Web document, which you find yourself laying your eyes on. And in so doing, probably don't yet understand the context of the next words you are about to read. Except, that they are there. They take up space. And reading through them consumes YOUR oh-so-precious time - which is upper-bounded, need she remind you: penguin slop that would be populated if you had your EPIC JAVASCRIPT enabled

+

She is a programmer at a Big Ol' Tech™ company in Seattle (unfortunately, probably the one you're thinking of). And will continue to refer to herself in the third person for the remainder of this World Wide Web document, which you find yourself laying your eyes on. And in so doing, probably don't yet understand the context of the next words you are about to read. Except, that they are there. They take up space. And reading through them consumes YOUR oh-so-precious time - which is upper-bounded, need she remind you.

+

penguin slop that would be populated if you had your EPIC JAVASCRIPT enabled

She enjoys recreational programming and searching the space of math and CS outside of work; some of her current interests are in PLs, type theory, game programming (<3 pico8), and homelab stuff. She was an advocate of LISPy stuff while president of the USU Free Software and Linux Club, she still can't shake off Emacs and probably should augment her hand accordingly.

When not doing computer stuff, she runs, bikes, watches anime, rides trains, reads, or bakes bread. She may be heard practicing violin or speedcubing. Penguins are absolutely fascinating to her, and she collects books on said species as well as plenty of stuffed animals, and visits them when possible. Maybe she will one day (soon?) reincarnate to be one.

-

She has been vegetarian since early 2025. She is poly, bicycle, and a bit fucked up.

+

She has been vegetarian since early 2025. She is a vegetarian (except for when it comes to billionaires) arch user (you know because she just told you), bicycle, poly, and a bit fucked up.

-

Gus!

-

+

Gus! Bread!

+

@@ -79,19 +85,19 @@ "damp", "well-loved", "smug", "round", "salty", "windswept", "tiny"], species: ["fairy", "rockhopper", "adelie", "gentoo", "emperor", "chinstrap", "king", "macaroni", "little blue"], - subject: ["penguin", "chick", "colony", "creche", "huddle", "raft", "pen-pen", - "koupenchan", "flipper", "beak", "egg", "nest"], + subject: ["penguin", "chick", "colony", "creche", "huddle", "raft", "Pen Pen", + "Koupenchan", "flipper", "beak", "egg", "nest"], adverb: ["quietly", "eagerly", "rarely", "sideways", "smugly", "almost", "solemnly", "briskly", "suspiciously", "twice"], - verb: ["waddles", "toboggans", "steals", "polishes", "guards", "misplaces", "noots at", - "preens", "dives for", "cuddles", "counts", "hoards", "trades", "belly-flops onto"], + verb: ["waddles", "toboggans", "steals", "polishes", "guards", "misplaces", "pecks at", + "preens", "dives for", "cuddles", "counts", "hoards", "trades"], object: ["a pebble", "the nest", "three krill", "a shiny fish", "its egg", "the whole colony", "a stray feather", "a spare flipper", "the last herring", "someone's chick", "a very good rock", "the ice shelf"], - ending: ["before breakfast", "on the ice shelf", "without warning", "in the huddle", - "for no reason", "under a glacier", "at dawn", "beneath the waves", - "at the rookery", "on the sofa", "during the blizzard", "again"], - noise: ["Squawk.", "Noot?", "Gak!"], + ending: ["after the endless summer", "on the ice shelf", "without warning", "in the huddle", + "for no reason", "under a glacier", "beneath the waves", + "at the rookery", "during the blizzard", "again"], + end: ["Squawk.", "Noot?", "Gak!", "Shuffle.", "Waddle."], }; const rand = (list) => list[Math.floor(Math.random() * list.length)]; @@ -107,7 +113,7 @@ const gen = () => { const text = rand(patterns).map((cat) => rand(words[cat])).join(" "); const out = text[0].toUpperCase() + text.slice(1) + "."; - return Math.random() < 0.25 ? out + " " + rand(words.noise) : out; + return Math.random() < 0.25 ? out + " " + rand(words.end) : out; } const n = 5 + Math.floor(Math.random() * 6); document.getElementById("genpenguin").textContent = ""; @@ -130,5 +136,6 @@ p = pSet; })); + -- cgit v1.3