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:
parent
3393d37311
commit
6647fb94f4
9 changed files with 690 additions and 59 deletions
96
Caddyfile
96
Caddyfile
|
@ -1,6 +1,6 @@
|
||||||
(https_redirect) {
|
(https_redirect) {
|
||||||
@do_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
|
protocol http
|
||||||
}
|
}
|
||||||
redir @do_https_redirect https://{host}{uri}
|
redir @do_https_redirect https://{host}{uri}
|
||||||
|
@ -22,7 +22,8 @@
|
||||||
uri @do_gif_redirect path_regexp \.png$ .gif
|
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 https_redirect
|
||||||
import gif_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 header_regexp not_chrome User-Agent Googlebot/|eightyeightthirtyone
|
||||||
not path /dot_git/*
|
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
|
# easter egg that makes old browsers show the retro page
|
||||||
@retro_redirect {
|
@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
|
root /opt/x227f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
route /buttons/stats.json {
|
||||||
|
uri strip_prefix /buttons
|
||||||
|
file_server {
|
||||||
|
root /opt/x227f
|
||||||
|
}
|
||||||
|
}
|
||||||
handle_path /buttons/i/* {
|
handle_path /buttons/i/* {
|
||||||
try_files {path} {path}.png {path}.gif {path}.jpg {path}.webp {path}.avif {path}.bmp
|
try_files {path} {path}.png {path}.gif {path}.jpg {path}.webp {path}.avif {path}.bmp
|
||||||
root * /opt/x227f/buttons
|
root * /opt/x227f/buttons
|
||||||
file_server
|
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 {
|
handle_errors {
|
||||||
@should_be_404 {
|
@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) {
|
(matrix_media_proxy) {
|
||||||
handle /_matrix/media/*/download/matdoes.dev/discord_* {
|
handle /_matrix/media/*/download/matdoes.dev/discord_* {
|
||||||
header Access-Control-Allow-Origin *
|
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 {
|
matrix.matdoes.dev, matrix.matdoes.dev:8448 {
|
||||||
import matrix_media_proxy
|
handle /.well-known/matrix/server {
|
||||||
|
header content-type application/json
|
||||||
handle {
|
respond "{\"m.server\":\"matrix.matdoes.dev\"}"
|
||||||
|
}
|
||||||
|
handle /.well-known/matrix/client {
|
||||||
reverse_proxy 127.0.0.1:81
|
reverse_proxy 127.0.0.1:81
|
||||||
}
|
}
|
||||||
}
|
|
||||||
matrix.matdoes.dev:8448 {
|
|
||||||
import matrix_media_proxy
|
|
||||||
|
|
||||||
handle {
|
handle {
|
||||||
reverse_proxy 127.0.0.1:8449
|
reverse_proxy 127.0.0.1:6167
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
stats.matrix.matdoes.dev {
|
stats.matrix.matdoes.dev {
|
||||||
reverse_proxy 127.0.0.1:81
|
reverse_proxy 127.0.0.1:81
|
||||||
}
|
}
|
||||||
|
@ -201,6 +248,24 @@ s.matdoes.dev {
|
||||||
mail.matdoes.dev {
|
mail.matdoes.dev {
|
||||||
respond "mat's mail server :)"
|
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 {
|
matdoes.dev:3 {
|
||||||
header {
|
header {
|
||||||
colon3 :3
|
colon3 :3
|
||||||
|
@ -211,6 +276,11 @@ matdoes.dev:3 {
|
||||||
respond ":3
|
respond ":3
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meowww.matdoes.dev {
|
||||||
|
reverse_proxy 127.0.0.1:11351
|
||||||
|
}
|
||||||
|
|
||||||
hetzner.matdoes.dev {
|
hetzner.matdoes.dev {
|
||||||
redir https://matdoes.dev{uri}
|
redir https://matdoes.dev{uri}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,204 @@
|
||||||
import links from './links.gif'
|
import links from './links.gif'
|
||||||
import projects from '../_projects.json'
|
import projects from '../_projects.json'
|
||||||
|
|
||||||
|
import { initNeko } from './oneko'
|
||||||
|
|
||||||
import type { BlogPostPreview } from '../blog.json/+server.js'
|
import type { BlogPostPreview } from '../blog.json/+server.js'
|
||||||
import Button from './Button.svelte'
|
import Button from './Button.svelte'
|
||||||
|
import { browser } from '$app/environment'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
export let data
|
export let data
|
||||||
export let posts: BlogPostPreview[] = data.posts
|
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>
|
</script>
|
||||||
|
|
||||||
<table id="main-table">
|
<table id="main-table">
|
||||||
<tr>
|
<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">
|
<div id="welcome">
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -88,55 +276,50 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td valign="top">
|
||||||
<div class="sidebar-list">
|
<div class="right-sidebar">
|
||||||
<h2>BLOG POSTS</h2>
|
<h2>BLOG POSTS</h2>
|
||||||
{#each posts as post}
|
{#each posts as post}
|
||||||
<div><a href={post.slug}>{post.title}</a></div>
|
<div><a href={post.slug}>{post.title}</a></div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
<div class="right-sidebar">
|
||||||
</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">
|
|
||||||
<h2>PROJECTS</h2>
|
<h2>PROJECTS</h2>
|
||||||
{#each projects as project}
|
{#each projects as project}
|
||||||
<div><a href={project.href}>{project.name}</a></div>
|
<div><a href={project.href}>{project.name}</a></div>
|
||||||
|
@ -145,19 +328,25 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td> </td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
<td>
|
<td>
|
||||||
<img src="//counter.matdoes.dev" alt="visitor counter" id="counter" />
|
<img src="//counter.matdoes.dev" alt="visitor counter" id="counter" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
</table>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.sidebar-list {
|
.right-sidebar {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
padding-right: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-table {
|
#main-table {
|
||||||
|
@ -167,6 +356,7 @@
|
||||||
#welcome {
|
#welcome {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
@ -187,6 +377,8 @@
|
||||||
|
|
||||||
#sections {
|
#sections {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
margin-top: 8em;
|
||||||
}
|
}
|
||||||
.section p {
|
.section p {
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
|
@ -206,4 +398,128 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: monospace;
|
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>
|
</style>
|
||||||
|
|
|
@ -4,8 +4,10 @@ export const prerender = true
|
||||||
|
|
||||||
export const load: Load = async ({ fetch }) => {
|
export const load: Load = async ({ fetch }) => {
|
||||||
const posts = await fetch('/blog.json').then((r: Response) => r.json())
|
const posts = await fetch('/blog.json').then((r: Response) => r.json())
|
||||||
|
const status = await fetch('/status.json').then((r: Response) => r.json())
|
||||||
|
|
||||||
return {
|
return {
|
||||||
posts,
|
posts,
|
||||||
|
status,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,10 @@ body,
|
||||||
font-family: 'Comic Sans MS';
|
font-family: 'Comic Sans MS';
|
||||||
src: url(/retro/comicsans.ttf) format('truetype');
|
src: url(/retro/comicsans.ttf) format('truetype');
|
||||||
}
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Press Start 2P';
|
||||||
|
src: url(/retro/pressstart2p.ttf) format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background-color-alt: rgba(255, 255, 255, 0.05) !important;
|
--background-color-alt: rgba(255, 255, 255, 0.05) !important;
|
||||||
|
@ -47,6 +51,16 @@ h2 {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#oneko {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
z-index: 2147483647;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
#main-title:after {
|
#main-title:after {
|
||||||
content: 'silly edition';
|
content: 'silly edition';
|
||||||
display: block;
|
display: block;
|
||||||
|
|
218
src/routes/retro/oneko.ts
Normal file
218
src/routes/retro/oneko.ts
Normal 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
|
||||||
|
}
|
10
src/routes/status.json/+server.ts
Normal file
10
src/routes/status.json/+server.ts
Normal 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
1
static/id_rsa.pub
Normal 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
BIN
static/retro/oneko.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
BIN
static/retro/pressstart2p.ttf
Normal file
BIN
static/retro/pressstart2p.ttf
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue