feat: convert to static html (#5)

### 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: #5
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit is contained in:
Naomi Carrigan 2025-03-14 18:02:25 -07:00 committed by Naomi Carrigan
parent 2ab93f06fd
commit 35dda3d73f
59 changed files with 441 additions and 10642 deletions

View File

@ -1,38 +0,0 @@
name: Node.js CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
lint:
name: Lint and Test
steps:
- name: Checkout Source Files
uses: actions/checkout@v4
- name: Use Node.js v22
uses: actions/setup-node@v4
with:
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9
- name: Install Dependencies
run: pnpm install
- name: Lint Source Files
run: pnpm run lint
- name: Verify Build
run: pnpm run build
- name: Run Tests
run: pnpm run test:ci

View File

@ -1,34 +0,0 @@
name: Code Analysis
on:
push:
branches:
- main
jobs:
sonar:
name: SonarQube
steps:
- name: Checkout Source Files
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: SonarCube Scan
uses: SonarSource/sonarqube-scan-action@v4
timeout-minutes: 10
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: "https://quality.nhcarrigan.com"
with:
args: >
-Dsonar.sources=.
-Dsonar.projectKey=portfolio
- name: SonarQube Quality Gate check
uses: sonarsource/sonarqube-quality-gate-action@v1
with:
pollingTimeoutSec: 600
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: "https://quality.nhcarrigan.com"

41
.gitignore vendored
View File

@ -1,41 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# coverage
.coverage
.scannerwork

12
.vscode/settings.json vendored
View File

@ -1,12 +0,0 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": [
"typescript"
],
"sonarlint.connectedMode.project": {
"connectionId": "Naomi",
"projectKey": "portfolio"
}
}

3
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,3 @@
# Code of Conduct
Our Code of Conduct can be found here: https://docs.nhcarrigan.com/#/coc

3
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,3 @@
# Contributing
Our contributing guidelines can be found here: https://docs.nhcarrigan.com/#/contributing

5
LICENSE.md Normal file
View File

@ -0,0 +1,5 @@
# License
This software is licensed under our [global software license](https://docs.nhcarrigan.com/#/license).
Copyright held by Naomi Carrigan.

3
PRIVACY.md Normal file
View File

@ -0,0 +1,3 @@
# Privacy Policy
Our privacy policy can be found here: https://docs.nhcarrigan.com/#/privacy

View File

@ -1,6 +1,6 @@
# Portfolio
My professional portfolio website.
The static pages for our landing site.
## Live Version
@ -8,7 +8,7 @@ This page is currently deployed. [View the live website.](https://nhcarrigan.com
## Feedback and Bugs
If you have feedback or a bug report, please feel free to open a GitHub issue!
If you have feedback or a bug report, please feel free to open an issue!
## Contributing

3
SECURITY.md Normal file
View File

@ -0,0 +1,3 @@
# Security Policy
Our security policy can be found here: https://docs.nhcarrigan.com/#/security

3
TERMS.md Normal file
View File

@ -0,0 +1,3 @@
# Terms of Service
Our Terms of Service can be found here: https://docs.nhcarrigan.com/#/terms

2
do.env
View File

@ -1,2 +0,0 @@
DO_ACCESS_KEY="op://Environment Variables - Development/DO Spaces/username"
DO_SECRET_KEY="op://Environment Variables - Development/DO Spaces/credential"

View File

@ -1,34 +0,0 @@
import NaomisConfig from "@nhcarrigan/eslint-config";
export default [
...NaomisConfig,
{
rules: {
"@typescript-eslint/naming-convention": "off",
"unicorn/filename-case": "off",
"max-lines": "off",
"max-lines-per-function": "off",
"complexity": "off",
"import/no-default-export": "off",
"import/extensions": ["warn", "never"]
}
},
{
files: ["src/config/*.ts"],
rules: {
"stylistic/max-len": "off"
}
},
{
files: ["src/icons/*.ts"],
rules: {
"@typescript-eslint/consistent-type-assertions": "off"
}
},
{
files: ["test/**/*.spec.ts"],
rules: {
"max-nested-callbacks": "off"
}
}
]

View File

@ -1,30 +0,0 @@
import { ListObjectsV2Command, S3Client } from "@aws-sdk/client-s3";
import { writeFile } from "fs/promises";
import { join } from "path";
(async () => {
const s3 = new S3Client({
endpoint: "https://sfo3.digitaloceanspaces.com",
region: "sfo3",
credentials: {
accessKeyId: process.env.DO_ACCESS_KEY as string,
secretAccessKey: process.env.DO_SECRET_KEY as string
}
})
const command = new ListObjectsV2Command({
Bucket: "nhcarrigan-cdn",
Prefix: "koikatsu"
})
const { Contents } = await s3.send(command);
const names = Contents?.map((content) => content.Key?.replace("koikatsu/", "")).filter((name) => name) || [];
await writeFile(join(process.cwd(), "src", "config", "Koikatsu.ts"), `/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
export const Koikatsu = ${JSON.stringify(names, null, 2)};`, "utf-8");
})()

View File

@ -1,15 +0,0 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
eslint: {
ignoreDuringBuilds: true
},
images: {
remotePatterns: [
{
hostname: "cdn.nhcarrigan.com"
}
]
}
};
export default nextConfig;

View File

@ -1,53 +0,0 @@
{
"name": "portfolio",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint src test --max-warnings 0",
"test": "op run --env-file=do.env --no-masking -- vitest run --coverage",
"test:ci": "vitest run --coverage --exclude=test/cdn.spec.ts",
"koikatsu": "op run --env-file=do.env --no-masking -- tsx koikatsu.ts",
"scan": "SONAR_TOKEN='op://Environment Variables - Development/SonarCloud/portfolio' op run -- sonar-scanner -Dsonar.organization=nhcarrigan -Dsonar.projectKey=nhcarrigan_portfolio -Dsonar.sources=. -Dsonar.host.url=https://sonarcloud.io"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "6.6.0",
"@fortawesome/free-brands-svg-icons": "6.6.0",
"@fortawesome/free-solid-svg-icons": "6.6.0",
"@fortawesome/react-fontawesome": "0.2.2",
"@pixiv/three-vrm": "3.1.6",
"@react-three/drei": "9.117.2",
"@react-three/fiber": "9.0.0-beta.1",
"crisp-sdk-web": "1.0.25",
"next": "15.0.2",
"next-hubspot": "1.4.0-beta.1",
"next-plausible": "3.12.2",
"react": "18.3.1",
"react-dom": "18.3.1",
"three": "0.170.0"
},
"devDependencies": {
"@aws-sdk/client-s3": "3.693.0",
"@nhcarrigan/eslint-config": "5.0.0-rc2",
"@nhcarrigan/typescript-config": "4.0.0",
"@types/node": "22.8.4",
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
"@types/three": "0.170.0",
"@vitest/coverage-istanbul": "2.1.4",
"eslint": "9.13.0",
"jsdom": "25.0.1",
"postcss": "8.4.47",
"tailwindcss": "3.4.14",
"tsx": "4.19.2",
"typescript": "5.6.3",
"vitest": "2.1.4"
},
"resolutions": {
"react": "18.3.1",
"react-dom": "18.3.1",
"next": "15.0.2"
}
}

7772
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

Before

Width:  |  Height:  |  Size: 629 B

251
site/games.html Normal file
View File

