Compare commits
17 commits
ejs-migrat
...
main
Author | SHA1 | Date | |
---|---|---|---|
651987661f | |||
3c83b759a8 | |||
08d9fd1ffb | |||
e194941b5c | |||
8da480329b | |||
9b58091459 | |||
6d7b18dc01 | |||
0ee0b19d16 | |||
b47f03ff2a | |||
fc7959737f | |||
eb07a77584 | |||
161e28d5f3 | |||
af797ba515 | |||
6f6c9f229f | |||
f9b8428d2b | |||
2e77b84df3 | |||
bfc0c43b12 |
5
.dockerignore
Normal file
|
@ -0,0 +1,5 @@
|
|||
.git
|
||||
info/
|
||||
node_modules/
|
||||
public/
|
||||
src/
|
138
.gitignore
vendored
|
@ -1,130 +1,24 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
# build output
|
||||
dist/
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
# environment variables
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
.env.production
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
|
|
4
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
11
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
12
Dockerfile
|
@ -1,4 +1,8 @@
|
|||
FROM lipanski/docker-static-website:latest
|
||||
|
||||
# Copy your static files
|
||||
COPY . .
|
||||
FROM node:lts-alpine3.17
|
||||
WORKDIR /usr/src/app
|
||||
COPY ["package.json", "pnpm-lock.yaml", "dist/*", "./"]
|
||||
RUN pnpm i
|
||||
EXPOSE 4321
|
||||
RUN chown -R node /usr/src/app
|
||||
USER node
|
||||
CMD ["pnpm", "start"]
|
||||
|
|
7
LICENSE
|
@ -1,3 +1,6 @@
|
|||
Copyright (c) 2024 nin0dev (https://github.com/nin0-dev, https://codeberg.org/nin0dev, https://git.nin0.dev/nin0, https://nin0.dev)
|
||||
Usage and reproduction is prohibited.
|
||||
All rights reserved.
|
||||
The Rust Bad License
|
||||
|
||||
If you fork this and include The Rust Programming Language files in any way, shape, or form; you will be liable for damages ranging from 10-20 thousand dollars per file.
|
||||
Otherwise, usage and reproduction is prohibited.
|
||||
All rights reserved.
|
47
README.md
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Astro Starter Kit: Minimal
|
||||
|
||||
```sh
|
||||
npm create astro@latest -- --template minimal
|
||||
```
|
||||
|
||||
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal)
|
||||
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal)
|
||||
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/minimal/devcontainer.json)
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```text
|
||||
/
|
||||
├── public/
|
||||
├── src/
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
11
astro.config.mjs
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
import node from "@astrojs/node";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
output: "server",
|
||||
adapter: node({
|
||||
mode: "standalone"
|
||||
})
|
||||
});
|
|
@ -1,60 +0,0 @@
|
|||
import pluginJs from "@eslint/js";
|
||||
import stylisticJs from "@stylistic/eslint-plugin-js";
|
||||
import globals from "globals";
|
||||
|
||||
export default [
|
||||
{files: ["**/*.{js,mjs,cjs,ts}"],},
|
||||
{languageOptions: { globals: globals.node }},
|
||||
pluginJs.configs.recommended,
|
||||
{
|
||||
ignores: ["dist/*", "**/jquery.js"]
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
"@stylistic/js": stylisticJs,
|
||||
},
|
||||
rules: {
|
||||
"yoda": "error",
|
||||
"eqeqeq": ["error", "always", { "null": "ignore" }],
|
||||
"prefer-destructuring": ["error", {
|
||||
"VariableDeclarator": { "array": false, "object": true },
|
||||
"AssignmentExpression": { "array": false, "object": false }
|
||||
}],
|
||||
"operator-assignment": ["error", "always"],
|
||||
"no-useless-computed-key": "error",
|
||||
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
|
||||
"no-invalid-regexp": "error",
|
||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||
"no-duplicate-imports": "error",
|
||||
"dot-notation": "error",
|
||||
"no-fallthrough": "error",
|
||||
"for-direction": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"no-cond-assign": "error",
|
||||
"no-dupe-else-if": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-loss-of-precision": "error",
|
||||
"no-misleading-character-class": "error",
|
||||
"no-prototype-builtins": "error",
|
||||
"no-regex-spaces": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-unsafe-optional-chaining": "error",
|
||||
"no-useless-backreference": "error",
|
||||
"use-isnan": "error",
|
||||
"prefer-const": "error",
|
||||
"prefer-spread": "error",
|
||||
"semi": [2, "always"],
|
||||
"@stylistic/js/indent": ["error", 4],
|
||||
"@stylistic/js/quotes": [2, "double", { "avoidEscape": true }]
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["public/**/*.js"],
|
||||
rules: {
|
||||
"no-undef": "off", // due to being separate files eslint goes insane
|
||||
"@typescript-eslint/no-unused-vars": ["off"] // noone cares lol
|
||||
}
|
||||
}
|
||||
];
|
4
info/contacts/discord.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
platform: "Discord"
|
||||
name: "nin0.dev"
|
||||
---
|
6
info/contacts/email.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
platform: "Email"
|
||||
name: "support@nin0.dev"
|
||||
url: "mailto:support@nin0.dev"
|
||||
note: "Replies may be slow"
|
||||
---
|
6
info/contacts/pgp.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
platform: "PGP key"
|
||||
name: "here"
|
||||
url: "/pgp"
|
||||
note: "Used across commits or other signed messages. Any key of mine on a keyserver is outdated."
|
||||
---
|
6
info/contacts/telegram.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
platform: "Telegram"
|
||||
url: "https://nin0dev.t.me"
|
||||
name: "@nin0dev"
|
||||
note: "Use for quick replies"
|
||||
---
|
31
package.json
|
@ -1,23 +1,18 @@
|
|||
{
|
||||
"name": "website",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"name": "",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "echo \"Error: no test specified\" && exit 1"
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"author": "nin0dev",
|
||||
"license": "All Rights Reserved",
|
||||
"dependencies": {
|
||||
"@stylistic/eslint-plugin-js": "^2.4.0",
|
||||
"ejs": "^3.1.10",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"express": "^4.19.2",
|
||||
"nodemon": "^3.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.8.0",
|
||||
"eslint": "9.x",
|
||||
"globals": "^15.8.0"
|
||||
"7.css": "^0.16.0",
|
||||
"@astrojs/node": "^8.3.2",
|
||||
"astro": "^4.12.2",
|
||||
"lanyard-wrapper": "^2.0.1"
|
||||
}
|
||||
}
|
||||
}
|
3736
pnpm-lock.yaml
|
@ -1,81 +0,0 @@
|
|||
|
||||
:root {
|
||||
--online-color: #23a55a;
|
||||
--idle-color: #f0b232;
|
||||
--dnd-color: #f23f43;
|
||||
--offline-color: #80848e;
|
||||
}
|
||||
body {
|
||||
padding: 30px;
|
||||
font-family: "Segoe UI", "Roboto", sans-serif !important;
|
||||
background-color: #56a0d1;
|
||||
}
|
||||
#main-window {
|
||||
max-width: 600px;
|
||||
}
|
||||
@media (pointer:coarse) {
|
||||
body {
|
||||
overflow: scroll !important;
|
||||
}
|
||||
#main-window {
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
.window-body {
|
||||
padding: 10px;
|
||||
}
|
||||
#header {
|
||||
display: flex;
|
||||
}
|
||||
#header h3 {
|
||||
font-weight: 400;
|
||||
margin-left: 20px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
#pfp {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 10px black;
|
||||
border-color: var(--offline-color);
|
||||
border-style: solid;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: blue;
|
||||
}
|
||||
li {
|
||||
padding: 2px;
|
||||
}
|
||||
#presence img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
#presence-content {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
margin-left: 10px;
|
||||
}
|
||||
#presence {
|
||||
display: flex;
|
||||
}
|
||||
#bottom-actions {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: right;
|
||||
}
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
* {
|
||||
/* no this is not to protect my content or whatever. this is just to make draggable windows work in a non deranged way */
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 3.2 KiB |
|
@ -1,69 +0,0 @@
|
|||
const shouldLog = false;
|
||||
function log(content) {
|
||||
if (shouldLog) console.log(content);
|
||||
}
|
||||
function onUpdate(data) {
|
||||
// set status
|
||||
log(data);
|
||||
const pfp = document.getElementById("pfp");
|
||||
switch(data.discord_status) {
|
||||
case "online":
|
||||
pfp.style.borderColor = "var(--online-color)";
|
||||
break;
|
||||
case "idle":
|
||||
pfp.style.borderColor = "var(--idle-color)";
|
||||
break;
|
||||
case "dnd":
|
||||
pfp.style.borderColor = "var(--dnd-color)";
|
||||
break;
|
||||
case "offline":
|
||||
pfp.style.borderColor = "var(--offline-color)";
|
||||
break;
|
||||
}
|
||||
// set presence
|
||||
log(data.activities);
|
||||
let listening = false;
|
||||
let content = "";
|
||||
data.activities.forEach(presence => {
|
||||
if(presence.application_id === "463151177836658699" && presence.assets.small_text !== "Paused") { // premid
|
||||
listening = true;
|
||||
artist = presence.state.substring(0, presence.state.indexOf(" -"));
|
||||
if (artist === "") {
|
||||
artist = presence.state;
|
||||
}
|
||||
content = `Listening to ${presence.details} - ${artist}`;
|
||||
}
|
||||
if(presence.application_id === "1108588077900898414") { // vencord lastfm
|
||||
listening = true;
|
||||
content = `Listening to ${presence.details} - ${presence.state}`;
|
||||
}
|
||||
if(presence.application_id === "1054951789318909972") { // vendetta lastfm
|
||||
listening = true;
|
||||
content = `Listening to ${presence.details} - ${presence.state}`;
|
||||
}
|
||||
if(presence.id === "spotify:1") { // built in spotify hooluy shit normal presence)
|
||||
listening = true;
|
||||
content = `Listening to ${presence.details} - ${presence.state}`;
|
||||
}
|
||||
if(presence.type === 0 && presence.application_id !== "463151177836658699" && presence.application_id !== "1108588077900898414") { // generic playing status that isn't vencord lastfm or premid
|
||||
listening = false;
|
||||
content = `Playing ${presence.name}`;
|
||||
}
|
||||
|
||||
});
|
||||
document.getElementById("presence").style.display = content === "" ? "none": "flex";
|
||||
document.getElementById("presence-content").innerText = content;
|
||||
document.getElementById("presence-icon").src = listening ? "img/music.ico" : "img/game.ico";
|
||||
}
|
||||
LanyardWrapper.connectWebSocket("886685857560539176", onUpdate)
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function showCredits() {
|
||||
document.getElementById("credits").style.display = "block";
|
||||
document.getElementById("credits-button").style.display = "none";
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
dragElement(document.getElementById("main-window"));
|
||||
});
|
|
@ -1,239 +0,0 @@
|
|||
// oneko.js: https://github.com/adryd325/oneko.js
|
||||
|
||||
(function oneko() {
|
||||
const isReducedMotion =
|
||||
window.matchMedia("(prefers-reduced-motion: reduce)") === true ||
|
||||
window.matchMedia("(prefers-reduced-motion: reduce)").matches === true;
|
||||
|
||||
if (isReducedMotion) return;
|
||||
|
||||
const nekoEl = document.createElement("div");
|
||||
|
||||
let nekoPosX = 32;
|
||||
let nekoPosY = 32;
|
||||
|
||||
let mousePosX = 0;
|
||||
let mousePosY = 0;
|
||||
|
||||
let frameCount = 0;
|
||||
let idleTime = 0;
|
||||
let idleAnimation = 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 init() {
|
||||
nekoEl.id = "oneko";
|
||||
nekoEl.ariaHidden = true;
|
||||
nekoEl.style.width = "32px";
|
||||
nekoEl.style.height = "32px";
|
||||
nekoEl.style.position = "fixed";
|
||||
nekoEl.style.pointerEvents = "none";
|
||||
nekoEl.style.imageRendering = "pixelated";
|
||||
nekoEl.style.left = `${nekoPosX - 16}px`;
|
||||
nekoEl.style.top = `${nekoPosY - 16}px`;
|
||||
nekoEl.style.zIndex = Number.MAX_VALUE;
|
||||
|
||||
let nekoFile = "./oneko.gif";
|
||||
const curScript = document.currentScript;
|
||||
if (curScript && curScript.dataset.cat) {
|
||||
nekoFile = curScript.dataset.cat;
|
||||
}
|
||||
nekoEl.style.backgroundImage = `url(${nekoFile})`;
|
||||
|
||||
document.body.appendChild(nekoEl);
|
||||
|
||||
document.addEventListener("mousemove", function (event) {
|
||||
mousePosX = event.clientX;
|
||||
mousePosY = event.clientY;
|
||||
});
|
||||
|
||||
window.requestAnimationFrame(onAnimationFrame);
|
||||
}
|
||||
|
||||
let lastFrameTimestamp;
|
||||
|
||||
function onAnimationFrame(timestamp) {
|
||||
// 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();
|
||||
}
|
||||
window.requestAnimationFrame(onAnimationFrame);
|
||||
}
|
||||
|
||||
function setSprite(name, frame) {
|
||||
const sprite = spriteSets[name][frame % spriteSets[name].length];
|
||||
nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`;
|
||||
}
|
||||
|
||||
function resetIdleAnimation() {
|
||||
idleAnimation = null;
|
||||
idleAnimationFrame = 0;
|
||||
}
|
||||
|
||||
function idle() {
|
||||
idleTime += 1;
|
||||
|
||||
// every ~ 20 seconds
|
||||
if (
|
||||
idleTime > 10 &&
|
||||
Math.floor(Math.random() * 200) === 0 &&
|
||||
idleAnimation == null
|
||||
) {
|
||||
const 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;
|
||||
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, 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();
|
||||
})();
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
12
server.js
|
@ -1,12 +0,0 @@
|
|||
var express = require("express");
|
||||
var app = express();
|
||||
|
||||
app.set("view engine", "ejs");
|
||||
app.use(express.static("public"))
|
||||
|
||||
app.get("/", function(req, res) {
|
||||
res.render("index");
|
||||
});
|
||||
|
||||
app.listen(8080);
|
||||
console.log("Server is listening on port 8080");
|
9
src/components/Button.astro
Normal file
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
const {position, text, url, id} = Astro.props;
|
||||
---
|
||||
<style>
|
||||
button, a {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
{url ? <a href={url}><button id={id}>{text}</button></a> : <button id={id}>{text}</button>}
|
10
src/components/ButtonCollection.astro
Normal file
|
@ -0,0 +1,10 @@
|
|||
<style>
|
||||
div {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: 1fr;
|
||||
}
|
||||
</style>
|
||||
<div class="button-collection">
|
||||
<slot />
|
||||
</div>
|
8
src/components/Contacts.astro
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
import Social from "./Social.astro"
|
||||
|
||||
const contacts = await Astro.glob("../../info/contacts/*.md")
|
||||
---
|
||||
<ul>
|
||||
{contacts.map(contact => <Social platform={contact.frontmatter.platform} url={contact.frontmatter.url} name={contact.frontmatter.name} note={contact.frontmatter.note}/>)}
|
||||
</ul>
|
23
src/components/FearOfTechnology.astro
Normal file
|
@ -0,0 +1,23 @@
|
|||
<style>
|
||||
div {
|
||||
color: black;
|
||||
background-color: lightcoral;
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
a {
|
||||
color: blue;
|
||||
}
|
||||
</style>
|
||||
|
||||
<noscript>
|
||||
<div>
|
||||
<p>
|
||||
<h4>ENABLE JAVASCRIPT OR I WILL KILL YOU</h4>
|
||||
I will not. However, you seem to be living in fear of technology, as you have JavaScript disabled. This means that some features (eg. dragging, submitting forms) will not work. Enable JS to fully enjoy this website!
|
||||
<br/>
|
||||
Rest assured, there's no external tracking. You can check this site's source code on the <a href="https://git.nin0.dev/nin0/website">nin0/website</a> repository on my Forgejo.
|
||||
</p>
|
||||
</div>
|
||||
</noscript>
|
16
src/components/GroupBox.astro
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
const {name} = Astro.props
|
||||
---
|
||||
|
||||
<style>
|
||||
fieldset {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 0px;
|
||||
padding: 0px 15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<fieldset>
|
||||
<legend>{name}</legend>
|
||||
<slot />
|
||||
</fieldset>
|
31
src/components/MainWindow.astro
Normal file
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
import Contacts from "../components/Contacts.astro";
|
||||
import GroupBox from "../components/GroupBox.astro";
|
||||
import Me from "../components/Me.astro";
|
||||
import Presence from "../components/Presence.astro";
|
||||
import Window from "../components/Window.astro";
|
||||
import Button from "./Button.astro";
|
||||
import ButtonCollection from "./ButtonCollection.astro";
|
||||
import Spacer from "./Spacer.astro";
|
||||
---
|
||||
<Window title="Home" maxWidth="600px" showClose={true}>
|
||||
<Me />
|
||||
<Presence />
|
||||
<GroupBox name="About me">
|
||||
<p>
|
||||
I'm a Canadian self-taught software developer that makes things some people find useful in Python, HTML, JavaScript, and Kotlin.
|
||||
<br/>
|
||||
I occasionally code with Java or C#, and am currently learning Rust.
|
||||
</p>
|
||||
</GroupBox>
|
||||
<GroupBox name="Reach out">
|
||||
<Contacts />
|
||||
</GroupBox>
|
||||
<ButtonCollection>
|
||||
<Button position="left" text="Projects (soon)" url="/projects"/>
|
||||
<Spacer />
|
||||
<Button position="left" text="Code (soon)" url="/code"/>
|
||||
<Spacer />
|
||||
<Button position="left" text="Blog (ETA: 100 years)" url="/code"/>
|
||||
</ButtonCollection>
|
||||
</Window>
|
45
src/components/Me.astro
Normal file
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
import {fetchUserData, type DiscordStatus} from "lanyard-wrapper";
|
||||
|
||||
const rawUserData = await fetchUserData("886685857560539176");
|
||||
const statusColor = () => {
|
||||
switch (rawUserData.discord_status) {
|
||||
case "online":
|
||||
return "#23a55a";
|
||||
case "idle":
|
||||
return "#f0b232";
|
||||
case "dnd":
|
||||
return "#f23f43";
|
||||
case "offline":
|
||||
return "#80848e";
|
||||
default:
|
||||
return "#80848e";
|
||||
}
|
||||
};
|
||||
---
|
||||
|
||||
<style define:vars={{statusColor: statusColor()}}>
|
||||
div {
|
||||
display: flex;
|
||||
}
|
||||
h3 {
|
||||
font-weight: 400;
|
||||
margin-left: 20px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
img {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 10px black;
|
||||
border-color: var(--statusColor);
|
||||
border-style: solid;
|
||||
border-width: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<img src="logo.png" alt="the nin0dev logo">
|
||||
<h3>nin0dev <span style="font-size: 0.4em;">(he/him)</span></h3>
|
||||
</div>
|
55
src/components/Presence.astro
Normal file
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
import {fetchUserData} from "lanyard-wrapper";
|
||||
|
||||
const rawUserData = await fetchUserData("886685857560539176");
|
||||
const activity = (() => {
|
||||
let finalActivity = {
|
||||
type: undefined,
|
||||
name: undefined,
|
||||
artist: undefined
|
||||
};
|
||||
rawUserData.activities.some((activity => {
|
||||
if(activity.type === 0) {
|
||||
finalActivity = {
|
||||
type: "playing",
|
||||
name: activity.name,
|
||||
artist: undefined
|
||||
};
|
||||
return;
|
||||
}
|
||||
}));
|
||||
if(finalActivity.type !== undefined) return finalActivity;
|
||||
if(rawUserData.listening_to_spotify) {
|
||||
finalActivity = {
|
||||
type: "listening",
|
||||
name: rawUserData.spotify.song,
|
||||
artist: rawUserData.spotify.artist
|
||||
};
|
||||
};
|
||||
return finalActivity;
|
||||
})();
|
||||
---
|
||||
|
||||
<style>
|
||||
div {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
}
|
||||
p {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
margin-left: 10px;
|
||||
}
|
||||
img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
</style>
|
||||
|
||||
{
|
||||
activity.type !== undefined &&
|
||||
<div>
|
||||
<img src={activity.type === "playing" ? "/game.ico" : "/music.ico"}>
|
||||
<p>{`${activity.type === "playing" ? "Playing" : "Listening to"} ${activity.name}${activity.type === "listening" ? ` - ${activity.artist}` : ""}`}</p>
|
||||
</div>
|
||||
}
|
16
src/components/Social.astro
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
const {platform, name, url, note} = Astro.props;
|
||||
---
|
||||
|
||||
<style>
|
||||
bl {
|
||||
font-weight: 500;
|
||||
}
|
||||
it {
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
|
||||
<li>
|
||||
<bl>{platform}</bl>: {url ? <a href={url}>{name}</a> : name}{note && <it> ({note})</it>}
|
||||
</li>
|
1
src/components/Spacer.astro
Normal file
|
@ -0,0 +1 @@
|
|||
<span />
|
35
src/components/Window.astro
Normal file
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
const {title, showClose, maxWidth} = Astro.props
|
||||
---
|
||||
|
||||
<style define:vars={{ maxWidth }}>
|
||||
.window {
|
||||
max-width: var(--maxWidth);
|
||||
position: absolute;
|
||||
}
|
||||
.window-body {
|
||||
padding: 20px !important;
|
||||
}
|
||||
@media (pointer:coarse) {
|
||||
.window {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="background" class="window">
|
||||
<div class="window glass active" style="max-width: 100%">
|
||||
<div class="title-bar">
|
||||
<div class="title-bar-text">{title}</div>
|
||||
{
|
||||
showClose &&
|
||||
<div class="title-bar-controls">
|
||||
<button aria-label="Close" onclick="window.close()"></button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="window-body has-space">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
11
src/css/style.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
body {
|
||||
background-color: #56a0d1;
|
||||
padding: 30px;
|
||||
font-family: sans-serif !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
@media (pointer:coarse) {
|
||||
body {
|
||||
overflow: scroll;
|
||||
}
|
||||
}
|
1
src/env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference types="astro/client" />
|
76
src/layouts/BaseLayout.astro
Normal file
|
@ -0,0 +1,76 @@
|
|||
---
|
||||
import "7.css/dist/7.css";
|
||||
import "../css/style.css";
|
||||
|
||||
const {tabTitle} = Astro.props;
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{tabTitle}</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
<script>
|
||||
function dragElement(element) {
|
||||
// Step 2: Set up variables to keep track of the element's position.
|
||||
var initialX = 0;
|
||||
var initialY = 0;
|
||||
var currentX = 0;
|
||||
var currentY = 0;
|
||||
|
||||
// Step 3: Check if there is a special header element associated with the draggable element.
|
||||
if (document.getElementById("title-bar")) {
|
||||
// Step 4: If present, assign the `dragMouseDown` function to the header's `onmousedown` event.
|
||||
// This allows you to drag the window around by its header.
|
||||
document.getElementById("title-bar").onmousedown = startDragging;
|
||||
} else {
|
||||
// Step 5: If not present, assign the function directly to the draggable element's `onmousedown` event.
|
||||
// This allows you to drag the window by holding down anywhere on the window.
|
||||
element.onmousedown = startDragging;
|
||||
}
|
||||
|
||||
// Step 6: Define the `startDragging` function to capture the initial mouse position and set up event listeners.
|
||||
function startDragging(e) {
|
||||
e = e || window.event;
|
||||
e.preventDefault();
|
||||
// Step 7: Get the mouse cursor position at startup.
|
||||
initialX = e.clientX;
|
||||
initialY = e.clientY;
|
||||
// Step 8: Set up event listeners for mouse movement (`elementDrag`) and mouse button release (`closeDragElement`).
|
||||
document.onmouseup = stopDragging;
|
||||
document.onmousemove = dragElement;
|
||||
}
|
||||
|
||||
// Step 9: Define the `elementDrag` function to calculate the new position of the element based on mouse movement.
|
||||
function dragElement(e) {
|
||||
e = e || window.event;
|
||||
e.preventDefault();
|
||||
// Step 10: Calculate the new cursor position.
|
||||
currentX = initialX - e.clientX;
|
||||
currentY = initialY - e.clientY;
|
||||
initialX = e.clientX;
|
||||
initialY = e.clientY;
|
||||
// Step 11: Update the element's new position by modifying its `top` and `left` CSS properties.
|
||||
element.style.top = (element.offsetTop - currentY) + "px";
|
||||
element.style.left = (element.offsetLeft - currentX) + "px";
|
||||
}
|
||||
|
||||
// Step 12: Define the `stopDragging` function to stop tracking mouse movement by removing the event listeners.
|
||||
function stopDragging() {
|
||||
document.onmouseup = null;
|
||||
document.onmousemove = null;
|
||||
}
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelectorAll(".window").forEach(window => {
|
||||
dragElement(window);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
10
src/pages/index.astro
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
import FearOfTechnology from "../components/FearOfTechnology.astro";
|
||||
import MainWindow from "../components/MainWindow.astro";
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
---
|
||||
|
||||
<BaseLayout tabTitle="Home - nin0dev">
|
||||
<FearOfTechnology />
|
||||
<MainWindow />
|
||||
</BaseLayout>
|
29
src/pages/pgp.astro
Normal file
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
---
|
||||
<BaseLayout>
|
||||
<pre style="background-color: #00000000; border: none;">
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: This key is used across my commits or other signed messages.
|
||||
Comment: Any key on a keyserver is outdated! Only trust this one.
|
||||
Comment:
|
||||
Comment: User-ID: nin0dev <personal@nin0.dev>
|
||||
Comment: Valid from: 2024-08-31 3:59 PM
|
||||
Comment: Type: 255-bit EdDSA (secret key available)
|
||||
Comment: Usage: Signing, Encryption, Certifying User-IDs
|
||||
Comment: Fingerprint: DF9F7E5428C871653C9BD9203FA8F96ABAE04214
|
||||
|
||||
|
||||
mDMEZtN2NBYJKwYBBAHaRw8BAQdAOFU6FqqSqYlpGklj2y9B+B+gRU3lguVcRlAC
|
||||
vOMrcVK0G25pbjBkZXYgPHBlcnNvbmFsQG5pbjAuZGV2PoiTBBMWCgA7FiEE359+
|
||||
VCjIcWU8m9kgP6j5arrgQhQFAmbTdjQCGwMFCwkIBwICIgIGFQoJCAsCBBYCAwEC
|
||||
HgcCF4AACgkQP6j5arrgQhTMmQD9H5KNyUZpTMyd4CmRPFLRGN5q++iVZsZsr7Uf
|
||||
cRrz5TQA/3JYbsPZUOCdph7EwaWvaw+gBcIOy/35RpJ8EymYITgBuDgEZtN2NBIK
|
||||
KwYBBAGXVQEFAQEHQEMs8HdWWT/y7VyA3uAae4onSdKEcUT3YzasrUM9YntMAwEI
|
||||
B4h4BBgWCgAgFiEE359+VCjIcWU8m9kgP6j5arrgQhQFAmbTdjQCGwwACgkQP6j5
|
||||
arrgQhT3TQD+JseaqzRoIM+Ar4SKXz9PuKcF/azRQKAw8xYDI4rGhRMA/17bJscS
|
||||
hOnTlOLotYpasOGdJcEgxPHv4pTiAtTZL/gH
|
||||
=1Gg8
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
</pre>
|
||||
</BaseLayout>
|
3
tsconfig.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/base"
|
||||
}
|
105
views/index.ejs
|
@ -1,105 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<style>
|
||||
* {
|
||||
font-family: sans-serif !important;
|
||||
}
|
||||
</style>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>nin0dev</title>
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/7.css">
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<link rel="icon" type="image/x-icon" href="logo-but-round.png">
|
||||
<meta name="theme-color" content="#00D0D0">
|
||||
<meta property="og:url" content="https://nin0.dev" />
|
||||
<meta property="og:title" content="nin0dev" />
|
||||
<meta property="og:description" content="Hey, I'm nin0dev, a Canadian software developer." />
|
||||
<meta property="og:image" content="https://nin0.dev/logo.png" />
|
||||
<script src="js/drag.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="window" id="main-window" style="position: absolute;">
|
||||
<div class="title-bar">
|
||||
<div class="title-bar-text">Home</div>
|
||||
</div>
|
||||
<div class="window-body">
|
||||
<div id="header">
|
||||
<img src="img/logo.png" alt="the nin0dev logo" id="pfp">
|
||||
<h3>nin0dev <span style="font-size: 0.4em;">(he/him)</span></h3>
|
||||
</div>
|
||||
<br/>
|
||||
<div id="presence" style="display: none; margin-bottom: 12px;">
|
||||
<img src="img/game.ico" id="presence-icon">
|
||||
<p id="presence-content"></p>
|
||||
</div>
|
||||
<fieldset>
|
||||
<legend>About me</legend>
|
||||
I'm a Canadian self-taught software developer that makes useless things in Python, HTML, JavaScript, and Kotlin.
|
||||
<br/>
|
||||
yeah that's it
|
||||
</fieldset>
|
||||
<br/>
|
||||
<fieldset>
|
||||
<legend>My projects</legend>
|
||||
<ul style="margin-top: 5px; margin-bottom: 5px; padding-left: 20px;">
|
||||
<li>
|
||||
VendroidEnhanced: A Discord client for Android that loads the mobile website and injects Vencord.
|
||||
<ul>
|
||||
<li><a href="https://github.com/VendroidEnhanced/Vendroid">GitHub repo</a></li>
|
||||
<li>Actively maintained</li>
|
||||
<li>Fork of <a href="https://github.com/Vencord/Vendroid">Vencord/Vendroid</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
website: The website you're currently viewing
|
||||
<ul>
|
||||
<li><a href="https://github.com/nin0-dev/website">GitHub repo</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
nin0-bot: An in-development kitchen-sink Discord Bot with moderation, fun and utility
|
||||
<ul>
|
||||
<li><a href="https://github.com/nin0-dev/Sink">GitHub repo</a></li>
|
||||
<li>In development</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
</br>
|
||||
<fieldset>
|
||||
<legend>Reach out!</legend>
|
||||
<ul style="margin-top: 5px; margin-bottom: 5px; padding-left: 20px;">
|
||||
<li>Discord: @nin0.dev</li>
|
||||
<li>
|
||||
Email: <a href="mailto:support@nin0.dev">support@nin0.dev</a>
|
||||
|
||||
</li>
|
||||
<li>Telegram: <a href="https://t.me/nin0dev">@nin0dev</a></li>
|
||||
<li>GitHub: <a href="https://github.com/nin0-dev">nin0-dev</a></li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
<span id="credits" style="display: none;">
|
||||
<br/>
|
||||
<fieldset>
|
||||
<legend>Credits</legend>
|
||||
<ul style="margin-top: 5px; margin-bottom: 5px; padding-left: 20px;">
|
||||
<li>UI library: <a href="https://jdan.github.io/98.css/">98.css by jdan</a></li>
|
||||
<li>Presence/status API: <a href="https://discord.gg/lanyard">Lanyard</a></li>
|
||||
<li>Icons: <a href="https://win98icons.alexmeub.com/">official Windows 98 icons</a></li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
</span>
|
||||
|
||||
<div id="bottom-actions" style="margin-top: 13px;">
|
||||
<button onclick="showCredits()" id="credits-button">Credits</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/oneko.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/lanyard-wrapper/dist/index.browser.js"></script>
|
||||
<script src="js/index.js"></script>
|
||||
</body>
|
||||
</html>
|