add gravity easter egg
25
Caddyfile
|
@ -1,6 +1,7 @@
|
||||||
(https_redirect) {
|
(https_redirect) {
|
||||||
@do_https_redirect {
|
@do_https_redirect {
|
||||||
not header_regexp veryoldbrowser User-Agent Navigator|MSIE|Mosaic|Kindle|^curl|NintendoBrowser/
|
not header_regexp veryoldbrowser User-Agent Navigator|MSIE|Mosaic|Kindle|^curl|NintendoBrowser/
|
||||||
|
not host *.onion *.*.onion
|
||||||
protocol http
|
protocol http
|
||||||
}
|
}
|
||||||
redir @do_https_redirect https://{host}{uri}
|
redir @do_https_redirect https://{host}{uri}
|
||||||
|
@ -205,7 +206,7 @@ Disallow: /"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
matrix.matdoes.dev, matrix.matdoes.dev:8448 {
|
matrix.matdoes.dev matrix.matdoes.dev:8448 {
|
||||||
handle /.well-known/matrix/server {
|
handle /.well-known/matrix/server {
|
||||||
header content-type application/json
|
header content-type application/json
|
||||||
respond "{\"m.server\":\"matrix.matdoes.dev\"}"
|
respond "{\"m.server\":\"matrix.matdoes.dev\"}"
|
||||||
|
@ -229,7 +230,7 @@ fedi.matdoes.dev {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.matdoes.dev {
|
f.matdoes.dev media.f.matdoes.dev cache.f.matdoes.dev {
|
||||||
@chrome {
|
@chrome {
|
||||||
header_regexp chrome User-Agent Chrome\/[0-9./]+\s(Mobile\s)?Safari\/[0-9./]+$
|
header_regexp chrome User-Agent Chrome\/[0-9./]+\s(Mobile\s)?Safari\/[0-9./]+$
|
||||||
not header User-Agent *Googlebot/*
|
not header User-Agent *Googlebot/*
|
||||||
|
@ -241,7 +242,9 @@ f.matdoes.dev {
|
||||||
git.matdoes.dev {
|
git.matdoes.dev {
|
||||||
reverse_proxy 127.0.0.1:3000
|
reverse_proxy 127.0.0.1:3000
|
||||||
}
|
}
|
||||||
s.matdoes.dev {
|
http://s.matdoes.dev https://s.matdoes.dev {
|
||||||
|
import https_redirect
|
||||||
|
|
||||||
reverse_proxy 127.0.0.1:28019
|
reverse_proxy 127.0.0.1:28019
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,3 +299,19 @@ hetzner.matdoes.dev {
|
||||||
xmpp.matdoes.dev {
|
xmpp.matdoes.dev {
|
||||||
respond ""
|
respond ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mcassets.matdoes.dev {
|
||||||
|
reverse_proxy 127.0.0.1:10573
|
||||||
|
}
|
||||||
|
|
||||||
|
www.www.matdoes.dev {
|
||||||
|
header {
|
||||||
|
Content-Type text/html
|
||||||
|
Server "meow"
|
||||||
|
}
|
||||||
|
respond "<meta http-equiv=\"refresh\" content=\"15;url=https://matdoes.dev\">nyaaaaaaaaaaaaaaaaaaa" 200
|
||||||
|
}
|
||||||
|
|
||||||
|
www.matdoes.dev {
|
||||||
|
redir https://matdoes.dev{uri}
|
||||||
|
}
|
22
package.json
|
@ -14,38 +14,40 @@
|
||||||
"postinstall": "patch-package"
|
"postinstall": "patch-package"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@img/sharp-linux-x64": "^0.33.2",
|
"@img/sharp-linux-x64": "^0.33.3",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||||
"@types/cookie": "^0.6.0",
|
"@types/cookie": "^0.6.0",
|
||||||
"@types/html-minifier": "^4.0.5",
|
"@types/html-minifier": "^4.0.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
"@types/matter-js": "^0.19.6",
|
||||||
"@typescript-eslint/parser": "^7.3.1",
|
"@typescript-eslint/eslint-plugin": "^7.6.0",
|
||||||
"eslint": "^8.57.0",
|
"@typescript-eslint/parser": "^7.6.0",
|
||||||
|
"eslint": "^9.0.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"mdsvex": "^0.11.0",
|
"mdsvex": "^0.11.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"prettier-plugin-svelte": "^3.2.2",
|
"prettier-plugin-svelte": "^3.2.2",
|
||||||
"sharp": "^0.33.2",
|
"sharp": "^0.33.3",
|
||||||
"svelte": "4.2.12",
|
"svelte": "4.2.12",
|
||||||
"svelte-check": "^3.6.7",
|
"svelte-check": "^3.6.9",
|
||||||
"svelte-preprocess": "^5.1.3",
|
"svelte-preprocess": "^5.1.3",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.4"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lukeed/uuid": "^2.0.1",
|
"@lukeed/uuid": "^2.0.1",
|
||||||
"@sveltejs/adapter-node": "^5.0.1",
|
"@sveltejs/adapter-node": "^5.0.1",
|
||||||
"@sveltejs/adapter-static": "^3.0.1",
|
"@sveltejs/adapter-static": "^3.0.1",
|
||||||
"@sveltejs/kit": "^2.5.4",
|
"@sveltejs/kit": "^2.5.5",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"cbor-x": "^1.5.8",
|
"cbor-x": "^1.5.9",
|
||||||
"cookie": "^0.6.0",
|
"cookie": "^0.6.0",
|
||||||
"html-minifier": "^4.0.0",
|
"html-minifier": "^4.0.0",
|
||||||
|
"matter-js": "^0.19.0",
|
||||||
"patch-package": "^8.0.0",
|
"patch-package": "^8.0.0",
|
||||||
"postinstall-postinstall": "^2.1.0",
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"svelte-body": "^1.4.0",
|
"svelte-body": "^1.4.0",
|
||||||
"vite": "5.1.6"
|
"vite": "5.2.8"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
|
|
|
@ -19,6 +19,12 @@
|
||||||
--text-font: 'Atkinson Hyperlegible', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
--text-font: 'Atkinson Hyperlegible', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#page {
|
#page {
|
||||||
|
|
157
src/lib/gravity.ts
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
import { browser } from '$app/environment'
|
||||||
|
import Matter from 'matter-js'
|
||||||
|
|
||||||
|
const GRAVITY_QUERY_SELECTOR = 'p, h1, h2, .button, .icon'
|
||||||
|
|
||||||
|
export function initGravity(): () => void {
|
||||||
|
const { Engine, Bodies, Composite, Runner, Mouse, MouseConstraint } = Matter
|
||||||
|
|
||||||
|
console.log('gravity enabled')
|
||||||
|
|
||||||
|
// create an engine
|
||||||
|
const engine = Engine.create({ enableSleeping: true })
|
||||||
|
|
||||||
|
const floorBody = Bodies.rectangle(
|
||||||
|
window.innerWidth / 2,
|
||||||
|
window.innerHeight - 5,
|
||||||
|
window.innerWidth,
|
||||||
|
10,
|
||||||
|
{
|
||||||
|
isStatic: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Composite.add(engine.world, [floorBody])
|
||||||
|
|
||||||
|
const trackedElements = new Map<HTMLElement, Matter.Body>()
|
||||||
|
|
||||||
|
async function enableGravityOnElement(el: HTMLElement) {
|
||||||
|
await new Promise((resolve) => requestAnimationFrame(resolve))
|
||||||
|
|
||||||
|
let originalTextContent: string | null = null
|
||||||
|
if (el.classList.contains('copyright') || el.id === 'main-title') {
|
||||||
|
el.style.width = 'fit-content'
|
||||||
|
el.style.margin = '0 auto'
|
||||||
|
if (el.id === 'main-title') {
|
||||||
|
originalTextContent = el.textContent
|
||||||
|
el.textContent = 'matdoesdev'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const bb = el.getBoundingClientRect()
|
||||||
|
if (originalTextContent) el.textContent = originalTextContent
|
||||||
|
|
||||||
|
const body = Bodies.rectangle(
|
||||||
|
bb.left + bb.width / 2,
|
||||||
|
bb.top + bb.height / 2,
|
||||||
|
bb.width,
|
||||||
|
bb.height,
|
||||||
|
{ restitution: 1 }
|
||||||
|
)
|
||||||
|
el.style.position = 'absolute'
|
||||||
|
el.style.left = bb.left + 'px'
|
||||||
|
el.style.top = bb.top + 'px'
|
||||||
|
el.style.width = bb.width + 'px'
|
||||||
|
el.style.height = bb.height + 'px'
|
||||||
|
el.style.userSelect = 'none'
|
||||||
|
// el.style.outline = '1px solid red'
|
||||||
|
|
||||||
|
Composite.add(engine.world, [body])
|
||||||
|
trackedElements.set(el, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeAnchorUndraggable(el: HTMLAnchorElement) {
|
||||||
|
// el.style.pointerEvents = 'none'
|
||||||
|
// el.style.cursor = 'pointer'
|
||||||
|
el.draggable = false
|
||||||
|
|
||||||
|
// document.addEventListener('click', (e) => {
|
||||||
|
// el.style.pointerEvents = ''
|
||||||
|
// const clickedEl = document.elementFromPoint(e.clientX, e.clientY)
|
||||||
|
// el.style.pointerEvents = 'none'
|
||||||
|
// // if (clickedEl instanceof HTMLAnchorElement) {
|
||||||
|
// // e.preventDefault()
|
||||||
|
// // goto(clickedEl.href)
|
||||||
|
// // }
|
||||||
|
// if (el.contains(clickedEl)) {
|
||||||
|
// e.preventDefault()
|
||||||
|
// const url = new URL(el.href)
|
||||||
|
// if (url.origin === window.location.origin) {
|
||||||
|
// goto(el.href)
|
||||||
|
// } else {
|
||||||
|
// window.location.href = el.href
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll(GRAVITY_QUERY_SELECTOR).forEach((el) => {
|
||||||
|
enableGravityOnElement(el as HTMLElement)
|
||||||
|
})
|
||||||
|
document.querySelectorAll('a').forEach((el) => {
|
||||||
|
makeAnchorUndraggable(el as HTMLAnchorElement)
|
||||||
|
})
|
||||||
|
|
||||||
|
// update positions every frame
|
||||||
|
Matter.Events.on(engine, 'beforeUpdate', function (event) {
|
||||||
|
trackedElements.forEach((body, el) => {
|
||||||
|
if (body.position.y < 0) {
|
||||||
|
// move up
|
||||||
|
body.position.y = window.innerHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
el.style.left = body.position.x - el.offsetWidth / 2 + 'px'
|
||||||
|
el.style.top = body.position.y - el.offsetHeight / 2 + 'px'
|
||||||
|
el.style.transform = `rotate(${body.angle}rad)`
|
||||||
|
|
||||||
|
// rotate
|
||||||
|
// el.style.transform = ''
|
||||||
|
|
||||||
|
// const [originalWidthStyle, originalHeightStyle] = [el.style.width, el.style.height]
|
||||||
|
// el.style.maxWidth = originalWidthStyle
|
||||||
|
// el.style.maxHeight = originalHeightStyle
|
||||||
|
// el.style.width = ''
|
||||||
|
// el.style.height = ''
|
||||||
|
// const bb = el.getBoundingClientRect()
|
||||||
|
// el.style.transform = `rotate(${body.angle}rad)`
|
||||||
|
// el.style.width = originalWidthStyle
|
||||||
|
// el.style.height = originalHeightStyle
|
||||||
|
// el.style.maxWidth = ''
|
||||||
|
// el.style.maxHeight = ''
|
||||||
|
|
||||||
|
// const fakeBody = Bodies.rectangle(
|
||||||
|
// bb.left + bb.width / 2,
|
||||||
|
// bb.top + bb.height / 2,
|
||||||
|
// bb.width,
|
||||||
|
// bb.height,
|
||||||
|
// {
|
||||||
|
// angle: body.angle,
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
// body.vertices = fakeBody.vertices
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// create runner
|
||||||
|
const runner = Runner.create()
|
||||||
|
|
||||||
|
// run the engine
|
||||||
|
Runner.run(runner, engine)
|
||||||
|
|
||||||
|
// add mouse control
|
||||||
|
var mouse = Mouse.create(document.body),
|
||||||
|
mouseConstraint = MouseConstraint.create(engine, {
|
||||||
|
mouse: mouse,
|
||||||
|
constraint: {
|
||||||
|
stiffness: 0.2,
|
||||||
|
render: {
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
Composite.add(engine.world, mouseConstraint)
|
||||||
|
|
||||||
|
console.log('running engine')
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
Runner.stop(runner)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
import '../../app.css'
|
import '../../app.css'
|
||||||
import { fly } from 'svelte/transition'
|
import { fly } from 'svelte/transition'
|
||||||
import type { LayoutData } from '../$types'
|
import type { LayoutData } from '../$types'
|
||||||
|
import { browser } from '$app/environment'
|
||||||
|
|
||||||
export let data: LayoutData
|
export let data: LayoutData
|
||||||
|
|
||||||
|
@ -11,7 +12,7 @@
|
||||||
let previousPathname = data.pathname
|
let previousPathname = data.pathname
|
||||||
let currentPathName = data.pathname
|
let currentPathName = data.pathname
|
||||||
let flyDirection = 1 // 1 is right, -1 is left
|
let flyDirection = 1 // 1 is right, -1 is left
|
||||||
$: {
|
$: if (browser) {
|
||||||
if (previousPathname !== currentPathName) previousPathname = currentPathName
|
if (previousPathname !== currentPathName) previousPathname = currentPathName
|
||||||
currentPathName = data.pathname
|
currentPathName = data.pathname
|
||||||
|
|
||||||
|
@ -19,6 +20,35 @@
|
||||||
if (previousPathname === '/') flyDirection = 1
|
if (previousPathname === '/') flyDirection = 1
|
||||||
else if (previousPathname === '/blog' && currentPathName !== '/') flyDirection = 1
|
else if (previousPathname === '/blog' && currentPathName !== '/') flyDirection = 1
|
||||||
else flyDirection = -1
|
else flyDirection = -1
|
||||||
|
onPathChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
let pathChangeTimestamps: number[] = []
|
||||||
|
|
||||||
|
let stopGravity: (() => void) | null = null
|
||||||
|
|
||||||
|
async function onPathChange() {
|
||||||
|
// if we switched paths more than 10 times in the past 5 seconds, import $lib/gravity.js
|
||||||
|
pathChangeTimestamps.push(Date.now())
|
||||||
|
while (pathChangeTimestamps[0] < Date.now() - 5000) pathChangeTimestamps.shift()
|
||||||
|
console.log(pathChangeTimestamps)
|
||||||
|
if (pathChangeTimestamps.length >= 10) {
|
||||||
|
const { initGravity } = await import('$lib/gravity')
|
||||||
|
// wait 200ms for the animation to finish
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||||
|
// and an animation frame
|
||||||
|
await new Promise((resolve) => requestAnimationFrame(resolve))
|
||||||
|
|
||||||
|
const lastPathChangeTimestamp = pathChangeTimestamps[pathChangeTimestamps.length - 1]
|
||||||
|
// only if it was over 200ms ago
|
||||||
|
if (lastPathChangeTimestamp < Date.now() - 200) {
|
||||||
|
if (currentPathName === '/') {
|
||||||
|
stopGravity = initGravity()
|
||||||
|
} else {
|
||||||
|
stopGravity?.()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -33,15 +63,12 @@
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>© {copyrightYear} mat</p>
|
<p class="copyright">© {copyrightYear} mat</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
{/key}
|
{/key}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* :global(body) {
|
|
||||||
overflow: hidden;
|
|
||||||
} */
|
|
||||||
#page {
|
#page {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1,014 B |
Before Width: | Height: | Size: 292 B After Width: | Height: | Size: 261 B |