diff --git a/Caddyfile b/Caddyfile index ceb232d..0f94919 100644 --- a/Caddyfile +++ b/Caddyfile @@ -1,6 +1,6 @@ (https_redirect) { @do_https_redirect { - not header_regexp veryoldbrowser User-Agent Navigator|MSIE|Mosaic|Kindle + not header_regexp veryoldbrowser User-Agent Navigator|MSIE|Mosaic|Kindle|^curl|NintendoBrowser/ protocol http } redir @do_https_redirect https://{host}{uri} @@ -22,7 +22,8 @@ uri @do_gif_redirect path_regexp \.png$ .gif } -matdoes.dev:80 matdoes.dev:443 http://matctazmu565vivubva3p3bulaneangiff47xmnezzjx2nuinwjoxjyd.onion { +# this is necessary for http:// to work on old browsers +http://matdoes.dev https://matdoes.dev http://matctazmu565vivubva3p3bulaneangiff47xmnezzjx2nuinwjoxjyd.onion { import https_redirect import gif_redirect @@ -36,9 +37,9 @@ matdoes.dev:80 matdoes.dev:443 http://matctazmu565vivubva3p3bulaneangiff47xmnezz not header_regexp not_chrome User-Agent Googlebot/|eightyeightthirtyone not path /dot_git/* } - respond @chrome "This site is best viewed with Firefox (or any browser that isn't Chrome). + # respond @chrome "This site is best viewed with Firefox (or any browser that isn't Chrome). -If you're unable to use Firefox, you can also access this website via SSH, Gemini, Gopher, Finger, Telnet, and some others." 403 +#If you're unable to use Firefox, you can also access this website via SSH, Gemini, Gopher, Finger, Telnet, and some others." 403 # easter egg that makes old browsers show the retro page @retro_redirect { @@ -99,11 +100,31 @@ If you're unable to use Firefox, you can also access this website via SSH, Gemin root /opt/x227f } } + route /buttons/stats.json { + uri strip_prefix /buttons + file_server { + root /opt/x227f + } + } handle_path /buttons/i/* { try_files {path} {path}.png {path}.gif {path}.jpg {path}.webp {path}.avif {path}.bmp root * /opt/x227f/buttons file_server } + handle_path /minecraft-uuids/api/* { + rewrite * {path}.gz + file_server { + root /opt/minecraft-uuids-api + } + header Content-Type text/plain + header Content-Encoding gzip + } + + route /status.json { + reverse_proxy 127.0.0.1:9247 { + rewrite / + } + } handle_errors { @should_be_404 { @@ -130,6 +151,33 @@ If you're unable to use Firefox, you can also access this website via SSH, Gemin } } +staging.matdoes.dev { + root * /www-staging + file_server { + precompressed br gzip + } + + # redirect to .json if the user requested application/json + @json { + header Accept application/json + not path *.json + } + @json_index { + header Accept application/json + path / + } + rewrite @json_index /.json + rewrite @json {path}.json + + # don't require .html + try_files {path} {path}.html + + handle /robots.txt { + respond "User-agent: * +Disallow: /" + } +} + (matrix_media_proxy) { handle /_matrix/media/*/download/matdoes.dev/discord_* { header Access-Control-Allow-Origin * @@ -157,21 +205,20 @@ If you're unable to use Firefox, you can also access this website via SSH, Gemin } } } -matrix.matdoes.dev { - import matrix_media_proxy - - handle { +matrix.matdoes.dev, matrix.matdoes.dev:8448 { + handle /.well-known/matrix/server { + header content-type application/json + respond "{\"m.server\":\"matrix.matdoes.dev\"}" + } + handle /.well-known/matrix/client { reverse_proxy 127.0.0.1:81 } -} -matrix.matdoes.dev:8448 { - import matrix_media_proxy - handle { - reverse_proxy 127.0.0.1:8449 + reverse_proxy 127.0.0.1:6167 } } + stats.matrix.matdoes.dev { reverse_proxy 127.0.0.1:81 } @@ -201,6 +248,24 @@ s.matdoes.dev { mail.matdoes.dev { respond "mat's mail server :)" } + +jmap.matdoes.dev { + log { + output stdout + } + + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "*" + header Access-Control-Allow-Headers "*" + + reverse_proxy https://localhost:8080 { + transport http { + tls + tls_insecure_skip_verify + } + } +} + matdoes.dev:3 { header { colon3 :3 @@ -211,6 +276,11 @@ matdoes.dev:3 { respond ":3 " } + +meowww.matdoes.dev { + reverse_proxy 127.0.0.1:11351 +} + hetzner.matdoes.dev { redir https://matdoes.dev{uri} } diff --git a/src/routes/retro/+page.svelte b/src/routes/retro/+page.svelte index c81a20e..2f98a12 100644 --- a/src/routes/retro/+page.svelte +++ b/src/routes/retro/+page.svelte @@ -4,16 +4,204 @@ import links from './links.gif' import projects from '../_projects.json' + import { initNeko } from './oneko' + import type { BlogPostPreview } from '../blog.json/+server.js' import Button from './Button.svelte' + import { browser } from '$app/environment' + import { onMount } from 'svelte' export let data export let posts: BlogPostPreview[] = data.posts + export let status = data.status + + const lastComplaintAt = new Date(status.last_complaint_at) + const hoursSinceLastComplaint = Math.floor( + (Date.now() - lastComplaintAt.getTime()) / 1000 / 60 / 60 + ) + const daysSinceLastComplaint = Math.floor(hoursSinceLastComplaint / 24) + + const lastUpdatedAt = new Date(status.last_updated_at) + + function timeAgo(date: Date) { + const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000) + let interval = seconds / 31536000 + if (interval > 1) return Math.floor(interval) + ' years' + interval = seconds / 2592000 + if (interval > 1) return Math.floor(interval) + ' months' + interval = seconds / 86400 + if (interval > 1) return Math.floor(interval) + ' days' + interval = seconds / 3600 + if (interval > 1) return Math.floor(interval) + ' hours' + interval = seconds / 60 + if (interval > 1) return Math.floor(interval) + ' minutes' + return Math.floor(seconds) + ' seconds' + } + + function getCurrentTimeInCst(): string { + return new Date().toLocaleString('en-US', { + timeZone: 'America/Chicago', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + hour12: true, + }) + } + + let currentTimeInCst = getCurrentTimeInCst() + let nekoEl: HTMLDivElement + let nekoSpriteName: keyof typeof nekoSpriteIdsToNames = 'idle' + onMount(() => { + const startFollowingMouse = initNeko(nekoEl, (spriteName) => { + nekoSpriteName = spriteName as any + }) + nekoEl.onclick = startFollowingMouse + + const interval = setInterval(() => { + currentTimeInCst = getCurrentTimeInCst() + }, 1000) + return () => clearInterval(interval) + }) + + const nekoSpriteIdsToNames = { + idle: 'Idle', + alert: 'Alert', + scratchSelf: 'Idle', + scratchWallN: 'Idle', + scratchWallS: 'Idle', + scratchWallE: 'Idle', + scratchWallW: 'Idle', + tired: 'Tired', + sleeping: 'Sleeping', + N: 'Chasing', + NE: 'Chasing', + E: 'Chasing', + SE: 'Chasing', + S: 'Chasing', + SW: 'Chasing', + W: 'Chasing', + NW: 'Chasing', + } + const nekoSpriteIdsToStatuses = { + idle: 'idle', + alert: 'down', + scratchSelf: 'idle', + scratchWallN: 'idle', + scratchWallS: 'idle', + scratchWallE: 'idle', + scratchWallW: 'idle', + tired: 'idle', + sleeping: 'idle', + N: 'up', + NE: 'up', + E: 'up', + SE: 'up', + S: 'up', + SW: 'up', + W: 'up', + NW: 'up', + } - + - - - - - + + + + + + + - +
+
@@ -88,55 +276,50 @@
+ + + + + + +
+ + + + +
+
contact
+

+ my preferred method of contact is matrix, but you can also email me (i have a catch-all on this domain). i'm also on + the fediverse. +

+
+
- + -
- - - - - -
- - - - -
-
contact
-

- my preferred method of contact is matrix, but you can also email me (i have a catch-all on this domain). i'm also on - the fediverse. -

-
-
-
-
visitor counter

Page last updated: February 24, 2024

Page last updated: March 19, 2024

diff --git a/src/routes/retro/+page.ts b/src/routes/retro/+page.ts index 87714a1..34a9b3a 100644 --- a/src/routes/retro/+page.ts +++ b/src/routes/retro/+page.ts @@ -4,8 +4,10 @@ export const prerender = true export const load: Load = async ({ fetch }) => { const posts = await fetch('/blog.json').then((r: Response) => r.json()) + const status = await fetch('/status.json').then((r: Response) => r.json()) return { posts, + status, } } diff --git a/src/routes/retro/app.css b/src/routes/retro/app.css index 9a9946e..3436f33 100644 --- a/src/routes/retro/app.css +++ b/src/routes/retro/app.css @@ -18,6 +18,10 @@ body, font-family: 'Comic Sans MS'; src: url(/retro/comicsans.ttf) format('truetype'); } +@font-face { + font-family: 'Press Start 2P'; + src: url(/retro/pressstart2p.ttf) format('truetype'); +} :root { --background-color-alt: rgba(255, 255, 255, 0.05) !important; @@ -47,6 +51,16 @@ h2 { box-sizing: border-box; } +#oneko { + width: 32px; + height: 32px; + image-rendering: pixelated; + z-index: 2147483647; + position: absolute; + + cursor: pointer; +} + #main-title:after { content: 'silly edition'; display: block; diff --git a/src/routes/retro/oneko.ts b/src/routes/retro/oneko.ts new file mode 100644 index 0000000..71e40c3 --- /dev/null +++ b/src/routes/retro/oneko.ts @@ -0,0 +1,218 @@ +// 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() { + 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 + }) + } + + 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 +} diff --git a/src/routes/status.json/+server.ts b/src/routes/status.json/+server.ts new file mode 100644 index 0000000..5628e55 --- /dev/null +++ b/src/routes/status.json/+server.ts @@ -0,0 +1,10 @@ +import { type RequestHandler } from '@sveltejs/kit' + +export const prerender = true + +export const GET: RequestHandler = async ({}) => { + // status.json is hardcoded in the caddy config to redirect somewhere else. + // we have it in svelte too so we can use it for prerendering. + + return await fetch('https://matdoes.dev/status.json') +} diff --git a/static/id_rsa.pub b/static/id_rsa.pub new file mode 100644 index 0000000..ed7caa6 --- /dev/null +++ b/static/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCou7N2XEEDrWfr3HTcge3UNbjtX5gMfPiNfLf1/0twK56mqqBGbtEARurq+pi1H42jQ3g3TAtUDtz2yZhelvfVJGX3oZJGDQ3GR8SPziqFMZRF16rNDt73QPZXPScoqwAQNAIHUdSxSo0CkOfpg2mMq9uZ0c5I4UF/LMh8Y7szek7iW9CaALWuJgoP46Jb35sOqK1ODFSMgsT1kPp7oZIbaO6X7nLJ6nDFX3R8mIIxQtJ0bK1huZ3iiLyeR7ZjY/l7rC6k21XNi3JC2La5w9IuNO2kR0kTaHBsAgwlC1BWoD7M0QUEIhvQYymPgTOb0L92xIfE8v0zembn+A06ir55ihhnwHJ0KdlKfcUZr6w8Lb9wI3XOdL2OmA1P9La9O+V4b+PW0FUWFV3lCZY+g/USoQx9nsyphZsXe7ZUPY8By5a8GrVEy+zOMp//nRKtFZ0m3+IxsfC/d3abVGhVi68ZeSsoTBnf7nkK6T99BEXG9OF3W3Oz1VzDqT+BwP4aqpU= mat@archlinux diff --git a/static/retro/oneko.gif b/static/retro/oneko.gif new file mode 100644 index 0000000..a009c2c Binary files /dev/null and b/static/retro/oneko.gif differ diff --git a/static/retro/pressstart2p.ttf b/static/retro/pressstart2p.ttf new file mode 100644 index 0000000..2442aff Binary files /dev/null and b/static/retro/pressstart2p.ttf differ