@ -0,0 +1,251 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>NHCarrigan</title>
</head>
<body>
<main>
<h1>NHCarrigan</h1>
<main class="w-[95%] text-center max-w-4xl mx-auto mt-16 rounded-lg">
<h1 class="text-5xl">Games</h1>
<section style="display: grid; grid-template-columns: 1fr 1fr 1fr;">
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/aow-3.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/aow-3.jpg" alt="Digital illustration of a female character in a flowing teal dress standing on a hillside overlooking a fantasy landscape with distant buildings and fields." />
</a>
<p><a href=https://store.steampowered.com/app/226840/Age_of_Wonders_III/ target="_blank" rel="noreferrer">Age of Wonders 3</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/aow-4.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/aow-4.jpg" alt="A 3D fantasy character dressed in a light blue and white flowing robe with exposed midriff stands wielding a magical staff with purple crystals. In the background looms a dark, gothic castle silhouetted against a misty green sky with a large moon. The scene is atmospheric with purple and blue lighting effects, and mysterious armored figures can be seen in the shadows on either side." />
</a>
<p><a href=https://store.steampowered.com/app/1669000/Age_of_Wonders_4/ target="_blank" rel="noreferrer">Age of Wonders 4</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/ale-and-tavern.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/ale-and-tavern.jpg" alt="A 3D rendered character in a video game or animation style, standing barefoot on a circular platform. They wear a dark purple corset-style top with buttons down the front, paired with a matching flared skirt. Around their thighs are decorative garter-like bands with an intricate pattern. The character has reddish-brown hair styled in two side ponytails and stands in a neutral pose against a backdrop of wooden paneled walls." />
</a>
<p><a href=https://store.steampowered.com/app/2683150/Ale__Tale_Tavern/ target="_blank" rel="noreferrer">Ale and Tale Tavern</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/angel-legion.png" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/angel-legion.png" alt="A 3D rendered anime-style female character model shown in a confident pose against a dark gray background. She has a short black bob haircut and is wearing a burgundy bikini-style outfit with gold accents, including a central pendant. Her accessories include gold bracelets and a red decorative garter or belt around one thigh. She wears elaborate burgundy strappy sandals that lace up her calves. The character is standing with hands on hips in a pose that casts a distinct shadow. The rendering features smooth shading and a slight bluish rim lighting effect that highlights the character's silhouette." />
</a>
<p><a href=https://store.steampowered.com/app/1333350/Angel_Legion/ target="_blank" rel="noreferrer">Angel Legion</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/bloody-spell.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/bloody-spell.jpg" alt="A fantasy character in a pink outfit and tiara stands barefoot on rocky ground in a rainy, dark setting. She holds a weapon and is surrounded by puddles and a shadowy landscape with hints of ancient architecture." />
</a>
<p><a href=https://store.steampowered.com/app/992300/_Bloody_Spell/ target="_blank" rel="noreferrer">Bloody Spell</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/codevein.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/codevein.jpg" alt="3D render of an anime-style character with pale skin and light hair, wearing a short purple dress with a textured pattern, standing against a plain gray background." />
</a>
<p><a href=https://store.steampowered.com/app/678960/CODE_VEIN/ target="_blank" rel="noreferrer">Code Vein</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/cyberpunk.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/cyberpunk.jpg" alt="A person with long dark hair wearing sunglasses and a printed open coat over a gray jumpsuit, standing barefoot against a maroon background" />
</a>
<p><a href=https://store.steampowered.com/app/1091500/Cyberpunk_2077/ target="_blank" rel="noreferrer">Cyberpunk 2077</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/deathly-stillness.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/deathly-stillness.jpg" alt="3D rendering of a figure wearing a Japanese-style school uniform with a white top, pink sailor collar, and purple pleated skirt, standing against a dark background. The figure has short brown hair and is shown in a full-body pose." />
</a>
<p><a href=https://store.steampowered.com/app/1727650/Deathly_Stillness/ target="_blank" rel="noreferrer">Deathly Stillness</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/demonologist.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/demonologist.jpg" alt="A blonde woman in a black Gothic-style outfit stands in a dimly lit room with large windows. She wears glasses, a corset-like top, fishnet stockings, and lace gloves. The room has a patterned carpet and appears to be in an old building." />
</a>
<p><a href=https://store.steampowered.com/app/1929610/Demonologist/ target="_blank" rel="noreferrer">Demonologist</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/dos-2.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/dos-2.jpg" alt="A digital artwork depicting a warrior character in a dynamic pose, wielding a flaming sword. They wear dark armor with gold accents and red trim, with a tattered cape. Their brown hair partially covers their face, and they are barefoot. The background shows a desert-like environment with pale grasses. The sword glows with intense orange flames, creating dramatic lighting effects. The character's stance suggests they are mid-combat or preparing to strike." />
</a>
<p><a href=https://store.steampowered.com/app/435150/Divinity_Original_Sin_2__Definitive_Edition/ target="_blank" rel="noreferrer">Divinity: Original Sin 2</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/dw-8.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/dw-8.jpg" alt="A 3D rendered video game character wearing an ornate turquoise and white dress with bell sleeves and a matching cape. The outfit features intricate trim details, a bow at the waist, and layered fabric elements. The character wears round glasses and has brown hair styled in a neat bob cut. The character is shown barefoot against a dark blue background." />
</a>
<p><a href=https://store.steampowered.com/app/322520/DYNASTY_WARRIORS_8_Empires/ target="_blank" rel="noreferrer">Dynasty Warriors 8 Empires</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/dw-9.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/dw-9.jpg" alt="Animated character in a purple traditional Chinese robe wielding a large sword in a wooden temple-like structure with ornate pillars and lanterns." />
</a>
<p><a href=https://store.steampowered.com/app/1341200/DYNASTY_WARRIORS_9_Empires/ target="_blank" rel="noreferrer">Dynasty Warriors 9: Empires</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/empyrion.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/empyrion.jpg" alt="A 3D character model showing a female figure wearing a bright neon green futuristic bodysuit with black armored accents and panels. The suit features geometric patterns and protective plating across the torso, limbs, and joints. The character has brown hair styled with side-swept bangs and stands in a neutral pose against a dark background. Text at the top of the image reads "Origin: Human" and "Naomi"." />
</a>
<p><a href=https://store.steampowered.com/app/383120/Empyrion__Galactic_Survival/ target="_blank" rel="noreferrer">Empyrion</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/encased.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/encased.jpg" alt="A 3D-rendered character in a futuristic setting, wearing a form-fitting white and black bodysuit with a red insignia on the chest. The character has short brown hair and glasses, standing confidently in an urban environment with other figures visible in the background." />
</a>
<p><a href=https://store.steampowered.com/app/921800/Encased_A_SciFi_PostApocalyptic_RPG/ target="_blank" rel="noreferrer">Encased</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/enshrouded.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/enshrouded.jpg" alt="A 3D rendered character standing in a nighttime wilderness setting, illuminated by a large full moon and orange ambient lighting. The figure wears tattered, primitive clothing consisting of a rough-textured tunic and shorts with wrapped cloth around the lower legs. The clothing appears weathered and torn. The character is positioned on rocky terrain with foliage visible in the background. The lighting creates a dramatic contrast between cool moonlight and warm ground reflections." />
</a>
<p><a href=https://store.steampowered.com/app/1203620/Enshrouded/ target="_blank" rel="noreferrer">Enshrouded</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/fallen-enchantress.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/fallen-enchantress.jpg" alt="Digital artwork of a fantasy character with white hair in a long white dress holding a staff, standing against a backdrop of cloudy skies and distant ruins." />
</a>
<p><a href=https://store.steampowered.com/app/228260/Fallen_Enchantress_Legendary_Heroes/ target="_blank" rel="noreferrer">Fallen Enchantress</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/fallout-4.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/fallout-4.jpg" alt="A woman in post-apocalyptic armor stands in a desolate urban landscape. She wears sunglasses, leather armor pieces, and holds a weapon. The background shows damaged buildings and debris in a hazy, dusty environment." />
</a>
<p><a href=https://store.steampowered.com/app/377160/Fallout_4/ target="_blank" rel="noreferrer">Fallout 4</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/gw2.png" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/gw2.png" alt="Fantasy video game character in a revealing purple and gold outfit with a crown, standing in a lush mountainous landscape with floating islands and waterfalls. Character selection interface visible at the bottom of the image." />
</a>
<p><a href=https://store.steampowered.com/app/1284210/Guild_Wars_2/ target="_blank" rel="noreferrer">Guild Wars 2</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/idle-angels.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/idle-angels.jpg" alt="A detailed anime-style illustration of an angelic warrior character against a sunset cityscape background. The character has long blonde hair and wears elaborate white and gold armor with flowing white robes accented by blue and orange fabrics. She has multiple large white wings spread dramatically behind her and wears a golden crown-like headpiece. Golden chains float decoratively around her dress and armor. She's depicted in a dynamic pose with futuristic city towers and a reddish-orange sky visible in the background. The artwork combines elements of fantasy and sci-fi, with ornate details in the character's costume and architectural elements of the setting." />
</a>
<p><a href=https://play.google.com/store/apps/details?id=com.mujoysg.hxbb&hl=en-US&pli=1 target="_blank" rel="noreferrer">Idle Angels</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/idling-god.png" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/idling-god.png" alt="Anime-style illustration of a young woman in a dark floral kimono standing in a lush green field. Behind her, a vibrant galaxy-like spiral shimmers in the sky. Yellow flowers bloom in the foreground, and glowing particles float around her. The scene has a magical, ethereal quality." />
</a>
<p><a href=https://store.steampowered.com/app/466170/Idling_to_Rule_the_Gods/ target="_blank" rel="noreferrer">Idling to Rule the Gods</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/amalur.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/amalur.jpg" alt="A female video game character stands in a dark, wooden structure. She has short hair and wears rustic clothing. The scene includes a health and magic bar in the top left corner and a minimap in the top right. The environment is dimly lit with some grass on the ground." />
</a>
<p><a href=https://store.steampowered.com/app/1041720/Kingdoms_of_Amalur_ReReckoning/ target="_blank" rel="noreferrer">Kingdoms of Amalur</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/koikatsu.png" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/koikatsu.png" alt="Anime-style illustration of a girl lying on her back, holding a smartphone above her face. She's surrounded by various objects including a pink rabbit toy, headphones, candies, a game controller, and a potted plant. The scene depicts a cluttered, colorful bedroom or living space." />
</a>
<p><a href=https://store.steampowered.com/app/1073440/__Koikatsu_Party/ target="_blank" rel="noreferrer">Koikatsu</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/lords-of-the-fallen.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/lords-of-the-fallen.jpg" alt="A female video game character in medieval-style clothing stands in a rugged stone environment. She wears a tattered brown tunic, leather armor, and leg wraps. Her pose is fierce and ready for action, with dark hair framing her face. The background shows rocky terrain and ancient stone walls." />
</a>
<p><a href=https://store.steampowered.com/app/1501750/Lords_of_the_Fallen/ target="_blank" rel="noreferrer">Lords of the Fallen</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/mh-world.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/mh-world.jpg" alt="a female character in a fantasy or adventure game setting. She is wearing a dark outfit with ornate details, including a hooded cape and armor-like elements. The character is standing in a lush, jungle-like environment, with tall trees and foliage in the background. She is holding a large weapon, likely a sword or staff, in her hand, suggesting she is a warrior or adventurer. The character has a serious expression on her face, indicating she is focused and ready for action. Overall, the image conveys a sense of mystery, power, and exploration in a fantastical and immersive world." />
</a>
<p><a href=https://store.steampowered.com/app/582010/Monster_Hunter_World/ target="_blank" rel="noreferrer">Monster Hunter: World</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/mka.png" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/mka.png" alt="A 3D rendered female video game character model wearing a shiny green outfit consisting of a sleeveless top and short skirt with thigh straps. The character has long brown hair and is standing in a neutral pose with a dark cape draped behind her." />
</a>
<p><a href=https://en.wikipedia.org/wiki/Mortal_Kombat:_Armageddon target="_blank" rel="noreferrer">Mortal Kombat: Armageddon</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/bannerlord.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/bannerlord.jpg" alt="Digital illustration of a character in a white and pink dress with bell sleeves and a crown, walking on a dirt path through a forested area with tall grass and flowers." />
</a>
<p><a href=https://store.steampowered.com/app/261550/Mount__Blade_II_Bannerlord/ target="_blank" rel="noreferrer">Mount & Blade II</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/nwn.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/nwn.jpg" alt="A 3D rendered scene of a medieval-style courtyard at night. A woman in a long teal dress stands in the center of a cobblestone path. Wooden archways and stone walls with lit lanterns frame the path, creating a moody atmosphere." />
</a>
<p><a href=https://store.steampowered.com/app/704450/Neverwinter_Nights_Enhanced_Edition/ target="_blank" rel="noreferrer">Neverwinter Nights</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/nioh-2.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/nioh-2.jpg" alt="A female video game character stands on a wooden bridge in a Japanese-style garden. She wears samurai-like armor and holds a long bow. The character has long dark hair and glasses. The scene is rendered in a soft, slightly blurred style with pink cherry blossom trees in the background." />
</a>
<p><a href=https://store.steampowered.com/app/1325200/Nioh_2__The_Complete_Edition/ target="_blank" rel="noreferrer">Nioh 2</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/once-human.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/once-human.jpg" alt="A 3D rendered character in a futuristic or sci-fi style outfit. The figure is wearing a light-colored bodysuit with straps and mechanical details, holding a large gun. The character has blonde hair and stands against a dark background in a ready pose." />
</a>
<p><a href=https://store.steampowered.com/app/2139460/Once_Human/ target="_blank" rel="noreferrer">Once Human</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/zomboid.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/zomboid.jpg" alt="Pixelated video game character in a green dress standing in a dark hallway. She has brown hair and wears glasses." />
</a>
<p><a href=https://store.steampowered.com/app/108600/Project_Zomboid/ target="_blank" rel="noreferrer">Project Zomboid</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/saints-row-4.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/saints-row-4.jpg" alt="A 3D rendered figure standing against a dark background with binary code and chain-link patterns. The figure wears a turquoise green dress with a corseted bodice featuring lace-up details. The dress has a ruffled sweetheart neckline and a full skirt with multiple tiers of frills. The figure has long straight dark hair and wears glasses. The figure is barefoot and appears to be rendered in a video game or digital art style. The lighting creates dramatic highlights and shadows on the dress and figure." />
</a>
<p><a href=https://store.steampowered.com/app/206420/Saints_Row_IV/ target="_blank" rel="noreferrer">Saints Row IV</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/sc3.png" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/sc3.png" alt="A 3D video game character model of a woman with short dark hair wearing a teal green dress with purple accents. The dress has a laced front, thigh-high slit, and is paired with purple arm bands. The character stands in a combat-ready pose within an ornate frame against a dark background. The name 'NAOMI' is displayed beneath the character." />
</a>
<p><a href=https://en.wikipedia.org/wiki/Soulcalibur_III target="_blank" rel="noreferrer">Soul Calibur III</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/soul-calibur.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/soul-calibur.jpg" alt="A stylized female character in a video game or 3D rendering, wearing a white top and long red skirt, holding a sword and striking a dramatic pose with one arm raised. She stands on a stone platform with mountains in the background." />
</a>
<p><a href=https://store.steampowered.com/app/544750/SOULCALIBUR_VI/ target="_blank" rel="noreferrer">Soul Calibur VI</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/spirit-city.png" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/spirit-city.png" alt="3D render of an anime-style character in a cozy room. The character wears a pink dress, glasses, and has short brown hair. The room features a round window, bookshelves, plants, and a purple cushion with a cat-shaped teapot. The scene has a warm, soft lighting." />
</a>
<p><a href=https://store.steampowered.com/app/2113850/Spirit_City_Lofi_Sessions/ target="_blank" rel="noreferrer">Spirit City: Lofi Sessions</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/swtor.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/swtor.jpg" alt="A virtual character in a red dress sits on a throne in a futuristic corridor. The character has short hair and is barefoot. Above the character is the text 'Avalon'. The scene appears to be from a video game or digital simulation, with a dark, metallic environment surrounding the central figure." />
</a>
<p><a href=https://store.steampowered.com/app/1286830/STAR_WARS_The_Old_Republic/ target="_blank" rel="noreferrer">Star Wars: The Old Republic</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/sunset-overdrive.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/sunset-overdrive.jpg" alt="A 3D rendered character stands barefoot on a red carpet in what appears to be a garage or workshop setting. She wears black-rimmed glasses, a purple tank top, and a cream-colored knee-length skirt with red abstract designs on it. The character has short brown hair styled in a modern cut. Behind her are various automotive items including what appears to be a yellow vehicle and some storage units. Part of a vintage "Around the World" poster is visible on the wall." />
</a>
<p><a href=https://store.steampowered.com/app/847370/Sunset_Overdrive/ target="_blank" rel="noreferrer">Sunset Overdrive</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/v-rising.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/v-rising.jpg" alt="A 3D rendered character with elven features, glowing cyan eyes, and brown hair. The figure wears a blue and gray outfit consisting of a long coat, fitted pants, and a metallic necklace. The character is depicted in a dark, moody setting." />
</a>
<p><a href=https://store.steampowered.com/app/1604030/V_Rising/ target="_blank" rel="noreferrer">V Rising</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/vroid.png" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/vroid.png" alt="Anime-style character in a futuristic black outfit with glowing blue accents standing in a forest setting. The character has short brown hair and large eyes, wearing a long coat with high slits revealing thigh-high boots." />
</a>
<p><a href=https://store.steampowered.com/app/1486350/VRoid_Studio_v1294/ target="_blank" rel="noreferrer">VRoid</a></p>
</div>
<div style="height: 300px; width: 300px; border: 2px solid var(--foreground);">
<a href="https://cdn.nhcarrigan.com/games/samurai-4.jpg" target="_blank">
<img style="height: 250px; max-width: 250px; object-fit: contain" src="https://cdn.nhcarrigan.com/games/samurai-4.jpg" alt="A video game character stands on a dock wearing a traditional Japanese kimono in royal blue with delicate flower patterns and a red hem. The kimono is accessorized with a decorative obi belt and a sword at the waist. The character has round glasses and shoulder-length blonde hair. In the background, wooden ships are moored at the harbor against a mountainous coastline." />
</a>
<p><a href=https://store.steampowered.com/app/312780/Way_of_the_Samurai_4/ target="_blank" rel="noreferrer">Way of the Samurai 4</a></p>
</div>
</section>
</main>
</main>
</body>
<script src="https://cdn.nhcarrigan.com/headers/index.js"></script>
</html>

168
site/index.html Normal file
View File

@ -0,0 +1,168 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>NHCarrigan</title>
</head>
<body>
<main>
<h1>NHCarrigan</h1>
<img
src="https://cdn.nhcarrigan.com/logo.png"
style="width: 150px; border-radius: 50%"
alt="NHCarrigan Logo"
/>
<p>Software Engineering and Community Management Consulting Firm</p>
<div style="display: flex; justify-content: center">
<a href="https://git.nhcarrigan.com" target="_blank" style="width: 50%"
><img
src="https://cdn.nhcarrigan.com/blinkies/home/code.gif"
alt="Code"
width="100%"
/></a>
<a
href="https://docs.nhcarrigan.com"
target="_blank"
style="width: 50%"
><img
src="https://cdn.nhcarrigan.com/blinkies/home/documentation.gif"
alt="Documentation"
width="100%"
/></a>
</div>
<div style="display: flex; justify-content: center">
<a
href="https://forum.nhcarrigan.com"
target="_blank"
style="width: 50%"
><img
src="https://cdn.nhcarrigan.com/blinkies/home/contact.gif"
alt="Contact"
width="100%"
/></a>
<a
href="https://forms.nhcarrigan.com/commission"
target="_blank"
style="width: 50%"
><img
src="https://cdn.nhcarrigan.com/blinkies/home/hire.gif"
alt="Hire us!"
width="100%"
/></a>
</div>
<div style="display: flex; justify-content: center">
<a
href="https://buy.stripe.com/cN24iTfqu1j6b3afZ2"
target="_blank"
style="width: 50%"
><img
src="https://cdn.nhcarrigan.com/blinkies/home/coffee.gif"
alt="Buy us coffee"
width="100%"
/></a>
<a
href="https://merch.nhcarrigan.link/"
target="_blank"
style="width: 50%"
><img
src="https://cdn.nhcarrigan.com/blinkies/home/merch.gif"
alt="merchandise"
width="100%"
/></a>
</div>
<div style="display: flex; justify-content: center">
<a
href="https://testimonials.nhcarrigan.com/"
target="_blank"
style="width: 50%"
><img
src="https://cdn.nhcarrigan.com/blinkies/home/reviews.gif"
style="width: 100%"
alt="Reviews"
/></a>
<a
href="https://resume.nhcarrigan.com/"
target="_blank"
style="width: 50%"
><img
src="https://cdn.nhcarrigan.com/blinkies/home/resume.gif"
style="width: 100%"
alt="resume"
/></a>
</div>
<h2>Naomi Carrigan</h2>
<img
src="https://cdn.nhcarrigan.com/profile.png"
style="width: 150px; border-radius: 50%"
alt="Naomi's Avatar"
/>
<p>Founder and Lead Developer</p>
<div style="display: flex; justify-content: center">
<img
src="https://cdn.nhcarrigan.com/blinkies/home/transfem.gif"
style="width: 50%"
alt="transfem"
/>
<img
src="https://cdn.nhcarrigan.com/blinkies/home/technomancer.gif"
style="width: 50%"
alt="technomancer"
/>
</div>
<div style="display: flex; justify-content: center">
<a
href="https://hub.vroid.com/en/characters/6033404747153826650/models/3483506204509065121"
target="_blank"
rel="noreferrer"
style="width: 50%"
><img
src="https://cdn.nhcarrigan.com/blinkies/home/cutie.gif"
style="width: 100%"
alt="total cutie"
/></a>
<img
src="https://cdn.nhcarrigan.com/blinkies/home/unhinged.gif"
style="width: 50%"
alt="charmingly unhinged"
/>
</div>
<div style="display: flex; justify-content: center">
<a href="./games" style="width: 50%"
><img
src="https://cdn.nhcarrigan.com/blinkies/home/gamer.gif"
style="width: 100%"
alt="gamer"
/></a>
<img
src="https://cdn.nhcarrigan.com/blinkies/home/gay.gif"
style="width: 50%"
alt="hella gay"
/>
</div>
<div style="display: flex; justify-content: center">
<img
src="https://cdn.nhcarrigan.com/blinkies/home/violence.gif"
style="width: 50%"
alt="prone to violence"
/>
<img
src="https://cdn.nhcarrigan.com/blinkies/home/pirate.gif"
style="width: 50%"
alt="info should be free"
/>
</div>
<div style="display: flex; justify-content: center">
<img
src="https://cdn.nhcarrigan.com/blinkies/home/witch.gif"
style="width: 50%"
alt="witch"
/>
<img
src="https://cdn.nhcarrigan.com/blinkies/home/vampire.gif"
style="width: 50%"
alt="vampire"
/>
</div>
</main>
</body>
<script src="https://cdn.nhcarrigan.com/headers/index.js"></script>
</html>

