feat: I did some stuff but never pushed (#66)

### Explanation

_No response_

### Issue

_No response_

### Attestations

- [ ] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/)
- [ ] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/).
- [ ] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/).

### Dependencies

- [ ] I have pinned the dependencies to a specific patch version.

### Style

- [ ] I have run the linter and resolved any errors.
- [ ] My pull request uses an appropriate title, matching the conventional commit standards.
- [ ] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request.

### Tests

- [ ] My contribution adds new code, and I have added tests to cover it.
- [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes.
- [ ] All new and existing tests pass locally with my changes.
- [ ] Code coverage remains at or above the configured threshold.

### Documentation

_No response_

### Versioning

_No response_

Reviewed-on: https://codeberg.org/nhcarrigan/portfolio/pulls/66
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit is contained in:
Naomi Carrigan 2025-01-05 03:53:02 +00:00 committed by Naomi the Technomancer
parent d2ffbc914d
commit a66c296b9b
11 changed files with 165 additions and 302 deletions

View File

@ -2,5 +2,11 @@
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit" "source.fixAll.eslint": "explicit"
}, },
"eslint.validate": ["typescript"] "eslint.validate": [
"typescript"
],
"sonarlint.connectedMode.project": {
"connectionId": "nhcarrigan",
"projectKey": "nhcarrigan_portfolio"
}
} }

View File

@ -25,40 +25,50 @@ const About = (): JSX.Element => {
<p className="mb-2">{`My expertise spans:`}</p> <p className="mb-2">{`My expertise spans:`}</p>
<ul className="w-4/5 m-auto"> <ul className="w-4/5 m-auto">
<li className="mb-2 relative before:content-['🩵'] <li
before:absolute before:left-[-1em]"> className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]"
>
<strong>{`Inclusive Community Building:`}</strong> <strong>{`Inclusive Community Building:`}</strong>
{` Cultivated {` Cultivated
welcoming communities of 20,000 to 300K+ members across Discord, welcoming communities of 20,000 to 300K+ members across Discord,
Slack, and GitHub, with a focus on supporting underrepresented Slack, and GitHub, with a focus on supporting underrepresented
groups in tech.`} groups in tech.`}
</li> </li>
<li className="mb-2 relative before:content-['🩵'] <li
before:absolute before:left-[-1em]"> className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]"
>
<strong>{`Empowering Education:`}</strong> <strong>{`Empowering Education:`}</strong>
{` Contributed to and {` Contributed to and
maintained open-source curricula used by millions of aspiring maintained open-source curricula used by millions of aspiring
developers globally, focusing on accessibility and engaging developers globally, focusing on accessibility and engaging
learning experiences.`} learning experiences.`}
</li> </li>
<li className="mb-2 relative before:content-['🩵'] <li
before:absolute before:left-[-1em]"> className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]"
>
<strong>{`Software Engineering for Inclusivity:`}</strong> <strong>{`Software Engineering for Inclusivity:`}</strong>
{` Developed {` Developed
sophisticated bots and tools that not only streamline moderation sophisticated bots and tools that not only streamline moderation
and boost engagement but also promote safe, inclusive spaces for and boost engagement but also promote safe, inclusive spaces for
all community members.`} all community members.`}
</li> </li>
<li className="mb-2 relative before:content-['🩵'] <li
before:absolute before:left-[-1em]"> className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]"
>
<strong>{`Developer Experience (DX):`}</strong> <strong>{`Developer Experience (DX):`}</strong>
{` Led initiatives to {` Led initiatives to
improve documentation, SDK support, and overall developer improve documentation, SDK support, and overall developer
satisfaction, with an emphasis on making resources accessible to satisfaction, with an emphasis on making resources accessible to
newcomers in the field.`} newcomers in the field.`}
</li> </li>
<li className="mb-2 relative before:content-['🩵'] <li
before:absolute before:left-[-1em]"> className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]"
>
<strong>{`Mentorship and Advocacy:`}</strong> <strong>{`Mentorship and Advocacy:`}</strong>
{` Implemented programs to {` Implemented programs to
support aspiring developers, particularly those from support aspiring developers, particularly those from
@ -88,6 +98,19 @@ const About = (): JSX.Element => {
industry. Let's connect to discuss how I can best support your industry. Let's connect to discuss how I can best support your
organisation!`} organisation!`}
</p> </p>
<p className="mb-2">
{`Want to get to know Naomi better? Check out her `}
<a
className="underline"
href="https://loan.nhcarrigan.com"
rel="noopener noreferrer"
target="_blank"
>
{`Life of a Naomi game`}
</a>
{`!`}
</p>
</section> </section>
</main> </main>
); );

View File

