Compare commits
1 commit
09e377b9d2
...
0aa83f7a86
Author | SHA1 | Date | |
---|---|---|---|
0aa83f7a86 |
37 changed files with 2513 additions and 683 deletions
48
.gitignore
vendored
48
.gitignore
vendored
|
@ -1,34 +1,24 @@
|
|||
# dependencies (bun install)
|
||||
node_modules
|
||||
|
||||
# output
|
||||
out
|
||||
dist
|
||||
*.tgz
|
||||
|
||||
# code coverage
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# logs
|
||||
# Logs
|
||||
logs
|
||||
_.log
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# caches
|
||||
.eslintcache
|
||||
.cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# IntelliJ based IDEs
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
|
13
README.md
13
README.md
|
@ -1,15 +1,2 @@
|
|||
# ipv4.army
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.2.11. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||
|
|
35
biome.json
Normal file
35
biome.json
Normal file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": false
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": true,
|
||||
"ignore": []
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab",
|
||||
"lineEnding": "lf"
|
||||
},
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double",
|
||||
"indentStyle": "tab",
|
||||
"lineEnding": "lf",
|
||||
"jsxQuoteStyle": "double",
|
||||
"semicolons": "always"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
[loader]
|
||||
".woff2" = "file"
|
15
index.html
Normal file
15
index.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<html lang="en" data-bs-theme="dark" data-bs-core="default">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="dark">
|
||||
<meta name="description" content="A Dedicated Backend Developer." />
|
||||
<title>Seth @ IPv4 dot Army</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.tsx" defer></script>
|
||||
</body>
|
||||
</html>
|
247
index.ts
247
index.ts
|
@ -1,247 +0,0 @@
|
|||
import { build, serve, gzipSync, file, type BunRequest, gc } from "bun";
|
||||
|
||||
import pkg from "./package.json";
|
||||
|
||||
const development = process.env.NODE_ENV === "development";
|
||||
|
||||
let heartrate = 0;
|
||||
let lanyard = {};
|
||||
|
||||
require("node:fs/promises").rm("./dist", { recursive: true, force: true }).catch(() => {
|
||||
// ignore
|
||||
});
|
||||
|
||||
const buildWeb = async () => {
|
||||
return await build({
|
||||
entrypoints: ['./src/index.html'],
|
||||
outdir: './dist',
|
||||
minify: !development,
|
||||
sourcemap: (development ? "inline" : "none"),
|
||||
splitting: true,
|
||||
publicPath: "/assets/",
|
||||
loader: {
|
||||
".woff2": "file"
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (!development) {
|
||||
await buildWeb()
|
||||
}
|
||||
|
||||
const server = serve({
|
||||
routes: {
|
||||
"/": async () => {
|
||||
if (development) {
|
||||
await buildWeb()
|
||||
}
|
||||
return new Response(gzipSync(await file("./dist/index.html").arrayBuffer()), {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
"Cache-Control": "no-cache",
|
||||
"Content-Encoding": "gzip",
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
"/assets/:file": async (req: BunRequest<"/assets/:file">) => {
|
||||
const reqFile = file(`./dist/${req.params.file}`)
|
||||
return new Response(gzipSync(await reqFile.arrayBuffer()), {
|
||||
headers: {
|
||||
"Content-Type": reqFile.type,
|
||||
"Cache-Control": "public, max-age=31536000",
|
||||
"Content-Encoding": "gzip",
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
"/public/:file": async (req: BunRequest<"/public/:file">) => {
|
||||
const reqFile = file(`./public/${req.params.file}`)
|
||||
let fileRes = await reqFile.text()
|
||||
|
||||
fileRes = fileRes.replace("{{LANYARD}}", `${JSON.stringify({ example: "lanyard data" })}`)
|
||||
fileRes = fileRes.replace("{{HYPERATE}}", `${JSON.stringify({ example: "hyperate data" })}`)
|
||||
|
||||
return new Response(gzipSync(fileRes), {
|
||||
headers: {
|
||||
"Content-Type": reqFile.type,
|
||||
"Cache-Control": "public, max-age=31536000",
|
||||
"Content-Encoding": "gzip",
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
"/public/font/:file": async (req: BunRequest<"/public/font/:file">) => {
|
||||
const reqFile = file(`./public/font/${req.params.file}`)
|
||||
return new Response(gzipSync(await reqFile.arrayBuffer()), {
|
||||
headers: {
|
||||
"Content-Type": reqFile.type,
|
||||
"Cache-Control": "public, max-age=31536000",
|
||||
"Content-Encoding": "gzip",
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
"/api/server": () => {
|
||||
const string = JSON.stringify(process)
|
||||
const json = JSON.parse(string)
|
||||
|
||||
// clear possibly data that could be sensitive
|
||||
json.argv = {}
|
||||
json.debugPort = 0
|
||||
json.env = {}
|
||||
json.execArgv = []
|
||||
json.execPath = ""
|
||||
json.stderr = {}
|
||||
json.stdin = {}
|
||||
json.stdout = {}
|
||||
json.title = ""
|
||||
|
||||
json.availableMemory = process.availableMemory()
|
||||
json.constrainedMemory = process.constrainedMemory()
|
||||
json.cpuUsage = process.cpuUsage()
|
||||
json.memoryUsage = process.memoryUsage()
|
||||
json.uptime = process.uptime()
|
||||
json.package = pkg
|
||||
|
||||
return new Response(gzipSync(JSON.stringify({ data: json })), {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Cache-Control": "no-cache",
|
||||
"Content-Encoding": "gzip",
|
||||
}
|
||||
})
|
||||
},
|
||||
"/api/health": () => {
|
||||
return new Response(gzipSync(JSON.stringify({ data: "ok" })), {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Cache-Control": "no-cache",
|
||||
"Content-Encoding": "gzip",
|
||||
}
|
||||
})
|
||||
},
|
||||
"/api/ws": (req, server) => {
|
||||
if (server.upgrade(req)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new Response("Upgrade failed", { status: 500 });
|
||||
},
|
||||
"/api/gc": () => {
|
||||
gc(true)
|
||||
return new Response(gzipSync(JSON.stringify({ data: "triggered" })), {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Cache-Control": "no-cache",
|
||||
"Content-Encoding": "gzip",
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
websocket: {
|
||||
open: async (ws) => {
|
||||
ws.subscribe("lanyard");
|
||||
ws.send(JSON.stringify({ type: "lanyard", data: lanyard }), true);
|
||||
|
||||
ws.subscribe("hyperate");
|
||||
ws.send(JSON.stringify({ type: "hyperate", data: { hr: heartrate } }), true);
|
||||
},
|
||||
message: async (ws, message) => {
|
||||
ws.send(JSON.stringify({ type: "echo", data: message }), true)
|
||||
},
|
||||
close: async (ws) => {
|
||||
console.log("WebSocket closed", ws.id);
|
||||
},
|
||||
drain: async (ws) => {
|
||||
console.log("WebSocket drain", ws.id);
|
||||
},
|
||||
},
|
||||
development,
|
||||
port: 3000,
|
||||
});
|
||||
|
||||
const lanyardSocket = new WebSocket("wss://lanyard.creations.works/socket");
|
||||
|
||||
const setLanyard = (data: object) => {
|
||||
lanyard = data;
|
||||
|
||||
return server.publish("lanyard", JSON.stringify({ type: "lanyard", data }), true);
|
||||
}
|
||||
|
||||
lanyardSocket.onmessage = ({ data }) => {
|
||||
data = JSON.parse(data);
|
||||
|
||||
switch (data.op) {
|
||||
case 0: {
|
||||
setLanyard(data.d)
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
lanyardSocket.send(JSON.stringify({
|
||||
op: 2,
|
||||
d: {
|
||||
subscribe_to_id: "1273447359417942128"
|
||||
}
|
||||
}))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hyperate = new WebSocket(
|
||||
"wss://app.hyperate.io/socket/websocket?token=wv39nM6iyrNJulvpmMQrimYPIXy2dVrYRjkuHpbRapKT2VSh65ngDGHdCdCtmEN9",
|
||||
);
|
||||
|
||||
let hrTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const setHeartrate = async (hr: number) => {
|
||||
heartrate = hr;
|
||||
|
||||
return server.publish("hyperate", JSON.stringify({ type: "hyperate", data: { hr } }), true);
|
||||
}
|
||||
|
||||
const setHrInterval = () => {
|
||||
hrTimeout = setTimeout(() => {
|
||||
setHeartrate(0);
|
||||
}, 6000);
|
||||
};
|
||||
|
||||
hyperate.onopen = () => {
|
||||
hyperate.send(
|
||||
JSON.stringify({
|
||||
topic: "hr:0BCA",
|
||||
event: "phx_join",
|
||||
payload: {},
|
||||
ref: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
setInterval(() => {
|
||||
hyperate.send(
|
||||
JSON.stringify({
|
||||
topic: "phoenix",
|
||||
event: "heartbeat",
|
||||
payload: {},
|
||||
ref: 0,
|
||||
}),
|
||||
);
|
||||
}, 10000);
|
||||
|
||||
return setHrInterval();
|
||||
};
|
||||
|
||||
hyperate.onmessage = ({ data }) => {
|
||||
const { event, payload } = JSON.parse(data);
|
||||
switch (event) {
|
||||
case "hr_update": {
|
||||
clearTimeout(hrTimeout);
|
||||
setHrInterval();
|
||||
setHeartrate(payload.hr);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
32
package.json
32
package.json
|
@ -1,21 +1,25 @@
|
|||
{
|
||||
"name": "ipv4.army",
|
||||
"module": "index.ts",
|
||||
"name": "ipv4.army-vite",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development bun run --hot . --watch",
|
||||
"start": "bun run ."
|
||||
"dev": "bunx --bun vite",
|
||||
"lint": "bunx biome check --fix --unsafe",
|
||||
"build": "bunx --bun tsc -b && bunx --bun vite build",
|
||||
"preview": "bunx --bun vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"halfmoon": "^2.0.2",
|
||||
"lucide-preact": "^0.487.0",
|
||||
"preact": "^10.26.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"microlight": "^0.0.7",
|
||||
"tsx-dom": "^3.1.0"
|
||||
"@fullhuman/postcss-purgecss": "^7.0.2",
|
||||
"@preact/preset-vite": "^2.10.1",
|
||||
"lightningcss": "^1.29.3",
|
||||
"typescript": "~5.7.2",
|
||||
"vite": "^6.2.0"
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,17 +0,0 @@
|
|||
import { Glob } from "bun";
|
||||
|
||||
const woff2 = new Glob("./*.woff2");
|
||||
|
||||
for await (const file of woff2.scan(".")) {
|
||||
const font = Bun.file(file);
|
||||
|
||||
const name = font.name?.split("-")[1];
|
||||
|
||||
await Bun.write(`./${name}`, await font.arrayBuffer());
|
||||
console.log(`Renamed ${file} to ${name}`);
|
||||
|
||||
await font.delete();
|
||||
console.log(`Deleted original font file: ${file}`);
|
||||
}
|
||||
|
||||
console.log("Done")
|
59
src/App.css
59
src/App.css
|
@ -1,59 +0,0 @@
|
|||
.scanlines {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scanlines:before,
|
||||
.scanlines:after {
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.scanlines:before {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
z-index: 2147483649;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
opacity: 0.75;
|
||||
animation: scanline 6s linear infinite;
|
||||
}
|
||||
|
||||
.scanlines:after {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 2147483648;
|
||||
background: linear-gradient(to bottom,
|
||||
transparent 50%,
|
||||
rgba(0, 0, 0, 0.3) 51%);
|
||||
background-size: 100% 4px;
|
||||
animation: scanlines 1s steps(60) infinite;
|
||||
}
|
||||
|
||||
/* ANIMATE UNIQUE SCANLINE */
|
||||
@keyframes scanline {
|
||||
0% {
|
||||
transform: translate3d(0, 200000%, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scanlines {
|
||||
0% {
|
||||
background-position: 0 50%;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.scanlines {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.microlight>span:nth-child(6) {
|
||||
color: var(--status-color);
|
||||
}
|
15
src/App.tsx
15
src/App.tsx
|
@ -1,15 +0,0 @@
|
|||
import Lanyard from './components/Lanyard';
|
||||
import Hyperate from './components/Hyperate';
|
||||
|
||||
export default () => {
|
||||
return <div>
|
||||
<p>seth> cat ./about.txt</p>
|
||||
<p>A Dedicated Backend Developer, with a passion for high-fidelity audio, gaming, and web development.</p>
|
||||
|
||||
<p>seth> curl /tmp/discord-ipc</p>
|
||||
<p><Lanyard /></p>
|
||||
|
||||
<p>seth> cat /tmp/heartrate</p>
|
||||
<p><Hyperate /></p>
|
||||
</div>
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
const { protocol, host } = window.location;
|
||||
|
||||
class Socket extends EventTarget {
|
||||
private _socket: WebSocket;
|
||||
|
||||
constructor(url: string) {
|
||||
super();
|
||||
|
||||
this._socket = new WebSocket(url);
|
||||
this._socket.onmessage = (event) => {
|
||||
const { type, data } = JSON.parse(event.data);
|
||||
|
||||
switch (type) {
|
||||
case "lanyard": {
|
||||
this.emitLanyard(data);
|
||||
break;
|
||||
}
|
||||
case "hyperate": {
|
||||
this.emitHyperate(data.hr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setInterval(() => {
|
||||
this._socket.send("ping");
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
emitLanyard(lanyard: object) {
|
||||
this.dispatchEvent(new CustomEvent('lanyard', { detail: lanyard }));
|
||||
}
|
||||
emitHyperate(heartRate: number) {
|
||||
this.dispatchEvent(new CustomEvent('hyperate', { detail: heartRate }));
|
||||
}
|
||||
}
|
||||
|
||||
export default new Socket(`${protocol.replace("http", "ws")}//${host}/api/ws`);
|
|
@ -1,19 +0,0 @@
|
|||
import { createRef } from "tsx-dom";
|
||||
import microlight from "microlight";
|
||||
|
||||
import socket from "../../Socket";
|
||||
|
||||
export default () => {
|
||||
const paragraph = createRef<HTMLParagraphElement>();
|
||||
|
||||
socket.addEventListener('hyperate', (event: Event) => {
|
||||
const heartRate = (event as CustomEvent).detail;
|
||||
if (paragraph.current) {
|
||||
paragraph.current.innerText = `${heartRate} BPM`;
|
||||
}
|
||||
microlight.reset();
|
||||
});
|
||||
return <div>
|
||||
<p class="microlight" ref={paragraph}>0 BPM</p>
|
||||
</div>;
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
import { createRef } from "tsx-dom";
|
||||
import microlight from "microlight";
|
||||
|
||||
import socket from "../../Socket";
|
||||
|
||||
const statusTypes: { [key: string]: string } = {
|
||||
online: "green",
|
||||
idle: "yellow",
|
||||
dnd: "red",
|
||||
invisible: "inherent",
|
||||
offline: "inherent",
|
||||
}
|
||||
|
||||
const activityTypes: { [key: number]: string } = {
|
||||
0: "Playing",
|
||||
1: "Streaming",
|
||||
2: "Listening to",
|
||||
3: "Watching",
|
||||
4: "Custom",
|
||||
5: "Competing in",
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const paragraph = createRef<HTMLParagraphElement>();
|
||||
|
||||
socket.addEventListener('lanyard', (event: Event) => {
|
||||
const lanyard = (event as CustomEvent).detail;
|
||||
if (paragraph.current) {
|
||||
paragraph.current.style = `--status-color: ${statusTypes[lanyard.discord_status]};`;
|
||||
paragraph.current.innerText = JSON.stringify({
|
||||
status: lanyard.discord_status,
|
||||
activities: lanyard.activities.map((act: { type: number, name: string }) => { return `${activityTypes[act.type]} ${act.name}` }),
|
||||
}, null, 1);
|
||||
}
|
||||
microlight.reset();
|
||||
});
|
||||
return <div>
|
||||
<p class="microlight" ref={paragraph}>{JSON.stringify({})}</p>
|
||||
</div>;
|
||||
}
|
0
src/components/app.css
Normal file
0
src/components/app.css
Normal file
13
src/components/app.tsx
Normal file
13
src/components/app.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import Container from "./container";
|
||||
import Navbar from "./navbar";
|
||||
|
||||
import "./app.css";
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<Container />
|
||||
</>
|
||||
);
|
||||
};
|
49
src/components/container/index.tsx
Normal file
49
src/components/container/index.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { useState } from "preact/hooks";
|
||||
|
||||
import Heart from "../heart";
|
||||
|
||||
const statusMap = {
|
||||
online: "border-success-subtle",
|
||||
idle: "border-warning-subtle",
|
||||
dnd: "border-danger-subtle",
|
||||
offline: "border-light-subtle",
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const [status, setStatus] = useState<keyof typeof statusMap>("offline");
|
||||
|
||||
fetch("https://lanyard.creations.works/v1/users/1273447359417942128")
|
||||
.then((req) => req.json())
|
||||
.then((res) => {
|
||||
if (res.data.discord_status) {
|
||||
setStatus(res.data.discord_status);
|
||||
} else {
|
||||
setStatus("offline");
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="container bg-body-tertiary shadow text-center position-absolute top-50 start-50 translate-middle mx-auto py-4">
|
||||
<img
|
||||
src="favicon.svg"
|
||||
class={`img-thumbnail rounded-circle border border-4 ${statusMap[status]}`}
|
||||
alt="..."
|
||||
width="100px"
|
||||
height="100px"
|
||||
/>
|
||||
<br />
|
||||
<h1>Seth</h1>
|
||||
<h2 class="lead">
|
||||
Dedicated Backend Developer
|
||||
<br />
|
||||
<br />
|
||||
<small class="text-body-secondary">
|
||||
With a passsion for high-fidelity audio, gaming, and web development
|
||||
</small>
|
||||
</h2>
|
||||
<Heart />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
18
src/components/heart/index.css
Normal file
18
src/components/heart/index.css
Normal file
|
@ -0,0 +1,18 @@
|
|||
:root {
|
||||
--bpm: 0;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.heart {
|
||||
animation: pulse calc(60s / var(--bpm)) infinite ease;
|
||||
}
|
71
src/components/heart/index.tsx
Normal file
71
src/components/heart/index.tsx
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { useState } from "preact/hooks";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
export default () => {
|
||||
const [heartrate, setHeartrate] = useState(0);
|
||||
|
||||
const ws = new WebSocket(
|
||||
"wss://app.hyperate.io/socket/websocket?token=wv39nM6iyrNJulvpmMQrimYPIXy2dVrYRjkuHpbRapKT2VSh65ngDGHdCdCtmEN9",
|
||||
);
|
||||
|
||||
let hrTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const setHrInterval = () => {
|
||||
hrTimeout = setTimeout(() => {
|
||||
return setHeartrate(0);
|
||||
}, 6000);
|
||||
};
|
||||
|
||||
ws.onopen = () => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
topic: "hr:0BCA",
|
||||
event: "phx_join",
|
||||
payload: {},
|
||||
ref: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
setInterval(() => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
topic: "phoenix",
|
||||
event: "heartbeat",
|
||||
payload: {},
|
||||
ref: 0,
|
||||
}),
|
||||
);
|
||||
}, 10000);
|
||||
|
||||
return setHrInterval();
|
||||
};
|
||||
|
||||
ws.onmessage = ({ data }) => {
|
||||
const { event, payload } = JSON.parse(data);
|
||||
switch (event) {
|
||||
case "hr_update": {
|
||||
clearTimeout(hrTimeout);
|
||||
setHrInterval();
|
||||
setHeartrate(payload.hr);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={heartrate === 0 ? "display:none" : `--bpm: ${heartrate};`}
|
||||
class="heart"
|
||||
>
|
||||
♥️
|
||||
<br />
|
||||
<span>{heartrate} BPM</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
46
src/components/navbar/index.tsx
Normal file
46
src/components/navbar/index.tsx
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { Minimize, Minus, X } from "lucide-preact";
|
||||
|
||||
const close = () => {
|
||||
window.self.close();
|
||||
window.history.back();
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<>
|
||||
<nav
|
||||
class="navbar shadow fixed-top"
|
||||
style="background-color: var(--bs-content-bg); border-bottom: var(--bs-border-width) solid var(--bs-content-border-color);"
|
||||
>
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-brand">
|
||||
<img
|
||||
src="favicon.svg"
|
||||
alt="Logo"
|
||||
width="24"
|
||||
height="24"
|
||||
class="d-inline-block align-text-top"
|
||||
/>
|
||||
Seth
|
||||
</div>
|
||||
<span class="navbar-text">IPv4 dot Army</span>
|
||||
<div class="d-flex hstack gap-2">
|
||||
<button type="button" class="btn btn-outline-success btn-sm">
|
||||
<Minus size={20} />
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-warning btn-sm">
|
||||
<Minimize size={20} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-danger btn-sm"
|
||||
onClick={close}
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
@import "./App.css";
|
||||
|
||||
html,
|
||||
head,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Circular Std', sans-serif;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #DEDEDE;
|
||||
font: 2vh Inconsolata, monospace;
|
||||
text-shadow: 0 0 5px #C8C8C8;
|
||||
background: radial-gradient(at bottom right, rgba(0, 150, 0, 0.1) 0%, rgba(0, 0, 0, 1) 100%);
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#1a1d1f">
|
||||
<meta name="description"
|
||||
content="A Dedicated Backend Developer, with a passion for high-fidelity audio, gaming, and web development.">
|
||||
<meta name="keywords" content="Seth, IPv4, Army, Seth@IPv4, web development, audio, gaming">
|
||||
<meta name="author" content="Seth">
|
||||
<title>Seth @ IPv4 dot Army</title>
|
||||
<link rel="icon" href="/public/favicon.svg" />
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
<style id="font"></style>
|
||||
</head>
|
||||
|
||||
<body class="scanlines">
|
||||
<script src="index.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,74 +0,0 @@
|
|||
import "tsx-dom";
|
||||
|
||||
import App from './App';
|
||||
|
||||
const font = document.getElementById("font")
|
||||
if (font) {
|
||||
font.innerText = `@font-face {
|
||||
font-family: 'Circular Std';
|
||||
src: url('/public/font/Black.woff2') format('woff2');
|
||||
font-weight: black;
|
||||
font-style: normal;
|
||||
font-display: swap
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Circular Std';
|
||||
src: url('/public/font/BlackItalic.woff2') format('woff2');
|
||||
font-weight: black;
|
||||
font-style: italic;
|
||||
font-display: swap
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Circular Std';
|
||||
src: url('/public/font/Bold.woff2') format('woff2');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
font-display: swap
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Circular Std';
|
||||
src: url('/public/font/BoldItalic.woff2') format('woff2');
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
font-display: swap
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Circular Std';
|
||||
src: url('/public/font/Book.woff2') format('woff2');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Circular Std';
|
||||
src: url('/public/font/BookItalic.woff2') format('woff2');
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
font-display: swap
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Circular Std';
|
||||
src: url('/public/font/Medium.woff2') format('woff2');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Circular Std';
|
||||
src: url('/public/font/MediumItalic.woff2') format('woff2');
|
||||
font-weight: 500;
|
||||
font-style: italic;
|
||||
font-display: swap
|
||||
}`
|
||||
}
|
||||
document.body.appendChild(<App />);
|
||||
|
||||
// You're garbage, let me collect you.
|
||||
fetch("/api/gc")
|
9
src/main.tsx
Normal file
9
src/main.tsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { render } from "preact";
|
||||
import "./index.css";
|
||||
import "halfmoon/css/halfmoon.min.css";
|
||||
import App from "./components/app.tsx";
|
||||
|
||||
render(
|
||||
<App />,
|
||||
(document.getElementById("app") as HTMLElement) || document.body,
|
||||
);
|
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
27
tsconfig.app.json
Normal file
27
tsconfig.app.json
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"react": ["./node_modules/preact/compat/"],
|
||||
"react-dom": ["./node_modules/preact/compat/"]
|
||||
},
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
|
@ -1,29 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
// Environment setup & latest features
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM",
|
||||
],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "tsx-dom",
|
||||
"allowJs": true,
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
20
tsconfig.node.json
Normal file
20
tsconfig.node.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
28
vite.config.ts
Normal file
28
vite.config.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
import preact from "@preact/preset-vite";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
import postCSSPurgeCSS from "@fullhuman/postcss-purgecss";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
css: {
|
||||
...(import.meta.env.NODE_ENV === "production"
|
||||
? {
|
||||
transformer: "postcss",
|
||||
postcss: {
|
||||
plugins: [
|
||||
postCSSPurgeCSS({
|
||||
content: ["./index.html", "./src/**/*.{ts,tsx}"],
|
||||
}),
|
||||
],
|
||||
},
|
||||
}
|
||||
: { transformer: "lightningcss" }),
|
||||
},
|
||||
build: {
|
||||
cssMinify: "lightningcss",
|
||||
},
|
||||
plugins: [preact()],
|
||||
});
|
Loading…
Add table
Reference in a new issue