View File

@ -1,46 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { ArtComponent } from "../../components/art";
import { Rule } from "../../components/rule";
import { Art } from "../../config/Art";
import type { JSX } from "react";
/**
* Renders the /art page.
* @returns A React Component.
*/
const Arts = (): JSX.Element => {
return (
<main
className="w-[95%] text-center
max-w-4xl mx-auto mt-16 rounded-lg"
>
<h1 className="text-5xl">{`Art`}</h1>
<section>
<p className="mb-2">{`See various art depicting Naomi.`}</p>
<Rule />
<div className="grid sm:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-y-5">
{Art.toSorted((a, b) => {
return a.name.localeCompare(b.name);
}).map((art) => {
return (
<ArtComponent
alt={art.alt}
artist={art.artist}
img={art.img}
key={art.name}
name={art.name}
url={art.url}
/>
);
})}
</div>
</section>
</main>
);
};
export default Arts;

View File

@ -1,45 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Game } from "../../components/game";
import { Rule } from "../../components/rule";
import { Games } from "../../config/Games";
import type { JSX } from "react";
/**
* Renders the /games page.
* @returns A React Component.
*/
const Gamez = (): JSX.Element => {
return (
<main
className="w-[95%] text-center
max-w-4xl mx-auto mt-16 rounded-lg"
>
<h1 className="text-5xl">{`Games`}</h1>
<section>
<p className="mb-2">{`See how Naomi has appeared in various games.`}</p>
<Rule />
<div className="grid sm:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-y-5">
{Games.toSorted((a, b) => {
return a.name.localeCompare(b.name);
}).map((game) => {
return (
<Game
alt={game.alt}
img={game.img}
key={game.name}
name={game.name}
url={game.url}
/>
);
})}
</div>
</section>
</main>
);
};
export default Gamez;

View File

@ -1,24 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--current: #995c00;
--former: #99004d;
}
* {
font-family: "OpenDyslexic";
}
nav {
color: var(--foreground);
background-color: var(--background);
font-family: "OpenDyslexic Mono", monospace;
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}

View File

@ -1,39 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { KoikatsuComponent } from "../../components/koikatsu";
import { Rule } from "../../components/rule";
import { Koikatsu } from "../../config/Koikatsu";
import type { JSX } from "react";
/**
* Renders the /games page.
* @returns A React Component.
*/
const KoikatsuPage = (): JSX.Element => {
return (
<main
className="w-[95%] text-center
max-w-4xl mx-auto mt-16 rounded-lg"
>
<h1 className="text-5xl">{` Koikatsu`}</h1>
<section>
<p className="mb-2">{`Images of Naomi generated in Koikatsu. Note that the list of images is dynamically generated, so we aren't able to provide alt text for these.`}</p>
<Rule />
<div className="grid sm:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-y-5">
{Koikatsu.toSorted((a, b) => {
return a.localeCompare(b);
}).map((img) => {
return (
<KoikatsuComponent img={img} key={img} />
);
})}
</div>
</section>
</main>
);
};
export default KoikatsuPage;

View File

@ -1,69 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Inter } from "next/font/google";
import Script from "next/script";
import { Navigation } from "../components/navigation";
import type { Metadata } from "next";
import type { JSX, ReactNode } from "react";
// eslint-disable-next-line import/no-unassigned-import
import "./globals.css";
// eslint-disable-next-line new-cap
const inter = Inter({ subsets: [ "latin" ] });
const metadata: Metadata = {
description:
// eslint-disable-next-line stylistic/max-len
"This page tells you everything you could ever want to know about Naomi and her consulting firm nhcarrigan.",
openGraph: {
images: "https://cdn.nhcarrigan.com/background.png",
},
title: "Naomi's Portfolio",
twitter: {
card: "summary_large_image",
images: "https://cdn.nhcarrigan.com/background.png",
site: "@naomi_lgbt",
},
};
/**
* The top-level wrapper for the React application.
* Handles mounting the shadow DOM.
* @param opts - The rendering options.
* @param opts.children - The children elements to render.
* @returns A JSX element.
*/
const RootLayout = ({
children,
}: Readonly<{
children: ReactNode;
}>): JSX.Element => {
return (
<html lang="en">
<Script
async={true}
defer={true}
src="https://cdn.nhcarrigan.com/headers/index.js"
strategy={"afterInteractive"}
type="text/javascript"
></Script>
<link
href="https://cdn.nhcarrigan.com/logo.png"
rel="icon"
sizes="any"
/>
<body className={inter.className}>
<header>
<Navigation />
</header>
{children}
</body>
</html>
);
};
export { metadata };
export default RootLayout;

View File

@ -1,202 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/* eslint-disable react/no-multi-comp */
"use client";
import Image from "next/image";
import { useState, type JSX } from "react";
import { CharacterComponent } from "../../components/character";
import { Characters } from "../../config/Legacy";
const HorizontalConnector = ({
colour,
}: {
readonly colour: string;
}): JSX.Element => {
return (
<div className="w-full h-1 m-auto" style={{ background: colour }}></div>
);
};
const TopToRightConnector = ({
colour,
}: {
readonly colour: string;
}): JSX.Element => {
return (
<div className="w-full h-full grid grid-cols-2">
<div
className="w-full h-full m-auto"
style={{ borderRight: `2px solid ${colour}` }}
></div>
<div
className="w-full h-full m-auto"
style={{
borderBottom: `2px solid ${colour}`,
borderLeft: `2px solid ${colour}`,
}}
></div>
<div className="w-full h-full m-auto"></div>
<div
className="w-full h-full m-auto"
style={{ borderTop: `2px solid ${colour}` }}
></div>
</div>
);
};
const TopToLeftConnector = ({
colour,
}: {
readonly colour: string;
}): JSX.Element => {
return (
<div className="w-full h-full grid grid-cols-2">
<div
className="w-full h-full m-auto"
style={{
borderBottom: `2px solid ${colour}`,
borderRight: `2px solid ${colour}`,
}}
></div>
<div
className="w-full h-full m-auto"
style={{ borderLeft: `2px solid ${colour}` }}
></div>
<div
className="w-full h-full m-auto"
style={{ borderTop: `2px solid ${colour}` }}
></div>
<div className="w-full h-full m-auto"></div>
</div>
);
};
const TopToRightToBottomConnector = ({
colour,
}: {
readonly colour: string;
}): JSX.Element => {
return (
<div className="w-full h-full grid grid-cols-2">
<div
className="w-full h-full m-auto"
style={{ borderRight: `2px solid ${colour}` }}
></div>
<div
className="w-full h-full m-auto"
style={{
borderBottom: `2px solid ${colour}`,
borderLeft: `2px solid ${colour}`,
}}
></div>
<div
className="w-full h-full m-auto"
style={{ borderRight: `2px solid ${colour}` }}
></div>
<div
className="w-full h-full m-auto"
style={{
borderLeft: `2px solid ${colour}`,
borderTop: `2px solid ${colour}`,
}}
></div>
</div>
);
};
/**
* Renders the /legacy page.
* @returns A React Component.
*/
const Legacy = (): JSX.Element => {
const [ focused, setFocused ] = useState("naomi-carrigan");
const handleClick = (id: string) => {
return (): void => {
setFocused(id);
};
};
return (
<main className="w-4/5 text-center max-w-4xl mx-auto mt-16 rounded-lg">
<h1 className="text-5xl">{`The Carrigan Legacy`}</h1>
<section>
<Image alt="Nine anime-style female characters with diverse colorful outfits including dresses, crop tops, skirts, and pants, posing together against a light background, some making peace signs or pointing at the viewer" height="1080" src={`https://cdn.nhcarrigan.com/characters/landscape/legacy.png`} width="1920"></Image>
<p className="mt-2">
{`This page serves to show off all of our characters, who are all part of the same family (and thus, the same legacy).`}
</p>
<p>{`Click on a character to learn more about them!`}</p>
<div className="grid grid-cols-6">
<CharacterComponent
focused={focused === "naomi-carrigan"}
id={"naomi-carrigan"}
onClick={handleClick("naomi-carrigan")}
/>
<HorizontalConnector colour="pink" />
<CharacterComponent
focused={focused === "melody-iuvo"}
id={"melody-iuvo"}
onClick={handleClick("melody-iuvo")}
/>
<HorizontalConnector colour="green" />
<CharacterComponent
focused={focused === "aria-iuvo"}
id={"aria-iuvo"}
onClick={handleClick("aria-iuvo")}
/>
<div></div>
<TopToRightToBottomConnector colour="blue" />
<CharacterComponent
focused={focused === "becca-lyria"}
id={"becca-lyria"}
onClick={handleClick("becca-lyria")}
/>
<HorizontalConnector colour="pink" />
<CharacterComponent
focused={focused === "rosalia-nightsong"}
id={"rosalia-nightsong"}
onClick={handleClick("rosalia-nightsong")}
/>
<TopToRightConnector colour="blue" />
<CharacterComponent
focused={focused === "cordelia-taryne"}
id={"cordelia-taryne"}
onClick={handleClick("cordelia-taryne")}
/>
<CharacterComponent
focused={focused === "gwen-abalise"}
id={"gwen-carrigan"}
onClick={handleClick("gwen-abalise")}
/>
<div></div>
<div></div>
<CharacterComponent
focused={focused === "maylin-taryne"}
id={"maylin-taryne"}
onClick={handleClick("maylin-taryne")}
/>
<HorizontalConnector colour="blue" />
<TopToLeftConnector colour="blue" />
</div>
</section>
<section>
<h2 className="text-2xl">{Characters[focused]?.name}</h2>
<Image alt={Characters[focused]?.alt ?? "Image"} height="1080" src={`https://cdn.nhcarrigan.com/characters/landscape/${focused}.png`} width="1920"></Image>
<p><strong>{Characters[focused]?.class}</strong>{" - "}<span className="italic">{`${Characters[focused]?.age.toString() ?? "unknown"} years old`}</span> </p>
<h3 className="text-xl">{`Biography`}</h3>
<div className="text-justify">
<p>{Characters[focused]?.bio.join(" ")}</p>
</div>
<h3 className="text-xl">{`Combat Profile`}</h3>
<div className="text-justify">
<p>{Characters[focused]?.combat.join(" ")}</p>
</div>
</section>
</main>
);
};
export default Legacy;

View File

@ -1,62 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import Image from "next/image";
import React, { type JSX } from "react";
import { NaomiNavItems } from "../../config/NavItems";
/**
* Renders Naomi's landing page component.
* @returns A JSX element.
*/
const Naomi = (): JSX.Element => {
return (
<main className="w-4/5 text-center max-w-4xl mx-auto mt-16 rounded-lg">
<h1 className="text-4xl mb-2">{`Naomi Carrigan`}</h1>
<Image
alt="Naomi Carrigan"
className="rounded-full m-auto"
height={200}
src="https://cdn.nhcarrigan.com/profile.png"
width={200}
/>
<p className="text-2xl">{`Software Engineer | Community Manager | Technomancer`}</p>
<p className="mb-4">
{`Hiya! I'm Naomi. I'm a developer, and I build inclusive spaces on the internet!`}
</p>
<ul>
{NaomiNavItems.map((item, index) => {
return (
<li className="mb-4" key={item.href}>
<a
href={item.href}
rel={item.href.startsWith("http")
? "noreferrer"
: ""}
target={item.href.startsWith("http")
? "_blank"
: "_self"}
>
{index % 2 === 1
? "🩷"
: "🩵"} {item.text}{" "}
</a>
</li>
);
})}
</ul>
<p>
{`Can't find what you are looking for? `}
<a
className="underline"
href="https://sitemap.nhcarrigan.com"
>{`Check out our sitemap`}</a>
{`.`}
</p>
</main>
);
};
export default Naomi;

View File

@ -1,65 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import Image from "next/image";
import React, { type JSX } from "react";
import { NavItems } from "../config/NavItems";
/**
* Renders the main React component.
* @returns A JSX element.
*/
const Home = (): JSX.Element => {
return (
<main className="w-4/5 text-center max-w-4xl mx-auto mt-16 rounded-lg">
<h1 className="text-4xl mb-2">{`NHCarrigan`}</h1>
<Image
alt="NHCarrigan"
className="rounded-full m-auto"
height={200}
src="https://cdn.nhcarrigan.com/logo.png"
width={200}
/>
<p className="text-2xl">{`Software Engineering and Community Management Consulting Firm`}</p>
<p className="mb-4">
{`Welcome to the landing page for our organisation! Use the links below to navigate.
If you are looking for information about Naomi `}
<a className="underline" href="/naomi">{`see her landing page`}</a>
{`.`}
</p>
<ul>
{NavItems.map((item, index) => {
return (
<li className="mb-4" key={item.href}>
<a
href={item.href}
rel={item.href.startsWith("http")
? "noreferrer"
: ""}
target={item.href.startsWith("http")
? "_blank"
: "_self"}
>
{index % 2 === 1
? "🩷"
: "🩵"} {item.text}{" "}
</a>
</li>
);
})}
</ul>
<p>
{`Can't find what you are looking for? `}
<a
className="underline"
href="https://sitemap.nhcarrigan.com"
>{`Check out our sitemap`}</a>
{`.`}
</p>
</main>
);
};
export default Home;

