summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--html/assets/coffee-koupen.jpgbin0 -> 96014 bytes
-rw-r--r--html/assets/oneko.gifbin0 -> 12988 bytes
-rw-r--r--html/assets/oneko.js279
-rw-r--r--html/index.html41
4 files changed, 303 insertions, 17 deletions
diff --git a/html/assets/coffee-koupen.jpg b/html/assets/coffee-koupen.jpg
new file mode 100644
index 0000000..4d5f3a7
--- /dev/null
+++ b/html/assets/coffee-koupen.jpg
Binary files differ
diff --git a/html/assets/oneko.gif b/html/assets/oneko.gif
new file mode 100644
index 0000000..799c632
--- /dev/null
+++ b/html/assets/oneko.gif
Binary files 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 24" width="32" height="24"><path d="M1 3h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v2H9v1h1v2h1v2h-1v1H8v-1H7v-2H6v-2H5v1H4v1H3v1H1" fill="%23ff69b4" stroke="%23b19cd9" stroke-width="0.5"/><path d="M2 5h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1H8v2h1v2h1v2H8v-2H7v-2H6v-1H5v1H4v1H3v1H2" fill="white" stroke="%23ff69b4" stroke-width="0.5"/></svg>')
+ 0 0, auto !important;
+ }
body {
max-width: 800px;
@@ -34,15 +38,15 @@
text-decoration: underline;
}
.pro {
- text-decoration: none;
+ text-decoration: underline dotted;
}
</style>
</head>
<body>
<h1><a class="pro">Elizabeth</a> Hunt (<a class="pro">she</a>) (ca 2003)</h1>
<div>
- [me at <a href="https://en.wikipedia.org/wiki/Logarithmic_integral_function" target="_blank">Li(z)</a> dot coffee (<a class="tt" href="mailto:me@liz.coffee">mailto</a>)]·
- [<a href="https://code.liz.coffee">code</a>] ·
+ [me at <a href="https://en.wikipedia.org/wiki/Logarithmic_integral_function" target="_blank">Li(z)</a> dot coffee (<a class="tt" href="mailto:me@liz.coffee">mailto</a>)] ·
+ [<a href="https://code.liz.coffee">git hub</a>] ·
[<a href="https://linkedin.com/in/emprespresso">linkedin</a>] ·
[<a href="https://instagram.com/emprespresso">instagram</a>] ·
[<a href="https://x.com/emprespresso">twitter</a>]
@@ -51,18 +55,20 @@
<marquee>If you <a href="https://web.archive.org/web/20260720071542/https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/marquee">don't like the marquee tag</a> then <a class="pro">she</a> will find you and <a class="pro">she</a> will blast you to smithereens.</marquee>
<hr />
- <p><a class="pro">She</a> is not particularly exceptional at anything but can functionally do a fair bit of stuff, of which <a class="pro">her</a> extremely variable ability perhaps is the only exceptional thing about <a class="pro">her</a>.</p>
+ <div><p><a class="pro">She</a> is not particularly exceptional at anything but can functionally do a fair bit of stuff, of which <a class="pro">her</a> extremely variable ability perhaps is the only exceptional thing about <a class="pro">her</a>.</p></div>
+ <div style="margin-right: 10px; max-width: 200px; float:left; text-align: center"><img src="assets/coffee-koupen.jpg" width="100%"><br><i>Koupenchan becomes the color of what it drinks</i></div>
- <p><a class="pro">She</a> 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 <a class="pro">her</a>self 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 <a class="pro">she</a> remind you: <span id="genpenguin">penguin slop that would be populated if you had your EPIC JAVASCRIPT enabled</span></p>
+ <div><p><a class="pro">She</a> 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 <a class="pro">her</a>self 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 <a class="pro">she</a> remind you.</p><div>
+ <div><p id="genpenguin">penguin slop that would be populated if you had your EPIC JAVASCRIPT enabled</p></div>
<p><a class="pro">She</a> enjoys recreational programming and searching the space of math and CS outside of work; some of <a class="pro">her</a> current interests are in PLs, type theory, game programming (&lt;3 pico8), and homelab stuff. <a class="pro">She</a> was an advocate of LISPy stuff while president of the <a href="https://linux.usu.edu">USU Free Software and Linux Club</a>, <a class="pro">she</a> still can't shake off Emacs and probably should augment <a class="pro">her</a> <a href="assets/emacs.png">hand accordingly</a>.</p>
<p>When not doing computer stuff, <a class="pro">she</a> runs, bikes, watches anime, rides trains, reads, or bakes bread. <a class="pro">She</a> may be heard practicing violin or speedcubing. Penguins are absolutely fascinating to <a class="pro">her</a>, and <a class="pro">she</a> collects books on said species as well as plenty of stuffed animals, and visits them when possible. Maybe <a class="pro">she</a> will one day <span style="display: none">(soon?) </span>reincarnate to be one.</p>
- <p><a class="pro">She</a> has been vegetarian since early 2025. <a class="pro">She</a> is poly, bicycle, and a bit fucked up.</p>
+ <p><a class="pro">She</a> has been vegetarian since early 2025. <a class="pro">She</a> is a vegetarian (except for when it comes to billionaires) arch user (you know because <a class="pro">she</a> just told you), bicycle, poly, and a bit fucked up.</p>
- <p>Gus!</p>
- <p><img src="assets/gus.jpeg" height="400"></p>
+ <p>Gus! Bread!</p>
+ <div><img src="assets/gus.jpeg" width="300"> <img src="assets/bread.jpeg" width="300"></div>
<div style="display: flex; align-items: center; justify-content: center;"><img width="100" src="assets/penguin.gif"><a href="https://liz.coffee"><img src="https://adelie.liz.coffee/img/blinkies/liz--coffee.gif" width="150" height="20"></a></div>
<hr>
@@ -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;
}));
</script>
+ <script src="assets/oneko.js"></script>
</body>
</html>