Compare commits
49 commits
ejs-migrat
...
main
Author | SHA1 | Date | |
---|---|---|---|
058139a8d7 | |||
49803ba398 | |||
7271006f49 | |||
|
96194316d3 | ||
|
f584041619 | ||
f118b8b11c | |||
995dca803c | |||
b567a81a22 | |||
cd4495767f | |||
315fd85117 | |||
7dbaa56b81 | |||
83c4444c8e | |||
77a85e1611 | |||
e98811546b | |||
11517475ed | |||
83cb45f9f8 | |||
6c9d093c8c | |||
99ba9cef4b | |||
0bb40ff89f | |||
|
98412adab9 | ||
8ed804a5e6 | |||
80d0b4992c | |||
e439cde473 | |||
74f2fd5ec3 | |||
cf292c95f1 | |||
4268b094f7 | |||
6015db1f4f | |||
22251c3b2b | |||
74fd12d199 | |||
60358d32d6 | |||
fd44e9866e | |||
4ee9a3723c | |||
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/
|
||||
|
|
18
.prettierrc.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"plugins": ["prettier-plugin-astro"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.astro",
|
||||
"options": {
|
||||
"parser": "astro"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tabWidth": 4,
|
||||
"useTabs": true,
|
||||
"singleQuote": false,
|
||||
"jsxSingleQuote": false,
|
||||
"semi": true,
|
||||
"arrowParens": "avoid",
|
||||
"trailingComma": "none"
|
||||
}
|
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"
|
||||
}
|
||||
]
|
||||
}
|
4
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true
|
||||
}
|
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
|
||||
```
|
||||
|
||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal)
|
||||
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal)
|
||||
[](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).
|
3
astro.config.mjs
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
export default defineConfig({});
|
|
@ -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
|
||||
}
|
||||
}
|
||||
];
|
40
package.json
|
@ -1,23 +1,21 @@
|
|||
{
|
||||
"name": "website",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^5.7.4",
|
||||
"color.js": "^1.2.0",
|
||||
"sharp": "^0.34.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-astro": "^0.14.1"
|
||||
},
|
||||
"packageManager": "pnpm@10.12.3+sha512.467df2c586056165580ad6dfb54ceaad94c5a30f80893ebdec5a44c5aa73c205ae4a5bb9d5ed6bb84ea7c249ece786642bbb49d06a307df218d03da41c317417"
|
||||
}
|
||||
|
|
4060
pnpm-lock.yaml
generated
4
pnpm-workspace.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
- sharp
|
||||
- workerd
|
9
public/.well-known/webfinger
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"subject": "acct:personal@nin0.dev",
|
||||
"links": [
|
||||
{
|
||||
"rel": "http://openid.net/specs/connect/1.0/issuer",
|
||||
"href": "https://auth.nin0.dev"
|
||||
}
|
||||
]
|
||||
}
|
BIN
public/background.png
Normal file
After Width: | Height: | Size: 283 KiB |
|
@ -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;
|
||||
}
|
BIN
public/fonts/Inter/Inter Italic.ttf
Normal file
BIN
public/fonts/Inter/Inter.ttf
Normal file
93
public/fonts/Inter/OFL.txt
Normal file
|
@ -0,0 +1,93 @@
|
|||
Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 3.2 KiB |
|
@ -1,56 +0,0 @@
|
|||
// wow i love being fake programmer
|
||||
// source: https://jams.hackclub.com/batch/webOS/part-3
|
||||
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.
|
||||
console.log(element.offsetTop);
|
||||
console.log(currentX);
|
||||
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;
|
||||
console.log(element.offsetTop - currentY);
|
||||
console.log(element.offsetLeft - currentX);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
})();
|
BIN
public/logo.png
Normal file
After Width: | Height: | Size: 5.2 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");
|
6
src/assets/svg/envelope.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 20.0234 14.1406">
|
||||
<g>
|
||||
<path d="M9.80469 7.65625C10.0312 7.65625 10.2344 7.56641 10.4805 7.34766L18.3477 0.339844C17.9336 0.113281 17.4609 0.0078125 16.8555 0.0078125L2.75781 0.0078125C2.15625 0.0078125 1.68359 0.113281 1.26172 0.339844L9.13281 7.34766C9.37891 7.56641 9.58594 7.65625 9.80469 7.65625ZM0.1875 12.5703L6.01953 6.74609L0.195312 1.55469C0.0859375 1.82031 0 2.23438 0 2.75L0 11.3984C0 11.8711 0.0703125 12.2461 0.1875 12.5703ZM2.62891 14.1406L16.9883 14.1406C17.5547 14.1406 18.0273 14.0195 18.3789 13.8281L12.375 7.82422L11.4336 8.66406C10.8984 9.14062 10.3984 9.35156 9.80469 9.35156C9.21875 9.35156 8.71875 9.14062 8.18359 8.66406L7.24219 7.82422L1.23828 13.8281C1.58984 14.0195 2.05859 14.1406 2.62891 14.1406ZM19.4297 12.5703C19.543 12.2461 19.6172 11.8711 19.6172 11.3984L19.6172 2.75C19.6172 2.23438 19.5312 1.82031 19.4219 1.55469L13.5938 6.74609Z" fill="currentColor"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1 KiB |
4
src/assets/svg/external.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg role="img" aria-hidden="true" viewBox="0 0 24 24" stroke-width="2" fill="none">
|
||||
<path d="M21 3h-6m6 0l-9 9m9-9v6" stroke-width="2" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 348 B |
5
src/assets/svg/fm.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated by Pixelmator Pro 3.3.8 -->
|
||||
<svg width="16" viewBox="0 0 293 179" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor" stroke="none" d="M -0.282013 158.117004 C -0.282013 168.822006 8.071991 177.434998 18.773987 177.434998 C 30 177.434998 38.35199 168.822006 38.35199 158.117004 C 38.35199 147.153992 29.998993 138.804001 18.773987 138.804001 C 8.071991 138.804001 -0.282013 147.153992 -0.282013 158.117004 Z M 52.72699 76.673996 L 52.72699 175.869003 L 80.135986 175.869003 L 80.135986 76.673996 L 110.939026 76.673996 L 110.939026 55.271004 L 80.135986 55.271004 L 80.135986 44.306 C 80.135986 27.862 87.184998 22.641006 98.669983 22.641006 C 106.762024 22.641006 112.244019 24.466003 118.508972 27.862 L 122.945984 4.888 C 115.638 1.494995 107.02301 -0.332001 96.582001 -0.332001 C 73.609009 -0.332001 52.72699 10.630997 52.72699 43.261002 L 52.72699 55.271004 L 35.238007 55.271004 L 35.238007 76.673996 Z M 206.153992 79.026001 C 203.020996 59.447998 190.231018 52.397003 173.523987 52.397003 C 156.817993 52.397003 142.461975 59.968002 136.195007 78.500999 L 132.802002 55.271004 L 110.614014 55.271004 L 110.614014 175.869003 L 138.02301 175.869003 L 138.02301 107.739998 C 138.02301 84.504997 150.031006 75.629997 162.822021 75.629997 C 176.133972 75.629997 181.617004 84.504997 181.617004 98.862 L 181.617004 175.867996 L 208.763977 175.867996 L 208.763977 107.477997 C 208.763977 84.503998 221.03302 75.628998 233.825012 75.628998 C 246.877014 75.628998 252.356995 84.503998 252.356995 98.861 L 252.356995 175.867004 L 279.765991 175.867004 L 279.765991 89.207001 C 279.765991 63.363998 264.625977 52.397003 244.526001 52.397003 C 227.560974 52.397003 212.419006 59.968002 206.153992 79.026001 Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
9
src/assets/svg/forgejo.svg
Normal file
|
@ -0,0 +1,9 @@
|
|||
<svg viewBox="0 0 212 212" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(6,6)">
|
||||
<path d="M58 168 v-98 a50 50 0 0 1 50-50 h20" stroke="currentColor" stroke-width="25" fill="none" />
|
||||
<path d="M58 168 v-30 a50 50 0 0 1 50-50 h20" stroke="currentColor" stroke-width="25" fill="none" />
|
||||
<circle cx="142" cy="20" r="18" stroke="currentColor" stroke-width="15" fill="none" />
|
||||
<circle cx="142" cy="88" r="18" stroke="currentColor" stroke-width="15" fill="none" />
|
||||
<circle cx="58" cy="180" r="18" stroke="currentColor" stroke-width="15" fill="none" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 593 B |
1
src/assets/svg/github.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 98 96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="currentColor"/></svg>
|
After Width: | Height: | Size: 965 B |
1
src/assets/svg/heart.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M480-147q-14 0-28.5-5T426-168l-69-63q-106-97-191.5-192.5T80-634q0-94 63-157t157-63q53 0 100 22.5t80 61.5q33-39 80-61.5T660-854q94 0 157 63t63 157q0 115-85 211T602-230l-68 62q-11 11-25.5 16t-28.5 5Z"/></svg>
|
After Width: | Height: | Size: 322 B |
6
src/assets/svg/plane.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 18.0508 17.6562">
|
||||
<g>
|
||||
<path d="M10.0156 17.6562C10.6836 17.6562 11.1406 17.1562 11.4297 16.4023L16.6484 2.74609C16.7773 2.41797 16.8438 2.11719 16.8438 1.86328C16.8438 1.22266 16.4336 0.816406 15.793 0.816406C15.5352 0.816406 15.2344 0.882812 14.9102 1.00781L1.21875 6.24609C0.511719 6.51172 0 6.96875 0 7.64062C0 8.43359 0.574219 8.76953 1.37109 9.01562L5.125 10.1719C5.75 10.3672 6.13672 10.3633 6.59766 9.94922L15.5117 1.79297C15.6328 1.68359 15.7852 1.69531 15.875 1.78516C15.9609 1.875 15.9766 2.02734 15.8633 2.14844L7.72656 11.0547C7.33594 11.4805 7.30078 11.9297 7.48438 12.5312L8.62891 16.2422C8.87891 17.0586 9.21875 17.6562 10.0156 17.6562Z" fill="currentColor"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 851 B |
6
src/assets/svg/xmark.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 12.9879 12.5923">
|
||||
<g>
|
||||
<path d="M0.250141 12.3415C0.589669 12.6732 1.16232 12.669 1.4856 12.3457L6.29154 7.53973L11.0947 12.3443C11.4236 12.6732 11.9956 12.6782 12.3287 12.3401C12.6619 12.0005 12.6633 11.4413 12.3344 11.111L7.53122 6.30005L12.3344 1.49692C12.6633 1.16802 12.6683 0.602398 12.3287 0.269275C11.9892-0.0702532 11.4236-0.0716594 11.0947 0.263651L6.29154 5.06678L1.4856 0.262244C1.16232-0.0660346 0.583263-0.0780657 0.250141 0.267869C-0.0815755 0.607398-0.0773569 1.17224 0.245922 1.49552L5.05187 6.30005L0.245922 11.1138C-0.0773569 11.4357-0.0879818 12.0083 0.250141 12.3415Z" fill="currentColor"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 776 B |
31
src/components/AssBrowser.astro
Normal file
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
import GroupBox from "@components/GroupBox.astro";
|
||||
---
|
||||
|
||||
<style is:global>
|
||||
.ua {
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
|
||||
<ass-browser>
|
||||
<GroupBox>
|
||||
<p>
|
||||
<h2>YOUR BROWSER IS ASS</h2>
|
||||
<span>
|
||||
Your browser, <span class="ua"></span> does not support the full
|
||||
set of web features which means that nin0.dev may not work as well
|
||||
as it would on a non-ass browser.
|
||||
<br />
|
||||
<br />
|
||||
Consider switching to a browser that is not ass such as <a
|
||||
href="https://www.apple.com/ca/safari/">Safari</a
|
||||
>, <a href="https://brave.com/">Brave</a>, or if you really have
|
||||
no option, <a href="https://www.google.com/intl/en_ca/chrome/"
|
||||
>Chrome</a
|
||||
>. (these are personal preferences and I am not paid to say
|
||||
this)
|
||||
</span>
|
||||
</p>
|
||||
</GroupBox>
|
||||
</ass-browser>
|
55
src/components/Avatar.astro
Normal file
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
import { Image } from "astro:assets";
|
||||
---
|
||||
|
||||
<div class="avatar-wrapper">
|
||||
<Image
|
||||
alt="My profile picture"
|
||||
src={"/logo.png"}
|
||||
class="avatar"
|
||||
width={80}
|
||||
height={80}
|
||||
aria-hidden
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.avatar-wrapper {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
clip-path: var(--clip-avatar-outer);
|
||||
background-color: var(--overlay0);
|
||||
}
|
||||
.avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
clip-path: var(--clip-avatar-inner);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const colours = {
|
||||
online: "var(--green)",
|
||||
idle: "var(--yellow)",
|
||||
dnd: "var(--red)",
|
||||
offline: "var(--overlay0)"
|
||||
};
|
||||
const avatarWrapper: HTMLElement =
|
||||
document.querySelector(".avatar-wrapper");
|
||||
|
||||
window.addEventListener("lanyard-update", (e: CustomEvent) => {
|
||||
const presence = e.detail;
|
||||
avatarWrapper.animate(
|
||||
[
|
||||
{
|
||||
backgroundColor:
|
||||
colours[presence.discord_status] ?? colours.offline
|
||||
}
|
||||
],
|
||||
{
|
||||
duration: 100,
|
||||
fill: "forwards"
|
||||
}
|
||||
);
|
||||
});
|
||||
</script>
|
107
src/components/Button.astro
Normal file
|
@ -0,0 +1,107 @@
|
|||
---
|
||||
const { position, text, url, id, onClick, name } = Astro.props;
|
||||
import ExternalIcon from "@assets/svg/external.svg";
|
||||
---
|
||||
|
||||
<style>
|
||||
.button-wrapper {
|
||||
background: linear-gradient(#343343, #2e2e3e, #2e2e3e);
|
||||
transition: 0.54s
|
||||
linear(
|
||||
0,
|
||||
0.0021,
|
||||
0.0081 1.26%,
|
||||
0.0302 2.51%,
|
||||
0.106,
|
||||
0.2088 7.53%,
|
||||
0.5809 15.7%,
|
||||
0.7667 20.72%,
|
||||
0.8391 23.23%,
|
||||
0.9106 26.37%,
|
||||
0.9538 28.88%,
|
||||
0.9926 32.02%,
|
||||
1.0134,
|
||||
1.0269 37.04%,
|
||||
1.0359,
|
||||
1.0384 43.32%,
|
||||
1.0353 47.72%,
|
||||
1.0124 61.53%,
|
||||
1.003 70.32%,
|
||||
0.9988 81.62%,
|
||||
0.9992 99.83%
|
||||
);
|
||||
clip-path: var(--clip-button-outer);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
button,
|
||||
a {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
button {
|
||||
border: none;
|
||||
background-color: #2b2b3b;
|
||||
clip-path: var(--clip-button-inner);
|
||||
color: var(--text);
|
||||
padding: 10px 20px;
|
||||
}
|
||||
.button-wrapper:has(button:hover:active) {
|
||||
background: #2f2f41;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
button:hover:active {
|
||||
background-color: #2f2f41;
|
||||
}
|
||||
.link-button {
|
||||
padding: 15px;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.link-button-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.link-button-heading > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
line-height: 0;
|
||||
}
|
||||
:global(.link-button-heading svg) {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
</style>
|
||||
{
|
||||
url ? (
|
||||
<a href={url}>
|
||||
<div class="button-wrapper">
|
||||
<button id={id} class="link-button">
|
||||
{name && (
|
||||
<div class="link-button-heading">
|
||||
<span>
|
||||
<slot name="icon" />
|
||||
<b>{name}</b>
|
||||
</span>
|
||||
<ExternalIcon />
|
||||
</div>
|
||||
)}
|
||||
<slot />
|
||||
</button>
|
||||
</div>
|
||||
</a>
|
||||
) : (
|
||||
<div class="button-wrapper">
|
||||
<button id={id}>
|
||||
<slot />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
21
src/components/Chip.astro
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
const { ...attrs } = Astro.props;
|
||||
---
|
||||
|
||||
<a {...attrs}>
|
||||
<slot />
|
||||
</a>
|
||||
<style>
|
||||
a {
|
||||
clip-path: var(--clip-chip);
|
||||
background-color: rgba(var(--lavender-rgb), 0.2);
|
||||
color: rgba(var(--lavender-rgb), 0.75);
|
||||
font-weight: 600;
|
||||
padding: 2px 7px;
|
||||
margin-right: 10px;
|
||||
gap: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
20
src/components/GroupBox.astro
Normal file
|
@ -0,0 +1,20 @@
|
|||
<style>
|
||||
.group-box-wrapper {
|
||||
background: linear-gradient(#343343, #2e2e3e, #2e2e3e);
|
||||
clip-path: var(--clip-group-box-outer);
|
||||
}
|
||||
.group-box {
|
||||
padding: 15px;
|
||||
clip-path: var(--clip-group-box-inner);
|
||||
background-color: #2b2b3b;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="group-box-wrapper">
|
||||
<div class="group-box">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
19
src/components/Noscript.astro
Normal file
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
import GroupBox from "@components/GroupBox.astro";
|
||||
---
|
||||
|
||||
<noscript>
|
||||
<GroupBox>
|
||||
<p>
|
||||
<h2>ENABLE JAVASCRIPT OR I WILL KILL YOU</h2>
|
||||
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>
|
||||
</GroupBox>
|
||||
</noscript>
|
96
src/components/Window.astro
Normal file
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
const { title, showClose, maxWidth, hideByDefault, customClass, offset } =
|
||||
Astro.props;
|
||||
|
||||
const randomID = customClass || Math.random().toString().replace(".", "");
|
||||
|
||||
import XmarkIcon from "@assets/svg/xmark.svg";
|
||||
---
|
||||
|
||||
<style define:vars={{ maxWidth, offset }}>
|
||||
.window {
|
||||
width: var(--maxWidth);
|
||||
top: var(--offset);
|
||||
left: var(--offset);
|
||||
background: var(--surface1);
|
||||
clip-path: var(--clip-window-outer);
|
||||
position: absolute;
|
||||
}
|
||||
.window-inner {
|
||||
background-color: #20202c;
|
||||
clip-path: var(--clip-window-inner);
|
||||
}
|
||||
.title-bar {
|
||||
background-color: var(--crust);
|
||||
user-select: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
height: 42px;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
.title-bar span {
|
||||
align-self: center;
|
||||
}
|
||||
svg {
|
||||
fill: var(--text);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 24px;
|
||||
}
|
||||
#close {
|
||||
background: none;
|
||||
color: inherit;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 50px;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
outline: inherit;
|
||||
}
|
||||
.window-body {
|
||||
gap: 1rem;
|
||||
display: flex;
|
||||
padding: 1rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
@media (pointer: coarse) {
|
||||
.window {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
margin: 5%;
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div
|
||||
class="background"
|
||||
id={`window-${randomID}`}
|
||||
class="window"
|
||||
style={hideByDefault && "display: none;"}
|
||||
>
|
||||
<div class="window" style="max-width: 100%">
|
||||
<div class="window-inner">
|
||||
<div class="title-bar">
|
||||
<span>{title}</span>
|
||||
{
|
||||
showClose && (
|
||||
<button
|
||||
id="close"
|
||||
aria-label="Close"
|
||||
onclick={`document.querySelector("#window-${randomID}").style.display = "none";`}
|
||||
>
|
||||
<XmarkIcon width="12" height="12" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div class="window-body">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
29
src/components/windows/code/CodeWindow.astro
Normal file
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
import Button from "@components/Button.astro";
|
||||
import Window from "@components/Window.astro";
|
||||
|
||||
import ForgejoIcon from "@assets/svg/forgejo.svg";
|
||||
import GithubIcon from "@assets/svg/github.svg";
|
||||
---
|
||||
|
||||
<Window
|
||||
title="Code"
|
||||
maxWidth="400px"
|
||||
showClose={true}
|
||||
hideByDefault={true}
|
||||
customClass="code-window"
|
||||
offset="55px"
|
||||
>
|
||||
As mentioned in the main page, I write code. Some used by a lot, some not as
|
||||
much. You can find it below.
|
||||
<Button name="GitHub" url="https://gh.nin0.dev/">
|
||||
<GithubIcon slot="icon" />
|
||||
Here, you'll find most of my external contributions. I rarely start personal
|
||||
projects on GitHub.
|
||||
</Button>
|
||||
<Button name="Forgejo" url="https://git.nin0.dev/nin0">
|
||||
<ForgejoIcon slot="icon" />
|
||||
Some more personal/niche projects can be found on my own instance (git.nin0.dev),
|
||||
which will probably be down.
|
||||
</Button>
|
||||
</Window>
|
22
src/components/windows/main/Contacts.astro
Normal file
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
import Chip from "@components/Chip.astro";
|
||||
import Envelope from "@assets/svg/envelope.svg";
|
||||
import Plane from "@assets/svg/plane.svg";
|
||||
---
|
||||
|
||||
<div>
|
||||
<Chip href="mailto:support@nin0.dev">
|
||||
<Envelope />
|
||||
support@nin0.dev
|
||||
</Chip>
|
||||
<Chip href="https://nin0dev.t.me">
|
||||
<Plane />
|
||||
@nin0dev
|
||||
</Chip>
|
||||
</div>
|
||||
<style>
|
||||
div {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
26
src/components/windows/main/InfoStack.astro
Normal file
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
import Avatar from "components/Avatar.astro";
|
||||
import SpotifyCard from "./SpotifyCard.astro";
|
||||
---
|
||||
|
||||
<>
|
||||
<div class="avatar-stack">
|
||||
<Avatar />
|
||||
<div class="text-stack">
|
||||
<h2>nin0</h2>
|
||||
<sub>he/him</sub>
|
||||
</div>
|
||||
</div>
|
||||
<SpotifyCard />
|
||||
</>
|
||||
<style>
|
||||
.avatar-stack {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
.text-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
49
src/components/windows/main/MainWindow.astro
Normal file
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
import Contacts from "./Contacts.astro";
|
||||
import InfoStack from "./InfoStack.astro";
|
||||
import Window from "@components/Window.astro";
|
||||
import Button from "@components/Button.astro";
|
||||
import GroupBox from "@components/GroupBox.astro";
|
||||
---
|
||||
|
||||
<script>
|
||||
import { WindowManager } from "@models/WindowManager";
|
||||
import { Lanyard } from "@models/Lanyard";
|
||||
import { LANYARD_ID } from "utils/constants";
|
||||
|
||||
document
|
||||
.querySelector("#show-code-window")
|
||||
.addEventListener(
|
||||
"click",
|
||||
() =>
|
||||
(WindowManager.topmost =
|
||||
document.getElementById("window-code-window"))
|
||||
);
|
||||
|
||||
new Lanyard(LANYARD_ID, presence =>
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("lanyard-update", {
|
||||
detail: presence
|
||||
})
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
<Window title="Home" maxWidth="600px" showClose={false}>
|
||||
<InfoStack />
|
||||
|
||||
<Button position="left" id="show-code-window">My code</Button>
|
||||
|
||||
<GroupBox>
|
||||
<h3>About me</h3>
|
||||
<p>
|
||||
I'm a Canadian self-taught software developer. I also make things
|
||||
that some people use with varying degrees of usefulness.
|
||||
</p>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox>
|
||||
<h3>Reach out</h3>
|
||||
<Contacts />
|
||||
</GroupBox>
|
||||
</Window>
|
254
src/components/windows/main/SpotifyCard.astro
Normal file
|
@ -0,0 +1,254 @@
|
|||
---
|
||||
import LastFMIcon from "@assets/svg/fm.svg";
|
||||
import HeartIcon from "@assets/svg/heart.svg";
|
||||
---
|
||||
|
||||
<style is:global>
|
||||
ass-browser {
|
||||
display: none;
|
||||
}
|
||||
.ass-browser {
|
||||
ass-browser {
|
||||
display: block !important;
|
||||
}
|
||||
.spotify-card {
|
||||
background-color: #2b2b3b !important;
|
||||
}
|
||||
|
||||
.spotify-card-wrapper {
|
||||
background-color: #2b2b3b !important;
|
||||
}
|
||||
|
||||
.spotify-card-background {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="spotify-card-wrapper-shadow">
|
||||
<h3 style="margin-bottom: 10px;">Listening to</h3>
|
||||
<div class="spotify-card-wrapper">
|
||||
<div class="spotify-card">
|
||||
<svg aria-hidden="true" class="blur">
|
||||
<filter id="sharpBlur" color-interpolation-filters="sRGB">
|
||||
<feGaussianBlur stdDeviation="100"></feGaussianBlur>
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="1 0 0 0 0, 0 1 0 0 0, 0 0 1 0 0, 0 0 0 9 0"
|
||||
></feColorMatrix>
|
||||
<feComposite in2="SourceGraphic" operator="in"
|
||||
></feComposite>
|
||||
</filter>
|
||||
</svg>
|
||||
<div class="spotify-card-background"></div>
|
||||
|
||||
<div class="spotify-card-art-shadow">
|
||||
<div class="spotify-card-art"></div>
|
||||
<HeartIcon id="loved-icon" />
|
||||
</div>
|
||||
<div class="spotfiy-card-track-info">
|
||||
<div class="spotify-card-track-name"></div>
|
||||
<div class="spotify-card-track-artist"></div>
|
||||
<div class="spotify-card-track-album"></div>
|
||||
</div>
|
||||
|
||||
<div class="links">
|
||||
<a class="lastfm-link" href="https://www.last.fm/user/nin0dev"
|
||||
><LastFMIcon /></a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
h4 {
|
||||
color: var(--subtext0);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.blur {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.spotify-card-wrapper-shadow {
|
||||
display: none;
|
||||
filter: drop-shadow(0 0 5px #00000040);
|
||||
}
|
||||
|
||||
.spotify-card-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
background: var(--album-color);
|
||||
clip-path: var(--clip-sp-1);
|
||||
}
|
||||
|
||||
.spotify-card {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: 12px;
|
||||
padding: 15px;
|
||||
align-items: center;
|
||||
clip-path: var(--clip-sp-2);
|
||||
}
|
||||
|
||||
.spotify-card-background {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 105%;
|
||||
height: 105%;
|
||||
background: var(--album-art);
|
||||
background-size: 100% 100%;
|
||||
filter: url("#sharpBlur") brightness(0.75);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.spotify-card-art-shadow {
|
||||
filter: drop-shadow(0 0 5px #00000040);
|
||||
|
||||
#loved-icon {
|
||||
display: var(--loved);
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
bottom: -6px;
|
||||
right: -5px;
|
||||
fill: var(--red);
|
||||
stroke: #641c2c;
|
||||
stroke-width: 50px;
|
||||
filter: drop-shadow(0 0 19px #0000006d);
|
||||
}
|
||||
}
|
||||
|
||||
.spotify-card-art-wrapper {
|
||||
clip-path: var(--clip-sp-art-wrapper);
|
||||
background: var(--album-color);
|
||||
}
|
||||
.spotify-card-art {
|
||||
background-image: var(--album-art);
|
||||
background-size: 100%;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
clip-path: var(--clip-sp-art);
|
||||
}
|
||||
|
||||
.spotfiy-card-track-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.spotify-card-track-name {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.spotify-card-track-name::after {
|
||||
content: var(--track);
|
||||
font-weight: 500;
|
||||
font-size: 1.05rem;
|
||||
color: #ffffffd5;
|
||||
filter: drop-shadow(0 0 5px #0000006d);
|
||||
}
|
||||
|
||||
.spotify-card-track-artist::after,
|
||||
.spotify-card-track-album::after {
|
||||
color: #ffffffa1;
|
||||
font-weight: 500;
|
||||
font-size: 0.85rem;
|
||||
filter: drop-shadow(0 0 5px #00000040);
|
||||
}
|
||||
|
||||
.spotify-card-track-artist::after {
|
||||
content: var(--artist);
|
||||
}
|
||||
|
||||
.spotify-card-track-album::after {
|
||||
content: var(--album);
|
||||
}
|
||||
|
||||
.links {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: 10px;
|
||||
display: flex;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.links a {
|
||||
color: rgba(255, 255, 255, 0.516);
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import { mergeStyles } from "utils/mergeStyles";
|
||||
import { average } from "color.js";
|
||||
import { LASTFM_KEY, LASTFM_API, LASTFM_USER } from "utils/constants";
|
||||
import {
|
||||
type FMResponse,
|
||||
type FMRawTrack,
|
||||
type FMTrack,
|
||||
transformRawTrack
|
||||
} from "types/lastfm";
|
||||
const spotifyCardWrapper: HTMLElement = document.querySelector(
|
||||
".spotify-card-wrapper-shadow"
|
||||
);
|
||||
|
||||
async function getLastFmTracks() {
|
||||
const params = new URLSearchParams({
|
||||
method: "user.getrecenttracks",
|
||||
api_key: LASTFM_KEY,
|
||||
user: LASTFM_USER,
|
||||
limit: "50",
|
||||
format: "json",
|
||||
extended: "true"
|
||||
});
|
||||
|
||||
const response: FMResponse = await (
|
||||
await fetch(`${LASTFM_API}${params}`)
|
||||
).json();
|
||||
|
||||
if (
|
||||
response.recenttracks.track.some(t => {
|
||||
try {
|
||||
return t["@attr"].nowplaying === "true";
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
) {
|
||||
const rawTrack: FMRawTrack = response.recenttracks.track[0];
|
||||
const track: FMTrack = transformRawTrack(rawTrack);
|
||||
|
||||
mergeStyles(
|
||||
{
|
||||
display: "block",
|
||||
"--track": `'${track.name.replaceAll("'", "\\'")}'`,
|
||||
"--artist": `'${track.artist.replaceAll("'", "\\'")}'`,
|
||||
"--album": `'${track.album.replaceAll("'", "\\'")}'`,
|
||||
"--album-art": `url("${track.albumArt}")`,
|
||||
"--album-color": await average(track.albumArt, {
|
||||
format: "hex"
|
||||
}),
|
||||
"--loved": track.loved ? "block" : "none"
|
||||
},
|
||||
spotifyCardWrapper.style
|
||||
);
|
||||
|
||||
(document.querySelector(".lastfm-link") as HTMLLinkElement).href =
|
||||
track.url;
|
||||
|
||||
if (
|
||||
new URLSearchParams(window.location.search).has("iamanidiot") &&
|
||||
(track.artist.includes("Taylor Swift") ||
|
||||
track.name.includes("Taylor Swift"))
|
||||
) {
|
||||
spotifyCardWrapper.style = "";
|
||||
}
|
||||
} else {
|
||||
spotifyCardWrapper.style = "";
|
||||
}
|
||||
}
|
||||
|
||||
getLastFmTracks();
|
||||
setInterval(getLastFmTracks, 10000);
|
||||
</script>
|
81
src/css/catppuccin.css
Normal file
|
@ -0,0 +1,81 @@
|
|||
/* https://github.com/catppuccin/palette/blob/main/docs/css.md */
|
||||
:root {
|
||||
--rosewater: #f5e0dc;
|
||||
--rosewater-rgb: 245, 224, 220;
|
||||
--rosewater-hsl: 9.6, 55.556%, 91.176%;
|
||||
--flamingo: #f2cdcd;
|
||||
--flamingo-rgb: 242, 205, 205;
|
||||
--flamingo-hsl: 0, 58.73%, 87.647%;
|
||||
--pink: #f5c2e7;
|
||||
--pink-rgb: 245, 194, 231;
|
||||
--pink-hsl: 316.471, 71.831%, 86.078%;
|
||||
--mauve: #cba6f7;
|
||||
--mauve-rgb: 203, 166, 247;
|
||||
--mauve-hsl: 267.407, 83.505%, 80.98%;
|
||||
--red: #f38ba8;
|
||||
--red-rgb: 243, 139, 168;
|
||||
--red-hsl: 343.269, 81.25%, 74.902%;
|
||||
--maroon: #eba0ac;
|
||||
--maroon-rgb: 235, 160, 172;
|
||||
--maroon-hsl: 350.4, 65.217%, 77.451%;
|
||||
--peach: #fab387;
|
||||
--peach-rgb: 250, 179, 135;
|
||||
--peach-hsl: 22.957, 92%, 75.49%;
|
||||
--yellow: #f9e2af;
|
||||
--yellow-rgb: 249, 226, 175;
|
||||
--yellow-hsl: 41.351, 86.047%, 83.137%;
|
||||
--green: #a6e3a1;
|
||||
--green-rgb: 166, 227, 161;
|
||||
--green-hsl: 115.455, 54.098%, 76.078%;
|
||||
--teal: #94e2d5;
|
||||
--teal-rgb: 148, 226, 213;
|
||||
--teal-hsl: 170, 57.353%, 73.333%;
|
||||
--sky: #89dceb;
|
||||
--sky-rgb: 137, 220, 235;
|
||||
--sky-hsl: 189.184, 71.014%, 72.941%;
|
||||
--sapphire: #74c7ec;
|
||||
--sapphire-rgb: 116, 199, 236;
|
||||
--sapphire-hsl: 198.5, 75.949%, 69.02%;
|
||||
--blue: #89b4fa;
|
||||
--blue-rgb: 137, 180, 250;
|
||||
--blue-hsl: 217.168, 91.87%, 75.882%;
|
||||
--lavender: #b4befe;
|
||||
--lavender-rgb: 180, 190, 254;
|
||||
--lavender-hsl: 231.892, 97.368%, 85.098%;
|
||||
--text: #cdd6f4;
|
||||
--text-rgb: 205, 214, 244;
|
||||
--text-hsl: 226.154, 63.934%, 88.039%;
|
||||
--subtext1: #bac2de;
|
||||
--subtext1-rgb: 186, 194, 222;
|
||||
--subtext1-hsl: 226.667, 35.294%, 80%;
|
||||
--subtext0: #a6adc8;
|
||||
--subtext0-rgb: 166, 173, 200;
|
||||
--subtext0-hsl: 227.647, 23.611%, 71.765%;
|
||||
--overlay2: #9399b2;
|
||||
--overlay2-rgb: 147, 153, 178;
|
||||
--overlay2-hsl: 228.387, 16.757%, 63.725%;
|
||||
--overlay1: #7f849c;
|
||||
--overlay1-rgb: 127, 132, 156;
|
||||
--overlay1-hsl: 229.655, 12.775%, 55.49%;
|
||||
--overlay0: #6c7086;
|
||||
--overlay0-rgb: 108, 112, 134;
|
||||
--overlay0-hsl: 230.769, 10.744%, 47.451%;
|
||||
--surface2: #585b70;
|
||||
--surface2-rgb: 88, 91, 112;
|
||||
--surface2-hsl: 232.5, 12%, 39.216%;
|
||||
--surface1: #45475a;
|
||||
--surface1-rgb: 69 71 90;
|
||||
--surface1-hsl: 234.286, 13.208%, 31.176%;
|
||||
--surface0: #313244;
|
||||
--surface0-rgb: 49 50 68;
|
||||
--surface0-hsl: 236.842, 16.239%, 22.941%;
|
||||
--base: #1e1e2e;
|
||||
--base-rgb: 30, 30, 46;
|
||||
--base-hsl: 240, 21.053%, 14.902%;
|
||||
--mantle: #181825;
|
||||
--mantle-rgb: 24, 24, 37;
|
||||
--mantle-hsl: 240, 21.311%, 11.961%;
|
||||
--crust: #11111b;
|
||||
--crust-rgb: 17, 17, 27;
|
||||
--crust-hsl: 240, 22.727%, 8.627%;
|
||||
}
|
2532
src/css/clip-paths.css
Normal file
39
src/css/global.css
Normal file
|
@ -0,0 +1,39 @@
|
|||
@import "@css/catppuccin.css";
|
||||
@import "@css/clip-paths.css";
|
||||
@import "@css/inter.css";
|
||||
|
||||
:root {
|
||||
background: url("/background.png");
|
||||
background-size: cover;
|
||||
height: 100vh;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
* {
|
||||
font-family: BlinkMacSystemFont, "Inter", sans-serif;
|
||||
}
|
||||
body {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 300;
|
||||
color: var(--text);
|
||||
padding: 30px;
|
||||
overflow: hidden;
|
||||
}
|
||||
a {
|
||||
color: #89b4fa;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
ul {
|
||||
margin: 0;
|
||||
}
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
sub {
|
||||
color: var(--overlay0);
|
||||
}
|
11
src/css/inter.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
src: url("/fonts/Inter/Inter.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
src: url("/fonts/Inter/Inter Italic.ttf");
|
||||
}
|
46
src/css/me.css
Normal file
|
@ -0,0 +1,46 @@
|
|||
.me-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
.img-container {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
clip-path: var(--clip-avatar-outer);
|
||||
img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
clip-path: var(--clip-avatar-inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spotify-card-wrapper {
|
||||
background: var(--album-color);
|
||||
position: relative;
|
||||
clip-path: var(--clip-sp-1);
|
||||
.spotify-card {
|
||||
.secondary-meta {
|
||||
color: #a6adc8;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
clip-path: var(--clip-sp-2);
|
||||
}
|
||||
}
|
||||
|
||||
.spotify-card-wrapper::before {
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
clip-path: var(--clip-sp-3);
|
||||
position: absolute;
|
||||
background-image: var(--album-art);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
filter: blur(20px) brightness(0.5);
|
||||
}
|
2
src/env.d.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/// <reference types="astro/client" />
|
||||
/// <reference types="astro/astro-jsx" />
|
35
src/layouts/BaseLayout.astro
Normal file
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
import "@css/global.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} />
|
||||
<meta name="darkreader-lock">
|
||||
<title>{tabTitle}</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
<script>
|
||||
import { WindowManager } from "@models/WindowManager.ts";
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document
|
||||
.querySelectorAll(".window")
|
||||
.forEach(window => WindowManager.observe(window));
|
||||
|
||||
const isBrowserAss = () =>
|
||||
["Firefox"].some(ass => navigator.userAgent.includes(ass));
|
||||
if (isBrowserAss()) document.body.classList.add("ass-browser");
|
||||
|
||||
document
|
||||
.querySelectorAll(".ua")
|
||||
// @ts-ignore
|
||||
.forEach(u => (u.innerText = navigator.userAgent));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
77
src/models/Lanyard.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
import type {
|
||||
AnyLanyardPayload,
|
||||
LanyardInitStatePayload,
|
||||
LanyardPayload,
|
||||
LanyardPresence,
|
||||
LanyardPresenceUpdatePayload
|
||||
} from "types/lanyard";
|
||||
|
||||
export class Lanyard {
|
||||
private socket: WebSocket;
|
||||
private readonly userId: string;
|
||||
private readonly updateCallback: (presence: LanyardPresence) => void;
|
||||
private heartbeatInterval?: number;
|
||||
|
||||
constructor(
|
||||
userId: string,
|
||||
updateCallback: (presence: LanyardPresence) => void
|
||||
) {
|
||||
this.userId = userId;
|
||||
this.updateCallback = updateCallback;
|
||||
this.socket = new WebSocket("wss://api.lanyard.rest/socket");
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
private send(data: AnyLanyardPayload) {
|
||||
this.socket.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
private initialize() {
|
||||
this.socket.onmessage = event => {
|
||||
const payload: LanyardPayload = JSON.parse(event.data);
|
||||
switch (payload.op) {
|
||||
case 1: {
|
||||
// Initialize
|
||||
this.send({
|
||||
op: 2,
|
||||
d: {
|
||||
subscribe_to_ids: [this.userId]
|
||||
}
|
||||
});
|
||||
|
||||
this.send({ op: 3 });
|
||||
|
||||
const interval =
|
||||
"heartbeat_interval" in payload.d
|
||||
? payload.d.heartbeat_interval
|
||||
: 30000;
|
||||
|
||||
this.heartbeatInterval = window.setInterval(() => {
|
||||
this.send({ op: 3 });
|
||||
}, interval);
|
||||
break;
|
||||
}
|
||||
case 0: {
|
||||
const typedPayload = payload as
|
||||
| LanyardInitStatePayload
|
||||
| LanyardPresenceUpdatePayload;
|
||||
|
||||
const presence =
|
||||
typedPayload.t === "INIT_STATE"
|
||||
? typedPayload.d[this.userId]
|
||||
: typedPayload.d;
|
||||
|
||||
this.updateCallback(presence);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public close() {
|
||||
if (this.heartbeatInterval) {
|
||||
clearInterval(this.heartbeatInterval);
|
||||
}
|
||||
this.socket.close();
|
||||
}
|
||||
}
|
70
src/models/WindowManager.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
export class WindowManager {
|
||||
private static _topmost: HTMLElement | null;
|
||||
|
||||
static get topmost() {
|
||||
return this._topmost;
|
||||
}
|
||||
|
||||
static set topmost(e) {
|
||||
if (this._topmost) {
|
||||
this._topmost.style.zIndex = "0";
|
||||
}
|
||||
this._topmost = e;
|
||||
this._topmost.style.display = "block";
|
||||
this._topmost.style.zIndex = "1";
|
||||
}
|
||||
|
||||
static observe(element) {
|
||||
element.addEventListener("mousedown", () => {
|
||||
this.topmost = element;
|
||||
});
|
||||
|
||||
const titleBar = element.querySelector(".title-bar");
|
||||
|
||||
titleBar.addEventListener("mousedown", startDrag);
|
||||
titleBar.addEventListener("touchstart", startDrag, { passive: false });
|
||||
|
||||
function startDrag(e) {
|
||||
if (e.target !== titleBar) return;
|
||||
|
||||
let isTouch = e.type.startsWith("touch");
|
||||
let startX = isTouch ? e.touches[0].clientX : e.clientX;
|
||||
let startY = isTouch ? e.touches[0].clientY : e.clientY;
|
||||
|
||||
let offsetX = startX - element.offsetLeft;
|
||||
let offsetY = startY - element.offsetTop;
|
||||
|
||||
function onMove(e) {
|
||||
let clientX = isTouch ? e.touches[0].clientX : e.clientX;
|
||||
let clientY = isTouch ? e.touches[0].clientY : e.clientY;
|
||||
|
||||
let newX = clientX - offsetX;
|
||||
let newY = clientY - offsetY;
|
||||
|
||||
element.style.top = `${newY}px`;
|
||||
element.style.left = `${newX}px`;
|
||||
}
|
||||
|
||||
function stopMove() {
|
||||
let maxX = window.innerWidth - element.offsetWidth;
|
||||
let maxY = window.innerHeight - element.offsetHeight;
|
||||
|
||||
let finalX = Math.min(Math.max(element.offsetLeft, 0), maxX);
|
||||
let finalY = Math.min(Math.max(element.offsetTop, 0), maxY);
|
||||
|
||||
element.style.top = `${finalY}px`;
|
||||
element.style.left = `${finalX}px`;
|
||||
|
||||
document.removeEventListener("mousemove", onMove);
|
||||
document.removeEventListener("mouseup", stopMove);
|
||||
document.removeEventListener("touchmove", onMove);
|
||||
document.removeEventListener("touchend", stopMove);
|
||||
}
|
||||
|
||||
document.addEventListener("mousemove", onMove);
|
||||
document.addEventListener("mouseup", stopMove);
|
||||
document.addEventListener("touchmove", onMove, { passive: false });
|
||||
document.addEventListener("touchend", stopMove);
|
||||
}
|
||||
}
|
||||
}
|
14
src/pages/index.astro
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
import Noscript from "@components/Noscript.astro";
|
||||
import MainWindow from "@components/windows/main/MainWindow.astro";
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
import CodeWindow from "@components/windows/code/CodeWindow.astro";
|
||||
import AssBrowser from "@components/AssBrowser.astro";
|
||||
---
|
||||
|
||||
<BaseLayout tabTitle="nin0.dev">
|
||||
<Noscript />
|
||||
<AssBrowser />
|
||||
<MainWindow />
|
||||
<CodeWindow />
|
||||
</BaseLayout>
|
65
src/types/lanyard.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
export interface LanyardPresence {
|
||||
listening_to_spotify: boolean;
|
||||
spotify: {
|
||||
track_id: string;
|
||||
timestamps: {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
song: string;
|
||||
artist: string;
|
||||
album_art_url: string;
|
||||
album: string;
|
||||
};
|
||||
discord_status: string;
|
||||
activities: {
|
||||
type: number;
|
||||
timestamps: {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
state: string;
|
||||
name: string;
|
||||
id: string;
|
||||
details: string;
|
||||
assets: {
|
||||
small_text: string;
|
||||
small_image: string;
|
||||
large_text: string;
|
||||
large_image: string;
|
||||
};
|
||||
application_id: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface AnyLanyardPayload {
|
||||
op: 0 | 1 | 2 | 3;
|
||||
d?: object;
|
||||
}
|
||||
|
||||
export interface LanyardHelloPayload {
|
||||
op: 1;
|
||||
d: {
|
||||
heartbeat_interval: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LanyardInitStatePayload {
|
||||
op: 0;
|
||||
t: "INIT_STATE";
|
||||
d: {
|
||||
[user_id: string]: LanyardPresence;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LanyardPresenceUpdatePayload {
|
||||
op: 0;
|
||||
t: "PRESENCE_UPDATE";
|
||||
d: LanyardPresence;
|
||||
}
|
||||
|
||||
export type LanyardPayload =
|
||||
| LanyardHelloPayload
|
||||
| LanyardInitStatePayload
|
||||
| LanyardPresenceUpdatePayload
|
||||
| AnyLanyardPayload;
|
44
src/types/lastfm.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
export interface FMRawTrack {
|
||||
name: string;
|
||||
url: string;
|
||||
album: {
|
||||
"#text": string;
|
||||
};
|
||||
artist: {
|
||||
name: string;
|
||||
};
|
||||
image: Array<{
|
||||
size: string;
|
||||
"#text": string;
|
||||
}>;
|
||||
loved: string;
|
||||
"@attr"?: {
|
||||
nowplaying?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FMTrack {
|
||||
name: string;
|
||||
artist: string;
|
||||
album: string;
|
||||
url: string;
|
||||
albumArt: string;
|
||||
loved: boolean;
|
||||
}
|
||||
|
||||
export interface FMResponse {
|
||||
recenttracks: {
|
||||
track: FMRawTrack[];
|
||||
};
|
||||
}
|
||||
|
||||
export function transformRawTrack(raw: FMRawTrack): FMTrack {
|
||||
return {
|
||||
name: raw.name,
|
||||
artist: raw.artist.name,
|
||||
album: raw.album["#text"],
|
||||
url: raw.url,
|
||||
albumArt: raw.image[1]["#text"],
|
||||
loved: raw.loved === "1"
|
||||
};
|
||||
}
|
5
src/utils/constants.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export const LASTFM_KEY: string = "3c5623fa1abbd11c49f53ca18e992ead";
|
||||
export const LASTFM_API: string = "https://ws.audioscrobbler.com/2.0/?";
|
||||
export const LASTFM_USER: string = "nin0dev";
|
||||
export const LANYARD_ID: string = "886685857560539176";
|
||||
export const LANYARD_API: string = "wss://api.lanyard.rest/socket";
|
5
src/utils/mergeStyles.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export function mergeStyles(source, target: CSSStyleDeclaration) {
|
||||
for (const prop in source) {
|
||||
target.setProperty(prop, source[prop]);
|
||||
}
|
||||
}
|
13
tsconfig.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/base",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./src/",
|
||||
"paths": {
|
||||
"@components/*": ["components/*"],
|
||||
"@layouts/*": ["layouts/*"],
|
||||
"@css/*": ["css/*"],
|
||||
"@models/*": ["models/*"],
|
||||
"@assets/*": ["assets/*"]
|
||||
}
|
||||
}
|
||||
}
|
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>
|