View File

@ -1,137 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/* eslint-disable react/no-unknown-property, react/no-multi-comp */
"use client";
import { VRMLoaderPlugin, type VRM } from "@pixiv/three-vrm";
import { OrbitControls } from "@react-three/drei";
// @ts-expect-error -- It seems like the beta version has type-def issues.
import { Canvas } from "@react-three/fiber";
import { useState, useRef, type ChangeEvent, type JSX, useEffect } from "react";
import { PerspectiveCamera } from "three";
// eslint-disable-next-line import/extensions -- We need this apparently.
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { Rule } from "../../components/rule";
import { MainVRoid, VRoid } from "../../config/Vroid";
const concatenated = [
MainVRoid,
...VRoid.toSorted((a, b) => {
return a.name.localeCompare(b.name);
}),
];
/**
* Scene setup component for lighting and controls.
* @returns The Three.JS scene.
*/
const SceneSetup = (): JSX.Element => {
return (
<>
{/* Main directional light from front-top */}
{ /* @ts-expect-error -- why */}
<directionalLight
castShadow={true}
intensity={1}
position={[ 0, 5, 5 ]}
/>
{/* Fill light from back */}
{ /* @ts-expect-error -- why */}
<directionalLight
intensity={0.5}
position={[ 0, 3, -5 ]}
/>
{/* Ambient light for overall scene brightness */}
{ /* @ts-expect-error -- why */}
<ambientLight intensity={0.5} />
{/* Orbit controls for camera manipulation */}
<OrbitControls
enablePan={true}
enableRotate={true}
enableZoom={true}
maxDistance={20}
minDistance={1}
target={[ 0, 0.8, 0 ]}
/>
</>
);
};
/**
* Reference component for displaying and interacting with VRM models.
* @returns The reference page.
*/
const Reference = (): JSX.Element => {
const [ selected, setSelected ] = useState<string>(MainVRoid.file);
const aspect = 1920 / 1080;
const { current: camera }
= useRef(new PerspectiveCamera(30, aspect, 0.1, 1000));
const handleChange = (event: ChangeEvent): void => {
const { target } = event;
if (target instanceof HTMLSelectElement) {
setSelected(target.value);
}
};
const loader = new GLTFLoader();
loader.register((parser) => {
return new VRMLoaderPlugin(parser);
});
const [ model, setModel ] = useState<JSX.Element | null>(null);
useEffect(() => {
loader.load(`https://cdn.nhcarrigan.com/vrm/${selected}`, (gltf) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const { vrm } = gltf.userData as { vrm: VRM };
if (vrm.lookAt) {
vrm.lookAt.target = camera;
}
// Center the model
vrm.scene.position.set(0, 0, 0);
// Add rotation if needed
vrm.scene.rotation.y = Math.PI;
/* @ts-expect-error -- why */
setModel(<primitive object={vrm.scene} />);
});
}, [ selected ]);
useEffect(() => {
// Position camera to better frame the model
camera.position.set(0, 0, 3);
}, [ camera ]);
return (
<main className="w-4/5 text-center max-w-4xl mx-auto mt-16 rounded-lg">
<h1 className="text-5xl">{`Reference`}</h1>
<section>
<p className="mb-2">{`Want to draw art of Naomi? Here's a fully interactive reference page with all of her model's outfits!`}</p>
<select
className="px-2 bg-[--background] border-2
border-solid border-[--foreground]"
// eslint-disable-next-line react/jsx-no-bind
onChange={handleChange}
>
{concatenated.map((vroid) => {
return <option key={vroid.name} value={vroid.file}>
{vroid.name}
</option>;
})}
</select>
<Rule />
</section>
<div className="h-[600px]">
<Canvas camera={camera}>
<SceneSetup />
{model}
</Canvas>
</div>
</main>
);
};
export default Reference;

View File

@ -1,126 +0,0 @@
/* eslint-disable stylistic/max-len */
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Rule } from "../../components/rule";
import type { JSX } from "react";
/**
* Renders the /tech page.
* @returns A React Component.
*/
const PlayPage = (): JSX.Element => {
return (
<main
className="w-[95%] text-center
max-w-4xl mx-auto mt-16 rounded-lg"
>
<h1 className="text-5xl">{`Technologies`}</h1>
<p className="mb-2">
{`These are the technologies I use on a regular basis.`}
</p>
<section>
<Rule />
<h2 className="text-3xl">{`Environment`}</h2>
<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
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>
</section>
<section>
<Rule />
<h2 className="text-3xl">{`Languages`}</h2>
<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
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>
</section>
<section>
<Rule />
<h2 className="text-3xl">{`Coming Soon!`}</h2>
<div className="w-full flex flex-wrap justify-evenly">
<img
alt="Haskell"
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>
</section>
</main>
);
};
export default PlayPage;

View File

@ -1,49 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import Image from "next/image";
import type { JSX } from "react";
interface ArtProperties {
readonly name: string;
readonly img: string;
readonly artist: string;
readonly url: string;
readonly alt: string;
}
/**
* Renders the view for an art piece.
* @param properties - The art to render.
* @returns A JSX element.
*/
export const ArtComponent = (properties: ArtProperties): JSX.Element => {
const { name, img, artist, url, alt } = properties;
return (
<div className="w-[300px] h-[300px] border-2
border-solid border-[--foreground] m-auto text-center items-center">
<p className="text-l">
{`${name} by `}
<a className="underline" href={url} rel="noreferrer" target="_blank">
{artist}
</a>
</p>
<a
href={`https://cdn.nhcarrigan.com/art/${img}`}
rel="noreferrer"
target="_blank"
>
<Image
alt={alt}
className="m-auto object-contain max-h-[250px] max-w-[250px]"
height={250}
src={`https://cdn.nhcarrigan.com/art/${img}`}
width={250}
/>
</a>
</div>
);
};

View File

@ -1,50 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import Image from "next/image";
import type { JSX } from "react";
interface CertProperties {
readonly name: string;
readonly fileName: string;
readonly issuer: string;
readonly date: Date;
}
/**
* Renders the view for a certification.
* @param properties - The certification to render.
* @returns A JSX element.
*/
export const Certification = (properties: CertProperties): JSX.Element => {
const { name, fileName, issuer, date } = properties;
return (
<div className="w-[300px] h-[300px] border-2
border-solid border-[--foreground] m-auto text-center items-center">
<p className="text-xl">{name}</p>
<a
href={`https://cdn.nhcarrigan.com/certifications/${fileName}`}
rel="noreferrer"
target="_blank"
>
<Image
alt={name}
className="m-auto"
height={250}
src={`https://cdn.nhcarrigan.com/certifications/${fileName}`}
width={250}
/>
</a>
<p>{issuer}</p>
<p>
{date.toLocaleDateString("en-GB", {
month: "long",
year: "numeric",
})}
</p>
</div>
);
};

View File

@ -1,49 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import Image from "next/image";
import type { JSX } from "react";
interface CharacterProperties {
readonly id: string;
readonly onClick?: ()=> void;
readonly focused: boolean;
}
/**
* Renders the view for a legacy character.
* @param properties - The character to render.
* @returns A JSX element.
*/
export const CharacterComponent = (
properties: CharacterProperties,
): JSX.Element => {
const { id, onClick, focused } = properties;
return focused
? <div style={{ position: "relative" }}>
<Image
alt={id}
className="m-h-[525px] m-w-[400px] object-contain"
height={525}
onClick={onClick}
src={`https://cdn.nhcarrigan.com/characters/${id}.png`}
width={400}
/>
<div
aria-hidden="true"
className="absolute h-full w-full m-h-[525px] m-w-[400px]"
style={{ border: "5px solid yellow", left: 0, top: 0, zIndex: 1 }}
></div>
</div>
: <Image
alt={id}
className="m-h-[525px] m-w-[400px] object-contain"
height={525}
onClick={onClick}
src={`https://cdn.nhcarrigan.com/characters/${id}.png`}
width={400}
/>;
};

View File

@ -1,50 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import Image from "next/image";
import type { JSX } from "react";
interface GameProperties {
readonly name: string;
readonly img: string;
readonly url: string;
readonly alt: string;
}
/**
* Renders the view for a game.
* @param properties - The game to render.
* @returns A JSX element.
*/
export const Game = (properties: GameProperties): JSX.Element => {
const { name, img, url, alt } = properties;
return (
<div className="w-[300px] h-[300px] border-2
border-solid border-[--foreground] m-auto text-center items-center">
<a
className="text-xl underline"
href={url}
rel="noopener noreferrer"
target="_blank"
>
<p>{name}</p>
</a>
<a
href={`https://cdn.nhcarrigan.com/games/${img}`}
rel="noreferrer"
target="_blank"
>
<Image
alt={alt}
className="m-auto object-contain max-h-[250px] max-w-[250px]"
height={250}
src={`https://cdn.nhcarrigan.com/games/${img}`}
width={250}
/>
</a>
</div>
);
};

View File

@ -1,36 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import Image from "next/image";
import type { JSX } from "react";
/**
* Renders the view for a koikatsu shot.
* @param props - An object containing the image file name.
* @param props.img - The file name of the image to render.
* @returns A JSX element.
*/
export const KoikatsuComponent = ({ img }: { readonly img: string }):
JSX.Element => {
return (
<div className="w-[300px] border-2
border-solid border-[--foreground] m-auto text-center items-center">
<a
href={`https://cdn.nhcarrigan.com/koikatsu/${img}`}
rel="noreferrer"
target="_blank"
>
<Image
alt="This work is solely decorative. If you'd like alt text,
please reach out in our Discord and we'll be happy to provide it."
className="m-auto object-cover"
height={300}
src={`https://cdn.nhcarrigan.com/koikatsu/${img}`}
width={300}
/>
</a>
</div>
);
};

View File

@ -1,87 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
"use client";
import {
faBars,
faTimes,
faUpRightFromSquare,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Image from "next/image";
import React, { type JSX, useState, useCallback } from "react";
import { NavItems } from "../config/NavItems";
import { Rule } from "./rule";
/**
* Conditionally renders the navigation component when
* not on the home page.
* @returns A JSX element.
*/
export const Navigation = (): JSX.Element | null => {
const [ isOpen, setIsOpen ] = useState(false);
const toggleMenu = useCallback((): void => {
setIsOpen(!isOpen);
}, [ isOpen ]);
return (
<div className="fixed w-full top-0 z-50"
style={{ fontFamily: "OpenDyslexic Mono" }}>
<nav
className="w-full flex justify-between
items-center h-14 px-4 bg-[--background] text-[--foreground]"
>
<a href="/">
<Image
alt="nhcarrigan's logo"
height={50}
src="https://cdn.nhcarrigan.com/logo.png"
width={50}
/>
</a>
<div className="flex items-center">
<button onClick={toggleMenu} type="button">
<FontAwesomeIcon icon={isOpen
? faTimes
: faBars} size="2x" />
</button>
</div>
</nav>
{isOpen
? <div className="bg-[--background] text-[--foreground]
h-[50vh] overflow-y-auto">
{NavItems.map((item, index) => {
return (
<a
className="block py-2 px-4 text-2xl hover:bg-[--foreground]
hover:text-[--background]"
href={item.href}
key={item.href}
onClick={toggleMenu}
rel={item.href.startsWith("http")
? "noreferrer"
: ""}
target={item.href.startsWith("http")
? "_blank"
: "_self"}
>
{index % 2 === 1
? "🩷"
: "🩵"} {item.text}
{item.href.startsWith("http")
? <FontAwesomeIcon
aria-label="External link"
icon={faUpRightFromSquare} />
: null}
</a>
);
})}
</div>
: null}
<Rule />
</div>
);
};

View File

@ -1,101 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { JSX } from "react";
interface ProjectProperties {
name: string;
url: string;
source?: string;
description: string;
type: "Website" | "Bot" | "API" | "Game";
}
const Colours: { [key in ProjectProperties["type"]]: string } = {
API: "bg-sky-100 text-sky-900 dark:bg-sky-900 dark:text-sky-100",
Bot:
"bg-purple-100 text-purple-900 dark:bg-purple-900 dark:text-purple-100",
Game: "bg-green-100 text-green-900 dark:bg-green-900 dark:text-green-100",
Website: "bg-amber-100 text-amber-900 dark:bg-amber-900 dark:text-amber-100",
};
/**
* Renders the view for a project.
* @param properties - The project to render.
* @returns A JSX element.
*/
export const Project = (properties: ProjectProperties): JSX.Element => {
const { name, url, source, description, type } = properties;
return (
<div className="p-6 mb-2 bg-[--foreground] text-[--background]
border border-gray-200 rounded-lg shadow w-[50%] m-auto">
<h2 className="mb-2 text-2xl font-bold tracking-tight">{name}</h2>
<span
className={`inline-flex items-center text-xsfont-medium px-2.5 py-0.5
rounded-full ${Colours[type]}`}
>
{type}
</span>
<p className="mb-3 font-normal">
{description}
</p>
<a
className="inline-flex items-center px-3 py-2 text-sm
font-medium text-center text-[--foreground] bg-[--background]
rounded-lg focus:ring-4 focus:outline-none focus:ring-blue-300
dark:focus:ring-blue-800"
href={url}
rel="noopener noreferrer"
target="_blank"
>
{`See Project`}
<svg
aria-hidden="true"
className="rtl:rotate-180 w-3.5 h-3.5 ms-2"
fill="none"
viewBox="0 0 14 10"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 5h12m0 0L9 1m4 4L9 9"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</svg>
</a>
{source === undefined
? null
: <a
className="ml-1 inline-flex items-center px-3 py-2 text-sm
font-medium text-center text-[--foreground] bg-[--background]
rounded-lg focus:ring-4 focus:outline-none focus:ring-blue-300
dark:focus:ring-blue-800"
href={source}
rel="noopener noreferrer"
target="_blank"
>
{`View Source`}
<svg
aria-hidden="true"
className="rtl:rotate-180 w-3.5 h-3.5 ms-2"
fill="none"
viewBox="0 0 14 10"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 5h12m0 0L9 1m4 4L9 9"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</svg>
</a>
}
</div>
);
};

View File

@ -1,60 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Rule } from "./rule";
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import type { JSX } from "react";
interface ReviewProperties {
readonly name: string;
readonly date: Date;
readonly content: string;
readonly sourceIcon: IconDefinition;
readonly sourceUrl: string;
readonly sourceName: string;
}
/**
* Renders the view for a review.
* @param properties - The review to render.
* @returns A JSX element.
*/
export const Review = (properties: ReviewProperties): JSX.Element => {
const { name, date, content, sourceIcon, sourceUrl, sourceName } = properties;
return (
<li className="mb-10 ms-6">
<span className="absolute flex items-center justify-center
w-6 h-6 bg-[--background] text-[--foreground] rounded-full -start-3">
<FontAwesomeIcon icon={sourceIcon} />
</span>
<div className="items-center justify-between p-4 bg-white
border border-gray-200 rounded-lg shadow-sm sm:flex
dark:bg-gray-700 dark:border-gray-600">
<time className="mb-1 text-xs font-normal
text-gray-400 sm:order-last sm:mb-0">
{date.toLocaleDateString("en-GB")}
</time>
<div className="text-md font-normal text-gray-500 dark:text-gray-300">
{`Review from `}
<a
className="font-semibold text-[#abfcec] hover:underline"
href={sourceUrl}
rel="noopener noreferrer"
target="_blank"
>
{name}
</a>{` via `}
<span className="bg-gray-100 text-gray-800 text-xs font-normal
me-2 px-2.5 py-0.5 rounded dark:bg-gray-600 dark:text-gray-300">
{sourceName}
</span>
<Rule />
<p className="text-sm">{content}</p>
</div>
</div>
</li>
);
};

