@@ -97,7 +93,7 @@
+
Utilities
@@ -151,11 +149,11 @@
-
![Image unavailable]()
+
-
-
+
+
My personal manual on how the blog system of this site works no hugo or jenkyll, uses plain html, which is automatically generated with the help of a little javascript.
@@ -164,22 +162,22 @@
-
![Image unavailable]()
+
-
+
Blog comin' soon, check again in a while....
-
![Image unavailable]()
+
-
+
Blog comin' soon, check again in a while....
@@ -301,8 +299,12 @@
-
-
{opensource 2023-2024 V3.0}
+
diff --git a/javascript/beatSync.js b/javascript/beatSync.js
new file mode 100644
index 0000000..d950297
--- /dev/null
+++ b/javascript/beatSync.js
@@ -0,0 +1,56 @@
+document.addEventListener("DOMContentLoaded", function() {
+ const audioElement = document.getElementById('musicPlayer');
+ let audioContext; // Define audioContext outside of event listener
+
+ // Function to create AudioContext after user gesture
+ function initializeAudioContext() {
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
+ const analyser = audioContext.createAnalyser();
+
+ // Check if the audioElement is valid
+ if (audioElement instanceof HTMLMediaElement) {
+ const source = audioContext.createMediaElementSource(audioElement);
+ source.connect(analyser);
+ analyser.connect(audioContext.destination);
+
+ const bufferLength = analyser.frequencyBinCount;
+ const dataArray = new Uint8Array(bufferLength);
+
+ // Function to update background size based on bass frequency range
+ function updateBackgroundSize() {
+ analyser.getByteFrequencyData(dataArray);
+
+ // Extract bass frequencies (20Hz - 200Hz)
+ const bassArray = dataArray.slice(0, Math.floor(bufferLength * (50 / analyser.frequencyBinCount)));
+
+ // Calculate the average bass amplitude
+ let total = 0;
+ for (let i = 0; i < bassArray.length; i++) {
+ total += bassArray[i];
+ }
+ const averageBassAmplitude = total / bassArray.length;
+
+ // Set background size based on the average bass amplitude
+ const newSize = 130 + averageBassAmplitude / 10; // Adjust multiplier to control sensitivity
+ document.body.style.backgroundSize = `${Math.max(newSize, 100)}%`; // Ensure minimum size of 100%
+ }
+
+ // Start updating background size on audio play
+ audioElement.addEventListener('play', () => {
+ setInterval(updateBackgroundSize, 50); // Adjust interval as needed
+ });
+ } else {
+ console.error("The 'audioElement' is not a valid HTMLMediaElement.");
+ }
+ }
+
+ // Event listener for user gesture (e.g., click event)
+ document.addEventListener('click', function() {
+ // Check if AudioContext is already created or resumed
+ if (!audioContext) {
+ initializeAudioContext(); // Create AudioContext
+ } else if (audioContext.state === 'suspended') {
+ audioContext.resume(); // Resume AudioContext if it's suspended
+ }
+ });
+});
diff --git a/javascript/blog.js b/javascript/blog.js
new file mode 100644
index 0000000..36f327a
--- /dev/null
+++ b/javascript/blog.js
@@ -0,0 +1,31 @@
+// Get the value of the 'file' parameter from the URL
+const urlParams = new URLSearchParams(window.location.search);
+const fileParam = urlParams.get('file');
+
+if (fileParam) {
+ // Set the title of the page to correspond with the 'file' parameter
+ document.title = `💾/${fileParam}`;
+}
+
+// If the 'file' parameter is present
+if (fileParam) {
+ // Construct the URL to fetch the corresponding HTML file
+ const fileURL = `blogs/${fileParam}.html`;
+
+ // Fetch the HTML content of the file
+ fetch(fileURL)
+ .then(response => {
+ if (!response.ok) {
+ throw new Error('Network response was not ok');
+ }
+ return response.text();
+ })
+ .then(htmlContent => {
+ // Insert the fetched HTML content into the div with class 'blogContent'
+ const blogContentDiv = document.querySelector('.blogContent');
+ blogContentDiv.innerHTML = htmlContent;
+ })
+ .catch(error => {
+ console.error('There was a problem fetching the blog content:', error);
+ });
+}
diff --git a/javascript/bloghub.js b/javascript/bloghub.js
new file mode 100644
index 0000000..faa93e6
--- /dev/null
+++ b/javascript/bloghub.js
@@ -0,0 +1,32 @@
+// Wait for the DOM content to be fully loaded before adding event listeners
+document.addEventListener("DOMContentLoaded", function() {
+ // Function to show all blogs
+ function showAllBlogs() {
+ var blogs = document.querySelectorAll('.blog');
+ blogs.forEach(function(blog) {
+ blog.style.display = 'flex';
+ });
+ }
+
+ // Function to show blogs belonging to a specific category
+ function showCategory(category) {
+ var blogs = document.querySelectorAll('.blog');
+ blogs.forEach(function(blog) {
+ var blogCategory = blog.getAttribute('data-category');
+ if (blogCategory === category || category === 'ALL') {
+ blog.style.display = 'flex';
+ } else {
+ blog.style.display = 'none';
+ }
+ });
+ }
+
+ // Event listener for navigation items
+ var navItems = document.querySelectorAll('.blogNav');
+ navItems.forEach(function(navItem) {
+ navItem.addEventListener("click", function() {
+ var category = this.querySelector('.blogNavText').textContent.trim();
+ showCategory(category);
+ });
+ });
+});
diff --git a/javascript/index.js b/javascript/index.js
new file mode 100644
index 0000000..bfbf2b5
--- /dev/null
+++ b/javascript/index.js
@@ -0,0 +1,49 @@
+document.addEventListener("DOMContentLoaded", () => {
+ const whoamiSpan = document.getElementById("whoami");
+ const cursorSpan = document.getElementById("cursor");
+
+ const jobs = [
+ "Malware Analyst",
+ "Unreal Engine Spc",
+ "C++ Developer",
+ "1 Thessalonians 5:11"
+ ];
+
+ let i = 0;
+ let j = 0;
+ let isDeleting = false;
+ let speed = 100;
+
+ function type() {
+ const job = jobs[i % jobs.length];
+
+ if (!isDeleting) {
+ whoamiSpan.textContent = "$ whoami: " + job.substring(0, j);
+ cursorSpan.style.display = "inline-block";
+ } else {
+ whoamiSpan.textContent = "$ whoami: " + job.substring(0, j) + "_";
+ cursorSpan.style.display = "none";
+ }
+
+ if (!isDeleting && j === job.length) {
+ setTimeout(() => {
+ isDeleting = true;
+ speed = 25;
+ }, 2500); // wait for 2.5 seconds before deleting
+ } else if (isDeleting && j === 0) {
+ isDeleting = false;
+ i++;
+ speed = 100;
+ setTimeout(() => {
+ speed = 100;
+ }, 500); // wait for half a second before typing again
+ }
+
+ j += isDeleting ? -1 : 1;
+
+ setTimeout(type, speed);
+ }
+
+ type();
+ });
+
\ No newline at end of file
diff --git a/javascript/music.js b/javascript/music.js
new file mode 100644
index 0000000..b051685
--- /dev/null
+++ b/javascript/music.js
@@ -0,0 +1,68 @@
+const musicButton = document.getElementById('musicButton');
+const musicPlayer = document.getElementById('musicPlayer');
+
+let isPlaying = false;
+let currentSongIndex = 0;
+let playbackPosition = 0; // Variable to store the playback position
+const songs = [
+ "../../storage/music/song1.mp3",
+ "../../storage/music/song2.mp3",
+ "../../storage/music/song3.mp3",
+ "../../storage/music/song4.mp3",
+ "../../storage/music/song5.mp3",
+ "../../storage/music/song6.mp3",
+ "../../storage/music/song7.mp3",
+ "../../storage/music/song8.mp3",
+ "../../storage/music/song9.mp3",
+ "../../storage/music/song10.mp3",
+ "../../storage/music/song11.mp3",
+ "../../storage/music/song12.mp3",
+ "../../storage/music/song13.mp3",
+ "../../storage/music/song14.mp3"
+ // Add more songs as needed
+];
+
+function toggleMusic() {
+ if (isPlaying) {
+ playbackPosition = musicPlayer.currentTime; // Store the current playback position
+ musicPlayer.pause();
+ } else {
+ playCurrentSong();
+ }
+ isPlaying = !isPlaying;
+ updateButton(); // Update button appearance after toggling playback
+}
+
+function playCurrentSong() {
+ musicPlayer.src = songs[currentSongIndex];
+ musicPlayer.currentTime = playbackPosition; // Set the playback position
+ musicPlayer.load(); // Ensure the audio is loaded before playing
+ const playPromise = musicPlayer.play();
+ if (playPromise !== undefined) {
+ playPromise.then(_ => {
+ // Playback started successfully
+ console.log('Audio playback started.');
+ }).catch(error => {
+ // Auto-play prevented, handle accordingly
+ console.error('Auto-play prevented:', error);
+ });
+ }
+}
+
+function playNextSong() {
+ currentSongIndex = (currentSongIndex + 1) % songs.length;
+ playbackPosition = 0; // Reset playback position when playing the next song
+ playCurrentSong();
+}
+
+function updateButton() {
+ musicButton.src = isPlaying ? "../../icons/pause.png" : "../../icons/play.png";
+}
+
+musicButton.addEventListener('click', () => {
+ toggleMusic();
+});
+
+musicPlayer.addEventListener('ended', () => {
+ playNextSong();
+});
diff --git a/javascript/oneko-ie6.js b/javascript/oneko-ie6.js
new file mode 100644
index 0000000..a96d586
--- /dev/null
+++ b/javascript/oneko-ie6.js
@@ -0,0 +1,242 @@
+function neko() {
+ var nekoEl = document.createElement("div");
+
+ var nekoPosX = 32;
+ var nekoPosY = 32;
+
+ var mousePosX = 0;
+ var mousePosY = 0;
+
+ var frameCount = 0;
+ var idleTime = 0;
+ var idleAnimation = null;
+ var idleAnimationFrame = 0;
+ var direction;
+
+ var IE = document.all ? true : false;
+
+ var nekoSpeed = 10;
+ var 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 = "absolute";
+ nekoEl.style.pointerEvents = "none";
+ nekoEl.style.backgroundImage = "url('oneko.gif')";
+ nekoEl.style.imageRendering = "pixelated";
+ nekoEl.style.left = nekoPosX - 16 + "px";
+ nekoEl.style.top = nekoPosY - 16 + "px";
+ nekoEl.style.zIndex = Number.MAX_VALUE;
+
+ document.body.appendChild(nekoEl);
+ function mousePos(event) {
+ if (IE) {
+ event = window.event;
+ }
+ mousePosX = event.clientX;
+ mousePosY = event.clientY - 16;
+ }
+ document.onmousemove = mousePos;
+ window.onekoInterval = setInterval(frame, 100);
+ }
+
+ function setSprite(name, frame) {
+ var length = spriteSets[name].length;
+ if (IE) {
+ length = 0;
+ // Internet explorer is really fucking dumb
+ while (length < spriteSets[name].length) {
+ if (spriteSets[name][length] != null) {
+ length = length + 1;
+ continue;
+ }
+ break;
+ }
+ }
+ var sprite = spriteSets[name][frame % length];
+ nekoEl.style.backgroundPosition =
+ sprite["0"] * 32 + "px " + sprite["1"] * 32 + "px";
+ }
+
+ function resetIdleAnimation() {
+ idleAnimation = null;
+ idleAnimationFrame = 0;
+ }
+
+ function idle() {
+ idleTime = idleTime + 1;
+
+ // every ~ 20 seconds
+ if (
+ idleTime > 10 &&
+ Math.floor(Math.random() * 200) == 0 &&
+ idleAnimation == null
+ ) {
+ var 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 = idleAnimationFrame + 1;
+ }
+
+ function frame() {
+ frameCount = frameCount + 1;
+ var diffX = nekoPosX - mousePosX;
+ var diffY = nekoPosY - mousePosY;
+ var distance = Math.sqrt(Math.pow(diffX, 2) + Math.pow(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 = idleTime - 1;
+ return;
+ }
+
+ direction = "";
+ if (diffY / distance > 0.5) {
+ direction = "N";
+ } else if (diffY / distance < -0.5) {
+ direction = "S";
+ }
+ if (diffX / distance > 0.5) {
+ direction = direction + "W";
+ } else if (diffX / distance < -0.5) {
+ direction = direction + "E";
+ }
+ setSprite(direction, frameCount);
+
+ if (distance > nekoSpeed) {
+ nekoPosX = nekoPosX - (diffX / distance) * nekoSpeed;
+ nekoPosY = nekoPosY - (diffY / distance) * nekoSpeed;
+ } else {
+ nekoPosX = mousePosX;
+ nekoPosY = mousePosY;
+ }
+
+ nekoPosX = Math.min(
+ Math.max(16, nekoPosX),
+ document.getElementsByTagName("body")[0].clientWidth - 16
+ );
+ nekoPosY = Math.min(
+ Math.max(16, nekoPosY),
+ document.getElementsByTagName("body")[0].clientHeight - 16
+ );
+
+ nekoEl.style.left = nekoPosX - 16 + "px";
+ nekoEl.style.top = nekoPosY - 16 + "px";
+ }
+ init();
+}
+neko();
diff --git a/javascript/oneko-webring.js b/javascript/oneko-webring.js
new file mode 100644
index 0000000..9bc7fda
--- /dev/null
+++ b/javascript/oneko-webring.js
@@ -0,0 +1,306 @@
+// oneko.js: https://github.com/adryd325/oneko.js (webring variant)
+
+(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;
+
+ const nekoSites = [
+ "adryd.com",
+ "localhost",
+ "c7.pm",
+ "fade.nya.rest",
+ "fleepy.tv",
+ "maia.crimew.gay",
+ "spookyghost.zone",
+ "noelle.df1.dev",
+ "www.kibty.town",
+ "kibty.town",
+ "avasilver.dev",
+ "tris.fyi",
+ "breq.dev"
+ ];
+
+ try {
+ const searchParams = location.search
+ .replace("?", "")
+ .split("&")
+ .map((keyvaluepair) => keyvaluepair.split("="));
+
+ function posFind(string) {
+ const result = searchParams.find((a) => a[0] == string);
+ if (result && result[1]) return parseInt(result[1]);
+ }
+
+ nekoPosX = posFind("catx") || 32;
+ nekoPosY = posFind("caty") || 32;
+ mousePosX = posFind("catdx") || 0;
+ mousePosY = posFind("catdy") || 0;
+ } catch (e) {
+ console.error("oneko.js: failed to parse query params.");
+ console.error(e);
+ }
+
+ function onClick(event) {
+ let target;
+ if (event.target.tagName === "A" && event.target.getAttribute("href")) {
+ target = event.target;
+ } else if (
+ event.target.tagName == "IMG" &&
+ event.target.parentElement.tagName === "A" &&
+ event.target.parentElement.getAttribute("href")
+ ) {
+ target = event.target.parentElement;
+ } else {
+ return;
+ }
+ let newLocation;
+ try {
+ newLocation = new URL(target.href);
+ } catch (e) {;
+ return;
+ }
+ if (!nekoSites.includes(newLocation.host) || newLocation.pathname != "/")
+ return;
+ newLocation.searchParams.append("catx", Math.floor(nekoPosX));
+ newLocation.searchParams.append("caty", Math.floor(nekoPosY));
+ newLocation.searchParams.append("catdx", Math.floor(mousePosX));
+ newLocation.searchParams.append("catdy", Math.floor(mousePosY));
+ event.preventDefault();
+ window.location.href = newLocation.toString();
+ }
+ document.addEventListener("click", onClick);
+
+ 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();
+})();
diff --git a/javascript/oneko.js b/javascript/oneko.js
new file mode 100644
index 0000000..bb990af
--- /dev/null
+++ b/javascript/oneko.js
@@ -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 = 12;
+ 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 = -1;
+
+ let nekoFile = "../images/gifs/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();
+})();
diff --git a/javascript/preloader.js b/javascript/preloader.js
new file mode 100644
index 0000000..2d9be07
--- /dev/null
+++ b/javascript/preloader.js
@@ -0,0 +1,6 @@
+window.addEventListener('load', function() {
+ var preloader = document.getElementById('preloader');
+ var content = document.getElementById('content');
+
+ preloader.style.display = 'none'; // Hide the preloader
+});
diff --git a/javascript/print.js b/javascript/print.js
new file mode 100644
index 0000000..6eba8db
--- /dev/null
+++ b/javascript/print.js
@@ -0,0 +1,15 @@
+function printBlog() {
+ var blogContent = document.getElementsByClassName('blogContent');
+ if (blogContent.length > 0) {
+ var contentToPrint = '';
+ for (var i = 0; i < blogContent.length; i++) {
+ contentToPrint += blogContent[i].innerHTML;
+ }
+ var originalContent = document.body.innerHTML;
+ document.body.innerHTML = contentToPrint;
+ window.print();
+ document.body.innerHTML = originalContent;
+ } else {
+ console.error('No elements found with the class "blog"');
+ }
+ }
\ No newline at end of file
diff --git a/javascript/sparkles.js b/javascript/sparkles.js
new file mode 100644
index 0000000..3b65e20
--- /dev/null
+++ b/javascript/sparkles.js
@@ -0,0 +1,179 @@
+
+var colour="random";
+var sparkles=50;
+var x=ox=400;
+var y=oy=300;
+var swide=800;
+var shigh=600;
+var sleft=sdown=0;
+var tiny=new Array();
+var star=new Array();
+var starv=new Array();
+var starx=new Array();
+var stary=new Array();
+var tinyx=new Array();
+var tinyy=new Array();
+var tinyv=new Array();
+window.onload=function() { if (document.getElementById) {
+ var i, rats, rlef, rdow;
+ for (var i=0; i
1 || Math.abs(y-oy)>1) {
+ ox=x;
+ oy=y;
+ for (c=0; c0) sw_min=document.documentElement.clientWidth;
+ if (document.documentElement.clientHeight>0) sh_min=document.documentElement.clientHeight;
+ }
+ if (typeof(self.innerWidth)=='number' && self.innerWidth) {
+ if (self.innerWidth>0 && self.innerWidth0 && self.innerHeight0 && document.body.clientWidth0 && document.body.clientHeight