1
0
Fork 0
mirror of https://github.com/mat-1/matdoesdev.git synced 2025-08-02 14:46:04 +00:00

add website status to retro page

This commit is contained in:
mat 2024-03-19 00:31:42 -05:00
parent 3393d37311
commit 6647fb94f4
9 changed files with 690 additions and 59 deletions

View file

@ -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}
}

View file

@ -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',
}
</script>
<table id="main-table">
<tr>
<td>
<td valign="top" class="left-sidebar-container" width="200">
<table class="left-sidebar" width="200" cellspacing="0">
<tr>
<td class="website-status-container">
<h3>Website status</h3>
<div class="website-status-value">
<table>
<tr>
<td>probably</td>
<td><span class="status-up">Up!</span></td>
</tr>
</table>
</div>
</td>
</tr>
<tr class="spacing"></tr>
<tr>
<td class="current-time-cst-container">
<h3>Current time for me</h3>
<div class="current-time-cst-value">{currentTimeInCst}</div>
<div class="current-time-cst-info">(CST)</div>
</td>
</tr>
<tr class="spacing"></tr>
<tr>
<td class="active-minecraft-servers-container">
<h3>Active Minecraft servers</h3>
<div class="active-minecraft-servers-value">
<a
href="https://grafana.scanner.matdoes.dev/d/MVK-dYM4z/scanner-stats?orgId=1&refresh=1m"
>
{status.active_minecraft_servers.toLocaleString()}
</a>
</div>
</td>
</tr>
<tr class="spacing"></tr>
<tr>
<td class="days-since-last-complaint-container">
<h3>Days since last complaint</h3>
<div class="days-since-last-complaint-value">
{daysSinceLastComplaint.toString().padStart(3, '0')}
</div>
<div class="time-ago">({hoursSinceLastComplaint} hours ago)</div>
</td>
</tr>
<tr class="spacing"></tr>
<tr>
<td class="neko-status-container">
<div class="neko-status-title-container">
<h3>Neko status</h3>
<div
id="oneko"
aria-hidden="true"
style="background-image: url(/retro/oneko.gif)"
bind:this={nekoEl}
></div>
</div>
<div class="neko-status-value">
<span class="status-{nekoSpriteIdsToStatuses[nekoSpriteName]}"
>{nekoSpriteIdsToNames[nekoSpriteName]}</span
>
</div>
</td>
</tr>
<tr class="spacing"></tr>
<tr>
<td class="minecraft-uuids-scraped-container">
<h3>Minecraft UUIDs scraped</h3>
<div class="minecraft-uuids-scraped-value">
<a href="https://mowojang.matdoes.dev">
{status.minecraft_uuids_scraped.toLocaleString()}
</a>
</div>
</td>
</tr>
<tr class="spacing"></tr>
<tr>
<td class="buttons-scraped-container">
<h3>88x31s scraped</h3>
<div class="buttons-scraped-value">
<a href="/buttons">{status.buttons_scraped.toLocaleString()}</a>
</div>
</td>
</tr>
<tr class="spacing"></tr>
<tr>
<td class="data-last-updated">
<h3>Data last updated</h3>
<div class="data-last-updated-date">
{browser ? `${timeAgo(lastUpdatedAt)} ago` : lastUpdatedAt.toISOString()}
</div>
</td>
</tr>
</table>
</td>
<td valign="top">
<div id="welcome">
<table>
<tr>
@ -88,55 +276,50 @@
</td>
</tr>
</table>
<table id="sections">
<tr>
<td class="section contact">
<table width="300">
<tr>
<td>
<div><img src={contact} alt="contact" width="200" height="40" /></div>
<p>
my preferred method of contact is <a
href="https://matrix.to/#/@mat:matdoes.dev">matrix</a
>, but you can also email me (i have a catch-all on this domain). i'm also on
<a href="https://f.matdoes.dev/mat">the fediverse</a>.
</p>
</td>
</tr>
</table>
</td>
<td class="section links">
<table width="300">
<tr>
<td>
<div><img src={links} alt="links" width="200" height="40" /></div>
<p>
i have a github at <a href="https://github.com/mat-1">github.com/mat-1</a>,
and you can give me money through ko-fi at
<a href="https://ko-fi.com/matdoesdev">ko-fi.com/matdoesdev</a>.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</td>
<td>
<div class="sidebar-list">
<td valign="top">
<div class="right-sidebar">
<h2>BLOG POSTS</h2>
{#each posts as post}
<div><a href={post.slug}>{post.title}</a></div>
{/each}
</div>
</td>
</tr>
<tr>
<td>
<table id="sections">
<tr>
<td class="section contact">
<table width="300">
<tr>
<td>
<div><img src={contact} alt="contact" width="200" height="40" /></div>
<p>
my preferred method of contact is <a href="https://matrix.to/#/@mat:matdoes.dev"
>matrix</a
>, but you can also email me (i have a catch-all on this domain). i'm also on
<a href="https://f.matdoes.dev/mat">the fediverse</a>.
</p>
</td>
</tr>
</table>
</td>
<td class="section links">
<table width="300">
<tr>
<td>
<div><img src={links} alt="links" width="200" height="40" /></div>
<p>
i have a github at <a href="https://github.com/mat-1">github.com/mat-1</a>, and
you can give me money through ko-fi at
<a href="https://ko-fi.com/matdoesdev">ko-fi.com/matdoesdev</a>.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td>
<div class="sidebar-list">
<div class="right-sidebar">
<h2>PROJECTS</h2>
{#each projects as project}
<div><a href={project.href}>{project.name}</a></div>
@ -145,19 +328,25 @@
</td>
</tr>
<tr>
<td></td>
<td> </td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>
<img src="//counter.matdoes.dev" alt="visitor counter" id="counter" />
</td>
</tr>
<tr><td><p class="last-updated">Page last updated: February 24, 2024</p></td></tr>
<tr><td><p class="last-updated">Page last updated: March 19, 2024</p></td></tr>
</table>
<style>
.sidebar-list {
.right-sidebar {
text-align: right;
height: 400px;
overflow-y: scroll;
padding-right: 10px;
}
#main-table {
@ -167,6 +356,7 @@
#welcome {
margin: 0 auto;
max-width: 600px;
height: 100%;
}
h1 {
@ -187,6 +377,8 @@
#sections {
width: 100%;
table-layout: fixed;
margin-top: 8em;
}
.section p {
max-width: 300px;
@ -206,4 +398,128 @@
text-align: center;
font-family: monospace;
}
.left-sidebar {
width: 200px;
text-align: center;
}
.left-sidebar-container {
width: 200px;
}
.left-sidebar > tr > td {
background-color: #000;
border: 2px solid #222;
border-radius: 8px;
padding: 0.25em;
}
.days-since-last-complaint-container {
text-align: center;
}
.left-sidebar h3 {
margin-top: 0;
margin-bottom: 0.2em;
}
.days-since-last-complaint-value {
font-family: 'Courier New', Courier, monospace;
color: #11151c;
background: #bfbdb6;
width: fit-content;
margin: 0 auto;
padding: 0 0.15em;
border: 1px solid #000;
border-radius: 4px;
font-size: 1.5em;
vertical-align: 4px;
text-shadow: 2px 2px 0 #999;
}
.left-sidebar .spacing {
height: 12px;
}
.days-since-last-complaint-container .time-ago {
color: #999;
font-size: 0.8em;
}
.data-last-updated {
font-family: monospace;
}
.data-last-updated-date {
color: #eee;
}
.current-time-cst-container {
text-align: center;
}
.current-time-cst-value {
color: #aad94c;
}
.current-time-cst-info {
color: #acb6bf8c;
}
.website-status-container {
text-align: center;
}
.website-status-value {
color: #555;
vertical-align: middle;
}
.status-up,
.status-idle,
.status-down {
font-size: 1.5em;
font-family: 'Press Start 2P';
margin-left: 0.1em;
}
.status-up {
color: #0f0;
text-shadow:
0 0 2px #0f0,
0 0 2px #0f0,
0 0 2px #0f0,
0 0 2px #0f0;
}
.status-idle {
color: #ff0;
text-shadow:
0 0 2px #ff0,
0 0 2px #ff0,
0 0 2px #ff0,
0 0 2px #ff0;
}
.status-down {
color: #f00;
text-shadow:
0 0 2px #f00,
0 0 2px #f00,
0 0 2px #f00,
0 0 2px #f00;
}
.neko-status-title-container h3 {
display: inline-block;
}
#oneko {
display: inline-block;
}
.left-sidebar a {
/* don't make the links obviously clickable */
text-decoration: none;
color: inherit !important;
}
.active-minecraft-servers-value,
.minecraft-uuids-scraped-value,
.buttons-scraped-value {
border: 2px solid #333;
background-color: #111;
border-radius: 8px;
max-width: fit-content;
margin: 0 auto;
padding: 1px;
color: #d2a6ff;
}
</style>

View file

@ -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,
}
}