View File

@ -1,14 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { JSX } from "react";
/**
* Renders a customised horizontal rule.
* @returns A JSX element.
*/
export const Rule = (): JSX.Element => {
return <hr className="border-dashed border-2 border-[--primary-color]"></hr>;
};

View File

@ -1,129 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/**
* List of art to render.
*/
export const Art: Array<{
name: string;
img: string;
artist: string;
url: string;
alt: string;
}> = [
{
alt: "Pixel art portrait of a person with medium-length brown hair, purple glasses, and a blue top against a pink and blue striped background. A blue star-shaped hair clip adorns their hair.",
artist: "Jazzybee",
img: "profile.png",
name: "Avatar",
url: "https://jazzybee.itch.io/sdvcharactercreator",
},
{
alt: "Anime-style illustration of a character wearing a large brown witch hat decorated with roses and hanging charms against a cloudy background.",
artist: "Picrew",
img: "ai-bot.png",
name: "AI Bot",
url: "https://picrew.me/en/image_maker/1382748",
},
{
alt: "Anime-style portrait of a character with short burgundy hair and glasses, wearing a black blazer over a light blue shirt with a choker, against a pale blue patterned background.",
artist: "Picrew",
img: "mod-bot.png",
name: "Mod Bot",
url: "https://picrew.me/en/image_maker/27700",
},
{
alt: "Anime-style portrait of a character with wavy brown hair, glasses, and a black lacy top, winking and smiling",
artist: "Picrew",
img: "translation-bot.png",
name: "Translation Bot",
url: "https://picrew.me/en/image_maker/3595",
},
{
alt: "Anime-style character with brown hair and glasses wearing a teal hat and shirt with a heart design, smiling excitedly",
artist: "Picrew",
img: "task-bot.png",
name: "Task Bot",
url: "https://picrew.me/en/image_maker/700620",
},
{
alt: "Anime-style portrait of a character with glasses and wavy hair, wearing an off-shoulder pink top with a heart pendant necklace, against a bright pink circular background.",
artist: "Picrew",
img: "boost-bot.png",
name: "Boost Monitor Bot",
url: "https://picrew.me/en/image_maker/1310292",
},
{
alt: "Anime-style portrait of a character with blonde and black hair, blushing cheeks, and teary blue eyes, smiling against a turquoise background.",
artist: "Picrew",
img: "bridge.png",
name: "Social Media Bridge",
url: "https://picrew.me/en/image_maker/21208",
},
{
alt: "Gothic anime-style portrait of a character with purple eyeshadow, spider hair clip, and choker necklace in a forest setting",
artist: "Picrew",
img: "trick-or-treat.png",
name: "Trick or Treat",
url: "https://picrew.me/en/image_maker/1712061",
},
{
alt: "Cartoon character in a blue winter outfit with a pointed hat, holding a bloody sack, against a pink snowflake background",
artist: "Picrew",
img: "padoru.png",
name: "Padoru",
url: "https://picrew.me/en/image_maker/1843743",
},
{
alt: "Cartoon portrait of a character with brown hair and glasses, smiling against a transgender flag heart background",
artist: "Picrew",
img: "trans.png",
name: "Trans Pride",
url: "https://picrew.me/en/image_maker/100365/",
},
{
alt: "Anime-style portrait of a character with headphones and glasses, wearing a choker and holding a drink, against a pink pixelated background. Text 'xoxo 4Dear' visible.",
artist: "Picrew",
img: "alt-text.png",
name: "Alt-text Generator",
url: "https://picrew.me/en/image_maker/2003689/",
},
{
alt: "Anime-style illustration of a character with brown hair and large blue eyes, sipping a drink through a straw with a shush expression, wearing a blue sweater against a pink background.",
artist: "Picrew",
img: "anon.png",
name: "Anon Bot",
url: "https://picrew.me/en/image_maker/2307052/",
},
{
alt: "Anime-style illustration of a character with brown hair and glasses, making cat claw shapes with her hands. She wears a black outfit with white bows, surrounded by heart symbols on a turquoise background.",
artist: "Picrew",
img: "eval.png",
name: "Code Evaluator",
url: "https://picrew.me/en/image_maker/1787745/",
},
{
alt: "Anime-style portrait of a character with wavy brown hair and glasses, looking thoughtful against a background of pink cherry blossoms. She wears an off-shoulder dark top, and has a lit cigarette in her hand.",
artist: "Picrew",
img: "librarian.png",
name: "Librarian Bot",
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",
},
{
alt: "Anime-style portrait of a character with brown hair and blue eyes, wearking a pink top with a mesh chest, against a pink background.",
artist: "Picrew",
img: "logs.png",
name: "Mod Logs",
url: " https://picrew.me/en/image_maker/2539784",
},
];

View File

