Signed-off-by: darwinx64 <tiramisyuz@proton.me>
This commit is contained in:
darwinx64 2025-04-19 23:45:40 -05:00 committed by darwinx64
parent 8ed804a5e6
commit 98412adab9
No known key found for this signature in database
35 changed files with 1821 additions and 1424 deletions

View file

@ -1,6 +0,0 @@
---
platform: "Email"
name: "support@nin0.dev"
url: "mailto:support@nin0.dev"
note: "Replies may be slow"
---

View file

@ -1,6 +0,0 @@
---
platform: "Telegram"
url: "https://nin0dev.t.me"
name: "@nin0dev"
note: "Use for quick replies"
---

View file

@ -10,22 +10,21 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"7.css": "^0.16.0", "@astrojs/cloudflare": "^12.5.0",
"@astrojs/cloudflare": "^12.2.4", "@astrojs/node": "^9.2.0",
"@astrojs/node": "^9.1.3", "@astrojs/react": "^4.2.4",
"@astrojs/react": "^4.2.1",
"@fontsource-variable/noto-emoji": "^5.2.5", "@fontsource-variable/noto-emoji": "^5.2.5",
"@fontsource/inter": "^5.2.5", "@fontsource/inter": "^5.2.5",
"@types/react": "^19.0.10", "@types/react": "^19.1.2",
"@types/react-dom": "^19.0.4", "@types/react-dom": "^19.1.2",
"astro": "^5.5.2", "astro": "^5.7.4",
"color.js": "^1.2.0", "color.js": "^1.2.0",
"react": "^19.0.0", "react": "^19.1.0",
"react-dom": "^19.0.0" "react-dom": "^19.1.0"
}, },
"devDependencies": { "devDependencies": {
"prettier": "^3.5.3", "prettier": "^3.5.3",
"prettier-plugin-astro": "^0.14.1", "prettier-plugin-astro": "^0.14.1",
"wrangler": "^3.112.0" "wrangler": "^3.114.6"
} }
} }

2181
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View 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

View 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

View 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

View 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

6
src/assets/svg/plane.svg Normal file
View 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
View 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

View file

