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',
+ }
-
+ |
+
+
+
+
+
+
+
+
+
+
+ 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.
+
+ |
+
+
+ |
+
+
+ |
+
+
|
-
- |
-
-
-
-
-
-
-
-
-
-
- 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.
-
- |
-
-
- |
-
-
- |
-
-
- |
-
- |
+ |
+ |
+ |
+ |
+
+
+ |
|
- 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