@ -1,250 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/**
* List of screenshots from games to render.
*/
export const Games: Array<{
name: string;
img: string;
url: string;
alt: string;
}> = [
{
alt: "A fantasy character in a pink outfit and tiara stands barefoot on rocky ground in a rainy, dark setting. She holds a weapon and is surrounded by puddles and a shadowy landscape with hints of ancient architecture.",
img: "bloody-spell.jpg",
name: "Bloody Spell",
url: "https://store.steampowered.com/app/992300/_Bloody_Spell/",
},
{
alt: "Animated character in a purple traditional Chinese robe wielding a large sword in a wooden temple-like structure with ornate pillars and lanterns.",
img: "dw-9.jpg",
name: "Dynasty Warriors 9: Empires",
url: "https://store.steampowered.com/app/1341200/DYNASTY_WARRIORS_9_Empires/",
},
{
alt: "A stylized female character in a video game or 3D rendering, wearing a white top and long red skirt, holding a sword and striking a dramatic pose with one arm raised. She stands on a stone platform with mountains in the background.",
img: "soul-calibur.jpg",
name: "Soul Calibur VI",
url: "https://store.steampowered.com/app/544750/SOULCALIBUR_VI/",
},
{
alt: "A virtual character in a red dress sits on a throne in a futuristic corridor. The character has short hair and is barefoot. Above the character is the text 'Avalon'. The scene appears to be from a video game or digital simulation, with a dark, metallic environment surrounding the central figure.",
img: "swtor.jpg",
name: "Star Wars: The Old Republic",
url: "https://store.steampowered.com/app/1286830/STAR_WARS_The_Old_Republic/",
},
{
alt: "Anime-style illustration of a young woman in a dark floral kimono standing in a lush green field. Behind her, a vibrant galaxy-like spiral shimmers in the sky. Yellow flowers bloom in the foreground, and glowing particles float around her. The scene has a magical, ethereal quality.",
img: "idling-god.png",
name: "Idling to Rule the Gods",
url: "https://store.steampowered.com/app/466170/Idling_to_Rule_the_Gods/",
},
{
alt: "A female video game character stands on a wooden bridge in a Japanese-style garden. She wears samurai-like armor and holds a long bow. The character has long dark hair and glasses. The scene is rendered in a soft, slightly blurred style with pink cherry blossom trees in the background.",
img: "nioh-2.jpg",
name: "Nioh 2",
url: "https://store.steampowered.com/app/1325200/Nioh_2__The_Complete_Edition/",
},
{
alt: "3D rendering of a figure wearing a Japanese-style school uniform with a white top, pink sailor collar, and purple pleated skirt, standing against a dark background. The figure has short brown hair and is shown in a full-body pose.",
img: "deathly-stillness.jpg",
name: "Deathly Stillness",
url: "https://store.steampowered.com/app/1727650/Deathly_Stillness/",
},
{
alt: "A 3D rendered character in a futuristic or sci-fi style outfit. The figure is wearing a light-colored bodysuit with straps and mechanical details, holding a large gun. The character has blonde hair and stands against a dark background in a ready pose.",
img: "once-human.jpg",
name: "Once Human",
url: "https://store.steampowered.com/app/2139460/Once_Human/",
},
{
alt: "A woman in post-apocalyptic armor stands in a desolate urban landscape. She wears sunglasses, leather armor pieces, and holds a weapon. The background shows damaged buildings and debris in a hazy, dusty environment.",
img: "fallout-4.jpg",
name: "Fallout 4",
url: "https://store.steampowered.com/app/377160/Fallout_4/",
},
{
alt: "Fantasy video game character in a revealing purple and gold outfit with a crown, standing in a lush mountainous landscape with floating islands and waterfalls. Character selection interface visible at the bottom of the image.",
img: "gw2.png",
name: "Guild Wars 2",
url: "https://store.steampowered.com/app/1284210/Guild_Wars_2/",
},
{
alt: "A 3D rendered scene of a medieval-style courtyard at night. A woman in a long teal dress stands in the center of a cobblestone path. Wooden archways and stone walls with lit lanterns frame the path, creating a moody atmosphere.",
img: "nwn.jpg",
name: "Neverwinter Nights",
url: "https://store.steampowered.com/app/704450/Neverwinter_Nights_Enhanced_Edition/",
},
{
alt: "A female video game character in medieval-style clothing stands in a rugged stone environment. She wears a tattered brown tunic, leather armor, and leg wraps. Her pose is fierce and ready for action, with dark hair framing her face. The background shows rocky terrain and ancient stone walls.",
img: "lords-of-the-fallen.jpg",
name: "Lords of the Fallen",
url: "https://store.steampowered.com/app/1501750/Lords_of_the_Fallen/",
},
{
alt: "Anime-style character in a futuristic black outfit with glowing blue accents standing in a forest setting. The character has short brown hair and large eyes, wearing a long coat with high slits revealing thigh-high boots.",
img: "vroid.png",
name: "VRoid",
url: "https://store.steampowered.com/app/1486350/VRoid_Studio_v1294/",
},
{
alt: "Anime-style illustration of a girl lying on her back, holding a smartphone above her face. She's surrounded by various objects including a pink rabbit toy, headphones, candies, a game controller, and a potted plant. The scene depicts a cluttered, colorful bedroom or living space.",
img: "koikatsu.png",
name: "Koikatsu",
url: "https://store.steampowered.com/app/1073440/__Koikatsu_Party/",
},
{
alt: "A female video game character stands in a dark, wooden structure. She has short hair and wears rustic clothing. The scene includes a health and magic bar in the top left corner and a minimap in the top right. The environment is dimly lit with some grass on the ground.",
img: "amalur.jpg",
name: "Kingdoms of Amalur",
url: "https://store.steampowered.com/app/1041720/Kingdoms_of_Amalur_ReReckoning/",
},
{
alt: "A 3D-rendered character in a futuristic setting, wearing a form-fitting white and black bodysuit with a red insignia on the chest. The character has short brown hair and glasses, standing confidently in an urban environment with other figures visible in the background.",
img: "encased.jpg",
name: "Encased",
url: "https://store.steampowered.com/app/921800/Encased_A_SciFi_PostApocalyptic_RPG/",
},
{
alt: "A blonde woman in a black Gothic-style outfit stands in a dimly lit room with large windows. She wears glasses, a corset-like top, fishnet stockings, and lace gloves. The room has a patterned carpet and appears to be in an old building.",
img: "demonologist.jpg",
name: "Demonologist",
url: "https://store.steampowered.com/app/1929610/Demonologist/",
},
{
alt: "A 3D rendered female video game character model wearing a shiny green outfit consisting of a sleeveless top and short skirt with thigh straps. The character has long brown hair and is standing in a neutral pose with a dark cape draped behind her.",
img: "mka.png",
name: "Mortal Kombat: Armageddon",
url: "https://en.wikipedia.org/wiki/Mortal_Kombat:_Armageddon",
},
{
alt: "A 3D video game character model of a woman with short dark hair wearing a teal green dress with purple accents. The dress has a laced front, thigh-high slit, and is paired with purple arm bands. The character stands in a combat-ready pose within an ornate frame against a dark background. The name 'NAOMI' is displayed beneath the character.",
img: "sc3.png",
name: "Soul Calibur III",
url: "https://en.wikipedia.org/wiki/Soulcalibur_III",
},
{
alt: "A 3D rendered character with elven features, glowing cyan eyes, and brown hair. The figure wears a blue and gray outfit consisting of a long coat, fitted pants, and a metallic necklace. The character is depicted in a dark, moody setting.",
img: "v-rising.jpg",
name: "V Rising",
url: "https://store.steampowered.com/app/1604030/V_Rising/",
},
{
alt: "Digital illustration of a female character in a flowing teal dress standing on a hillside overlooking a fantasy landscape with distant buildings and fields.",
img: "aow-3.jpg",
name: "Age of Wonders 3",
url: "https://store.steampowered.com/app/226840/Age_of_Wonders_III/",
},
{
alt: "Digital illustration of a character in a white and pink dress with bell sleeves and a crown, walking on a dirt path through a forested area with tall grass and flowers.",
img: "bannerlord.jpg",
name: "Mount & Blade II: Bannerlord",
url: "https://store.steampowered.com/app/261550/Mount__Blade_II_Bannerlord/",
},
{
alt: "3D render of an anime-style character with pale skin and light hair, wearing a short purple dress with a textured pattern, standing against a plain gray background.",
img: "codevein.jpg",
name: "Code Vein",
url: "https://store.steampowered.com/app/678960/CODE_VEIN/",
},
{
alt: "Digital artwork of a fantasy character with white hair in a long white dress holding a staff, standing against a backdrop of cloudy skies and distant ruins.",
img: "fallen-enchantress.jpg",
name: "Fallen Enchantress: Legendary Heroes",
url: "https://store.steampowered.com/app/228260/Fallen_Enchantress_Legendary_Heroes/",
},
{
alt: "Pixelated video game character in a green dress standing in a dark hallway. She has brown hair and wears glasses.",
img: "zomboid.jpg",
name: "Project Zomboid",
url: "https://store.steampowered.com/app/108600/Project_Zomboid/",
},
{
alt: "3D render of an anime-style character in a cozy room. The character wears a pink dress, glasses, and has short brown hair. The room features a round window, bookshelves, plants, and a purple cushion with a cat-shaped teapot. The scene has a warm, soft lighting.",
img: "spirit-city.png",
name: "Spirit City: Lofi Sessions",
url: "https://store.steampowered.com/app/2113850/Spirit_City_Lofi_Sessions/",
},
{
alt: "a female character in a fantasy or adventure game setting. She is wearing a dark outfit with ornate details, including a hooded cape and armor-like elements. The character is standing in a lush, jungle-like environment, with tall trees and foliage in the background. She is holding a large weapon, likely a sword or staff, in her hand, suggesting she is a warrior or adventurer. The character has a serious expression on her face, indicating she is focused and ready for action. Overall, the image conveys a sense of mystery, power, and exploration in a fantastical and immersive world.",
img: "mh-world.jpg",
name: "Monster Hunter: World",
url: "https://store.steampowered.com/app/582010/Monster_Hunter_World/",
},
{
alt: "A 3D character model showing a female figure wearing a bright neon green futuristic bodysuit with black armored accents and panels. The suit features geometric patterns and protective plating across the torso, limbs, and joints. The character has brown hair styled with side-swept bangs and stands in a neutral pose against a dark background. Text at the top of the image reads \"Origin: Human\" and \"Naomi\".",
img: "empyrion.jpg",
name: "Empyrion: Galactic Survival",
url: "https://store.steampowered.com/app/383120/Empyrion__Galactic_Survival/",
},
{
alt: "A 3D rendered anime-style female character model shown in a confident pose against a dark gray background. She has a short black bob haircut and is wearing a burgundy bikini-style outfit with gold accents, including a central pendant. Her accessories include gold bracelets and a red decorative garter or belt around one thigh. She wears elaborate burgundy strappy sandals that lace up her calves. The character is standing with hands on hips in a pose that casts a distinct shadow. The rendering features smooth shading and a slight bluish rim lighting effect that highlights the character's silhouette.",
img: "angel-legion.png",
name: "Angel Legion",
url: "https://store.steampowered.com/app/1333350/Angel_Legion/",
},
{
alt: "A detailed anime-style illustration of an angelic warrior character against a sunset cityscape background. The character has long blonde hair and wears elaborate white and gold armor with flowing white robes accented by blue and orange fabrics. She has multiple large white wings spread dramatically behind her and wears a golden crown-like headpiece. Golden chains float decoratively around her dress and armor. She's depicted in a dynamic pose with futuristic city towers and a reddish-orange sky visible in the background. The artwork combines elements of fantasy and sci-fi, with ornate details in the character's costume and architectural elements of the setting.",
img: "idle-angels.jpg",
name: "Idle Angels",
url: "https://play.google.com/store/apps/details?id=com.mujoysg.hxbb&hl=en-US&pli=1",
},
{
alt: "A 3D rendered video game character wearing an ornate turquoise and white dress with bell sleeves and a matching cape. The outfit features intricate trim details, a bow at the waist, and layered fabric elements. The character wears round glasses and has brown hair styled in a neat bob cut. The character is shown barefoot against a dark blue background.",
img: "dw-8.jpg",
name: "Dynasty Warriors 8 Empires",
url: "https://store.steampowered.com/app/322520/DYNASTY_WARRIORS_8_Empires/",
},
{
alt: "A video game character stands on a dock wearing a traditional Japanese kimono in royal blue with delicate flower patterns and a red hem. The kimono is accessorized with a decorative obi belt and a sword at the waist. The character has round glasses and shoulder-length blonde hair. In the background, wooden ships are moored at the harbor against a mountainous coastline.",
img: "samurai-4.jpg",
name: "Way of the Samurai 4",
url: "https://store.steampowered.com/app/312780/Way_of_the_Samurai_4/",
},
{
alt: "A 3D fantasy character dressed in a light blue and white flowing robe with exposed midriff stands wielding a magical staff with purple crystals. In the background looms a dark, gothic castle silhouetted against a misty green sky with a large moon. The scene is atmospheric with purple and blue lighting effects, and mysterious armored figures can be seen in the shadows on either side.",
img: "aow-4.jpg",
name: "Age of Wonders 4",
url: "https://store.steampowered.com/app/1669000/Age_of_Wonders_4/",
},
{
alt: "A 3D rendered character standing in a nighttime wilderness setting, illuminated by a large full moon and orange ambient lighting. The figure wears tattered, primitive clothing consisting of a rough-textured tunic and shorts with wrapped cloth around the lower legs. The clothing appears weathered and torn. The character is positioned on rocky terrain with foliage visible in the background. The lighting creates a dramatic contrast between cool moonlight and warm ground reflections.",
img: "enshrouded.jpg",
name: "Enshrouded",
url: "https://store.steampowered.com/app/1203620/Enshrouded/",
},
{
alt: "A person with long dark hair wearing sunglasses and a printed open coat over a gray jumpsuit, standing barefoot against a maroon background",
img: "cyberpunk.jpg",
name: "Cyberpunk 2077",
url: "https://store.steampowered.com/app/1091500/Cyberpunk_2077/",
},
{
alt: "A 3D rendered character in a video game or animation style, standing barefoot on a circular platform. They wear a dark purple corset-style top with buttons down the front, paired with a matching flared skirt. Around their thighs are decorative garter-like bands with an intricate pattern. The character has reddish-brown hair styled in two side ponytails and stands in a neutral pose against a backdrop of wooden paneled walls.",
img: "ale-and-tavern.jpg",
name: "Ale and Tale Tavern",
url: "https://store.steampowered.com/app/2683150/Ale__Tale_Tavern/",
},
{
alt: "A digital artwork depicting a warrior character in a dynamic pose, wielding a flaming sword. They wear dark armor with gold accents and red trim, with a tattered cape. Their brown hair partially covers their face, and they are barefoot. The background shows a desert-like environment with pale grasses. The sword glows with intense orange flames, creating dramatic lighting effects. The character's stance suggests they are mid-combat or preparing to strike.",
img: "dos-2.jpg",
name: "Divinity: Original Sin 2",
url: "https://store.steampowered.com/app/435150/Divinity_Original_Sin_2__Definitive_Edition/",
},
{
alt: "A 3D rendered figure standing against a dark background with binary code and chain-link patterns. The figure wears a turquoise green dress with a corseted bodice featuring lace-up details. The dress has a ruffled sweetheart neckline and a full skirt with multiple tiers of frills. The figure has long straight dark hair and wears glasses. The figure is barefoot and appears to be rendered in a video game or digital art style. The lighting creates dramatic highlights and shadows on the dress and figure.",
img: "saints-row-4.jpg",
name: "Saints Row IV",
url: "https://store.steampowered.com/app/206420/Saints_Row_IV/",
},
{
alt: "A 3D rendered character stands barefoot on a red carpet in what appears to be a garage or workshop setting. She wears black-rimmed glasses, a purple tank top, and a cream-colored knee-length skirt with red abstract designs on it. The character has short brown hair styled in a modern cut. Behind her are various automotive items including what appears to be a yellow vehicle and some storage units. Part of a vintage \"Around the World\" poster is visible on the wall.",
img: "sunset-overdrive.jpg",
name: "Sunset Overdrive",
url: "https://store.steampowered.com/app/847370/Sunset_Overdrive/",
},
];

View File

@ -1,19 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
export const Koikatsu = [
"CharaStudio-2025-02-16-13-26-06-Render.png",
"CharaStudio-2025-02-16-13-26-56-Render.png",
"CharaStudio-2025-02-16-13-27-18-Render.png",
"CharaStudio-2025-02-16-13-27-50-Render.png",
"CharaStudio-2025-02-16-13-28-45-Render.png",
"CharaStudio-2025-02-16-13-29-10-Render.png",
"CharaStudio-2025-02-16-13-29-32-Render.png",
"CharaStudio-2025-02-16-13-29-43-Render.png",
"CharaStudio-2025-02-16-13-30-06-Render.png",
"CharaStudio-2025-02-16-13-30-29-Render.png",
"CharaStudio-2025-02-16-13-30-57-Render.png",
"CharaStudio-2025-02-16-13-31-46-Render.png",
];

View File

