diff --git a/searx/static/themes/simple/js/oneko-ie6.js b/searx/static/themes/simple/js/oneko-ie6.js new file mode 100644 index 000000000..a96d58631 --- /dev/null +++ b/searx/static/themes/simple/js/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/searx/static/themes/simple/js/oneko-webring.js b/searx/static/themes/simple/js/oneko-webring.js new file mode 100644 index 000000000..8376d00e9 --- /dev/null +++ b/searx/static/themes/simple/js/oneko-webring.js @@ -0,0 +1,291 @@ +// 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; + + // please use data-neko="true" on your A elements that link to another site with oneko-webring.js instead of this + // this is deprecated and will eventually be removed + const nekoSites = [ + "localhost", + ]; + + try { + const searchParams = location.search + .replace("?", "") + .split("&") + .map((keyvaluepair) => keyvaluepair.split("=")); + // This is so much repeated code, I don't like it + tmp = searchParams.find((a) => a[0] == "catx"); + if (tmp && tmp[1]) nekoPosX = parseInt(tmp[1]); + tmp = searchParams.find((a) => a[0] == "caty"); + if (tmp && tmp[1]) nekoPosY = parseInt(tmp[1]); + tmp = searchParams.find((a) => a[0] == "catdx"); + if (tmp && tmp[1]) mousePosX = parseInt(tmp[1]); + tmp = searchParams.find((a) => a[0] == "catdy"); + if (tmp && tmp[1]) mousePosY = parseInt(tmp[1]); + } catch (e) { + console.error("oneko.js: failed to parse query params."); + console.error(e); + } + + function onClick(event) { + const target = event.target.closest("A"); + if (target === null || !target.getAttribute("href")) { + return; + } + + let newLocation; + try { + newLocation = new URL(target.href); + } catch (e) { + return; + } + if ( + (nekoSites.includes(newLocation.host) && newLocation.pathname == "/") || + target.dataset.neko + ) { + 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/searx/static/themes/simple/js/oneko.gif b/searx/static/themes/simple/js/oneko.gif new file mode 100644 index 000000000..a009c2cc1 Binary files /dev/null and b/searx/static/themes/simple/js/oneko.gif differ diff --git a/searx/static/themes/simple/js/oneko.js b/searx/static/themes/simple/js/oneko.js new file mode 100644 index 000000000..cbc517491 --- /dev/null +++ b/searx/static/themes/simple/js/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 = 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 = 2147483647; + + 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/searx/static/themes/simple/js/oneko.js-main/LICENSE b/searx/static/themes/simple/js/oneko.js-main/LICENSE new file mode 100644 index 000000000..ec38643dc --- /dev/null +++ b/searx/static/themes/simple/js/oneko.js-main/LICENSE @@ -0,0 +1,7 @@ +Copyright © 2022 adryd + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/searx/static/themes/simple/js/oneko.js-main/README.md b/searx/static/themes/simple/js/oneko.js-main/README.md new file mode 100644 index 000000000..59796638f --- /dev/null +++ b/searx/static/themes/simple/js/oneko.js-main/README.md @@ -0,0 +1,19 @@ +# oneko.js + +A hacky script I wrote to put a cat on my site. + +The default image is `oneko.gif` in the same directory as the script. This can be changed by adding `data-cat="yourimage.png"` to your ` + + + diff --git a/searx/static/themes/simple/manifest.json b/searx/static/themes/simple/manifest.json index 19655e56a..04f6cae1b 100644 --- a/searx/static/themes/simple/manifest.json +++ b/searx/static/themes/simple/manifest.json @@ -5,6 +5,12 @@ "src": "js/searxng.head.js", "isEntry": true }, + "js/oneko.js": { + "file": "js/oneko.js", + "name": "js/oneko.js", + "src": "js/oneko.js", + "isEntry": true + }, "js/searxng.js": { "file": "js/searxng.min.js", "name": "js/searxng.min", @@ -26,4 +32,4 @@ "src": "less/style-rtl.less", "isEntry": true } -} \ No newline at end of file +} diff --git a/searx/templates/simple/base.html b/searx/templates/simple/base.html index 48e43ced9..4aca2457d 100644 --- a/searx/templates/simple/base.html +++ b/searx/templates/simple/base.html @@ -84,6 +84,7 @@ +