@ -1,29 +1,80 @@
--- ---
const { position, text, url, id, onClick } = Astro.props; const { position, text, url, id, onClick, name } = Astro.props;
import ExternalIcon from "@assets/svg/external.svg";
--- ---
<style> <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, button,
a { a {
width: 100%; width: 100%;
text-decoration: none;
cursor: pointer;
-webkit-user-drag: none;
} }
button { button {
background-color: #45475a; border: none;
border: 1px var(--text) solid; background-color: #2B2B3B;
border-radius: 10px; clip-path: var(--clip-button-inner);
color: var(--text); color: var(--text);
padding: 8px 20px; padding: 10px 20px;
} }
button:hover { .button-wrapper:has(button:hover:active) {
background-color: #585b70; 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> </style>
{ {
url ? ( url ? (
<a href={url}> <a href={url}>
<button id={id}>{text}</button> <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> </a>
) : ( ) : (
<button id={id}>{text}</button> <div class="button-wrapper">
<button id={id}><slot /></button>
</div>
) )
} }

View file

@ -1,11 +0,0 @@
<style>
div {
display: grid;
grid-auto-flow: row;
row-gap: 8px;
margin-top: 15px;
}
</style>
<div class="button-collection">
<slot />
</div>

View file

@ -3,17 +3,23 @@ const { name } = Astro.props;
--- ---
<style> <style>
div { .group-box-wrapper {
margin-top: 15px; background: linear-gradient(#343343, #2E2E3E, #2E2E3E);
margin-bottom: 0px; clip-path: var(--clip-group-box-outer)
padding: 0px 15px; }
border-radius: 15px; .group-box {
border: 1px solid #313244; padding: 15px;
background-color: #313244; clip-path: var(--clip-group-box-inner);
background-color: #2B2B3B;
display: flex;
flex-direction: column;
gap: 0.5rem;
} }
</style> </style>
<div> <div class="group-box-wrapper">
<h3>{name}</h3> <div class="group-box">
<slot /> <h3>{name}</h3>
<slot />
</div>
</div> </div>

View file

@ -1 +0,0 @@
<span></span>

View file

@ -3,6 +3,8 @@ const { title, showClose, maxWidth, hideByDefault, customClass, offset } =
Astro.props; Astro.props;
const randomID = customClass || Math.random().toString().replace(".", ""); const randomID = customClass || Math.random().toString().replace(".", "");
import XmarkIcon from "@assets/svg/xmark.svg";
--- ---
<style define:vars={{ maxWidth, offset }}> <style define:vars={{ maxWidth, offset }}>
@ -10,25 +12,30 @@ const randomID = customClass || Math.random().toString().replace(".", "");
width: var(--maxWidth); width: var(--maxWidth);
top: var(--offset); top: var(--offset);
left: var(--offset); left: var(--offset);
border-radius: 17px; background: var(--surface1);
border: 1px solid var(--text); clip-path: var(--clip-window-outer);
position: absolute; position: absolute;
} }
.window-inner {
background-color: #20202c;
clip-path: var(--clip-window-inner);
}
.title-bar { .title-bar {
.title-bar-text { user-select: none;
margin-top: 2.5px !important;
}
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
background-color: #313244; align-items: stretch;
border-radius: 15px 15px 0 0; height: 42px;
padding: 9px 18px 9px 18px; padding-left: 1rem;
}
.title-bar span {
align-self: center;
} }
svg { svg {
fill: var(--text); fill: var(--text);
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 24px !important; height: 24px;
} }
#close { #close {
background: none; background: none;
@ -36,15 +43,16 @@ const randomID = customClass || Math.random().toString().replace(".", "");
border: none; border: none;
padding: 0; padding: 0;
margin: 0; margin: 0;
height: 24px !important; width: 50px;
font: inherit; font: inherit;
cursor: pointer; cursor: pointer;
outline: inherit; outline: inherit;
} }
.window-body { .window-body {
padding: 20px !important; gap: 1rem;
background-color: #1e1e2e; display: flex;
border-radius: 0 0 17px 17px; padding: 0 1rem 1rem 1rem;
flex-direction: column;
} }
@media (pointer: coarse) { @media (pointer: coarse) {
.window { .window {
@ -64,28 +72,24 @@ const randomID = customClass || Math.random().toString().replace(".", "");
style={hideByDefault && "display: none;"} style={hideByDefault && "display: none;"}
> >
<div class="window" style="max-width: 100%"> <div class="window" style="max-width: 100%">
<div class="title-bar"> <div class="window-inner">
<div class="title-bar-text">{title}</div> <div class="title-bar">
{ <span>{title}</span>
showClose && ( {
<button showClose && (
id="close" <button
aria-label="Close" id="close"
onclick={`document.querySelector("#window-${randomID}").style.display = "none";`} aria-label="Close"
> onclick={`document.querySelector("#window-${randomID}").style.display = "none";`}
<svg
xmlns="http://www.w3.org/2000/svg"
height="24px"
viewBox="0 -960 960 960"
> >
<path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z" /> <XmarkIcon width="12" height="12" />
</svg> </button>
</button> )
) }
} </div>
</div> <div class="window-body">
<div class="window-body"> <slot />
<slot /> </div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,6 +1,9 @@
--- ---
import GroupBox from "@components/GroupBox.astro"; import Button from "@components/Button.astro";
import Window from "@components/Window.astro"; import Window from "@components/Window.astro";
import ForgejoIcon from "@assets/svg/forgejo.svg";
import GithubIcon from "@assets/svg/github.svg";
--- ---
<Window <Window
@ -11,24 +14,16 @@ import Window from "@components/Window.astro";
customClass="code-window" customClass="code-window"
offset="55px" offset="55px"
> >
<p> As mentioned in the main page, I write code. Some used by a lot, some
As mentioned in the main page, I write code. Some used by a lot, some not as much. You can find it below.
not as much. You can find it on: <Button name="GitHub" url="https://gh.nin0.dev/">
<GroupBox name="GitHub (@nin0-dev)"> <GithubIcon slot="icon" />
Here, you'll find most of my external contributions. I rarely start Here, you'll find most of my external contributions. I rarely start
personal projects on GitHub. personal projects on GitHub.
<br /> </Button>
<a href="https://gh.nin0.dev">Go!</a> <Button name="Forgejo" url="https://git.nin0.dev/nin0">
<br /> <ForgejoIcon slot="icon" />
<br /> Some more personal/niche projects can be found on my own instance
</GroupBox> (git.nin0.dev), which will probably be down.
<GroupBox name="Forgejo (@nin0)"> </Button>
Some more personal/niche projects can be found on my own instance
(git.nin0.dev), which will probably be down.
<br />
<a href="https://git.nin0.dev/nin0">Go!</a>
<br />
<br />
</GroupBox>
</p>
</Window> </Window>

View file

@ -0,0 +1,21 @@
---
const { ...attrs } = Astro.props
---
<a {...attrs}>
<slot name="icon" />
<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>

View file

@ -1,22 +1,22 @@
--- ---
import Social from "./Social.astro"; import Chip from "./Chip.astro";
import Envelope from "@assets/svg/envelope.svg";
const contacts: any = Object.values( import Plane from "@assets/svg/plane.svg";
import.meta.glob("../../../../info/contacts/*.md", {
eager: true
})
);
--- ---
<ul> <div>
{ <Chip href="mailto:support@nin0.dev">
contacts.map(contact => ( <Envelope slot="icon" />
<Social support@nin0.dev
platform={contact.frontmatter.platform} </Chip>
url={contact.frontmatter.url} <Chip href="https://nin0dev.t.me">
name={contact.frontmatter.name} <Plane slot="icon" />
note={contact.frontmatter.note} @nin0dev
/> </Chip>
)) </div>
<style>
div {
display: flex;
flex-wrap: wrap;
} }
</ul> </style>

View file

@ -4,29 +4,22 @@ import GroupBox from "@components/GroupBox.astro";
import Me from "./Me"; import Me from "./Me";
import Window from "@components/Window.astro"; import Window from "@components/Window.astro";
import Button from "@components/Button.astro"; import Button from "@components/Button.astro";
import ButtonCollection from "@components/ButtonCollection.astro";
import Spacer from "@components/Spacer.astro";
--- ---
<script> <script>
import { WindowManager } from "@models/WindowManager";
document document
.querySelector("#show-code-window") .querySelector("#show-code-window")
.addEventListener("click", () => { .addEventListener("click", () => WindowManager.topmost = document.getElementById("window-code-window"));
// @ts-ignore
document.querySelector("#window-code-window").style.display =
"block";
});
</script> </script>
<Window title="Home" maxWidth="600px" showClose={false}> <Window title="Home" maxWidth="600px" showClose={false}>
<Me client:only /> <Me client:only />
<ButtonCollection> <Button position="left" id="show-code-window">My code</Button>
<Button position="left" text="My code" id="show-code-window" />
</ButtonCollection>
<GroupBox name="About me"> <GroupBox name="About me">
<p> <p>
I'm a Canadian self-taught software developer. I'm a Canadian self-taught software developer.
<br />
I also make things that some people use with varying degrees of usefulness. I also make things that some people use with varying degrees of usefulness.
</p> </p>
</GroupBox> </GroupBox>

View file

@ -1,12 +1,12 @@
import "@css/me.css"; import "@css/me.css";
import { initLanyard } from "@js/lanyard/lanyard"; import type { LanyardPresence } from "types/lanyard";
import { type LanyardPresence } from "../../../js/lanyard/types"; import { Lanyard } from "models/Lanyard";
import { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { average } from "color.js"; import { average } from "color.js";
const lightenColor = (color: string, percent: number) => const lightenColor = (color: string, percent: number) =>
`hsl(from ${color} h s ${percent.toString()}%)`; `hsl(from ${color} h s ${percent.toString()}%)`;
const api_key = "3c5623fa1abbd11c49f53ca18e992ead"; const apiKey = "3c5623fa1abbd11c49f53ca18e992ead";
export default function Me() { export default function Me() {
// me-e-eeeee // me-e-eeeee
@ -21,19 +21,17 @@ export default function Me() {
loved: boolean; loved: boolean;
streak: number; streak: number;
spotifyURL?: string; spotifyURL?: string;
} }
| undefined | undefined
>(); >();
useEffect(() => { useEffect(() => {
initLanyard(presence => { new Lanyard("886685857560539176", p => setPresence(p));
setPresence(presence);
});
async function getLastFmTracks() { async function getLastFmTracks() {
const params = new URLSearchParams({ const params = new URLSearchParams({
method: "user.getrecenttracks", method: "user.getrecenttracks",
api_key, api_key: apiKey,
user: "nin0dev", user: "nin0dev",
limit: "50", limit: "50",
format: "json", format: "json",
@ -61,7 +59,7 @@ export default function Me() {
`https://ws.audioscrobbler.com/2.0/?${new URLSearchParams( `https://ws.audioscrobbler.com/2.0/?${new URLSearchParams(
{ {
method: "track.getinfo", method: "track.getinfo",
api_key, api_key: apiKey,
format: "json", format: "json",
track: currentlyPlayingTrack.name, track: currentlyPlayingTrack.name,
artist: currentlyPlayingTrack.artist.name artist: currentlyPlayingTrack.artist.name
@ -105,136 +103,141 @@ export default function Me() {
return ( return (
<> <>
<div className="me-container"> <div className="me-container">
<img <div
src="logo.png" className="img-container"
alt="the nin0 logo"
style={{ style={{
borderColor: presence backgroundColor: presence
? ["online", "idle"].includes( ? ["online", "idle"].includes(
presence.discord_status presence.discord_status
) )
? "#a6e3a1" ? "var(--green)"
: "#6c7086" : "var(--overlay0)"
: "#6c7086" : "var(--overlay0)"
}} }}
/> >
<h3> <img
nin0 <span style={{ fontSize: "0.9rem" }}>(he/him)</span> src="logo.png"
</h3> alt="the nin0 logo"
/>
</div>
<div>
<h2>
nin0
</h2>
<h4>
he/him
</h4>
</div>
</div> </div>
{currentlyPlaying && ( {currentlyPlaying && (
<div <div
className="spotify-card" className="spotify-card-wrapper"
style={{ style={{
borderColor: lightenColor( "--album-color": currentlyPlaying.mainColor,
currentlyPlaying.mainColor, "--album-art": `url(${currentlyPlaying.albumArt})`
90 } as React.CSSProperties}
)
}}
> >
<h3
style={{
color: lightenColor(currentlyPlaying.mainColor, 90)
}}
>
Listening to
</h3>
<div <div
className="spotify-card-background" className="spotify-card"
style={{
backgroundImage: `url(${currentlyPlaying.albumArt})`
}}
></div>
<div
className="spotify-card-content"
style={{
display: "flex",
alignItems: "center",
marginBottom: "10px"
}}
> >
<img <h3
src={currentlyPlaying.albumArt}
style={{ style={{
marginRight: "10px", color: lightenColor(currentlyPlaying.mainColor, 90)
width: "50px",
borderRadius: "5px"
}} }}
/> >
<div> Listening to
<p </h3>
<div
className="spotify-card-content"
style={{
display: "flex",
alignItems: "center",
marginBottom: "10px"
}}
>
<img
src={currentlyPlaying.albumArt}
style={{ style={{
margin: "0", marginRight: "10px",
fontWeight: "600", width: "50px",
color: lightenColor( borderRadius: "5px"
currentlyPlaying.mainColor,
90
)
}} }}
> />
{currentlyPlaying.title} <div>
</p> <p
<p style={{
style={{ margin: "0",
margin: "0", fontWeight: "600",
color: lightenColor( color: lightenColor(
currentlyPlaying.mainColor, currentlyPlaying.mainColor,
85 90
) )
}} }}
className="secondary-meta"
>
{currentlyPlaying.artist}
</p>
<p
style={{
margin: "0",
color: lightenColor(
currentlyPlaying.mainColor,
85
)
}}
className="secondary-meta"
>
{currentlyPlaying.album}
</p>
</div>
</div>
<p style={{ fontSize: "0.9rem" }} className="emoji">
{(() => {
const flags: {
color: string;
data: string;
}[] = [];
if (currentlyPlaying.loved)
flags.push({
color: "#f38ba8",
data: "💞 Loved track"
});
if (currentlyPlaying.streak > 1) {
flags.push({
color: "#f9e2af",
data: `⚡ Played ${currentlyPlaying.streak} times in a row`
});
}
return flags.map(flag => (
<span
style={{ color: flag.color }}
className="emoji"
key={flag.color}
> >
{flag.data}{" "} {currentlyPlaying.title}
{flags.indexOf(flag) !== </p>
flags.length - 1 && ( <p
<span style={{ color: "white" }}> style={{
{" "}{" "} margin: "0",
</span> color: lightenColor(
)} currentlyPlaying.mainColor,
</span> 85
)); )
})()} }}
</p> className="secondary-meta"
>
{currentlyPlaying.artist}
</p>
<p
style={{
margin: "0",
color: lightenColor(
currentlyPlaying.mainColor,
85
)
}}
className="secondary-meta"
>
{currentlyPlaying.album}
</p>
</div>
</div>
<p style={{ fontSize: "0.9rem" }} className="emoji">
{(() => {
const flags: {
color: string;
data: string;
}[] = [];
if (currentlyPlaying.loved)
flags.push({
color: "#f38ba8",
data: "💞 Loved track"
});
if (currentlyPlaying.streak > 1) {
flags.push({
color: "#f9e2af",
data: `⚡ Played ${currentlyPlaying.streak} times in a row`
});
}
return flags.map(flag => (
<span
style={{ color: flag.color }}
className="emoji"
key={flag.color}
>
{flag.data}{" "}
{flags.indexOf(flag) !==
flags.length - 1 && (
<span style={{ color: "white" }}>
{" "}{" "}
</span>
)}
</span>
));
})()}
</p>
</div>
</div> </div>
)} )}
</> </>

View file

@ -1,18 +0,0 @@
---
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>

81
src/css/catppuccin.css Normal file
View 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.600, 55.556%, 91.176%;
--flamingo: #f2cdcd;
--flamingo-rgb: 242, 205, 205;
--flamingo-hsl: 0.000, 58.730%, 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.980%;
--red: #f38ba8;
--red-rgb: 243, 139, 168;
--red-hsl: 343.269, 81.250%, 74.902%;
--maroon: #eba0ac;
--maroon-rgb: 235, 160, 172;
--maroon-hsl: 350.400, 65.217%, 77.451%;
--peach: #fab387;
--peach-rgb: 250, 179, 135;
--peach-hsl: 22.957, 92.000%, 75.490%;
--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.000, 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.500, 75.949%, 69.020%;
--blue: #89b4fa;
--blue-rgb: 137, 180, 250;
--blue-hsl: 217.168, 91.870%, 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.000%;
--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.490%;
--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.500, 12.000%, 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.000, 21.053%, 14.902%;
--mantle: #181825;
--mantle-rgb: 24, 24, 37;
--mantle-hsl: 240.000, 21.311%, 11.961%;
--crust: #11111b;
--crust-rgb: 17, 17, 27;
--crust-hsl: 240.000, 22.727%, 8.627%;
}

18
src/css/clip-paths.css Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,9 +1,11 @@
@import "@css/catppuccin.css";
:root { :root {
--background: url("/background.png"); --background: url("/background.png");
--text: #cdd6f4; -webkit-font-smoothing: antialiased;
} }
* { * {
font-family: Inter, sans-serif; font-family: BlinkMacSystemFont, Inter, sans-serif;
} }
body { body {
background: var(--background); background: var(--background);
@ -20,7 +22,12 @@ a {
overflow: scroll; overflow: scroll;
} }
} }
.emoji { .emoji {
font-family: "Inter", "Noto Emoji Variable"; font-family: "Inter", "Noto Emoji Variable";
} }
h1,h2,h3,h4,h5,h6,p,ul {
margin: 0;
}
h4,h5,h6 {
color: var(--overlay0)
}

View file

@ -1,49 +1,42 @@
.me-container { .me-container {
display: flex; display: flex;
h3 { align-items: center;
color: #89b4fa; gap: 0.75rem;
font-weight: 400; .img-container {
font-size: 2rem;
margin-left: 20px;
margin-top: auto;
margin-bottom: auto;
}
img {
width: 80px; width: 80px;
height: 80px; height: 80px;
border-radius: 6px; clip-path: var(--clip-avatar-outer);
box-shadow: 10px black; img {
border-style: solid; width: 80px;
border-width: 2px; height: 80px;
clip-path: var(--clip-avatar-inner);
}
} }
} }
.spotify-card { .spotify-card-wrapper {
h3 { background: var(--album-color);
margin: 13px 0;
}
.secondary-meta {
color: #a6adc8;
font-size: 0.85rem;
}
.spotify-card-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
border-radius: 15px;
filter: brightness(0.3) blur(8px);
background-size: cover;
background-position: center;
overflow: hidden;
}
position: relative; position: relative;
background-color: transparent; clip-path: var(--clip-sp-1);
padding: 5px 15px; .spotify-card {
margin-top: 15px; .secondary-meta {
border-radius: 15px; color: #a6adc8;
border: 2px solid #cdd6f4; font-size: 0.85rem;
z-index: 0; }
overflow: hidden; padding: 15px;
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);
} }

1
src/env.d.ts vendored
View file

@ -1 +1,2 @@
/// <reference types="astro/client" /> /// <reference types="astro/client" />
/// <reference types="astro/astro-jsx" />

View file

@ -1,37 +0,0 @@
// wow i love being fake programmer
// source: https://jams.hackclub.com/batch/webOS/part-3
export function dragElement(element) {
var initialX = 0;
var initialY = 0;
var currentX = 0;
var currentY = 0;
if (document.getElementById("title-bar"))
document.getElementById("title-bar").onmousedown = startDragging;
else element.onmousedown = startDragging;
function startDragging(e) {
e = e || window.event;
e.preventDefault();
initialX = e.clientX;
initialY = e.clientY;
document.onmouseup = stopDragging;
document.onmousemove = dragElement;
}
function dragElement(e) {
e = e || window.event;
e.preventDefault();
currentX = initialX - e.clientX;
currentY = initialY - e.clientY;
initialX = e.clientX;
initialY = e.clientY;
element.style.top = element.offsetTop - currentY + "px";
element.style.left = element.offsetLeft - currentX + "px";
}
function stopDragging() {
document.onmouseup = null;
document.onmousemove = null;
}
}

View file

@ -1 +0,0 @@
declare module "@js/lanyard/lanyard";

View file

@ -1,56 +0,0 @@
import type {
AnyLanyardPayload,
LanyardInitStatePayload,
LanyardPayload,
LanyardPresence,
LanyardPresenceUpdatePayload
} from "./types";
function sendToSocket(socket: WebSocket, data: AnyLanyardPayload) {
socket.send(JSON.stringify(data));
}
export function initLanyard(
updateCallback: (presence: LanyardPresence) => void
) {
const socket = new WebSocket("wss://api.lanyard.rest/socket");
socket.onmessage = data => {
const payload: LanyardPayload = JSON.parse(data.data);
switch (payload.op) {
case 1: {
// Initialize
sendToSocket(socket, {
op: 2,
d: {
subscribe_to_ids: ["886685857560539176"]
}
});
sendToSocket(socket, {
op: 3
});
setInterval(
() => {
sendToSocket(socket, {
op: 3
});
},
"heartbeat_interval" in payload.d
? payload.d.heartbeat_interval
: 30000
);
break;
}
case 0: {
const typedPayload = payload as
| LanyardInitStatePayload
| LanyardPresenceUpdatePayload;
updateCallback(
typedPayload.t === "INIT_STATE"
? typedPayload.d["886685857560539176"]
: typedPayload.d
);
}
}
};
}

View file

@ -1,5 +1,6 @@
--- ---
import "../css/style.css"; import "@css/global.css";
import "@css/clip-paths.css";
import "@fontsource/inter"; import "@fontsource/inter";
import "@fontsource-variable/noto-emoji"; import "@fontsource-variable/noto-emoji";
@ -17,12 +18,9 @@ const { tabTitle } = Astro.props;
<body> <body>
<slot /> <slot />
<script> <script>
import { dragElement } from "@js/drag"; import { WindowManager } from "@models/WindowManager.ts";
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".window").forEach(window => { document.querySelectorAll(".window").forEach(window => WindowManager.observe(window));
dragElement(window);
});
}); });
</script> </script>
</body> </body>

77
src/models/Lanyard.ts Normal file
View 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();
}
}

View 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);
}
}
}

View file

@ -2,7 +2,6 @@
import FearOfTechnology from "@windows/mainWindow/FearOfTechnology.astro"; import FearOfTechnology from "@windows/mainWindow/FearOfTechnology.astro";
import MainWindow from "@windows/mainWindow/MainWindow.astro"; import MainWindow from "@windows/mainWindow/MainWindow.astro";
import BaseLayout from "../layouts/BaseLayout.astro"; import BaseLayout from "../layouts/BaseLayout.astro";
import Window from "@components/Window.astro";
import CodeWindow from "@windows/code/CodeWindow.astro"; import CodeWindow from "@windows/code/CodeWindow.astro";
--- ---

View file

@ -7,8 +7,8 @@
"@windows/*": ["components/windows/*"], "@windows/*": ["components/windows/*"],
"@layouts/*": ["layouts/*"], "@layouts/*": ["layouts/*"],
"@css/*": ["css/*"], "@css/*": ["css/*"],
"@js/*": ["js/*"], "@models/*": ["models/*"],
"@info/*": ["info/*"] "@assets/*": ["assets/*"]
}, },
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "react" "jsxImportSource": "react"