@ -1,174 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
const Characters: Record<string, { age: number; alt: string; class: string; name: string; bio: Array<string>; combat: Array<string> }> = {
"aria-iuvo": {
age: 103,
alt: "A digital art illustration depicts a young anime-style girl with long dark red hair, pale skin, and a blank white mask-like face standing against a dark night sky, holding a glowing knife and gazing pensively at a full moon.",
bio: [
"Aria Iuvo is an elven thief with a mischievous streak a mile wide.",
"Despite being older than her more straight-laced sister Melody, Aria's elven blood keeps her looking spry and youthful.",
"With a magpie-like eye for shiny valuables, Aria can't resist pilfering any glittering bauble or jangling coin purse that catches her fancy.",
"Her elvish heritage grants her superhuman agility, stealth, and perception - talents she puts to great use in her life of thievery and adventure.",
],
class: "Thief",
combat: [
"In combat, Aria relies on stealth, speed and misdirection over brute force.",
"She darts in and out of shadows, using her superhuman agility to avoid attacks and strike from unexpected angles.",
"Her keen elven senses allow her to spot weaknesses in enemies' defences that others would miss.",
"Aria wields a pair of wickedly sharp daggers with deadly precision, and employs an array of smoke bombs, caltrops and other gadgets to befuddle her foes.",
"She fights with an impish grin, finding joy in the thrill of battle and loot, making her an unpredictable and frustrating adversary.",
],
name: "Aria Iuvo",
},
"becca-lyria": {
age: 21,
alt: "Anime girl with white hair in pigtails and glasses, wearing a purple outfit, sitting on a white bed with a glowing purple magic circle design on the floor in front of her",
bio: [
"Becca Lyria is a powerful necromancer with the ability to raise and command the dead.",
"Orphaned at a young age, Becca has little memory of her parents. She was later taken in by Naomi, who raised her as her own child.",
"Despite her dark powers, Becca formed an unlikely bond with the bubbly Rosalia during one of her adventures. Though their personalities clash, the two have remained close ever since.",
],
class: "Necromancer",
combat: [
"In battle, Becca wields her mastery over death to devastating effect. She can raise fallen enemies and allies alike, turning them into undead minions under her control.",
"Her necromantic spells allow her to sap the life force from foes, weakening them while simultaneously strengthening her own undead thralls.",
"Becca can also summon skeletal warriors and ghostly apparitions to fight alongside her, overwhelming enemies with a relentless assault of the undead.",
"While not physically imposing herself, Becca's ability to constantly raise new minions makes her a formidable and resilient opponent on the battlefield.",
],
name: "Becca Lyria",
},
"cordelia-taryne": {
age: 85,
alt: "Anime girl with blonde hair in two buns wearing a yellow and gold dress sits amidst a white magic circle and red roses against a dark red background",
bio: [
"Cordelia Taryne was born as the daughter of Aria, and spent her early life believing she was human.",
"As a young adult, Cordelia fell under the influence of a sinister vampire cult. During her initiation ritual, she was bitten by a vampire and transformed into an enthralled servant.",
"After years in servitude, Cordelia finally broke free from her vampire master's control. In doing so, she unlocked her full vampiric powers and abilities.",
"Strangely, overthrowing her master also revealed Cordelia's latent elvish heritage, which had been hidden until this point. She discovered she was actually half-elf.",
"Now an independent vampyr, Cordelia wields powerful blood magic that she uses to dominate and defeat those who stand in her way.",
],
class: "Vampyr",
combat: [
"As a vampyr, Cordelia has superhuman strength, speed, and resilience. Her undead body can shrug off injuries that would cripple a mortal.",
"She wields blood magic to directly sap the life force from enemies, weakening them while replenishing her own strength. Foes grow faint as she drains them.",
"Cordelia can mesmerize and mentally dominate weak-willed opponents, temporarily making them into her puppets to fight on her behalf.",
"If wounded, Cordelia can regenerate and knit her undead flesh back together by drinking the blood of the living.",
"Her vampiric bite can infect others, turning them into short-lived thralls under her control. She often bites foes to make them fight their own allies.",
],
name: "Cordelia Taryne",
},
"gwen-abalise": {
age: 28,
alt: "Anime-style female character with purple hair in a side ponytail swinging her sword, wearing black armour, standing in a grassy field with cherry blossom petals floating in the air and a waterfall in the background.",
bio: [
"Gwen is Naomi's first adopted child, taken in before Becca joined the family.",
"Feeling overshadowed and jealous when Becca arrived, Gwen took off on her own in a huff.",
"She spent several years wandering, honing her skills with the blade and getting into many fights.",
"In one of these fights, Gwen lost an eye, a permanent reminder of her reckless youth.",
"As she matured, Gwen began to regret leaving her adopted family behind.",
"She has recently started making efforts to reconnect with Naomi and mend their strained relationship.",
],
class: "Warrior",
combat: [
"Gwen is a formidable warrior, wielding a razor-sharp katana with deadly precision.",
"Despite being clad in heavy armor, she moves with surprising swiftness and agility on the battlefield.",
"Her missing eye does little to hinder her combat prowess, as her other senses have sharpened to compensate.",
"Gwen's fighting style is aggressive and relentless, constantly pressing the attack to overwhelm her foes.",
"Years of hard-fought battles have honed her instincts, allowing her to anticipate and counter enemy moves.",
"While she fights with fierce determination, there is an underlying anger that drives her - anger at herself and her past mistakes.",
],
name: "Gwen Abalise",
},
"maylin-taryne": {
age: 32,
alt: "A blonde female anime character with short hair aims a bow and arrow while jumping through the air, trees and a fence visible in the background under a bright blue sky with wispy clouds.",
bio: [
"Maylin was once a thrall, bound to serve the same vampire master as Cordelia.",
"When Cordelia overthrew their master and took his place, Maylin's vampiric powers faded away, leaving her human once more.",
"Cordelia, feeling responsible for Maylin's fate and driven by a sense of guilt, took the former thrall under her wing.",
"Despite no longer possessing supernatural abilities, Maylin discovered a deep connection to nature.",
"She honed her skills as a ranger, finding solace and purpose in the wilderness.",
"Maylin became like a daughter to Cordelia, the only human the vampire has ever grown close to.",
"Though she lacks elvish blood, Maylin's affinity for nature rivals that of the most skilled elven rangers.",
],
class: "Ranger",
combat: [
"Maylin is an exceptional ranger, with unparalleled prowess in archery.",
"Her mastery of the longbow allows her to strike targets with incredible precision from great distances.",
"Years of training and a deep understanding of the natural world have sharpened Maylin's instincts and reflexes.",
"In combat, she moves with the grace and agility of a predator, effortlessly navigating the battlefield.",
"Maylin's keen senses enable her to detect hidden threats and track enemies through even the most challenging terrain.",
"Her arrows, imbued with her connection to nature, seem to find their mark as if guided by an unseen force.",
"Though she no longer possesses vampiric abilities, Maylin's human resilience and determination make her a formidable ally and a feared adversary.",
],
name: "Maylin Taryne",
},
"melody-iuvo": {
age: 26,
alt: "3D anime-style girl with green hair in a ponytaiol aims a sniper rifle while kneeling in a grassy field with pine trees under a blue sky",
bio: [
"Melody Iuvo is an assassin known for her mastery of stealth and deception.",
"She began her career as an assistant to Naomi, but the two quickly fell in love and later married.",
"Melody is often seen as cold and calculating, but she has a softer side that she reveals to those she cares about.",
"Unlike her sister Aria, Melody did not inherit the elvish genes of their mother, and is fully human.",
"Her skills as an assassin have made her both feared and respected in the underworld.",
],
class: "Assassin",
combat: [
"As an assassin, Melody specializes in long-range combat using a sniper rifle.",
"Her mastery of stealth allows her to infiltrate enemy territory undetected and take out targets from afar.",
"Melody's calculating nature makes her an excellent strategist, able to plan complex assassinations with multiple contingencies.",
"In close-quarters combat, she relies on her agility and deception to outmaneuver opponents.",
"Melody's cold demeanor in combat belies her deadly efficiency as an assassin.",
],
name: "Melody Iuvo",
},
"naomi-carrigan": {
age: 30,
alt: "Illustration of an anime girl with glasses and short brown hair floating in a surreal space environment, surrounded by glowing teal snowflakes, silver sparkles, and layered geometric designs resembling railroad tracks or wheels against a dark starry sky background.",
bio: [
"Naomi Carrigan is a powerful technomancer, able to manipulate technology using magic.",
"She quickly rose to a leadership role in her family thanks to her potent magical skills and natural charisma.",
"Naomi met her wife Melody when the latter worked as her assistant. The two fell in love and were soon wed.",
"Upon hearing of Gwen's tough upbringing in an orphanage, Naomi didn't hesitate to adopt her. A few years later, she adopted Becca from the same orphanage.",
"As the matriarch, Naomi guides and protects her family, including her wife and two adopted daughters, as well as their extended family members.",
],
class: "Technomancer",
combat: [
"As a technomancer, Naomi can magically hack and control technological devices, using them to attack or defend.",
"She might launch lightning from her fingertips, or take over security systems and drones to turn them against her foes.",
"Naomi could erect force fields of hard light constructs resembling circuitry and digital glitches to protect herself and allies.",
"Her spells may manifest as beams, bolts or pulses of crackling energy, often in neon hues, that can stun or damage enemies.",
"In a world increasingly reliant on technology, Naomi's powers make her a formidable force on the battlefield as she bends machines to her will.",
],
name: "Naomi Carrigan",
},
"rosalia-nightsong": {
age: 18,
alt: "Anime-style girl with long dark hair and green eyes wearing a white dress, standing in a palace throne room with pink tile floors and ornate stained glass windows behind the throne",
bio: [
"Rosalia was born into a noble family, enjoying a life of privilege and comfort.",
"From a young age, she devoted herself to her Goddess, finding purpose and meaning in faith.",
"When the call came to take up arms and fight for righteousness, Rosalia answered without hesitation.",
"As a paladin, she now travels the world, righting wrongs and upholding justice in the name of her deity.",
"Despite their marriage, Rosalia and Becca often find themselves at odds due to their differing moral compasses.",
"Rosalia holds out hope that one day, Becca will turn away from the path of darkness and join her in the light.",
],
class: "Paladin",
combat: [
"Rosalia is a skilled warrior, wielding a mighty greatsword with both strength and precision.",
"As a paladin, she channels the power of her Goddess to enhance her combat abilities and protect her allies.",
"Her faith allows her to cast divine spells, such as healing and smiting her enemies with holy light.",
"Rosalia's unwavering conviction and righteous fury make her a formidable opponent on the battlefield.",
"She is not afraid to charge into the fray, using her greatsword to cleave through evil and defend the innocent.",
"With her divine powers and martial prowess, Rosalia is a beacon of hope and a force to be reckoned with.",
],
name: "Rosalia Nightsong",
},
};
export { Characters };

View File

@ -1,38 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/**
* Navigation items to render on home page and
* on main navbar.
*/
const NavItems = [
{ href: "https://docs.nhcarrigan.com/about/mission/", text: "Our Mission" },
{ href: "https://contact.nhcarrigan.com", text: "Contact Us" },
{ href: "https://donate.nhcarrigan.com", text: "Donate" },
{ href: "/tech", text: "Technologies We Use" },
{ href: "https://git.nhcarrigan.com", text: "Contribute to our Projects" },
{ href: "https://merch.nhcarrigan.link", text: "Merchandise" },
{ href: "https://docs.nhcarrigan.com", text: "Documentation" },
{ href: "https://chat.nhcarrigan.com", text: "Support" },
{ href: "https://forms.nhcarrigan.com/commission", text: "Hire Us" },
].sort((a, b) => {
return a.text.localeCompare(b.text);
});
const NaomiNavItems = [
{ href: "/legacy", text: "Characters" },
{ href: "/ref", text: "Reference Sheet" },
{ href: "/koikatsu", text: "Koikatsu Scenes" },
{ href: "/games", text: "Game Screenshots" },
{ href: "/art", text: "Art of Naomi" },
{ href: "https://resume.nhcarrigan.com", text: "Resume" },
{ href: "https://blog.nhcarrigan.com", text: "Blog" },
{ href: "https://testimonials.nhcarrigan.com", text: "Testimonials" },
].sort((a, b) => {
return a.text.localeCompare(b.text);
});
export { NavItems, NaomiNavItems };

View File

@ -1,70 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/**
* List of vroid models to render.
*/
const VRoid = [
{
file: "business.vrm",
name: "Business Suit",
},
{
file: "casual.vrm",
name: "Casual Outfit",
},
{
file: "formal.vrm",
name: "Formal Outfit",
},
{
file: "sleep.vrm",
name: "Sleepwear",
},
{
file: "swim.vrm",
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.vrm",
name: "Arcane Witch",
},
];
const MainVRoid = {
file: "main.vrm",
name: "Main Outfit",
};
export { VRoid, MainVRoid };

View File

@ -1,192 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* Custom FontAwesome icon definition for
* volunteer positions on the timeline.
*/
export const Volunteer: IconDefinition = {
icon: [
800,
798,
[],
"U+E002",
`M 369.00,0.21
C 369.00,0.21 423.00,0.21 423.00,0.21
423.00,0.21 435.00,0.91 435.00,0.91
480.07,4.06 524.94,15.89 566.00,34.69
608.59,54.20 648.70,80.88 681.91,114.09
681.91,114.09 689.04,122.00 689.04,122.00
744.21,179.68 780.10,250.35 793.75,329.00
795.84,341.01 799.98,377.55 799.96,389.00
799.96,389.00 799.96,401.00 799.96,401.00
799.96,401.00 799.00,411.00 799.00,411.00
799.00,411.00 799.00,424.00 799.00,424.00
799.00,424.00 798.09,434.00 798.09,434.00
796.18,461.36 791.12,488.69 783.28,515.00
767.34,568.47 741.19,613.80 706.20,657.00
687.38,680.22 656.65,708.80 632.00,725.79
625.02,730.60 622.97,730.16 620.28,731.93
620.28,731.93 613.00,738.21 613.00,738.21
613.00,738.21 599.00,746.57 599.00,746.57
556.64,770.81 501.54,789.82 453.00,795.72
427.94,798.76 402.25,798.29 377.00,798.00
377.00,798.00 367.00,797.09 367.00,797.09
344.92,795.54 318.29,791.85 297.00,785.85
297.00,785.85 286.00,781.78 286.00,781.78
286.00,781.78 278.00,780.10 278.00,780.10
278.00,780.10 264.00,775.66 264.00,775.66
226.39,762.60 184.34,739.01 153.00,714.58
88.50,664.30 40.30,593.29 16.72,515.00
7.01,482.76 1.05,448.68 1.00,415.00
1.00,415.00 0.00,399.00 0.00,399.00
0.00,399.00 0.00,386.00 0.00,386.00
0.00,386.00 1.91,364.00 1.91,364.00
4.61,325.33 13.92,286.04 28.20,250.00
69.21,146.50 153.89,63.69 258.00,24.42
282.20,15.30 309.43,8.22 335.00,4.27
335.00,4.27 359.00,1.17 359.00,1.17
359.00,1.17 369.00,0.21 369.00,0.21 Z
M 401.00,91.28
C 394.52,92.22 391.36,91.97 385.00,92.00
385.00,92.00 353.00,95.27 353.00,95.27
313.54,101.08 274.97,114.38 241.00,135.42
241.00,135.42 212.00,155.35 212.00,155.35
209.44,157.33 202.07,162.65 200.71,165.09
198.21,169.54 204.20,174.18 207.00,177.00
207.00,177.00 235.00,205.00 235.00,205.00
235.00,205.00 306.00,276.00 306.00,276.00
306.00,276.00 324.00,294.00 324.00,294.00
326.43,296.43 330.64,301.14 334.00,301.90
338.06,302.82 342.52,298.70 346.00,296.91
350.15,294.78 352.51,295.01 357.00,295.00
357.00,295.00 441.00,295.00 441.00,295.00
443.61,295.00 446.49,294.89 449.00,295.65
459.82,298.91 479.22,317.96 488.00,325.72
494.82,331.74 493.43,330.92 499.09,337.00
524.87,364.72 545.33,396.98 557.33,433.00
561.89,446.67 566.83,467.67 567.00,482.00
567.12,492.76 567.23,503.33 565.72,514.00
565.04,518.78 562.18,527.73 563.74,532.00
565.85,537.79 574.50,544.90 579.00,549.09
579.00,549.09 605.00,575.00 605.00,575.00
605.00,575.00 625.00,594.91 625.00,594.91
627.41,597.11 630.45,600.14 633.91,598.27
636.09,597.09 641.72,589.38 643.58,587.00
651.47,576.86 658.60,566.02 665.20,555.00
686.91,518.74 700.15,476.84 705.15,435.00
705.15,435.00 707.09,413.00 707.09,413.00
707.09,413.00 707.09,397.00 707.09,397.00
707.09,397.00 707.09,388.00 707.09,388.00
702.39,255.14 612.04,137.79 483.00,102.85
463.36,97.53 444.19,94.76 424.00,92.83
417.04,92.17 407.75,90.66 401.00,91.28 Z
M 333.00,173.00
C 340.67,173.43 344.75,176.62 351.00,180.66
353.99,182.59 359.37,186.09 363.00,185.90
368.27,185.63 373.85,178.62 377.17,175.00
383.79,167.80 391.65,160.51 402.00,161.04
412.91,161.61 419.27,171.98 429.00,175.15
436.12,177.47 448.46,172.80 456.00,171.21
459.79,170.41 466.47,168.69 467.64,174.11
468.77,179.32 459.61,192.07 456.86,197.00
448.92,211.22 435.53,237.53 432.00,253.00
432.00,253.00 376.00,253.00 376.00,253.00
364.56,252.98 366.17,249.64 360.14,236.00
360.14,236.00 342.75,199.00 342.75,199.00
338.30,190.08 333.23,183.21 333.00,173.00 Z
M 167.04,199.65
C 161.94,202.39 158.17,208.43 154.65,213.00
146.44,223.65 136.99,238.09 130.75,250.00
112.14,285.48 99.61,322.10 94.85,362.00
94.85,362.00 93.00,383.00 93.00,383.00
93.00,383.00 93.00,417.00 93.00,417.00
93.24,437.49 99.91,469.27 105.98,489.00
112.01,508.63 119.50,527.91 129.31,546.00
129.31,546.00 143.67,569.00 143.67,569.00
185.29,631.41 245.79,675.35 318.00,695.42
336.37,700.53 355.00,703.70 374.00,705.09
374.00,705.09 384.00,706.04 384.00,706.04
384.00,706.04 398.00,706.04 398.00,706.04
398.00,706.04 407.00,706.90 407.00,706.90
407.00,706.90 424.00,705.17 424.00,705.17
451.49,702.51 459.30,700.93 486.00,694.37
486.00,694.37 496.00,692.37 496.00,692.37
496.00,692.37 512.00,685.42 512.00,685.42
531.68,676.89 536.15,675.06 555.00,663.99
571.20,654.47 572.79,653.64 588.00,642.13
590.62,640.15 598.82,634.69 599.70,631.91
601.27,626.99 591.16,618.99 588.00,615.96
588.00,615.96 556.00,584.00 556.00,584.00
553.97,581.97 544.57,571.87 542.83,571.25
538.60,569.74 527.54,580.43 524.00,583.10
510.97,592.95 489.94,601.58 474.00,605.35
458.43,609.02 442.90,611.00 427.00,612.09
427.00,612.09 415.00,613.00 415.00,613.00
415.00,613.00 385.00,613.00 385.00,613.00
385.00,613.00 355.00,610.28 355.00,610.28
315.40,605.41 272.06,591.54 249.44,556.00
244.25,547.85 241.28,541.17 238.34,532.00
235.37,522.72 233.02,508.74 233.00,499.00
233.00,499.00 233.00,480.00 233.00,480.00
233.04,451.21 250.10,408.55 264.80,384.00
274.06,368.54 279.87,359.91 291.59,346.00
294.18,342.93 302.39,334.91 301.98,331.00
301.71,328.47 298.71,325.74 297.00,324.00
297.00,324.00 282.00,310.00 282.00,310.00
282.00,310.00 225.00,253.00 225.00,253.00
225.00,253.00 185.04,213.00 185.04,213.00
180.16,207.90 174.98,199.05 167.04,199.65 Z
M 357.00,261.74
C 362.72,260.35 377.34,261.00 384.00,261.00
384.00,261.00 435.00,261.00 435.00,261.00
439.06,261.01 443.34,260.74 446.61,263.65
451.40,267.92 451.17,280.20 446.61,284.57
442.51,288.50 434.33,288.06 429.00,288.00
429.00,288.00 413.00,287.00 413.00,287.00
413.00,287.00 403.00,287.95 403.00,287.95
403.00,287.95 386.00,287.95 386.00,287.95
386.00,287.95 376.00,287.04 376.00,287.04
376.00,287.04 364.00,287.04 364.00,287.04
360.74,286.99 356.98,287.29 354.21,285.26
347.76,280.53 346.80,267.20 357.00,261.74 Z
M 454.00,407.00
C 453.95,391.01 445.11,376.53 431.00,368.90
431.00,368.90 415.05,362.86 415.05,362.86
409.17,359.38 411.17,354.32 408.41,351.98
405.46,349.50 394.97,350.00 391.16,350.00
391.00,351.94 390.89,356.32 391.16,358.00
392.38,362.29 400.62,370.40 403.84,374.00
403.84,374.00 424.00,395.00 424.00,395.00
427.16,398.16 435.29,407.11 439.00,408.39
442.08,409.45 450.38,407.48 454.00,407.00 Z
M 410.00,545.00
C 433.68,543.90 458.71,522.64 459.00,498.00
459.03,495.25 459.21,490.47 458.26,488.00
456.58,483.60 444.91,472.91 441.00,469.00
441.00,469.00 392.00,420.00 392.00,420.00
392.00,420.00 365.00,393.00 365.00,393.00
362.74,390.74 357.57,384.58 354.10,385.45
351.39,386.14 349.42,390.63 348.45,393.00
345.07,401.28 345.96,409.21 346.00,418.00
346.07,433.59 353.45,446.21 366.00,455.25
370.26,458.32 374.19,460.50 379.00,462.57
382.60,464.12 386.99,465.36 388.98,469.05
390.19,471.30 390.00,474.49 390.00,477.00
390.00,477.00 390.00,513.00 390.00,513.00
381.30,507.46 375.61,499.25 374.00,489.00
374.00,489.00 351.00,490.97 351.00,490.97
348.18,491.12 343.87,490.90 342.02,493.51
339.48,497.11 343.57,508.12 345.06,512.00
352.06,530.24 370.17,544.94 390.00,545.00
390.00,545.00 390.00,559.00 390.00,559.00
390.00,559.00 410.00,559.00 410.00,559.00
410.00,559.00 410.00,545.00 410.00,545.00 Z`,
],
iconName: "yyy",
prefix: "xxx",
} as never;