@ -1,37 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { PlayComponent } from "../../components/play";
import { Rule } from "../../components/rule";
import { Play } from "../../config/Play";
import type { JSX } from "react";
/**
* Renders the /play page.
* @returns A React Component.
*/
const PlayPage = (): JSX.Element => {
return (
<main className="w-[95%] text-center
max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">{`Play with Naomi`}</h1>
<section>
<p className="mb-2">
{`I play some silly li'l mobile games to keep myself from working non-stop. Here's what I'm currently playing - will you play with me?`}
</p>
<Rule />
<div className="w-full">
{Play.toSorted((a, b) => {
return a.name.localeCompare(b.name);
}).map((game) => {
return <PlayComponent key={game.name} {...game} />;
})}
</div>
</section>
</main>
);
};
export default PlayPage;

View File

@ -13,8 +13,10 @@ import type { JSX } from "react";
*/ */
const PlayPage = (): JSX.Element => { const PlayPage = (): JSX.Element => {
return ( return (
<main className="w-[95%] text-center <main
max-w-4xl m-auto mt-16 mb-16 rounded-lg"> className="w-[95%] text-center
max-w-4xl m-auto mt-16 mb-16 rounded-lg"
>
<h1 className="text-5xl">{`Technologies`}</h1> <h1 className="text-5xl">{`Technologies`}</h1>
<p className="mb-2"> <p className="mb-2">
{`These are the technologies I use on a regular basis.`} {`These are the technologies I use on a regular basis.`}
@ -23,32 +25,98 @@ const PlayPage = (): JSX.Element => {
<Rule /> <Rule />
<h2 className="text-3xl">{`Environment`}</h2> <h2 className="text-3xl">{`Environment`}</h2>
<div className="w-full flex flex-wrap justify-evenly"> <div className="w-full flex flex-wrap justify-evenly">
<img alt="Arch Linux" className="w-[100px]" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/archlinux/archlinux-original.svg" /> <img
<img alt="VSCode" className="w-[100px]" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/vscode/vscode-original.svg"/> alt="Arch Linux"
<img alt="Prisma" className="w-[100px]" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/prisma/prisma-original.svg" /> className="w-[100px]"
<img alt="Node.js" className="w-[100px]" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/nodejs/nodejs-original.svg" /> src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/archlinux/archlinux-original.svg"
/>
<img
alt="VSCode"
className="w-[100px]"
src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/vscode/vscode-original.svg"
/>
<img
alt="Prisma"
className="w-[100px]"
src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/prisma/prisma-original.svg"
/>
<img
alt="Node.js"
className="w-[100px]"
src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/nodejs/nodejs-original.svg"
/>
</div> </div>
</section> </section>
<section> <section>
<Rule /> <Rule />
<h2 className="text-3xl">{`Languages`}</h2> <h2 className="text-3xl">{`Languages`}</h2>
<div className="w-full flex flex-wrap justify-evenly"> <div className="w-full flex flex-wrap justify-evenly">
<img alt="JavaScript" className="w-[100px]" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/javascript/javascript-original.svg" /> <img
<img alt="TypeScript" className="w-[100px]" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/typescript/typescript-original.svg" /> alt="JavaScript"
<img alt="Python" className="w-[100px]" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/python/python-original.svg" /> className="w-[100px]"
<img alt="Dotnet" className="w-[100px]" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/dotnetcore/dotnetcore-original.svg" /> src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/javascript/javascript-original.svg"
<img alt="Kotlin" className="w-[100px]" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/kotlin/kotlin-original.svg" /> />
<img alt="Rust" className="w-[100px]" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/rust/rust-original.svg" /> <img
<img alt="Ruby" className="w-[100px]" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/ruby/ruby-original.svg" /> alt="TypeScript"
className="w-[100px]"
src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/typescript/typescript-original.svg"
/>
<img
alt="Python"
className="w-[100px]"
src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/python/python-original.svg"
/>
<img
alt="Dotnet"
className="w-[100px]"
src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/dotnetcore/dotnetcore-original.svg"
/>
<img
alt="Kotlin"
className="w-[100px]"
src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/kotlin/kotlin-original.svg"
/>
<img
alt="Rust"
className="w-[100px]"
src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/rust/rust-original.svg"
/>
<img
alt="Ruby"
className="w-[100px]"
src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/ruby/ruby-original.svg"
/>
</div> </div>
</section> </section>
<section> <section>
<Rule /> <Rule />
<h2 className="text-3xl">{`Coming Soon!`}</h2> <h2 className="text-3xl">{`Coming Soon!`}</h2>
<div className="w-full flex flex-wrap justify-evenly"> <div className="w-full flex flex-wrap justify-evenly">
<img alt="Go" className="w-[100px]" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/go/go-original.svg" /> <img
<img alt="Dart" className="w-[100px]" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/dart/dart-original.svg" /> alt="Haskell"
<img alt="Flutter" className="w-[100px]" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/flutter/flutter-original.svg" /> className="w-[100px]"
src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/haskell/haskell-original.svg"
/>
<img
alt="Zig"
className="w-[100px]"
src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/zig/zig-original.svg"
/>
<img
alt="Go"
className="w-[100px]"
src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/go/go-original.svg"
/>
<img
alt="Dart"
className="w-[100px]"
src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/dart/dart-original.svg"
/>
<img
alt="Flutter"
className="w-[100px]"
src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/flutter/flutter-original.svg"
/>
</div> </div>
</section> </section>
</main> </main>

View File

@ -1,115 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/* eslint-disable react/jsx-no-bind */
"use client";
import { faAndroid, faAppStore } from "@fortawesome/free-brands-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useState, type JSX } from "react";
interface PlayProperties {
name: string;
userId: string;
android: string;
ios: string;
server?: string;
guild?: {
name: string;
id: string;
};
}
const copyToClipboard = (text: string): void => {
void navigator.clipboard.writeText(text);
};
/**
* Renders the view for a mobile game.
* @param properties - The game to render.
* @returns A JSX element.
*/
export const PlayComponent = (properties: PlayProperties): JSX.Element => {
const { name, android, ios, server, guild, userId } = properties;
const [ isFriendCodeCopied, setIsFriendCodeCopied ]
= useState<boolean>(false);
const [ isGuildIdCopied, setIsGuildIdCopied ]
= useState<boolean>(false);
const columns = guild
? "grid-cols-[2fr,1fr,200px]"
: "grid-cols-[2fr,200px]";
return (
<div className={`grid items-center gap-2.5 pb-10 w-full ${columns}`}>
<button
className="border-solid border-2 rounded-full p-2
text-[--background] bg-[--foreground]
hover:bg-[--background] hover:text-[--foreground]"
onClick={() => {
copyToClipboard(userId);
setIsFriendCodeCopied(true);
setTimeout(() => {
setIsFriendCodeCopied(false);
}, 2000);
}}
type="button"
>
<h2 className="text-2xl">{name}</h2>
<p>{`Friend Code: ${isFriendCodeCopied
? "Copied!"
: userId}`}</p>
{typeof server === "string"
? <p className="text-sm">{`Server: ${server}`}</p>
: null}
</button>
{guild
? <button
className="border-solid border-2 rounded-full p-2
text-[--background] bg-[--foreground]
hover:bg-[--background] hover:text-[--foreground]"
onClick={() => {
copyToClipboard(userId);
setIsGuildIdCopied(true);
setTimeout(() => {
setIsGuildIdCopied(false);
}, 2000);
}}
type="button"
>
<p className="text-2xl">{"Guild"}</p>
<p>{guild.name}</p>
<p className="text-sm">{`ID: ${isGuildIdCopied
? "Copied!"
: guild.id}`}</p>
</button>
: null}
<div className="flex">
<a
aria-label={`Play ${name} on Android`}
className="flex m-auto justify-between
items-center border-solid border-2 rounded-full p-2
bg-[#3DDC84] text-[#FFFFFF] hover:bg-[#FFFFFF] hover:text-[#3DDC84]"
href={android}
rel="noreferrer"
target="_blank"
>
<FontAwesomeIcon icon={faAndroid} size="3x" />
</a>
<a
aria-label={`Play ${name} on iOS`}
className="flex m-auto justify-between
items-center border-solid border-2 rounded-full p-2
bg-[#1ba7f8] text-[#FFFFFF] hover:bg-[#FFFFFF] hover:text-[#1ba7f8]"
href={ios}
rel="noreferrer"
target="_blank"
>
<FontAwesomeIcon icon={faAppStore} size="3x" />
</a>
</div>
</div>
);
};

View File

@ -112,4 +112,11 @@ export const Art: Array<{
name: "Librarian Bot", name: "Librarian Bot",
url: "https://picrew.me/en/image_maker/2435029", url: "https://picrew.me/en/image_maker/2435029",
}, },
{
alt: "An anime-style illustration of a character with shoulder-length wavy brown hair, blue eyes behind black-rimmed glasses, wearing a beige cardigan over a white top with a black choker. They are holding a blue book and pen, with a question mark speech bubble above their head. The background is pastel purple with decorative white sparkles and chain patterns.",
artist: "Picrew",
img: "tickets.png",
name: "Ticket System",
url: "https://picrew.me/en/image_maker/625876",
},
]; ];

View File

@ -216,5 +216,5 @@ export const Games: Array<{
img: "enshrouded.jpg", img: "enshrouded.jpg",
name: "Enshrouded", name: "Enshrouded",
url: "https://store.steampowered.com/app/1203620/Enshrouded/", url: "https://store.steampowered.com/app/1203620/Enshrouded/",
} },
]; ];

View File

@ -1,102 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
export const Play: Array<{
name: string;
userId: string;
android: string;
ios: string;
server?: string;
guild?: {
name: string;
id: string;
};
}> = [
{
android: "https://play.google.com/store/apps/details?id=jp.pokemon.pokemontcgp",
ios: "https://apps.apple.com/us/app/pok%C3%A9mon-tcg-pocket/id6479970832",
name: "Pokémon TCG Pocket",
userId: "3382202283069817",
},
{
android: "https://play.google.com/store/apps/details?id=com.mujoysg.hxbb",
guild: {
id: "92 (Battle Zone 271)",
name: "NHCarrigan",
},
ios: "https://apps.apple.com/us/app/idle-angels-goddess-warfare/id1478505280",
name: "Idle Angels",
userId: "6c2c7ce4a60544a3aee8670d8dddf1ed",
},
{
android: "https://play.google.com/store/apps/details?id=com.proximabeta.nikke",
guild: {
id: "28325",
name: "NHC",
},
ios: "https://apps.apple.com/us/app/goddess-of-victory-nikke/id1585915174",
name: "Goddess of Victory: Nikke",
userId: "05362866",
},
{
android: "https://play.google.com/store/apps/details?id=com.yoozoo.jgame.us",
guild: {
id: "100030",
name: "NHC",
},
ios: "https://apps.apple.com/us/app/echocalypse-scarlet-covenant/id6446244975",
name: "Echocalypse: Scarlet Covenant",
server: "Aurora (3054310105)",
userId: "17754",
},
{
android: "https://play.google.com/store/apps/details?id=jp.pokemon.pokemonsleep",
ios: "https://apps.apple.com/us/app/pok%C3%A9mon-sleep/id1579464667",
name: "Pokémon Sleep",
userId: "9952-8565-4043",
},
{
android: "https://play.google.com/store/apps/details?id=com.goddessidle.global.android",
guild: {
id: "NHCarrigan",
name: "NHCarrigan",
},
ios: "https://apps.apple.com/us/app/goddess-era-2331-draws/id1626294447",
name: "Goddess Era",
server: "S971",
userId: "449",
},
{
android: "https://play.google.com/store/apps/details?id=com.nintendo.zaba",
ios: "https://apps.apple.com/us/app/fire-emblem-heroes/id1181774280",
name: "Fire Emblem: Heroes",
userId: "1305386686",
},
{
android: "https://play.google.com/store/apps/details?id=com.HoYoverse.hkrpgoversea",
ios: "https://apps.apple.com/us/app/honkai-star-rail/id1599719154",
name: "Honkai Star Rail",
server: "America",
userId: "620952550",
},
{
android: "https://play.google.com/store/apps/details?id=com.nintendo.zaca",
ios: "https://apps.apple.com/us/app/animal-crossing-pocket-camp/id1179915619",
name: "Animal Crossing: Pocket Camp",
userId: "3848 6071 011",
},
{
android: "https://play.google.com/store/apps/details?id=com.omnidream.ohs&hl=en-US",
guild: {
id: "NHCarrigan",
name: "NHCarrigan",
},
ios: "https://apps.apple.com/us/app/omniheroes/id1620283683",
name: "Omniheroes",
server: "S651",
userId: "426716065110770702",
},
];

View File

@ -470,7 +470,7 @@ const Socials: Array<{
color: "#FFFFFF", color: "#FFFFFF",
icon: faBluesky, icon: faBluesky,
label: "BlueSky", label: "BlueSky",
link: "https://bsky.app/profile/naomi-lgbt.bsky.social", link: "https://bsky.app/profile/naomi.lgbt",
}, },
{ {
alt: "Telegram Logo", alt: "Telegram Logo",

View File

@ -28,6 +28,38 @@ const VRoid = [
file: "swim.vrm", file: "swim.vrm",
name: "Swimsuit", name: "Swimsuit",
}, },
{
file: "goth.vrm",
name: "Goth Outfit",
},
{
file: "kimono.vrm",
name: "Kimono",
},
{
file: "knight.vrm",
name: "Valiant Knight",
},
{
file: "miko.vrm",
name: "Divine Miko",
},
{
file: "onmyoji.vrm",
name: "Onmyōji",
},
{
file: "sailor.vrm",
name: "Sailor Fuku",
},
{
file: "sweater.vrm",
name: "Sweater Dress",
},
{
file: "witch",
name: "Arcane Witch",
},
]; ];
const MainVRoid = { const MainVRoid = {

View File

@ -1,19 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { describe, it, expect } from "vitest";
import { Play } from "../../src/config/Play";
describe("play objects", () => {
it("should have unique names", () => {
expect.assertions(1);
const set = new Set(
Play.map((p) => {
return p.name;
}),
);
expect(set, "are not unique").toHaveLength(Play.length);
});
});