View file

@ -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;

218
src/routes/retro/oneko.ts Normal file
View file

@ -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
}

View file

@ -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')
}

1
static/id_rsa.pub Normal file
View file

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCou7N2XEEDrWfr3HTcge3UNbjtX5gMfPiNfLf1/0twK56mqqBGbtEARurq+pi1H42jQ3g3TAtUDtz2yZhelvfVJGX3oZJGDQ3GR8SPziqFMZRF16rNDt73QPZXPScoqwAQNAIHUdSxSo0CkOfpg2mMq9uZ0c5I4UF/LMh8Y7szek7iW9CaALWuJgoP46Jb35sOqK1ODFSMgsT1kPp7oZIbaO6X7nLJ6nDFX3R8mIIxQtJ0bK1huZ3iiLyeR7ZjY/l7rC6k21XNi3JC2La5w9IuNO2kR0kTaHBsAgwlC1BWoD7M0QUEIhvQYymPgTOb0L92xIfE8v0zembn+A06ir55ihhnwHJ0KdlKfcUZr6w8Lb9wI3XOdL2OmA1P9La9O+V4b+PW0FUWFV3lCZY+g/USoQx9nsyphZsXe7ZUPY8By5a8GrVEy+zOMp//nRKtFZ0m3+IxsfC/d3abVGhVi68ZeSsoTBnf7nkK6T99BEXG9OF3W3Oz1VzDqT+BwP4aqpU= mat@archlinux

BIN
static/retro/oneko.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.