View File

@ -1,11 +0,0 @@
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
plugins: [],
};
export default config;

View File

@ -1,107 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { ListObjectsV2Command, S3Client } from "@aws-sdk/client-s3";
import { describe, it, expect } from "vitest";
import { Art } from "../src/config/Art";
import { Certifications } from "../src/config/Certifications";
import { Games } from "../src/config/Games";
import { Koikatsu } from "../src/config/Koikatsu";
const s3 = new S3Client({
credentials: {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
accessKeyId: process.env.DO_ACCESS_KEY as string,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
secretAccessKey: process.env.DO_SECRET_KEY as string,
},
endpoint: "https://sfo3.digitaloceanspaces.com",
region: "sfo3",
});
describe("cdn files", () => {
describe("for games", async() => {
const command = new ListObjectsV2Command({
Bucket: "nhcarrigan-cdn",
Prefix: "games",
});
const { Contents } = await s3.send(command);
const names = Contents?.map((content) => {
return content.Key?.replace("games/", "");
}).filter(Boolean) || [];
it.each(names)("should have %s in the Games config", (name) => {
expect.assertions(1);
const isGameInConfig = Games.some((game) => {
return game.img === name;
});
expect(isGameInConfig, "is not in config").toBeTruthy();
});
});
describe("for koikatsu", async() => {
const command = new ListObjectsV2Command({
Bucket: "nhcarrigan-cdn",
Prefix: "koikatsu",
});
const { Contents } = await s3.send(command);
const names = Contents?.map((content) => {
return content.Key?.replace("koikatsu/", "");
}).filter(Boolean) || [];
it.each(names)("should have %s in the Koikatsu config", (name) => {
expect.assertions(1);
const isKoikatsuInConfig = Koikatsu.includes(name);
expect(isKoikatsuInConfig, "is not in config").toBeTruthy();
});
});
describe("for art", async() => {
const command = new ListObjectsV2Command({
Bucket: "nhcarrigan-cdn",
Prefix: "art",
});
const { Contents } = await s3.send(command);
const names = Contents?.map((content) => {
return content.Key?.replace("art/", "");
}).filter(Boolean) || [];
it.each(names)("should have %s in the Art config", (name) => {
expect.assertions(1);
const isArtInConfig = Art.some((art) => {
return art.img === name;
});
expect(isArtInConfig, "is not in config").toBeTruthy();
});
});
describe("for certifications", async() => {
const command = new ListObjectsV2Command({
Bucket: "nhcarrigan-cdn",
Prefix: "certifications",
});
const { Contents } = await s3.send(command);
const names = Contents?.map((content) => {
return content.Key?.replace("certifications/", "");
}).filter(Boolean) || [];
it.each(names)("should have %s in the Certifications config", (name) => {
expect.assertions(1);
const isCertInConfig = Certifications.some((cert) => {
return cert.fileName === name;
});
expect(isCertInConfig, "is not in config").toBeTruthy();
});
});
});

View File

@ -1,37 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { describe, it, expect } from "vitest";
import { Art } from "../../src/config/Art";
describe("art objects", () => {
it("should have unique names", () => {
expect.assertions(1);
const set = new Set(
Art.map((a) => {
return a.name;
}),
);
expect(set, "are not unique").toHaveLength(Art.length);
});
it("should have unique img properties", () => {
expect.assertions(1);
const set = new Set(
Art.map((a) => {
return a.img;
}),
);
expect(set, "are not unique").toHaveLength(Art.length);
});
it("should have alt text", () => {
expect.assertions(1);
const noText = Art.filter((a) => {
return a.alt.length === 0;
});
expect(noText, "found missing alt").toHaveLength(0);
});
});

View File

@ -1,47 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { describe, it, expect } from "vitest";
import { Games } from "../../src/config/Games";
describe("games objects", () => {
it("should have unique names", () => {
expect.assertions(1);
const set = new Set(
Games.map((g) => {
return g.name;
}),
);
expect(set, "are not unique").toHaveLength(Games.length);
});
it("should have unique img properties", () => {
expect.assertions(1);
const set = new Set(
Games.map((g) => {
return g.img;
}),
);
expect(set, "are not unique").toHaveLength(Games.length);
});
it("should have unique urls", () => {
expect.assertions(1);
const set = new Set(
Games.map((g) => {
return g.url;
}),
);
expect(set, "are not unique").toHaveLength(Games.length);
});
it("should have alt text", () => {
expect.assertions(1);
const noText = Games.filter((g) => {
return g.alt.length === 0;
});
expect(noText, "found missing alt").toHaveLength(0);
});
});

View File

@ -1,17 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { describe, it, expect } from "vitest";
import { Koikatsu } from "../../src/config/Koikatsu";
describe("koikatsu strings", () => {
it("should be unique", () => {
expect.assertions(1);
const set = new Set(
Koikatsu,
);
expect(set, "are not unique").toHaveLength(Koikatsu.length);
});
});

View File

@ -1,45 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { describe, it, expect } from "vitest";
import { Characters } from "../../src/config/Legacy";
describe("character objects", () => {
it("should have unique names", () => {
expect.assertions(1);
const set = new Set(
Object.values(Characters).map((c) => {
return c.name;
}),
);
expect(set, "are not unique").toHaveLength(
Object.values(Characters).length,
);
});
it("should not have empty bios", () => {
expect.assertions(1);
const noBio = Object.values(Characters).filter((c) => {
return c.bio.length === 0;
});
expect(noBio, "found missing bio").toHaveLength(0);
});
it("should not have empty combat profiles", () => {
expect.assertions(1);
const noCombat = Object.values(Characters).filter((c) => {
return c.combat.length === 0;
});
expect(noCombat, "found missing combat").toHaveLength(0);
});
it("should have alt text", () => {
expect.assertions(1);
const noText = Object.values(Characters).filter((c) => {
return c.alt.length === 0;
});
expect(noText, "found missing alt").toHaveLength(0);
});
});

View File

@ -1,24 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { describe, it, expect } from "vitest";
import { NaomiNavItems, NavItems } from "../../src/config/NavItems";
describe("nav items", () => {
it("should be unique", () => {
expect.assertions(2);
const concat = [ ...NavItems, ...NaomiNavItems ];
const href = new Set(
concat.map((n) => {
return n.href;
}),
);
const text = new Set(concat.map((n) => {
return n.text;
}));
expect(href, "links are not unique").toHaveLength(concat.length);
expect(text, "names are not unique").toHaveLength(concat.length);
});
});

View File

@ -1,36 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { describe, it, expect } from "vitest";
import { MainVRoid, VRoid } from "../../src/config/Vroid";
const concatenated = [
MainVRoid,
...VRoid.sort((a, b) => {
return a.name.localeCompare(b.name);
}),
];
describe("vroid objects", () => {
it("should have unique names", () => {
expect.assertions(1);
const set = new Set(
concatenated.map((a) => {
return a.name;
}),
);
expect(set, "are not unique").toHaveLength(concatenated.length);
});
it("should have unique file properties", () => {
expect.assertions(1);
const set = new Set(
concatenated.map((a) => {
return a.file;
}),
);
expect(set, "are not unique").toHaveLength(concatenated.length);
});
});

View File

@ -1,38 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { describe, it, expect } from "vitest";
import { Volunteer } from "../../src/icons/Volunteer";
describe("volunteer icon", () => {
it("should have a valid width", () => {
expect.assertions(1);
expect(Volunteer.icon[0], "width is negative").toBeGreaterThan(0);
});
it("should have a valid height", () => {
expect.assertions(1);
expect(Volunteer.icon[1], "height is negative").toBeGreaterThan(0);
});
it("should not have any ligatures", () => {
expect.assertions(1);
expect(Volunteer.icon[2], "ligatures are present").toStrictEqual([]);
});
it("should have a valid unicode set", () => {
expect.assertions(1);
expect(Volunteer.icon[3], "unicode set is wrong").toBe("U+E002");
});
it("should have valid SVG path data", () => {
expect.assertions(1);
expect(Volunteer.icon[4], "path data is bad").toMatch(
// eslint-disable-next-line stylistic/max-len
/(?:[lm]\s?-?(?:(?:\d+(?:\.\d+)?)|(?:\.\d+))[\s,]?-?(?:(?:\d+(?:\.\d+)?)|(?:\.\d+)))|(?:[hv]\s?-?(?:(?:\d+(?:\.\d+)?)|(?:\.\d+)))|(?:c\s?-?(?:(?:\d+(?:\.\d+)?)|(?:\.\d+))(?:[\s,]?-?(?:(?:\d+(?:\.\d+)?)|(?:\.\d+))){5})|(?:q\s?-?(?:(?:\d+(?:\.\d+)?)|(?:\.\d+))(?:[\s,]?-?(?:(?:\d+(?:\.\d+)?)|(?:\.\d+))){3}(?:\s?t?\s?-?(?:(?:\d+(?:\.\d+)?)|(?:\.\d+))[\s,]?-?(?:(?:\d+(?:\.\d+)?)|(?:\.\d+)))*)|(?:a\s?-?(?:(?:\d+(?:\.\d+)?)|(?:\.\d+))(?:[\s,]?-?(?:(?:\d+(?:\.\d+)?)|(?:\.\d+))){2}[\s,]?(?:[01][\s,]+){2}(?:[\s,]?-?(?:(?:\d+(?:\.\d+)?)|(?:\.\d+))){2})|(?:s\s?-?(?:(?:\d+(?:\.\d+)?)|(?:\.\d+))(?:[\s,]?-?(?:(?:\d+(?:\.\d+)?)|(?:\.\d+))){3})|z/gi,
);
});
});

View File

@ -1,34 +0,0 @@
{
"extends": "@nhcarrigan/typescript-config",
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"noEmit": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"allowJs": true
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules",
"vitest.config.ts",
"koikatsu.ts"
]
}

View File

@ -1,20 +0,0 @@
import { coverageConfigDefaults, defineConfig } from "vitest/config";
export default defineConfig({
test: {
coverage: {
provider: "istanbul",
reporter: ["text", "html"],
all: true,
allowExternal: true,
thresholds: {
lines: 100,
statements: 100,
functions: 95,
branches: 100
},
extension: [".ts"],
exclude: [...coverageConfigDefaults.exclude, "tailwind.config.ts", "koikatsu.ts"]
}
}
});