My personal site hosted @ https://indexx.dev

feat: a.status.update display + component cleanup

Index 6065b1d8 a50e6de7

-50
README.md
···
-
# Astro Starter Kit: Basics
-
-
```sh
-
npm create astro@latest -- --template basics
-
```
-
-
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
-
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
-
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
-
-
> ๐Ÿง‘โ€๐Ÿš€ **Seasoned astronaut?** Delete this file. Have fun!
-
-
![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554)
-
-
## ๐Ÿš€ Project Structure
-
-
Inside of your Astro project, you'll see the following folders and files:
-
-
```text
-
/
-
โ”œโ”€โ”€ public/
-
โ”‚ โ””โ”€โ”€ favicon.svg
-
โ”œโ”€โ”€ src/
-
โ”‚ โ”œโ”€โ”€ layouts/
-
โ”‚ โ”‚ โ””โ”€โ”€ Layout.astro
-
โ”‚ โ””โ”€โ”€ pages/
-
โ”‚ โ””โ”€โ”€ index.astro
-
โ””โ”€โ”€ package.json
-
```
-
-
To learn more about the folder structure of an Astro project, refer to
-
[our guide on project structure](https://docs.astro.build/en/basics/project-structure/).
-
-
## ๐Ÿงž 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).
+12
package-lock.json
···
"astro": "^5.7.11",
"fs": "^0.0.1-security",
"marked": "^15.0.11",
+
"node-cache": "^5.1.2",
"ts-node": "^10.9.2"
},
"devDependencies": {
···
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
+
}
+
},
+
"node_modules/node-cache": {
+
"version": "5.1.2",
+
"resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
+
"integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
+
"dependencies": {
+
"clone": "2.x"
+
},
+
"engines": {
+
"node": ">= 8.0.0"
},
"node_modules/node-fetch": {
+1
package.json
···
"astro": "^5.7.11",
"fs": "^0.0.1-security",
"marked": "^15.0.11",
+
"node-cache": "^5.1.2",
"ts-node": "^10.9.2"
},
"devDependencies": {
public/android-chrome-192x192.png

This is a binary file and will not be displayed.

public/android-chrome-512x512.png

This is a binary file and will not be displayed.

public/apple-touch-icon.png

This is a binary file and will not be displayed.

public/favicon-16x16.png

This is a binary file and will not be displayed.

public/favicon-32x32.png

This is a binary file and will not be displayed.

public/favicon.ico

This is a binary file and will not be displayed.

+1
public/site.webmanifest
···
+
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
+2 -1
public/style.css
···
stroke-width: 2px;
stroke-dasharray: var(--offset, 69px) 278px;
stroke-dashoffset: 361px;
-
transition: stroke 0.25s ease var(--stroke-delay, 0s),
+
transition:
+
stroke 0.25s ease var(--stroke-delay, 0s),
stroke-dasharray 0.35s;
}
+25
src/components/Header.astro
···
+
<h1 class="animated-border unselectable spin">
+
<svg
+
width="24"
+
height="24"
+
viewBox="0 0 24 24"
+
fill="none"
+
xmlns="http://www.w3.org/2000/svg"
+
style="position: absolute; top: -5px; margin-left: -2px; transform: rotate(-10deg); color: gold;"
+
>
+
<path
+
fill-rule="evenodd"
+
clip-rule="evenodd"
+
d="M2.5 6.09143L7.21997 10.8114L12.0005 6.03088L16.7811 10.8114L21.5 6.09245V14.9691C21.5 16.626 20.1569 17.9691 18.5 17.9691H5.5C3.84314 17.9691 2.5 16.626 2.5 14.9691V6.09143ZM19.5 10.9087V14.9691C19.5 15.5214 19.0523 15.9691 18.5 15.9691H5.5C4.94771 15.9691 4.5 15.5214 4.5 14.9691V10.9077L7.21997 13.6277L12.0005 8.84717L16.7811 13.6277L19.5 10.9087Z"
+
fill="currentColor"></path>
+
</svg>
+
<span style="position: relative; z-index: 2;">index</span>
+
</h1>
+
<span style="display: block; margin-top: -5px;">hey, i'm index. have a great day ๐Ÿ‘‹</span>
+
<small style="display: block; margin-top: -5px;color: rgb(0, 84, 106);font-size: 0.8rem;">
+
i usually mess around in Typescript, and recently i've been exploring the
+
<a href="https://atproto.com/" target="_blank"><b>AT protocol</b>.</a>
+
</small>
+
<small style="display: block; margin-top: -5px;color: rgb(0, 84, 106);font-size: 0.8rem;">
+
i'm interested in making an open-source Goodreads alternative on the protocol :)
+
</small>
+27
src/components/ProjectCard.astro
···
+
---
+
interface Props {
+
title: string;
+
description: string;
+
date: string;
+
href: string;
+
note?: string;
+
}
+
+
const { title, description, date, href, note } = Astro.props;
+
---
+
+
<a href={href} target="_blank" class="text-reset">
+
<div class="project-card" style="color: #fff;">
+
<small class="text-muted">{date}</small>
+
<h4>{title}</h4>
+
{description}
+
{
+
note && (
+
<>
+
<br />
+
<small class="text-muted">{note}</small>
+
</>
+
)
+
}
+
</div>
+
</a>
+21
src/components/ProjectsPane.astro
···
+
---
+
import { projects } from "../data/projects";
+
import ProjectCard from "./ProjectCard.astro";
+
---
+
+
<section id="projects-pane" data-bs-theme="dark">
+
<h6
+
class="text-muted text-center"
+
style="text-transform: uppercase; letter-spacing: 5px;"
+
>
+
(SOME OF) MY PROJECTS
+
</h6>
+
+
<ul class="list-unstyled">
+
<li>
+
{projects.map((project) => (
+
<ProjectCard {...project} />
+
))}
+
</li>
+
</ul>
+
</section>
+30
src/components/SocialLink.astro
···
+
---
+
interface Props {
+
name: string;
+
href: string;
+
tooltip?: {
+
title: string;
+
placement?: string;
+
};
+
}
+
+
const { name, href, tooltip } = Astro.props;
+
---
+
+
<a
+
href={href}
+
target="_blank"
+
class="circle-hover"
+
{...tooltip && {
+
'data-bs-toggle': 'tooltip',
+
'data-bs-title': tooltip.title,
+
'data-bs-placement': tooltip.placement || 'bottom'
+
}}
+
>
+
<svg viewBox="0 0 70 36">
+
<path
+
d="M6.9739 30.8153H63.0244C65.5269 30.8152 75.5358 -3.68471 35.4998 2.81531C-16.1598 11.2025 0.894099 33.9766 26.9922 34.3153C104.062 35.3153 54.5169 -6.68469 23.489 9.31527"
+
></path>
+
</svg>
+
<span>{name}</span>
+
</a>
+28
src/components/SocialLinks.astro
···
+
---
+
import SocialLink from './SocialLink.astro';
+
+
const links = [
+
{
+
name: 'Bluesky',
+
href: 'https://bsky.app/profile/did:plc:sfjxpxxyvewb2zlxwoz2vduw'
+
},
+
{
+
name: 'Discord',
+
href: 'https://discord.com/users/589386198011871233',
+
tooltip: {
+
title: 'index.lua',
+
placement: "top"
+
}
+
},
+
{
+
name: 'GitHub',
+
href: 'https://github.com/indexxing/'
+
}
+
];
+
---
+
+
<ul class="d-flex mt-4 mb-1" style="padding: 0px; width: 50%; margin: auto; justify-content: space-evenly; flex-basis: 33%;">
+
{links.map(link => (
+
<SocialLink {...link} />
+
))}
+
</ul>
+37
src/components/Status.astro
···
+
---
+
type StatusRes = {
+
text: string,
+
createdAt: string,
+
link: string
+
}
+
+
const url = new URL("/api/status", Astro.request.url);
+
const data = await fetch(url);
+
const { text, createdAt, link } = await data.json() as StatusRes;
+
+
function getRelativeTime(dateStr: string) {
+
const date = new Date(dateStr);
+
const now = new Date();
+
const diff = now.getTime() - date.getTime();
+
+
const minutes = Math.floor(diff / 60000);
+
const hours = Math.floor(minutes / 60);
+
const days = Math.floor(hours / 24);
+
+
if (days > 0) return `${days} days ago`;
+
if (hours > 0) return `${hours} hours ago`;
+
if (minutes > 0) return `${minutes} minutes ago`;
+
return 'just now';
+
}
+
+
const timeAgo = getRelativeTime(createdAt);
+
+
const date = new Date(createdAt);
+
const now = new Date();
+
const diff = now.getTime() - date.getTime();
+
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
+
+
const oldStatusClasses = days > 3 ? 'opacity-75 text-decoration-line-through' : '';
+
---
+
+
<a href={link} target="_blank" class={`badge bg-white ${oldStatusClasses}`}>Index is.. "{text}", {timeAgo}</a>
-210
src/components/Welcome.astro
···
-
---
-
import astroLogo from '../assets/astro.svg';
-
import background from '../assets/background.svg';
-
---
-
-
<div id="container">
-
<img id="background" src={background.src} alt="" fetchpriority="high" />
-
<main>
-
<section id="hero">
-
<a href="https://astro.build"
-
><img src={astroLogo.src} width="115" height="48" alt="Astro Homepage" /></a
-
>
-
<h1>
-
To get started, open the <code><pre>src/pages</pre></code> directory in your project.
-
</h1>
-
<section id="links">
-
<a class="button" href="https://docs.astro.build">Read our docs</a>
-
<a href="https://astro.build/chat"
-
>Join our Discord <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"
-
><path
-
fill="currentColor"
-
d="M107.7 8.07A105.15 105.15 0 0 0 81.47 0a72.06 72.06 0 0 0-3.36 6.83 97.68 97.68 0 0 0-29.11 0A72.37 72.37 0 0 0 45.64 0a105.89 105.89 0 0 0-26.25 8.09C2.79 32.65-1.71 56.6.54 80.21a105.73 105.73 0 0 0 32.17 16.15 77.7 77.7 0 0 0 6.89-11.11 68.42 68.42 0 0 1-10.85-5.18c.91-.66 1.8-1.34 2.66-2a75.57 75.57 0 0 0 64.32 0c.87.71 1.76 1.39 2.66 2a68.68 68.68 0 0 1-10.87 5.19 77 77 0 0 0 6.89 11.1 105.25 105.25 0 0 0 32.19-16.14c2.64-27.38-4.51-51.11-18.9-72.15ZM42.45 65.69C36.18 65.69 31 60 31 53s5-12.74 11.43-12.74S54 46 53.89 53s-5.05 12.69-11.44 12.69Zm42.24 0C78.41 65.69 73.25 60 73.25 53s5-12.74 11.44-12.74S96.23 46 96.12 53s-5.04 12.69-11.43 12.69Z"
-
></path></svg
-
>
-
</a>
-
</section>
-
</section>
-
</main>
-
-
<a href="https://astro.build/blog/astro-5/" id="news" class="box">
-
<svg width="32" height="32" fill="none" xmlns="http://www.w3.org/2000/svg"
-
><path
-
d="M24.667 12c1.333 1.414 2 3.192 2 5.334 0 4.62-4.934 5.7-7.334 12C18.444 28.567 18 27.456 18 26c0-4.642 6.667-7.053 6.667-14Zm-5.334-5.333c1.6 1.65 2.4 3.43 2.4 5.333 0 6.602-8.06 7.59-6.4 17.334C13.111 27.787 12 25.564 12 22.666c0-4.434 7.333-8 7.333-16Zm-6-5.333C15.111 3.555 16 5.556 16 7.333c0 8.333-11.333 10.962-5.333 22-3.488-.774-6-4-6-8 0-8.667 8.666-10 8.666-20Z"
-
fill="#111827"></path></svg
-
>
-
<h2>What's New in Astro 5.0?</h2>
-
<p>
-
From content layers to server islands, click to learn more about the new features and
-
improvements in Astro 5.0
-
</p>
-
</a>
-
</div>
-
-
<style>
-
#background {
-
position: fixed;
-
top: 0;
-
left: 0;
-
width: 100%;
-
height: 100%;
-
z-index: -1;
-
filter: blur(100px);
-
}
-
-
#container {
-
font-family: Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;
-
height: 100%;
-
}
-
-
main {
-
height: 100%;
-
display: flex;
-
justify-content: center;
-
}
-
-
#hero {
-
display: flex;
-
align-items: start;
-
flex-direction: column;
-
justify-content: center;
-
padding: 16px;
-
}
-
-
h1 {
-
font-size: 22px;
-
margin-top: 0.25em;
-
}
-
-
#links {
-
display: flex;
-
gap: 16px;
-
}
-
-
#links a {
-
display: flex;
-
align-items: center;
-
padding: 10px 12px;
-
color: #111827;
-
text-decoration: none;
-
transition: color 0.2s;
-
}
-
-
#links a:hover {
-
color: rgb(78, 80, 86);
-
}
-
-
#links a svg {
-
height: 1em;
-
margin-left: 8px;
-
}
-
-
#links a.button {
-
color: white;
-
background: linear-gradient(83.21deg, #3245ff 0%, #bc52ee 100%);
-
box-shadow:
-
inset 0 0 0 1px rgba(255, 255, 255, 0.12),
-
inset 0 -2px 0 rgba(0, 0, 0, 0.24);
-
border-radius: 10px;
-
}
-
-
#links a.button:hover {
-
color: rgb(230, 230, 230);
-
box-shadow: none;
-
}
-
-
pre {
-
font-family:
-
ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono',
-
monospace;
-
font-weight: normal;
-
background: linear-gradient(14deg, #d83333 0%, #f041ff 100%);
-
-webkit-background-clip: text;
-
-webkit-text-fill-color: transparent;
-
background-clip: text;
-
margin: 0;
-
}
-
-
h2 {
-
margin: 0 0 1em;
-
font-weight: normal;
-
color: #111827;
-
font-size: 20px;
-
}
-
-
p {
-
color: #4b5563;
-
font-size: 16px;
-
line-height: 24px;
-
letter-spacing: -0.006em;
-
margin: 0;
-
}
-
-
code {
-
display: inline-block;
-
background:
-
linear-gradient(66.77deg, #f3cddd 0%, #f5cee7 100%) padding-box,
-
linear-gradient(155deg, #d83333 0%, #f041ff 18%, #f5cee7 45%) border-box;
-
border-radius: 8px;
-
border: 1px solid transparent;
-
padding: 6px 8px;
-
}
-
-
.box {
-
padding: 16px;
-
background: rgba(255, 255, 255, 1);
-
border-radius: 16px;
-
border: 1px solid white;
-
}
-
-
#news {
-
position: absolute;
-
bottom: 16px;
-
right: 16px;
-
max-width: 300px;
-
text-decoration: none;
-
transition: background 0.2s;
-
backdrop-filter: blur(50px);
-
}
-
-
#news:hover {
-
background: rgba(255, 255, 255, 0.55);
-
}
-
-
@media screen and (max-height: 368px) {
-
#news {
-
display: none;
-
}
-
}
-
-
@media screen and (max-width: 768px) {
-
#container {
-
display: flex;
-
flex-direction: column;
-
}
-
-
#hero {
-
display: block;
-
padding-top: 10%;
-
}
-
-
#links {
-
flex-wrap: wrap;
-
}
-
-
#links a.button {
-
padding: 14px 18px;
-
}
-
-
#news {
-
right: 16px;
-
left: 16px;
-
bottom: 2.5rem;
-
max-width: 100%;
-
}
-
-
h1 {
-
line-height: 1.5;
-
}
-
}
-
</style>
+49
src/data/projects.ts
···
+
export interface Project {
+
title: string;
+
description: string;
+
date: string;
+
href: string;
+
note?: string;
+
}
+
+
export const projects: Project[] = [
+
{
+
title: "Box Critters Localbox",
+
description:
+
"Looking to relive Box Critters, a defunct virtual world made by Rocketsnail Games, well you're in luck! In this GitHub organization, you can find: a Typescript, unofficial server remake of the game server, documentation surrounding the game, and an Electron desktop app for playing the game locally.",
+
date: "since November 2024",
+
href: "https://github.com/Box-Critters-Localbox/",
+
},
+
{
+
title: "Toonkins Retooned",
+
description:
+
"Looking to relive Toonkins, a defunct virtual world made by Shenanigames, well you're in luck! In this Github organization, you can find: a Typescript, unofficial server remake of the game server, and a decompiled Unity project reassembly of the game (Unity project source coming soon).",
+
date: "since October 2024",
+
href: "https://github.com/ToonkinsRetooned/",
+
note:
+
'Note: "Retooned" is purposely spelled wrong, I know how to spell I swear ๐Ÿ˜ญ',
+
},
+
{
+
title: "Poly+ Rewrite",
+
description:
+
"A rewrite of Poly+, my quality-of-life browser extension for Polytoria.com. Built entirely fresh using the WXT extension framework, Typescript, and with added better overall code quality. This version is not public yet.",
+
date: "since October 2024, work in progress (private)",
+
href: "#",
+
},
+
{
+
title: "PolyCode",
+
description:
+
"PolyCode provides free code snippets, tutorials, and toolbox resources for Polytoria.",
+
date: "since August 2023, archived",
+
href: "https://polycode.vercel.app",
+
note:
+
"Note: This was made awhile ago, and if I were to redo it, I'd implement SSR instead of doing JSON file fetching.",
+
},
+
{
+
title: "Poly+",
+
description:
+
"Poly+ is a quality-of-life Chromium-based extension for Polytoria!",
+
date: "since February 2023, archived",
+
href: "https://github.com/indexxing/PolyPlus/",
+
},
+
];
+6
src/layouts/Layout.astro
···
<meta name="site_name" content="hey, I'm Index" />
<meta name="description" content={description} />
+
<!-- ICONS -->
+
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
+
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
+
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
+
<link rel="manifest" href="/site.webmanifest">
+
<!-- RESOURCES -->
<link href="/bootstrap.min.css" rel="stylesheet" />
<script src="/bootstrap.bundle.min.js" is:inline></script>
+75
src/pages/api/status.ts
···
+
import type { APIRoute } from "astro";
+
import NodeCache from "node-cache";
+
+
const cache = new NodeCache({ stdTTL: 300 }); // cache for 5 minutes
+
const actorDid = "did:plc:zl4ugdpxfgrzlr5uz2nm7kcq";
+
+
type FeedResponse = {
+
feed: {
+
post: {
+
uri: string;
+
cid: string;
+
author: {
+
did: string;
+
handle: string;
+
displayName: string;
+
avatar: string;
+
labels: any[];
+
createdAt: string;
+
};
+
record: {
+
$type: string;
+
createdAt: string;
+
text: string;
+
};
+
replyCount: number;
+
repostCount: number;
+
likeCount: number;
+
quoteCount: number;
+
indexedAt: string;
+
labels: any[];
+
};
+
}[];
+
cursor: string;
+
};
+
+
export const GET: APIRoute = async () => {
+
let status = cache.get("status");
+
+
if (status) {
+
return new Response(JSON.stringify(status), {
+
headers: { "Content-Type": "application/json", "X-Cache": "HIT" },
+
});
+
}
+
+
try {
+
const res = await fetch(
+
`https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=${actorDid}&limit=1&filter=posts_no_replies`,
+
);
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
+
+
const json: FeedResponse = await res.json();
+
const latestPost = json.feed[0].post;
+
status = {
+
text: latestPost.record.text,
+
createdAt: latestPost.record.createdAt,
+
link: `https://bsky.app/profile/${actorDid}/post/${
+
latestPost.uri.split("/")[4]
+
}`,
+
};
+
+
cache.set("status", status);
+
+
return new Response(JSON.stringify(status), {
+
headers: { "Content-Type": "application/json", "X-Cache": "MISS" },
+
});
+
} catch (err) {
+
return new Response(
+
JSON.stringify({ error: "Failed to fetch status" }),
+
{
+
status: 500,
+
headers: { "Content-Type": "application/json" },
+
},
+
);
+
}
+
};
+16 -231
src/pages/index.astro
···
---
import Layout from "../layouts/Layout.astro";
+
+
import Header from "../components/Header.astro";
+
import SocialLinks from "../components/SocialLinks.astro";
+
import Status from "../components/Status.astro";
+
import ProjectsPane from "../components/ProjectsPane.astro";
---
<Layout title="hello" description="insert wave emoji">
<main id="page" class="text-center">
-
<h1 class="animated-border unselectable spin">
-
<svg
-
width="24"
-
height="24"
-
viewBox="0 0 24 24"
-
fill="none"
-
xmlns="http://www.w3.org/2000/svg"
-
style="position: absolute; top: -5px; margin-left: -2px; transform: rotate(-10deg); color: gold;"
-
>
-
<path
-
fill-rule="evenodd"
-
clip-rule="evenodd"
-
d="M2.5 6.09143L7.21997 10.8114L12.0005 6.03088L16.7811 10.8114L21.5 6.09245V14.9691C21.5 16.626 20.1569 17.9691 18.5 17.9691H5.5C3.84314 17.9691 2.5 16.626 2.5 14.9691V6.09143ZM19.5 10.9087V14.9691C19.5 15.5214 19.0523 15.9691 18.5 15.9691H5.5C4.94771 15.9691 4.5 15.5214 4.5 14.9691V10.9077L7.21997 13.6277L12.0005 8.84717L16.7811 13.6277L19.5 10.9087Z"
-
fill="currentColor"></path>
-
</svg>
-
<span style="position: relative; z-index: 2;">index</span>
-
</h1>
-
<span style="display: block; margin-top: -5px;"
-
>hey, i'm index. have a great day ๐Ÿ‘‹
-
</span>
-
<small
-
style="display: block; margin-top: -5px;color: rgb(0, 84, 106);font-size: 0.8rem;"
-
>i usually mess around in Typescript, and recently i've been exploring the <a
-
href="https://atproto.com/"
-
target="_blank"><b>AT protocol</b>.</a
-
>
-
</small
-
>
-
<small
-
style="display: block; margin-top: -5px;color: rgb(0, 84, 106);font-size: 0.8rem;"
-
>
-
i'm interested in making an open-source Goodreads alternative on the protocol :)
-
</small
-
>
-
<ul
-
class="d-flex mt-4 mb-1"
-
style="padding: 0px; width: 50%; margin: auto; justify-content: space-evenly; flex-basis: 33%;"
-
>
-
<a
-
href="https://github.com/indexxing/"
-
target="_blank"
-
class="circle-hover"
-
>
-
<svg viewBox="0 0 70 36">
-
<path
-
d="M6.9739 30.8153H63.0244C65.5269 30.8152 75.5358 -3.68471 35.4998 2.81531C-16.1598 11.2025 0.894099 33.9766 26.9922 34.3153C104.062 35.3153 54.5169 -6.68469 23.489 9.31527"
-
></path>
-
</svg>
-
GitHub
-
</a>
-
<a
-
id="discord-link"
-
href="#"
-
class="circle-hover"
-
data-bs-toggle="tooltip"
-
data-bs-title="index.lua"
-
>
-
<svg viewBox="0 0 70 36">
-
<path
-
d="M6.9739 30.8153H63.0244C65.5269 30.8152 75.5358 -3.68471 35.4998 2.81531C-16.1598 11.2025 0.894099 33.9766 26.9922 34.3153C104.062 35.3153 54.5169 -6.68469 23.489 9.31527"
-
></path>
-
</svg>
-
<span> Discord </span>
-
</a>
-
<a
-
href="https://bsky.app/profile/did:plc:sfjxpxxyvewb2zlxwoz2vduw"
-
target="_blank"
-
class="circle-hover"
-
>
-
<svg viewBox="0 0 70 36">
-
<path
-
d="M6.9739 30.8153H63.0244C65.5269 30.8152 75.5358 -3.68471 35.4998 2.81531C-16.1598 11.2025 0.894099 33.9766 26.9922 34.3153C104.062 35.3153 54.5169 -6.68469 23.489 9.31527"
-
></path>
-
</svg>
-
Bluesky
-
</a>
-
</ul>
+
<Header />
+
<Status />
+
<SocialLinks />
<a
+
id="projects-button"
href="#"
-
id="projects-button"
+
class="d-block mt-3"
data-bs-toggle="tooltip"
data-bs-title="(Some of) My Projects"
-
data-bs-placement="bottom">. . .</a
+
data-bs-placement="bottom"
>
+
. . .
+
</a>
</main>
-
<section id="projects-pane" data-bs-theme="dark">
-
<h6
-
class="text-muted text-center"
-
style="text-transform: uppercase; letter-spacing: 5px;"
-
>
-
(SOME OF) MY PROJECTS
-
</h6>
-
-
<ul class="list-unstyled">
-
<li>
-
<a
-
href="https://github.com/Box-Critters-Localbox/"
-
target="_blank"
-
class="text-reset"
-
>
-
<div class="project-card" style="color: #fff;">
-
<small class="text-muted">since November 2024</small>
-
<h4>Box Critters Localbox</h4>
-
Looking to relive Box Critters, a defunct virtual world made by Rocketsnail
-
Games, well you're in luck! In this GitHub organization, you can find:
-
a Typescript, unofficial server remake of the game server, documentation
-
surrounding the game, and an Electron desktop app for playing the game
-
locally.
-
</div>
-
</a>
-
<a
-
href="https://github.com/ToonkinsRetooned/"
-
target="_blank"
-
class="text-reset"
-
>
-
<div class="project-card" style="color: #fff;">
-
<small class="text-muted">since October 2024</small>
-
<h4>Toonkins Retooned</h4>
-
Looking to relive Toonkins, a defunct virtual world made by Shenanigames,
-
well you're in luck! In this Github organization, you can find: a Typescript,
-
unofficial server remake of the game server, and a decompiled Unity project
-
reassembly of the game (Unity project source coming soon).
-
<br />
-
<small class="text-muted"
-
>Note: "Retooned" is purposely spelled wrong, I know how to spell
-
I swear ๐Ÿ˜ญ</small
-
>
-
</div>
-
</a>
-
<a
-
href="https://github.com/indexxing/PolyPlus-Rewrite/"
-
target="_blank"
-
class="text-reset"
-
>
-
<div class="project-card" style="color: #fff;">
-
<small class="text-muted">since October 2024, coming soon</small>
-
<h4>Poly+ Rewrite</h4>
-
A rewrite of Poly+, my quality-of-life browser extension for Polytoria.com.
-
Built entirely fresh using the WXT extension framework, Typescript, and
-
with added better overall code quality. This version is not public yet.
-
</div>
-
</a>
-
<a
-
href="https://polycode.vercel.app/"
-
target="_blank"
-
class="text-reset"
-
>
-
<div class="project-card" style="color: #fff;">
-
<small class="text-muted">since August 2023, archived</small>
-
<h4>PolyCode</h4>
-
PolyCode provides free code snippets, tutorials, and toolbox resources
-
for Polytoria.
-
<br />
-
<small class="text-muted"
-
>Note: This was made awhile ago, and if I were to redo it, I'd
-
implement SSR instead of doing JSON file fetching.</small
-
>
-
</div>
-
</a>
-
<a
-
href="https://github.com/indexxing/PolyPlus/"
-
target="_blank"
-
class="text-reset mb-3"
-
>
-
<div class="project-card" style="color: #fff;">
-
<small class="text-muted">since February 2023, archived</small>
-
<h4>Poly+</h4>
-
Poly+ is a quality-of-life Chromium-based extension for Polytoria!
-
</div>
-
</a>
-
</li>
-
</ul>
-
</section>
+
<ProjectsPane />
<script>
-
const tooltips = document.querySelectorAll(
-
'[data-bs-toggle="tooltip"]'
-
) as NodeListOf<Element>;
-
[...tooltips].forEach((element) => {
-
//@ts-ignore
-
new bootstrap.Tooltip(element);
-
-
element.addEventListener("click", () => (element as HTMLElement).blur());
-
});
-
-
const page = document.getElementById("page")!;
-
const projectsButton = document.getElementById("projects-button")!;
-
projectsButton.addEventListener("click", function () {
-
projectsButton.blur();
-
page.classList.toggle("side");
-
if (!page.classList.contains("side")) {
-
// fade in
-
fade("projects-pane", 1, 0, 0.5);
-
} else {
-
// fade out
-
fade("projects-pane", 0, 1, 0.5);
-
}
-
});
-
-
/*
-
I'm lazy so thank you, Neel
-
credits: https://blog.abhranil.net/2011/11/03/simplest-javascript-fade-animation/
-
* slightly modified
-
*/
-
function fade(
-
id: string,
-
initialOpacity: number,
-
finalOpacity: number,
-
totalTime: number
-
) {
-
const element = document.getElementById(id);
-
if (!element) {
-
throw new Error("why");
-
}
-
if (initialOpacity == 0) {
-
element.style.visibility = "visible";
-
element.style.pointerEvents = "auto";
-
} else {
-
element.style.pointerEvents = "none";
-
setTimeout(() => {
-
element.style.visibility = "hidden";
-
}, totalTime);
-
}
-
-
const currentTick = new Date().getTime();
-
const elapsed = currentTick - totalTime;
-
const newOpacity =
-
initialOpacity +
-
((finalOpacity - initialOpacity) * elapsed) / totalTime;
-
if (
-
Math.abs(newOpacity - initialOpacity) >
-
Math.abs(finalOpacity - initialOpacity)
-
) {
-
element.style.opacity = finalOpacity.toString();
-
return;
-
}
-
-
element.style.opacity = newOpacity.toString();
-
}
+
import { initializeProjects } from "../scripts/projects";
+
initializeProjects();
</script>
</Layout>
+60
src/scripts/projects.ts
···
+
export function initializeProjects() {
+
const tooltips = document.querySelectorAll(
+
'[data-bs-toggle="tooltip"]',
+
) as NodeListOf<Element>;
+
[...tooltips].forEach((element) => {
+
//@ts-ignore
+
new bootstrap.Tooltip(element);
+
element.addEventListener(
+
"click",
+
() => (element as HTMLElement).blur(),
+
);
+
});
+
+
const page = document.getElementById("page")!;
+
const projectsButton = document.getElementById("projects-button")!;
+
projectsButton.addEventListener("click", function () {
+
projectsButton.blur();
+
page.classList.toggle("side");
+
if (!page.classList.contains("side")) {
+
fade("projects-pane", 1, 0, 0.5);
+
} else {
+
fade("projects-pane", 0, 1, 0.5);
+
}
+
});
+
}
+
+
function fade(
+
id: string,
+
initialOpacity: number,
+
finalOpacity: number,
+
totalTime: number,
+
) {
+
const element = document.getElementById(id);
+
if (!element) {
+
throw new Error("why");
+
}
+
if (initialOpacity == 0) {
+
element.style.visibility = "visible";
+
element.style.pointerEvents = "auto";
+
} else {
+
element.style.pointerEvents = "none";
+
setTimeout(() => {
+
element.style.visibility = "hidden";
+
}, totalTime);
+
}
+
+
const currentTick = new Date().getTime();
+
const elapsed = currentTick - totalTime;
+
const newOpacity = initialOpacity +
+
((finalOpacity - initialOpacity) * elapsed) / totalTime;
+
if (
+
Math.abs(newOpacity - initialOpacity) >
+
Math.abs(finalOpacity - initialOpacity)
+
) {
+
element.style.opacity = finalOpacity.toString();
+
return;
+
}
+
+
element.style.opacity = newOpacity.toString();
+
}