chore: use our configs, update dependencies (#34)

### Explanation

This gets us in line with our other project standards, and allows us to start testing!

### Issue

Closes #18

### Attestations

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

### Dependencies

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

### Style

- [x] I have run the linter and resolved any errors.
- [x] My pull request uses an appropriate title, matching the conventional commit standards.
- [x] 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

Major - My pull request introduces a breaking change.

Reviewed-on: https://codeberg.org/nhcarrigan/portfolio/pulls/34
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit is contained in:
Naomi Carrigan 2024-10-30 23:02:42 +00:00 committed by Naomi the Technomancer
parent b24f0e83c2
commit fe370dabb5
58 changed files with 5588 additions and 3166 deletions

View File

@ -1,3 +0,0 @@
{
"extends": "next/core-web-vitals"
}

28
eslint.config.mjs Normal file
View File

@ -0,0 +1,28 @@
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"
}
}
]

View File

@ -1,5 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
eslint: {
ignoreDuringBuilds: true
},
images: {
remotePatterns: [
{

View File

@ -3,29 +3,31 @@
"version": "0.1.0",
"private": true,
"scripts": {
"prebuild": "pnpm lint",
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "eslint src --max-warnings 0"
},
"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",
"next": "14.2.6",
"next": "15.0.2",
"next-plausible": "3.12.2",
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
"@types/node": "20.14.8",
"@types/react": "18.3.9",
"@types/react-dom": "18.3.0",
"eslint": "8.57.1",
"eslint-config-next": "14.2.6",
"@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",
"eslint": "9.13.0",
"postcss": "8.4.47",
"tailwindcss": "3.4.13",
"typescript": "5.6.2"
"tailwindcss": "3.4.14",
"typescript": "5.6.3"
}
}

2765
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,75 +1,94 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { JSX } from "react";
/**
* Renders the /about page.
* @returns A React Component.
*/
const About = (): JSX.Element => {
return (
<>
<main className="w-4/5 text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">About Naomi</h1>
<section>
<p className="mb-2">
Passionate technologist dedicated to building inclusive tech
communities and empowering individuals to break into the field. With
a rich background in community management, software engineering, and
developer experience, I strive to create accessible pathways for
diverse talent.
</p>
<main className="w-4/5 text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">{`About Naomi`}</h1>
<section>
<p className="mb-2">
{`Passionate technologist dedicated to building inclusive tech
communities and empowering individuals to break into the field. With
a rich background in community management, software engineering, and
developer experience, I strive to create accessible pathways for
diverse talent.`}
</p>
<p className="mb-2">My expertise spans:</p>
<ul className="w-4/5 m-auto">
<li className="mb-2 relative before:content-['🩵'] before:absolute before:left-[-1em]">
<strong>Inclusive Community Building:</strong> Cultivated
welcoming communities of 20,000 to 300K+ members across Discord,
Slack, and GitHub, with a focus on supporting underrepresented
groups in tech.
</li>
<li className="mb-2 relative before:content-['🩵'] before:absolute before:left-[-1em]">
<strong>Empowering Education:</strong> Contributed to and
maintained open-source curricula used by millions of aspiring
developers globally, focusing on accessibility and engaging
learning experiences.
</li>
<li className="mb-2 relative before:content-['🩵'] before:absolute before:left-[-1em]">
<strong>Software Engineering for Inclusivity:</strong> Developed
sophisticated bots and tools that not only streamline moderation
and boost engagement but also promote safe, inclusive spaces for
all community members.
</li>
<li className="mb-2 relative before:content-['🩵'] before:absolute before:left-[-1em]">
<strong>Developer Experience (DX):</strong> Led initiatives to
improve documentation, SDK support, and overall developer
satisfaction, with an emphasis on making resources accessible to
newcomers in the field.
</li>
<li className="mb-2">
<strong>Mentorship and Advocacy:</strong> Implemented programs to
support aspiring developers, particularly those from
underrepresented backgrounds, in their journey into tech careers.
</li>
</ul>
<p className="mb-2">
Key achievements include redesigning freeCodeCamp&apos;s Responsive
Web Design curriculum for greater accessibility, developing
AI-powered community management tools that foster inclusive
interactions, and creating engagement systems that significantly
increased participation from diverse user groups.
</p>
<p className="mb-2">{`My expertise spans:`}</p>
<ul className="w-4/5 m-auto">
<li className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]">
<strong>{`Inclusive Community Building:`}</strong>
{` Cultivated
welcoming communities of 20,000 to 300K+ members across Discord,
Slack, and GitHub, with a focus on supporting underrepresented
groups in tech.`}
</li>
<li className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]">
<strong>{`Empowering Education:`}</strong>
{` Contributed to and
maintained open-source curricula used by millions of aspiring
developers globally, focusing on accessibility and engaging
learning experiences.`}
</li>
<li className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]">
<strong>{`Software Engineering for Inclusivity:`}</strong>
{` Developed
sophisticated bots and tools that not only streamline moderation
and boost engagement but also promote safe, inclusive spaces for
all community members.`}
</li>
<li className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]">
<strong>{`Developer Experience (DX):`}</strong>
{` Led initiatives to
improve documentation, SDK support, and overall developer
satisfaction, with an emphasis on making resources accessible to
newcomers in the field.`}
</li>
<li className="mb-2">
<strong>{`Mentorship and Advocacy:`}</strong>
{` Implemented programs to
support aspiring developers, particularly those from
under-represented backgrounds, in their journey into tech careers.`}
</li>
</ul>
<p className="mb-2">
{`Key achievements include redesigning freeCodeCamp's Responsive
Web Design curriculum for greater accessibility, developing
AI-powered community management tools that foster inclusive
interactions, and creating engagement systems that significantly
increased participation from diverse user groups.`}
</p>
<p className="mb-2">
I&apos;m driven by the belief that technology should be a field open
to all. My approach combines technical expertise with a deep
commitment to diversity, equity, and inclusion, resulting in
solutions that not only drive engagement and innovation but also
break down barriers to entry in tech.
</p>
<p className="mb-2">
{`I'm driven by the belief that technology should be a field open
to all. My approach combines technical expertise with a deep
commitment to diversity, equity, and inclusion, resulting in
solutions that not only drive engagement and innovation but also
break down barriers to entry in tech.`}
</p>
<p className="mb-2">
Seeking opportunities to lead transformative projects that expand
access to tech education, foster inclusive community growth, and
empower individuals from all backgrounds to thrive in the tech
industry. Let&apos;s connect to discuss how I can best support your
organisation!
</p>
</section>
</main>
</>
<p className="mb-2">
{`Seeking opportunities to lead transformative projects that expand
access to tech education, foster inclusive community growth, and
empower individuals from all backgrounds to thrive in the tech
industry. Let's connect to discuss how I can best support your
organisation!`}
</p>
</section>
</main>
);
};

View File

@ -1,49 +1,69 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
"use client";
import { Activity } from "@/components/activity";
import { Review } from "@/components/review";
import { Rule } from "@/components/rule";
import { Testimonials } from "@/config/Testimonials";
import { useEffect, useState } from "react";
import { type JSX, useEffect, useState } from "react";
import { Activity } from "../../components/activity";
import { Rule } from "../../components/rule";
const Reviews = (): JSX.Element => {
const [activity, setActivity] = useState<
{
type: string;
date: Date;
repo: string;
/**
* Renders the /activity page.
* @returns A React Component.
*/
const ActivityComponent = (): JSX.Element => {
const [ activity, setActivity ] = useState<
Array<{
type: string;
date: Date;
repo: string;
repoName: string;
}[]
}>
>([]);
useEffect(() => {
fetch("/api/activity")
.then((data) => data.json())
.then((data) => setActivity(data));
void fetch("/api/activity").
then(async(data) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return (await data.json()) as Array<{
type: string;
date: Date;
repo: string;
repoName: string;
}>;
}).
then((data) => {
setActivity(data);
});
}, []);
return (
<>
<main className="w-[95%] text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">Recent Activity</h1>
<section>
<p className="mb-2">See what Naomi has been up to lately.</p>
<Rule />
<ol className="relative border-s border-[--primary] w-4/5 m-auto">
{activity.map((act, i) => (
<main className="w-[95%] text-center
max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">{`Recent Activity`}</h1>
<section>
<p className="mb-2">{`See what Naomi has been up to lately.`}</p>
<Rule />
<ol className="relative border-s border-[--primary] w-4/5 m-auto">
{activity.map((act, index) => {
return (
<Activity
key={act.date.toString()}
type={act.type}
date={act.date}
heart={index % 2 === 1
? "🩷"
: "🩵"}
key={act.date.toString()}
repo={act.repo}
repoName={act.repoName}
heart={i % 2 ? "🩷" : "🩵"}
type={act.type}
/>
))}
</ol>
</section>
</main>
</>
);
})}
</ol>
</section>
</main>
);
};
export default Reviews;
export default ActivityComponent;

View File

@ -1,17 +1,50 @@
import { getCodebergData } from "@/lib/codeberg";
import { getGithubData } from "@/lib/github";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { NextResponse } from "next/server";
import { getCodebergData } from "../../../lib/codeberg";
import { getGithubData } from "../../../lib/github";
export async function GET() {
/**
* GET route handler for the activity API.
* Loads recent activity from Codeberg and GitHub.
* @returns The formatted data.
*/
export async function GET(): Promise<NextResponse> {
const codeberg = await getCodebergData();
const github = await getGithubData();
const normalised: {
type: string;
date: Date;
repo: string;
const normalised: Array<{
type: string;
date: Date;
repo: string;
repoName: string;
}[] = [...codeberg.map(i => ({ type: i.op_type, date: new Date(i.created), repo: i.repo.html_url, repoName: i.repo.full_name })), ...github.map(i => ({ type: i.type, date: new Date(i.created_at), repo: i.repo.url.replace("api.github.com/repos", "github.com"), repoName: i.repo.name }))]
}> = [
...codeberg.map((index) => {
return {
date: new Date(index.created),
repo: index.repo.html_url,
repoName: index.repo.full_name,
type: index.op_type,
};
}),
...github.map((index) => {
return {
date: new Date(index.created_at),
repo: index.repo.url.replace("api.github.com/repos", "github.com"),
repoName: index.repo.name,
type: index.type,
};
}),
];
return NextResponse.json(normalised.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()).slice(0, 100))
return NextResponse.json(
normalised.
toSorted((a, b) => {
return new Date(b.date).getTime() - new Date(a.date).getTime();
}).
slice(0, 100),
);
}

View File

@ -1,23 +1,45 @@
import { ArtComponent } from "@/components/art";
import { Rule } from "@/components/rule";
import { Art } from "@/config/Art";
/**
* @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 m-auto mt-16 mb-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.sort((a, b) => a.name.localeCompare(b.name)).map((art) => (
<ArtComponent key={art.name} name={art.name} img={art.img} artist={art.artist} url={art.url} alt={art.alt}/>
))}
</div>
</section>
</main>
</>
<main
className="w-[95%] text-center
max-w-4xl m-auto mt-16 mb-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>
);
};

View File

@ -1,35 +1,45 @@
import { Certification } from "@/components/cert";
import { Rule } from "@/components/rule";
import { Certifications } from "@/config/Certifications";
import { Charm } from "next/font/google";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Certification } from "../../components/cert";
import { Rule } from "../../components/rule";
import { Certifications } from "../../config/Certifications";
import type { JSX } from "react";
/**
* Renders the /certs page.
* @returns A React Component.
*/
const Certs = (): JSX.Element => {
return (
<>
<main className="w-[95%] text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">Certs</h1>
<section>
<p className="mb-2">
This page lists the professional certifications Naomi has obtained
throughout her time as a developer.
</p>
<Rule />
<div className="grid sm:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-y-5">
{Certifications.sort(
(a, b) => b.date.getTime() - a.date.getTime(),
).map((cert) => (
<Certification
key={cert.name}
name={cert.name}
fileName={cert.fileName}
issuer={cert.issuer}
date={cert.date}
/>
))}
</div>
</section>
</main>
</>
<main className="w-[95%] text-center
max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">{`Certs`}</h1>
<section>
<p className="mb-2">
{`This page lists the professional certifications Naomi has obtained
throughout her time as a developer.`}
</p>
<Rule />
<div className="grid sm:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-y-5">
{Certifications.toSorted(
(a, b) => {
return b.date.getTime() - a.date.getTime();
},
).map((cert) => {
return <Certification
date={cert.date}
fileName={cert.fileName}
issuer={cert.issuer}
key={cert.name}
name={cert.name}
/>;
})}
</div>
</section>
</main>
);
};

View File

@ -1,58 +1,70 @@
import { Rule } from "@/components/rule";
import { Social } from "@/components/social";
import { Donate, HireMe, Socials } from "@/config/Socials";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Rule } from "../../components/rule";
import { Social } from "../../components/social";
import { Donate, HireMe, Socials } from "../../config/Socials";
import type { JSX } from "react";
/**
* Renders the /contact page.
* @returns A React Component.
*/
const Contact = (): JSX.Element => {
return (
<>
<main className="w-4/5 text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">Contact Us</h1>
<p>
We offer many different ways to get in touch with us. This page lists
all of our available social media accounts. Use the buttons below to
choose your preferred method.
</p>
<p>
Note that the items are sorted alphabetically, not by order of
response times. Monitoring levels and activity may vary by platform.
</p>
<p>
With {Socials.length} choices, you shouldn&apos;t have any complaints!
:3
</p>
<Rule />
<section>
<div>
{[HireMe, Donate].map(
({ link, label, alt, icon, color, background }) => (
<Social
key={label}
link={link}
label={label}
alt={alt}
icon={icon}
color={color}
background={background}
/>
),
)}
{Socials.sort((a, b) => a.label.localeCompare(b.label)).map(
({ link, label, alt, icon, color, background }) => (
<Social
key={label}
link={link}
label={label}
alt={alt}
icon={icon}
color={color}
background={background}
/>
),
)}
</div>
</section>
</main>
</>
<main className="w-4/5 text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">{`Contact Us`}</h1>
<p>
{`We offer many different ways to get in touch with us. This page lists
all of our available social media accounts. Use the buttons below to
choose your preferred method.`}
</p>
<p>
{`Note that the items are sorted alphabetically, not by order of
response times. Monitoring levels and activity may vary by platform.`}
</p>
<p>
{`With ${Socials.length.toLocaleString("en-GB")} choices, you shouldn't have any complaints!
:3`}
</p>
<Rule />
<section>
<div>
{[ HireMe, Donate ].map(
({ link, label, alt, icon, color, background }) => {
return <Social
alt={alt}
background={background}
color={color}
icon={icon}
key={label}
label={label}
link={link}
/>;
}
,
)}
{Socials.toSorted((a, b) => {
return a.label.localeCompare(b.label);
}).map(
({ link, label, alt, icon, color, background }) => {
return <Social
alt={alt}
background={background}
color={color}
icon={icon}
key={label}
label={label}
link={link}
/>;
}
,
)}
</div>
</section>
</main>
);
};

View File

@ -1,26 +1,44 @@
import { Certification } from "@/components/cert";
import { Game } from "@/components/game";
import { Rule } from "@/components/rule";
import { Certifications } from "@/config/Certifications";
import { Games } from "@/config/Games";
import { Charm } from "next/font/google";
/**
* @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 m-auto mt-16 mb-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.sort((a, b) => a.name.localeCompare(b.name)).map((game) => (
<Game key={game.name} name={game.name} img={game.img} alt={game.alt} url={game.url} />
))}
</div>
</section>
</main>
</>
<main
className="w-[95%] text-center
max-w-4xl m-auto mt-16 mb-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>
);
};

View File

@ -1,75 +1,91 @@
import type { Metadata } from "next";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Inter } from "next/font/google";
import "./globals.css";
import { ClientNavigation } from "@/components/navigation";
import Script from "next/script";
// eslint-disable-next-line import/default, import/no-named-as-default, import/no-named-as-default-member
import PlausibleProvider from "next-plausible";
import { ClientFooter } from "@/components/footer";
import { Footer } from "../components/footer";
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";
const inter = Inter({ subsets: ["latin"] });
// eslint-disable-next-line new-cap
const inter = Inter({ subsets: [ "latin" ] });
export const metadata: Metadata = {
title: "Naomi's Portfolio",
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",
site: "@naomi_lgbt",
card: "summary_large_image",
images: "https://cdn.nhcarrigan.com/background.png",
site: "@naomi_lgbt",
},
};
export default function RootLayout({
/**
* 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: React.ReactNode;
}>) {
children: ReactNode;
}>): JSX.Element => {
return (
<html lang="en">
<Script
strategy="beforeInteractive"
src="https://widgets.tree-nation.com/js/widgets/v1/widgets.min.js?v=1.0"
></Script>
<PlausibleProvider
domain="nhcarrigan.com"
customDomain="https://analytics.nhcarrigan.com"
domain="nhcarrigan.com"
trackFileDownloads={true}
trackOutboundLinks={true}
>
<link
rel="icon"
href="https://cdn.nhcarrigan.com/logo.png"
rel="icon"
sizes="any"
/>
<body className={inter.className}>
<header>
<ClientNavigation />
<Navigation />
</header>
{children}
<video
src="https://cdn.nhcarrigan.com/overlay.webm"
autoPlay
loop
muted
playsInline
autoPlay={true}
className="fixed top-0 left-0 w-full h-full object-cover opacity-25"
loop={true}
muted={true}
playsInline={true}
src="https://cdn.nhcarrigan.com/overlay.webm"
style={{ pointerEvents: "none" }}
/>
<footer>
<ClientFooter />
<Footer />
</footer>
</body>
<Script
type="text/javascript"
async={true}
defer={true}
id="hs-script-loader"
async
defer
src="//js.hs-scripts.com/47086586.js"
type="text/javascript"
></Script>
</PlausibleProvider>
</html>
);
}
};
export { metadata };
export default RootLayout;

View File

@ -1,87 +1,221 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { JSX } from "react";
/**
* Renders the /manifesto page.
* @returns A React Component.
*/
const Manifesto = (): JSX.Element => {
return (
<main className="w-4/5 text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-3xl">The Lion, The Witch, The Audacity of This Bitch</h1>
<p className="text-2xl italic">Naomi's Transfemme Manifesto</p>
<p>In a world of binaries and boundaries, we emerge - glorious, glistening, and unapologetically ourselves. We are the lionesses who refused to be tamed, the witches who brew potions of self-love, and the audacious beings who dared to rewrite the rules of gender. This is our manifesto, our spell-book, our battle cry.</p>
<main className="w-4/5 text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-3xl">{`The Lion, The Witch, The Audacity of This Bitch`}</h1>
<p className="text-2xl italic">{`Naomi's Transfemme Manifesto`}</p>
<p>
{`In a world of binaries and boundaries, we emerge - glorious, glistening,
and unapologetically ourselves. We are the lionesses who refused to be
tamed, the witches who brew potions of self-love, and the audacious
beings who dared to rewrite the rules of gender. This is our manifesto,
our spell-book, our battle cry.`}
</p>
<h2 className="text-2xl">I. We Are the Lions</h2>
<ol>
<li>Our manes are diverse: short, long, neon, natural - each strand a testament to our journey.</li>
<li>We prowl through life with grace, strength, and an occasional stumble (because even lionesses trip sometimes, darling).</li>
<li>Our roars echo through boardrooms, classrooms, and everywhere we have been told to stay silent.</li>
<li>We fiercely protect our pride - our chosen family, our allies, our community.</li>
<li>Like lions, we bask in the sun of our authenticity, soaking up the warmth of self-acceptance.</li>
</ol>
<h2 className="text-2xl">{`I. We Are the Lions`}</h2>
<ol>
<li>
{`Our manes are diverse: short, long, neon, natural - each strand a
testament to our journey.`}
</li>
<li>
{`We prowl through life with grace, strength, and an occasional stumble
(because even lionesses trip sometimes, darling).`}
</li>
<li>
{`Our roars echo through boardrooms, classrooms, and everywhere we have
been told to stay silent.`}
</li>
<li>
{`We fiercely protect our pride - our chosen family, our allies, our
community.`}
</li>
<li>
{`Like lions, we bask in the sun of our authenticity, soaking up the
warmth of self-acceptance.`}
</li>
</ol>
<h2 className="text-2xl">II. We Are the Witches</h2>
<ol>
<li>Our spellbook is filled with incantations of self-affirmation and hexes against transphobia.</li>
<li>We stir cauldrons of change, mixing potions of progress and elixirs of equality.</li>
<li>Our wands (be they makeup brushes, pens, or literal wands) cast spells of transformation.</li>
<li>We commune with the spirits of our trans ancestors, drawing strength from their legacy.</li>
<li>In our coven, every body is sacred, every identity valid, every expression magical.</li>
</ol>
<h2 className="text-2xl">{`II. We Are the Witches`}</h2>
<ol>
<li>
{`Our spellbook is filled with incantations of self-affirmation and
hexes against transphobia.`}
</li>
<li>
{`We stir cauldrons of change, mixing potions of progress and elixirs of
equality.`}
</li>
<li>
{`Our wands (be they makeup brushes, pens, or literal wands) cast spells
of transformation.`}
</li>
<li>
{`We commune with the spirits of our trans ancestors, drawing strength
from their legacy.`}
</li>
<li>
{`In our coven, every body is sacred, every identity valid, every
expression magical.`}
</li>
</ol>
<h2 className="text-2xl">III. We Have the Audacity</h2>
<ol>
<li>We have the audacity to exist loudly in a world that wishes us to be quiet.</li>
<li>We dare to love our bodies, curves, edges, and all, defying those who say we should not.</li>
<li>We boldly claim our place in women&apos;s spaces, sports, and narratives.</li>
<li>We redefine beauty standards with every strut, sashay, and hair flip.</li>
<li>We have the gall to demand respect, rights, and recognition - and honey, we look fabulous doing it.</li>
</ol>
<h2 className="text-2xl">{`III. We Have the Audacity`}</h2>
<ol>
<li>
{`We have the audacity to exist loudly in a world that wishes us to be
quiet.`}
</li>
<li>
{`We dare to love our bodies, curves, edges, and all, defying those who
say we should not.`}
</li>
<li>
{`We boldly claim our place in women's spaces, sports, and
narratives.`}
</li>
<li>
{`We redefine beauty standards with every strut, sashay, and hair flip.`}
</li>
<li>
{`We have the gall to demand respect, rights, and recognition - and
honey, we look fabulous doing it.`}
</li>
</ol>
<h2 className="text-2xl">IV. Our Decrees</h2>
<ol>
<li>We decree that gender is a playground, not a prison. Swing on the monkey bars of masculinity, slide down the curves of femininity, or build sandcastles of androgyny.</li>
<li>We proclaim that our bodies are our own, to modify or maintain as we see fit. Your opinion on our transitions is like our facial hair - unwanted and about to be removed.</li>
<li>We declare our right to safe spaces, medical care, and public restrooms without fear or judgment.</li>
<li>We assert our humanity in the face of legislation that tries to erase us. Our existence is not up for debate.</li>
<li>We affirm our right to joy, love, and celebration. Our lives are not just about struggle - we're here to thrive, baby!</li>
</ol>
<h2 className="text-2xl">{`IV. Our Decrees`}</h2>
<ol>
<li>
{`We decree that gender is a playground, not a prison. Swing on the
monkey bars of masculinity, slide down the curves of femininity, or
build sandcastles of androgyny.`}
</li>
<li>
{`We proclaim that our bodies are our own, to modify or maintain as we
see fit. Your opinion on our transitions is like our facial hair -
unwanted and about to be removed.`}
</li>
<li>
{`We declare our right to safe spaces, medical care, and public
restrooms without fear or judgment.`}
</li>
<li>
{`We assert our humanity in the face of legislation that tries to erase
us. Our existence is not up for debate.`}
</li>
<li>
{`We affirm our right to joy, love, and celebration. Our lives are not
just about struggle - we're here to thrive, baby!`}
</li>
</ol>
<h2 className="text-2xl">V. Our Commitments</h2>
<ol>
<li>We vow to lift as we climb, ensuring no trans sibling is left behind.</li>
<li>We pledge to celebrate the diversity within our community - every shade, shape, and expression of transness.</li>
<li>We promise to continue educating, even when it's exhausting, because knowledge is the enemy of ignorance.</li>
<li>We commit to loving ourselves fiercely, even on days when the world makes it hard.</li>
<li>We dedicate ourselves to creating art, music, literature, and memes that tell our stories and make our voices heard.</li>
</ol>
<h2 className="text-2xl">{`V. Our Commitments`}</h2>
<ol>
<li>
{`We vow to lift as we climb, ensuring no trans sibling is left behind.`}
</li>
<li>
{`We pledge to celebrate the diversity within our community - every
shade, shape, and expression of transness.`}
</li>
<li>
{`We promise to continue educating, even when it's exhausting, because
knowledge is the enemy of ignorance.`}
</li>
<li>
{`We commit to loving ourselves fiercely, even on days when the world
makes it hard.`}
</li>
<li>
{`We dedicate ourselves to creating art, music, literature, and memes
that tell our stories and make our voices heard.`}
</li>
</ol>
<h2 className="text-2xl">VI. The Transfemme Toolbox</h2>
<ol>
<li>Eyeliner sharp enough to wing and to slay our enemies.</li>
<li>A vocabulary expansive enough to articulate our identities and to eloquently tell transphobes where to go.</li>
<li>Heels high enough to reach new heights (and flats comfortable enough for when the revolution requires running).</li>
<li>A chosen name that feels like home and a dead name composted for future growth.</li>
<li>A support network of fellow lions, witches, and audacious bitches to remind us we're not alone.</li>
</ol>
<h2 className="text-2xl">{`VI. The Transfemme Toolbox`}</h2>
<ol>
<li>{`Eyeliner sharp enough to wing and to slay our enemies.`}</li>
<li>
{`A vocabulary expansive enough to articulate our identities and to
eloquently tell transphobes where to go.`}
</li>
<li>
{`Heels high enough to reach new heights (and flats comfortable enough
for when the revolution requires running).`}
</li>
<li>
{`A chosen name that feels like home and a dead name composted for
future growth.`}
</li>
<li>
{`A support network of fellow lions, witches, and audacious bitches to
remind us we're not alone.`}
</li>
</ol>
<h2 className="text-2xl">VII. Our Vision of the Future</h2>
<p>In our ideal world:</p>
<ol>
<li>Gender reveal parties are replaced by "I'll reveal my gender when I'm good and ready" parties.</li>
<li>The only time we're asked about our genitals is by our doctors or consensual partners.</li>
<li>Trans joy is as commonplace as cis assumptions once were.</li>
<li>Our stories are told by us, not about us, in media, literature, and history books.</li>
<li>The audacity of our existence is celebrated, not merely tolerated.</li>
</ol>
<h2 className="text-2xl">{`VII. Our Vision of the Future`}</h2>
<p>{`In our ideal world:`}</p>
<ol>
<li>
{`Gender reveal parties are replaced by "I'll reveal my gender when I'm
good and ready" parties.`}
</li>
<li>
{`The only time we're asked about our genitals is by our doctors or
consensual partners.`}
</li>
<li>{`Trans joy is as commonplace as cis assumptions once were.`}</li>
<li>
{`Our stories are told by us, not about us, in media, literature, and
history books.`}
</li>
<li>
{`The audacity of our existence is celebrated, not merely tolerated.`}
</li>
</ol>
<h2 className="text-2xl">The Tea is Served</h2>
<p>So here we stand, in all our lionhearted, witchy, audacious glory. We've stirred the pot, we've cast our spells, we've roared our truths. The teacup of tradition lays shattered at our feet, and darling, the brew we're serving now is a potent blend of authenticity, resistance, and fierce, uncompromising love.</p>
<h2 className="text-2xl">{`The Tea is Served`}</h2>
<p>
{`So here we stand, in all our lionhearted, witchy, audacious glory. We've
stirred the pot, we've cast our spells, we've roared our truths. The
teacup of tradition lays shattered at our feet, and darling, the brew
we're serving now is a potent blend of authenticity, resistance, and
fierce, uncompromising love.`}
</p>
<p>To those who support us: we see you, we appreciate you, we invite you to roar alongside us.
To those who don't understand us: we invite you to listen, to learn, and to expand your world.
To those who oppose us: your time is up, your reign is over, the future is gloriously, unapologetically trans.</p>
<p>
{`To those who support us: we see you, we appreciate you, we invite you to
roar alongside us. To those who don't understand us: we invite you to
listen, to learn, and to expand your world. To those who oppose us: your
time is up, your reign is over, the future is gloriously,
unapologetically trans.`}
</p>
<p>Remember, we didn't come this far, break this many barriers, and perfect our contouring skills just to be anything less than the majestic, magical, audacious bitches we are.</p>
<p>
{`Remember, we didn't come this far, break this many barriers, and perfect
our contouring skills just to be anything less than the majestic,
magical, audacious bitches we are.`}
</p>
<p className="text-2xl">The lion has spoken. The witch has cast her spell. This bitch is here to stay.</p>
<p className="text-2xl">
{`The lion has spoken. The witch has cast her spell. This bitch is here to
stay.`}
</p>
<p className="text-3xl">Long live the transfemme revolution! 🦁👑💄🏳</p>
</main>
<p className="text-3xl">
{`Long live the transfemme revolution! 🦁✨👑💄🏳️‍⚧️`}
</p>
</main>
);
};

View File

@ -1,134 +1,172 @@
import { Rule } from "@/components/rule";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import Image from "next/image";
import { Rule } from "../../components/rule";
import type { JSX } from "react";
/**
* Renders the /manual page.
* @returns A React Component.
*/
const Manual = (): JSX.Element => {
return (
<>
<main className="w-4/5 text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">Naomi&apos;s User Manual</h1>
<p className="text-3xl">Model T42-P9000</p>
<section>
<p className="mb-2">
Hiya! This page is meant to give you a run-down of what working with
me is like. It&apos;s like a li&apos;l insight into the way I best
interact with people and perform on a team.
</p>
<Image
className="m-auto"
src="https://cdn.nhcarrigan.com/hire.jpeg"
alt="If you knew I was so unstable, why'd you hire me?"
width={500}
height={500}
/>
<Rule />
</section>
<section>
<h2 className="text-3xl">My Values</h2>
<p className="mb-2">
I tend to be very firm in my values, to the degree that I shy away
from organisations or projects which do not align with my ethics.
</p>
<ul className="w-4/5 m-auto">
<li className="mb-2 relative before:content-['🩵'] before:absolute before:left-[-1em]">
First and foremost, do not pass judgement. I will not begrudge you
for your religious beliefs, political alignments, or any other
aspects of your life. And I expect the same courtesy in return.
You do not have to accept who I am, or support my choices, but we
need to maintain a respectful and cordial professional
relationship. Expressing hateful, mean-spirited, or vitriolic
comments does not foster such an environment, and my tolerance for
such is nil.
</li>
<li className="mb-2 relative before:content-['🩵'] before:absolute before:left-[-1em]">
Second, we shall do no harm. I very strongly believe that
technology is meant to advance our society, not regress it.
Software should not be used to automate people&apos;s careers
away, or replace creative works with pale imitations, or
harass/target someone, or restrict access to information.
</li>
</ul>
<Rule />
</section>
<section>
<h2 className="text-3xl">Environment</h2>
<p className="mb-2">
I have found that I best thrive and produce the greatest works in
specific environments.
</p>
<ul className="w-4/5 m-auto">
<li className="mb-2 relative before:content-['🩵'] before:absolute before:left-[-1em]">
Ideas should be freely discussed, challenged, and evaluated. I am
not a &quot;yes girl&quot; to blindly follow orders. If I hear a
plan that I think misses the mark, I will absolutely voice my
concerns. And I welcome the same scrutiny in return. Discussion
needs to be constructive and fruitful - if we&apos;re going around
in circles, it&apos;s time to table it and sleep on our thoughts.
</li>
<li className="mb-2 relative before:content-['🩵'] before:absolute before:left-[-1em]">
We are not our ideas, however. &quot;This is bad and you should
feel bad&quot; helps no one. &quot;I&apos;m not sure that is the
best approach, can we consider doing this instead?&quot; keeps the
conversation focused on the right things and moving forward.
</li>
<li className="mb-2 relative before:content-['🩵'] before:absolute before:left-[-1em]">
ADHD means my productivity is highly fluctuating. There are days
where I can sit and bang out a week of work in 16 hours, and there
are days where I stare mindlessly at the screen and then realise
the sun has set already. Flexibility in both schedule and
deadlines is very important to me.
</li>
<li className="mb-2 relative before:content-['🩵'] before:absolute before:left-[-1em]">
Likewise, insomnia can rear its ugly head and absolutely wreck my
sleep schedule without any warning. Asynchronous comms are always
preferred over meetings, but if a meeting must happen the
afternoon hours are the best. Too early and I&apos;ll likely fail
to wake up. Too late and I might be incoherent.
</li>
<li className="mb-2 relative before:content-['🩵'] before:absolute before:left-[-1em]">
If given something like a Trello or a Monday board, I will 100%
make it pretty and load every single task ever on there. I&apos;m
a sucker for organising workloads and the easiest way for me to be
transparent about the work I&apos;m doing is to just let you look
at a task board. If you say &quot;what did you do yesterday&quot;
I will not remember this for I have slept since then.
</li>
</ul>
<Rule />
</section>
<section>
<h2 className="text-3xl">Communication</h2>
<p>
As with many people, there are certain approaches to communication
that are more effective for me.
</p>
<ul className="w-4/5 m-auto">
<li className="mb-2 relative before:content-['🩵'] before:absolute before:left-[-1em]">
Don&apos;t just say &quot;hello&quot; in my DMs or ping me without
context. You&apos;ll pull me out of a workflow to wait for the
rest of the message. Send it all at once, or save the ping for the
last part.
</li>
<li className="mb-2 relative before:content-['🩵'] before:absolute before:left-[-1em]">
I have generalised anxiety disorder. Please for the love of all
things, do NOT say &quot;we need to talk&quot; or &quot;do you
have time to meet&quot;. I will 100% sit there right up until the
meeting starts stressing about getting fired and not actually
getting any work done. If you need to call me out, just rip the
bandage off and come out of the gate with it.
</li>
<li className="mb-2 relative before:content-['🩵'] before:absolute before:left-[-1em]">
I dunno if you noticed the tone changed in this document about a
million times. That&apos;s pretty much Naomi in a nutshell.
I&apos;ll go from collected and eloquent to manic and sending
messages faster than Discord can process. I try my hardest to stay
professional, but ADHD makes that very hard. I&apos;ll swear
sometimes, I&apos;ll say totally off-the-wall things. But I really
do try!
</li>
</ul>
</section>
</main>
</>
<main className="w-4/5 text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">{`Naomi's User Manual`}</h1>
<p className="text-3xl">{`Model T42-P9000`}</p>
<section>
<p className="mb-2">
{`Hiya! This page is meant to give you a run-down of what working with
me is like. It's like a li'l insight into the way I best
interact with people and perform on a team.`}
</p>
<Image
alt="If you knew I was so unstable, why'd you hire me?"
className="m-auto"
height={500}
src="https://cdn.nhcarrigan.com/hire.jpeg"
width={500}
/>
<Rule />
</section>
<section>
<h2 className="text-3xl">{`My Values`}</h2>
<p className="mb-2">
{`I tend to be very firm in my values, to the degree that I shy away
from organisations or projects which do not align with my ethics.`}
</p>
<ul className="w-4/5 m-auto">
<li
className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]"
>
{`First and foremost, do not pass judgement. I will not begrudge you
for your religious beliefs, political alignments, or any other
aspects of your life. And I expect the same courtesy in return.
You do not have to accept who I am, or support my choices, but we
need to maintain a respectful and cordial professional
relationship. Expressing hateful, mean-spirited, or vitriolic
comments does not foster such an environment, and my tolerance for
such is nil.`}
</li>
<li
className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]"
>
{`Second, we shall do no harm. I very strongly believe that
technology is meant to advance our society, not regress it.
Software should not be used to automate people's careers
away, or replace creative works with pale imitations, or
harass/target someone, or restrict access to information.`}
</li>
</ul>
<Rule />
</section>
<section>
<h2 className="text-3xl">{`Environment`}</h2>
<p className="mb-2">
{`I have found that I best thrive and produce the greatest works in
specific environments.`}
</p>
<ul className="w-4/5 m-auto">
<li
className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]"
>
{`Ideas should be freely discussed, challenged, and evaluated. I am
not a "yes girl" to blindly follow orders. If I hear a
plan that I think misses the mark, I will absolutely voice my
concerns. And I welcome the same scrutiny in return. Discussion
needs to be constructive and fruitful - if we're going around
in circles, it's time to table it and sleep on our thoughts.`}
</li>
<li
className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]"
>
{`We are not our ideas, however. "This is bad and you should
feel bad" helps no one. "I'm not sure that is the
best approach, can we consider doing this instead?" keeps the
conversation focused on the right things and moving forward.`}
</li>
<li
className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]"
>
{`ADHD means my productivity is highly fluctuating. There are days
where I can sit and bang out a week of work in 16 hours, and there
are days where I stare mindlessly at the screen and then realise
the sun has set already. Flexibility in both schedule and
deadlines is very important to me.`}
</li>
<li
className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]"
>
{`Likewise, insomnia can rear its ugly head and absolutely wreck my
sleep schedule without any warning. Asynchronous comms are always
preferred over meetings, but if a meeting must happen the
afternoon hours are the best. Too early and I'll likely fail
to wake up. Too late and I might be incoherent.`}
</li>
<li
className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]"
>
{`If given something like a Trello or a Monday board, I will 100%
make it pretty and load every single task ever on there. I'm
a sucker for organising workloads and the easiest way for me to be
transparent about the work I'm doing is to just let you look
at a task board. If you say "what did you do yesterday"
I will not remember this for I have slept since then.`}
</li>
</ul>
<Rule />
</section>
<section>
<h2 className="text-3xl">{`Communication`}</h2>
<p>
{`As with many people, there are certain approaches to communication
that are more effective for me.`}
</p>
<ul className="w-4/5 m-auto">
<li
className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]"
>
{`Don't just say "hello" in my DMs or ping me without
context. You'll pull me out of a workflow to wait for the
rest of the message. Send it all at once, or save the ping for the
last part.`}
</li>
<li
className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]"
>
{`I have generalised anxiety disorder. Please for the love of all
things, do NOT say "we need to talk" or "do you
have time to meet". I will 100% sit there right up until the
meeting starts stressing about getting fired and not actually
getting any work done. If you need to call me out, just rip the
bandage off and come out of the gate with it.`}
</li>
<li
className="mb-2 relative before:content-['🩵']
before:absolute before:left-[-1em]"
>
{`I dunno if you noticed the tone changed in this document about a
million times. That's pretty much Naomi in a nutshell.
I'll go from collected and eloquent to manic and sending
messages faster than Discord can process. I try my hardest to stay
professional, but ADHD makes that very hard. I'll swear
sometimes, I'll say totally off-the-wall things. But I really
do try!`}
</li>
</ul>
</section>
</main>
);
};

View File

@ -1,55 +1,68 @@
"use client";
import { NavItems } from "@/config/NavItems";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import Image from "next/image";
import React, { useEffect, useState } from "react";
import React, { type JSX } from "react";
import { NavItems } from "../config/NavItems";
export default function Home() {
const [isDarkMode, setIsDarkMode] = useState(false);
useEffect(() => {
const savedTheme = localStorage.getItem("theme");
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)",
).matches;
const isDark = savedTheme === "dark" || (!savedTheme && prefersDark);
document.documentElement.classList.toggle("dark", isDark);
setIsDarkMode(isDark);
}, []);
/**
* Renders the main React component.
* @returns A JSX element.
*/
const Home = (): JSX.Element => {
return (
<div>
<div className="absolute top-0 right-[33vw] z-100 w-[100vh] h-[100vh] rotate-90 hidden lg:block">
<svg viewBox="0 0 500 500" className="absolute">
<div
className="absolute top-0 right-[33vw] z-100
w-[100vh] h-[100vh] rotate-90 hidden lg:block"
>
<svg className="absolute" viewBox="0 0 500 500">
<path
d="M0,100 C150,200 350,0 500,100 L500,00 L0,0 Z"
style={{ stroke: "none", fill: "var(--foreground)" }}
style={{ fill: "var(--foreground)", stroke: "none" }}
></path>
</svg>
</div>
<main className="grid grid-cols-[2fr,1fr] min-h-screen">
<section className="bg-[--background] text-[--foreground] flex flex-col items-left justify-center text-left pl-5">
<h1 className="text-4xl mb-2">Naomi Carrigan</h1>
<section
className="bg-[--background] text-[--foreground]
flex flex-col items-left justify-center text-left pl-5"
>
<h1 className="text-4xl mb-2">{`Naomi Carrigan`}</h1>
<Image
src="https://cdn.nhcarrigan.com/profile.png"
alt="Naomi Carrigan"
width={200}
height={200}
className="rounded-full"
height={200}
src="https://cdn.nhcarrigan.com/profile.png"
width={200}
/>
<p className="text-3xl">Software Engineer</p>
<p className="text-3xl">Community Manager</p>
<p className="text-3xl">{`Software Engineer`}</p>
<p className="text-3xl">{`Community Manager`}</p>
</section>
<section className="bg-[--foreground] text-[--background] flex flex-col items-center justify-center">
{NavItems.map((item, index) => (
<a
key={item.href}
href={item.href}
className="block py-2 px-4 text-2xl hover:bg-[--background] hover:text-[--foreground]"
>
{index % 2 ? "🩷" : "🩵"} {item.text}
</a>
))}
<section
className="bg-[--foreground] text-[--background]
flex flex-col items-center justify-center"
>
{NavItems.map((item, index) => {
return (
<a
className="block py-2 px-4 text-2xl
hover:bg-[--background] hover:text-[--foreground]"
href={item.href}
key={item.href}
>
{index % 2 === 1
? "🩷"
: "🩵"} {item.text}
</a>
);
})}
</section>
</main>
</div>
);
}
};
export default Home;

View File

@ -1,32 +1,41 @@
import { Partner } from "@/components/partner";
import { Rule } from "@/components/rule";
import { Partners } from "@/config/Partners";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Partner } from "../../components/partner";
import { Rule } from "../../components/rule";
import { Partners } from "../../config/Partners";
import type { JSX } from "react";
/**
* Renders the /polycule page.
* @returns A React Component.
*/
const Polycule = (): JSX.Element => {
return (
<>
<main className="w-[95%] text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">Naomi&apos;s Polycule</h1>
<section>
<p className="mb-2">
Meet the people who love and support Naomi to the ends of the earth.
</p>
<Rule />
<div className="w-full">
{Partners.map((member) => (
<Partner
key={member.name}
name={member.name}
url={member.url}
avatar={member.avatar}
anniversary={member.anniversary}
relationship={member.relationship}
/>
))}
</div>
</section>
</main>
</>
<main className="w-[95%] text-center
max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">{`Naomi's Polycule`}</h1>
<section>
<p className="mb-2">
{`Meet the people who love and support Naomi to the ends of the earth.`}
</p>
<Rule />
<div className="w-full">
{Partners.map((member) => {
return <Partner
anniversary={member.anniversary}
avatar={member.avatar}
key={member.name}
name={member.name}
relationship={member.relationship}
url={member.url}
/>;
})}
</div>
</section>
</main>
);
};

View File

@ -1,39 +1,47 @@
import { Certification } from "@/components/cert";
import { Review } from "@/components/review";
import { Rule } from "@/components/rule";
import { Certifications } from "@/config/Certifications";
import { Testimonials } from "@/config/Testimonials";
import { Charm } from "next/font/google";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Review } from "../../components/review";
import { Rule } from "../../components/rule";
import { Testimonials } from "../../config/Testimonials";
import type { JSX } from "react";
/**
* Renders the /reviews page.
* @returns A React Component.
*/
const Reviews = (): JSX.Element => {
return (
<>
<main className="w-[95%] text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">Client Reviews</h1>
<section>
<p className="mb-2">
We think we&apos;re pretty great to work with, but don&apos;t take
our word for it. Here&apos;s what our clients have to say.
</p>
<Rule />
<ol className="relative border-s border-[--primary] w-4/5 m-auto">
{Testimonials.sort(
(a, b) => b.date.getTime() - a.date.getTime(),
).map((review) => (
<Review
key={review.date.toISOString()}
name={review.name}
date={review.date}
content={review.content}
sourceIcon={review.sourceIcon}
sourceUrl={review.sourceUrl}
sourceName={review.sourceName}
/>
))}
</ol>
</section>
</main>
</>
<main className="w-[95%] text-center max-w-4xl
m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">{`Client Reviews`}</h1>
<section>
<p className="mb-2">
{`We think we're pretty great to work with, but don't take
our word for it. Here's what our clients have to say.`}
</p>
<Rule />
<ol className="relative border-s border-[--primary] w-4/5 m-auto">
{Testimonials.toSorted(
(a, b) => {
return b.date.getTime() - a.date.getTime();
},
).map((review) => {
return <Review
content={review.content}
date={review.date}
key={review.date.toISOString()}
name={review.name}
sourceIcon={review.sourceIcon}
sourceName={review.sourceName}
sourceUrl={review.sourceUrl}
/>;
})}
</ol>
</section>
</main>
);
};

View File

@ -1,37 +1,41 @@
import { Certification } from "@/components/cert";
import { Game } from "@/components/game";
import { Member } from "@/components/member";
import { Rule } from "@/components/rule";
import { Certifications } from "@/config/Certifications";
import { Games } from "@/config/Games";
import { TeamMembers } from "@/config/TeamMembers";
import { Charm } from "next/font/google";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Member } from "../../components/member";
import { Rule } from "../../components/rule";
import { TeamMembers } from "../../config/TeamMembers";
import type { JSX } from "react";
/**
* Renders the /team page.
* @returns A React Component.
*/
const Team = (): JSX.Element => {
return (
<>
<main className="w-[95%] text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">Our Team</h1>
<section>
<p className="mb-2">
Meet the people behind nhcarrigan&apos;s success!
</p>
<Rule />
<div className="w-full">
{TeamMembers.map((member) => (
<Member
key={member.name}
name={member.name}
url={member.url}
avatar={member.avatar}
joinDate={member.joinDate}
role={member.role}
/>
))}
</div>
</section>
</main>
</>
<main className="w-[95%] text-center
max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">{`Our Team`}</h1>
<section>
<p className="mb-2">
{`Meet the people behind nhcarrigan's success!`}
</p>
<Rule />
<div className="w-full">
{TeamMembers.map((member) => {
return <Member
avatar={member.avatar}
joinDate={member.joinDate}
key={member.name}
name={member.name}
role={member.role}
url={member.url}
/>;
})}
</div>
</section>
</main>
);
};

View File

@ -1,92 +1,103 @@
import { Job } from "@/components/job";
import { Rule } from "@/components/rule";
import { Social } from "@/components/social";
import { Jobs } from "@/config/Jobs";
import { Donate, HireMe, Socials } from "@/config/Socials";
import { Volunteer } from "@/icons/Volunteer";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { faCalendar, faTasks } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Job } from "../../components/job";
import { Rule } from "../../components/rule";
import { Jobs } from "../../config/Jobs";
import { Volunteer } from "../../icons/Volunteer";
import type { JSX } from "react";
/**
* Renders the /work page.
* @returns A React Component.
*/
const Work = (): JSX.Element => {
return (
<>
<main className="w-4/5 text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">Our Work</h1>
<main className="w-4/5 text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">{`Our Work`}</h1>
<p>
{`We run a software engineering and community management firm known as
nhcarrigan.`}
</p>
<Rule />
<section>
<h2 className="text-3xl">{`Legend`}</h2>
<p>
We run a software engineering and community management firm known as
nhcarrigan.
{`Our work is listed here in reverse chronological order. The symbols
and colours have a specific meaning:`}
</p>
<table className="m-auto w-1/2">
<thead>
<tr>
<th>{`Symbol`}</th>
<th>{`Meaning`}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<FontAwesomeIcon className="h-14" icon={faCalendar} />
</td>
<td>{`Fixed-Rate Contract (hourly/salary)`}</td>
</tr>
<tr>
<td>
<FontAwesomeIcon className="h-14" icon={faTasks} />
</td>
<td>{`Project-based Contract`}</td>
</tr>
<tr>
<td>
<FontAwesomeIcon className="h-14" icon={Volunteer} />
</td>
<td>{`Planned`}</td>
</tr>
<tr>
<td>
<p className="h-14 w-14 border-[--current]
border-double border-4"></p>
</td>
<td className="text-[--current]">{`Current Contract`}</td>
</tr>
<tr>
<td>
<p className="h-14 w-14 border-[--former]
border-dashed border-2"></p>
</td>
<td className="text-[--former]">{`Former Contract`}</td>
</tr>
</tbody>
</table>
<Rule />
<section>
<h2 className="text-3xl">Legend</h2>
<p>
Our work is listed here in reverse chronological order. The symbols
and colours have a specific meaning:
</p>
<table className="m-auto w-1/2">
<thead>
<tr>
<th>Symbol</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<FontAwesomeIcon className="h-14" icon={faCalendar} />
</td>
<td>Fixed-Rate Contract (hourly/salary)</td>
</tr>
<tr>
<td>
<FontAwesomeIcon className="h-14" icon={faTasks} />
</td>
<td>Project-based Contract</td>
</tr>
<tr>
<td>
<FontAwesomeIcon className="h-14" icon={Volunteer} />
</td>
<td>Planned</td>
</tr>
<tr>
<td>
<p className="h-14 w-14 border-[--current] border-double border-4"></p>
</td>
<td className="text-[--current]">Current Contract</td>
</tr>
<tr>
<td>
<p className="h-14 w-14 border-[--former] border-dashed border-2"></p>
</td>
<td className="text-[--former]">Former Contract</td>
</tr>
</tbody>
</table>
<Rule />
</section>
<section>
<h2 className="text-3xl">Timeline</h2>
<ol className="relative border-s border-[--primary] w-4/5 m-auto">
{Jobs.sort((a, b) => b.start.getTime() - a.start.getTime()).map(
(job) => (
<Job
key={job.title + " - " + job.company}
title={job.title}
company={job.company}
start={job.start}
end={job.end}
link={job.link}
type={job.type}
description={job.description}
logo={job.logo}
/>
),
)}
</ol>
</section>
</main>
</>
</section>
<section>
<h2 className="text-3xl">{`Timeline`}</h2>
<ol className="relative border-s border-[--primary] w-4/5 m-auto">
{Jobs.toSorted((a, b) => {
return b.start.getTime() - a.start.getTime();
}).map(
(job) => {
return <Job
company={job.company}
description={job.description}
end={job.end}
key={`${job.title} - ${job.company}`}
link={job.link}
logo={job.logo}
start={job.start}
title={job.title}
type={job.type}
/>;
}
,
)}
</ol>
</section>
</main>
);
};

View File

@ -1,50 +1,75 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Rule } from "./rule";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { JSX } from "react";
interface ActivityProps {
type: string;
date: Date;
repo: string;
repoName: string;
heart: string;
interface ActivityProperties {
readonly type: string;
readonly date: Date;
readonly repo: string;
readonly repoName: string;
readonly heart: string;
}
const TypeToString: Record<string, string> = {
commit_repo: "committed to",
delete_branch: "deleted a branch on",
merge_pull_request: "merged a PR in",
create_pull_request: "created a PR in",
create_branch: "created a branch in",
PushEvent: "committed to",
DeleteEvent: "deleted a branch on",
PullRequestEvent: "created or merged a PR in",
PullRequestReviewEvent: "reviewed a PR in",
DeleteEvent: "deleted a branch on",
IssueCommentEvent: "commented on",
IssuesEvent: "created or updated an issue in",
PullRequestEvent: "created or merged a PR in",
PullRequestReviewCommentEvent: "commented on a PR in",
IssueCommentEvent: "commented on",
IssuesEvent: "created or updated an issue in",
close_issue: "closed an issue in",
create_issue: "created an issue in",
PullRequestReviewEvent: "reviewed a PR in",
PushEvent: "committed to",
close_issue: "closed an issue in",
commit_repo: "committed to",
create_branch: "created a branch in",
create_issue: "created an issue in",
create_pull_request: "created a PR in",
delete_branch: "deleted a branch on",
merge_pull_request: "merged a PR in",
};
export const Activity = (props: ActivityProps): JSX.Element => {
const { type, date, repo, repoName, heart } = props;
const getType = (type: string): string => {
return TypeToString[type] ?? `performed a ${type}`;
};
/**
* Renders the view for a Github or Codeberg activity.
* @param properties - The activity to render.
* @returns A JSX element.
*/
export const Activity = (properties: ActivityProperties): JSX.Element => {
const { type, date, repo, repoName, heart } = properties;
return (
<li className="ms-6">
<span className="absolute flex items-center justify-center w-6 h-6 rounded-full -start-3">
<span
className="absolute flex items-center
justify-center w-6 h-6 rounded-full -start-3"
>
{heart}
</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">
<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.toLocaleString("en-GB")}
</time>
<div className="text-sm font-normal text-gray-500 lex dark:text-gray-300">
Naomi has {TypeToString[type] ?? "performed a " + type}{" "}
<div
className="text-sm font-normal
text-gray-500 lex dark:text-gray-300"
>
{`Naomi has ${getType(type)} `}
<a
href={repo}
target="_blank"
rel="noopener noreferrer"
className="font-semibold text-[#abfcec] hover:underline"
href={repo}
rel="noopener noreferrer"
target="_blank"
>
{repoName}
</a>{" "}

View File

@ -1,30 +1,47 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import Image from "next/image";
import type { JSX } from "react";
interface ArtProps {
name: string;
img: string;
artist: string;
url: string;
alt: string;
interface ArtProperties {
readonly name: string;
readonly img: string;
readonly artist: string;
readonly url: string;
readonly alt: string;
}
export const ArtComponent = (props: ArtProps): JSX.Element => {
const { name, img, artist, url, alt } = props;
/**
* 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} target="_blank" rel="noreferrer">{artist}</a></p>
<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}`}
target="_blank"
rel="noreferrer"
target="_blank"
>
<Image
src={`https://cdn.nhcarrigan.com/art/${img}`}
alt={alt}
width={250}
height={250}
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,44 +1,48 @@
import { Volunteer } from "@/icons/Volunteer";
import {
faCalendar,
faQuestionCircle,
faTasks,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import Image from "next/image";
import { useState } from "react";
import type { JSX } from "react";
interface CertProps {
name: string;
fileName: string;
issuer: string;
date: Date;
interface CertProperties {
readonly name: string;
readonly fileName: string;
readonly issuer: string;
readonly date: Date;
}
export const Certification = (props: CertProps): JSX.Element => {
const { name, fileName, issuer, date } = props;
/**
* 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">
<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}`}
target="_blank"
rel="noreferrer"
target="_blank"
>
<Image
src={`https://cdn.nhcarrigan.com/certifications/${fileName}`}
alt={name}
width={250}
height={250}
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",
year: "numeric",
})}
</p>
</div>

View File

@ -1,31 +1,46 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
"use client";
import React from "react";
import { faComments } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faComments,
} from "@fortawesome/free-solid-svg-icons";
import { usePathname } from "next/navigation";
import Script from "next/script";
import React, { type JSX } from "react";
const Footer = (): JSX.Element => {
return (
<>
<div className="fixed w-full bottom-0 z-50 flex justify-between items-center h-14 px-4 bg-[--background] text-[--foreground]">
<p>&copy; Naomi Carrigan</p>
<a href="https://chat.nhcarrigan.com" target="_blank" rel="noreferrer">
<FontAwesomeIcon icon={faComments} size="lg" />
</a>
<div className="h-4/5" id="tree-nation-offset-website"></div>
<Script id="tree-nation">{`TreeNationOffsetWebsite({code: 'a17464e0cd351220', lang: 'en', theme: 'dark'}).render('#tree-nation-offset-website');`}</Script>
</div>
</>
);
};
export function ClientFooter() {
/**
* Conditionally renders the footer component when
* not on the home page.
* @returns A JSX element.
*/
export const Footer = (): JSX.Element | null => {
const pathname = usePathname();
const isRootPath = pathname === "/";
if (isRootPath) return null;
return <Footer />;
}
if (isRootPath) {
return null;
}
return (
<div
className="fixed w-full bottom-0 z-50 flex
justify-between items-center h-14 px-4
bg-[--background] text-[--foreground]"
>
<p>{`© Naomi Carrigan`}</p>
<a href="https://chat.nhcarrigan.com" rel="noreferrer" target="_blank">
<FontAwesomeIcon icon={faComments} size="lg" />
</a>
<div className="h-4/5" id="tree-nation-offset-website"></div>
<Script
src="https://widgets.tree-nation.com/js/widgets/v1/widgets.min.js?v=1.0"
strategy="beforeInteractive"
></Script>
<Script
id="tree-nation"
strategy="afterInteractive"
>{`TreeNationOffsetWebsite({code: 'a17464e0cd351220', lang: 'en', theme: 'dark'}).render('#tree-nation-offset-website');`}</Script>
</div>
);
};

View File

@ -1,36 +1,48 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import Image from "next/image";
import type { JSX } from "react";
interface GameProps {
name: string;
img: string;
url: string;
alt: string;
interface GameProperties {
readonly name: string;
readonly img: string;
readonly url: string;
readonly alt: string;
}
export const Game = (props: GameProps): JSX.Element => {
const { name, img, url, alt } = props;
/**
* 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">
<div className="w-[300px] h-[300px] border-2
border-solid border-[--foreground] m-auto text-center items-center">
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="text-xl underline"
href={url}
rel="noopener noreferrer"
target="_blank"
>
<p>{name}</p>
</a>
<a
href={`https://cdn.nhcarrigan.com/games/${img}`}
target="_blank"
rel="noreferrer"
target="_blank"
>
<Image
src={`https://cdn.nhcarrigan.com/games/${img}`}
alt={alt}
width={250}
height={250}
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,85 +1,111 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
"use client";
import { Volunteer } from "@/icons/Volunteer";
import {
faCalendar,
faQuestionCircle,
faTasks,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useState } from "react";
import { type JSX, useCallback, useState } from "react";
import { Volunteer } from "../icons/Volunteer";
interface JobProps {
title: string;
company: string;
start: Date;
end: Date | null;
link: string;
type: "volunteer" | "fixed" | "project" | "hypothetical";
description: string;
logo: string;
interface JobProperties {
readonly title: string;
readonly company: string;
readonly start: Date;
readonly end: Date | null;
readonly link: string;
readonly type: "volunteer" | "fixed" | "project" | "hypothetical";
readonly description: string;
readonly logo: string;
}
const IconMap = {
volunteer: Volunteer,
fixed: faCalendar,
project: faTasks,
fixed: faCalendar,
hypothetical: faQuestionCircle,
project: faTasks,
volunteer: Volunteer,
};
export const Job = (props: JobProps): JSX.Element => {
const { title, company, start, end, link, type, description, logo } = props;
const [showDescription, setShowDescription] = useState(false);
const toggleDescription = () => {
setShowDescription(!showDescription);
};
const color =
type === "hypothetical"
? "text-[--primary]"
: end
? "text-[--former]"
: "text-[--current]";
const border =
type === "hypothetical"
? "border-[--primary]"
: end
? "border-[--former]"
: "border-[--current]";
const getColor
= (type: JobProperties["type"], end: JobProperties["end"]): string => {
if (type === "hypothetical") {
return "text-[--primary]";
}
if (end) {
return "text-[--former]";
}
return "text-[--current]";
};
const borderStyle = end ? "border-dashed border-2" : "border-double border-4";
const getBorder
= (type: JobProperties["type"], end: JobProperties["end"]): string => {
if (type === "hypothetical") {
return "border-[--primary]";
}
if (end) {
return "border-[--former]";
}
return "border-[--current]";
};
/**
* Renders the view for a job.
* @param properties - The job to render.
* @returns A JSX element.
*/
export const Job = (properties: JobProperties): JSX.Element => {
const { title, company, start, end, type, description } = properties;
const [ showDescription, setShowDescription ] = useState(false);
const toggleDescription = useCallback((): void => {
setShowDescription(!showDescription);
}, [ showDescription ]);
const color = getColor(type, end);
const border = getBorder(type, end);
const borderStyle = end
? "border-dashed border-2"
: "border-double border-4";
return (
<li className={`mb-10 ms-6 ${color}`}>
<span
className={`absolute flex items-center justify-center w-6 h-6 bg-[--background] rounded-full -start-3 ${color}`}
>
<FontAwesomeIcon icon={IconMap[type]} className="text-3xl" />
<FontAwesomeIcon className="text-3xl" icon={IconMap[type]} />
</span>
<h3 className={`flex items-center mb-1 text-lg font-semibold`}>
{title} for {company}
{`${title} for ${company}`}
</h3>
<time className="block mb-2 text-sm font-normal leading-none">
{start.toLocaleDateString("en-US", {
month: "long",
year: "numeric",
})}{" "}
-{" "}
year: "numeric",
})}{" - "}
{end
? end.toLocaleDateString("en-US", {
month: "long",
year: "numeric",
})
month: "long",
year: "numeric",
})
: "Present"}
</time>
{showDescription && (
<div
{showDescription
? <div
className="mb-4 text-base font-normal"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: description }}
></div>
)}
: null}
<button
onClick={toggleDescription}
className={`inline-flex items-center px-4 py-2 text-sm font-medium ${borderStyle} ${border}`}
onClick={toggleDescription}
type="button"
>
{showDescription ? "Hide Details" : "Show Details"}
{showDescription
? "Hide Details"
: "Show Details"}
</button>
</li>
);

View File

@ -1,27 +1,41 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import Image from "next/image";
import { useState } from "react";
import type { JSX } from "react";
interface MemberProps {
name: string;
avatar: string;
role: string;
url: string;
joinDate: Date;
interface MemberProperties {
readonly name: string;
readonly avatar: string;
readonly role: string;
readonly url: string;
readonly joinDate: Date;
}
export const Member = (props: MemberProps): JSX.Element => {
const { name, avatar, role, url, joinDate } = props;
/**
* Renders the view for a team member.
* @param properties - The member to render.
* @returns A JSX element.
*/
export const Member = (properties: MemberProperties): JSX.Element => {
const { name, avatar, role, url, joinDate } = properties;
return (
<div className="flex items-start gap-2.5 pb-10 w-full">
<Image
className="rounded-full"
src={`https://cdn.nhcarrigan.com/avatars/${avatar}`}
alt={`${name}'s avatar.`}
width={75}
className="rounded-full"
height={75}
src={`https://cdn.nhcarrigan.com/avatars/${avatar}`}
width={75}
/>
<div className="flex flex-col w-full leading-1.5 p-4 border-gray-200 bg-gray-100 rounded-e-xl rounded-es-xl dark:bg-gray-700">
<div
className="flex flex-col w-full leading-1.5
p-4 border-gray-200 bg-gray-100 rounded-e-xl
rounded-es-xl dark:bg-gray-700"
>
<div>
<a
className="text-sm font-semibold text-[#abfcec]"
@ -33,7 +47,7 @@ export const Member = (props: MemberProps): JSX.Element => {
<p className="text-sm font-normal text-gray-500 dark:text-gray-400">
{joinDate.toLocaleDateString("en-GB", {
month: "long",
year: "numeric",
year: "numeric",
})}
</p>
</div>

View File

@ -1,84 +1,108 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
"use client";
import React, { useState, useEffect } from "react";
import Image from "next/image";
import { Rule } from "./rule";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faBars,
faTimes,
faSun,
faMoon,
} from "@fortawesome/free-solid-svg-icons";
import { NavItems } from "@/config/NavItems";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Image from "next/image";
import { usePathname } from "next/navigation";
import React, { type JSX, useState, useEffect, useCallback } from "react";
import { NavItems } from "../config/NavItems";
import { Rule } from "./rule";
const Navigation = (): JSX.Element => {
const [isOpen, setIsOpen] = useState(false);
const [isDarkMode, setIsDarkMode] = useState(false);
/**
* 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 [ isDarkMode, setIsDarkMode ] = useState(false);
useEffect(() => {
const savedTheme = localStorage.getItem("theme");
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)",
).matches;
const isDark = savedTheme === "dark" || (!savedTheme && prefersDark);
document.documentElement.classList.toggle("dark", isDark);
setIsDarkMode(isDark);
const storedTheme = localStorage.getItem("theme");
if (storedTheme === "dark") {
document.documentElement.classList.add("dark");
setIsDarkMode(true);
} else {
document.documentElement.classList.remove("dark");
setIsDarkMode(false);
}
}, []);
const toggleMenu = () => {
const toggleMenu = useCallback((): void => {
setIsOpen(!isOpen);
};
}, [ isOpen ]);
const toggleDarkMode = () => {
const toggleDarkMode = useCallback((): void => {
document.documentElement.classList.toggle("dark", !isDarkMode);
localStorage.setItem("theme", !isDarkMode ? "dark" : "light");
localStorage.setItem("theme", isDarkMode
? "light"
: "dark");
setIsDarkMode(!isDarkMode);
};
}, [ isDarkMode ]);
const pathname = usePathname();
const isRootPath = pathname === "/";
if (isRootPath) {
return null;
}
return (
<div className="fixed w-full top-0 z-50">
<nav className="w-full flex justify-between items-center h-14 px-4 bg-[--background] text-[--foreground]">
<nav
className="w-full flex justify-between
items-center h-14 px-4 bg-[--background] text-[--foreground]"
>
<a href="/">
<Image
src="https://cdn.nhcarrigan.com/logo.png"
alt="nhcarrigan's logo"
width={50}
height={50}
src="https://cdn.nhcarrigan.com/logo.png"
width={50}
/>
</a>
<div className="flex items-center">
<button onClick={toggleDarkMode} className="mr-4">
<FontAwesomeIcon icon={isDarkMode ? faSun : faMoon} size="lg" />
<button className="mr-4" onClick={toggleDarkMode} type="button">
<FontAwesomeIcon icon={isDarkMode
? faSun
: faMoon} size="lg" />
</button>
<button onClick={toggleMenu}>
<FontAwesomeIcon icon={isOpen ? faTimes : faBars} size="2x" />
<button onClick={toggleMenu} type="button">
<FontAwesomeIcon icon={isOpen
? faTimes
: faBars} size="2x" />
</button>
</div>
</nav>
{isOpen && (
<div className="bg-[--background] text-[--foreground]">
{NavItems.map((item, index) => (
<a
key={item.href}
href={item.href}
className="block py-2 px-4 text-2xl hover:bg-[--foreground] hover:text-[--background]"
onClick={() => setIsOpen(false)}
>
{index % 2 ? "🩷" : "🩵"} {item.text}
</a>
))}
{isOpen
? <div className="bg-[--background] text-[--foreground]">
{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}
>
{index % 2 === 1
? "🩷"
: "🩵"} {item.text}
</a>
);
})}
</div>
)}
: null}
<Rule />
</div>
);
};
export function ClientNavigation() {
const pathname = usePathname();
const isRootPath = pathname === "/";
if (isRootPath) return null;
return <Navigation />;
}

View File

@ -1,27 +1,39 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import Image from "next/image";
import { useState } from "react";
import type { JSX } from "react";
interface PartnerProps {
name: string;
avatar: string;
relationship: string;
url: string;
anniversary: Date;
interface PartnerProperties {
readonly name: string;
readonly avatar: string;
readonly relationship: string;
readonly url: string;
readonly anniversary: Date;
}
export const Partner = (props: PartnerProps): JSX.Element => {
const { name, avatar, relationship, url, anniversary } = props;
/**
* Renders the view for a polycule member.
* @param properties - The member to render.
* @returns A JSX element.
*/
export const Partner = (properties: PartnerProperties): JSX.Element => {
const { name, avatar, relationship, url, anniversary } = properties;
return (
<div className="flex items-start gap-2.5 pb-10 w-full">
<Image
className="rounded-full"
src={`https://cdn.nhcarrigan.com/avatars/${avatar}`}
alt={`${name}'s avatar.`}
width={75}
className="rounded-full"
height={75}
src={`https://cdn.nhcarrigan.com/avatars/${avatar}`}
width={75}
/>
<div className="flex flex-col w-full leading-1.5 p-4 border-gray-200 bg-gray-100 rounded-e-xl rounded-es-xl dark:bg-gray-700">
<div className="flex flex-col w-full leading-1.5 p-4
border-gray-200 bg-gray-100 rounded-e-xl
rounded-es-xl dark:bg-gray-700">
<div>
<a
className="text-sm font-semibold text-[#abfcec]"
@ -32,9 +44,9 @@ export const Partner = (props: PartnerProps): JSX.Element => {
</a>
<p className="text-sm font-normal text-gray-500 dark:text-gray-400">
{anniversary.toLocaleDateString("en-GB", {
day: "numeric",
month: "long",
year: "numeric",
day: "numeric",
year: "numeric",
})}
</p>
</div>

View File

@ -1,39 +1,54 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @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 ReviewProps {
name: string;
date: Date;
content: string;
sourceIcon: IconDefinition;
sourceUrl: string;
sourceName: string;
interface ReviewProperties {
readonly name: string;
readonly date: Date;
readonly content: string;
readonly sourceIcon: IconDefinition;
readonly sourceUrl: string;
readonly sourceName: string;
}
export const Review = (props: ReviewProps): JSX.Element => {
const { name, date, content, sourceIcon, sourceUrl, sourceName } = props;
/**
* 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">
<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">
<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{" "}
{`Review from `}
<a
href={sourceUrl}
target="_blank"
rel="noopener noreferrer"
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">
</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 />

View File

@ -1,5 +1,14 @@
import styles from "./rule.module.css";
/**
* @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,32 +1,44 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import type { JSX } from "react";
interface SocialProps {
icon: IconDefinition;
link: string;
label: string;
alt: string;
color: string;
background: string;
interface SocialProperties {
readonly icon: IconDefinition;
readonly link: string;
readonly label: string;
readonly alt: string;
readonly color: string;
readonly background: string;
}
export const Social = (props: SocialProps): JSX.Element => {
const { icon, link, label, alt, background, color } = props;
/**
* Renders the view for a social media link.
* @param properties - The link to render.
* @returns A JSX element.
*/
export const Social = (properties: SocialProperties): JSX.Element => {
const { icon, link, label, alt, background, color } = properties;
return (
<a
className="flex max-w-[300px] w-[95%] m-auto justify-between items-center border-solid border-2 rounded-3xl h-14 p-8 my-4"
style={{
borderColor: color,
color,
background,
}}
href={link}
target="_blank"
rel="noreferrer"
aria-label={label}
className="flex max-w-[300px] w-[95%] m-auto justify-between
items-center border-solid border-2 rounded-3xl h-14 p-8 my-4"
href={link}
rel="noreferrer"
style={{
background: background,
borderColor: color,
color: color,
}}
target="_blank"
>
<FontAwesomeIcon icon={icon} aria-label={alt} size="3x" />
<FontAwesomeIcon aria-label={alt} icon={icon} size="3x" />
<strong>{label}</strong>
</a>
);

View File

@ -1,85 +1,94 @@
export const Art: {
name: string;
img: string;
artist: string;
url: string;
alt: string;
}[] = [
{
name: "Avatar",
img: "profile.png",
artist: "Jazzybee",
url: "https://jazzybee.itch.io/sdvcharactercreator",
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."
},
{
name: "AI Bot",
img: "ai-bot.png",
artist: "Picrew",
url: "https://picrew.me/en/image_maker/1382748",
alt: "Anime-style illustration of a character wearing a large brown witch hat decorated with roses and hanging charms against a cloudy background."
},
{
name: "Mod Bot",
img: "mod-bot.png",
artist: "Picrew",
url: "https://picrew.me/en/image_maker/27700",
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."
},
{
name: "Translation Bot",
img: "translation-bot.png",
artist: "Picrew",
url: "https://picrew.me/en/image_maker/3595",
alt: "Anime-style portrait of a character with wavy brown hair, glasses, and a black lacy top, winking and smiling"
},
{
name: "Task Bot",
img: "task-bot.png",
artist: "Picrew",
url: "https://picrew.me/en/image_maker/700620",
alt: "Anime-style character with brown hair and glasses wearing a teal hat and shirt with a heart design, smiling excitedly"
},
{
name: "Boost Monitor Bot",
img: "boost-bot.png",
artist: "Picrew",
url: "https://picrew.me/en/image_maker/1310292",
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."
},
{
name: "Social Media Bridge",
img: "bridge.png",
artist: "Picrew",
url: "https://picrew.me/en/image_maker/21208",
alt: "Anime-style portrait of a character with blonde and black hair, blushing cheeks, and teary blue eyes, smiling against a turquoise background."
},
{
name: "Trick or Treat",
img: "trick-or-treat.png",
artist: "Picrew",
url: "https://picrew.me/en/image_maker/1712061",
alt: "Gothic anime-style portrait of a character with purple eyeshadow, spider hair clip, and choker necklace in a forest setting"
},
{
name: "Padoru",
img: "padoru.png",
artist: "Picrew",
url: "https://picrew.me/en/image_maker/1843743",
alt: "Cartoon character in a blue winter outfit with a pointed hat, holding a bloody sack, against a pink snowflake background"
},
{
name: "Trans Pride",
img: "trans.png",
artist: "Picrew",
url: "https://picrew.me/en/image_maker/100365/",
alt: "Cartoon portrait of a character with brown hair and glasses, smiling against a transgender flag heart background"
},
{
name: "Alt-text Generator",
img: "alt-text.png",
artist: "Picrew",
url: "https://picrew.me/en/image_maker/2003689/",
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."
}
]
/**
* @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/",
},
];

View File

@ -1,97 +1,106 @@
export const Certifications: {
name: string;
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/**
* List of certifications to render.
*/
export const Certifications: Array<{
name: string;
fileName: string;
issuer: string;
date: Date;
}[] = [
issuer: string;
date: Date;
}> = [
{
name: "Responsive Web Design",
date: new Date("April 17 2020"),
fileName: "responsive-web-design.png",
issuer: "freeCodeCamp",
date: new Date("April 17 2020")
issuer: "freeCodeCamp",
name: "Responsive Web Design",
},
{
name: "JavaScript Algorithms and Data Structures",
date: new Date("April 24, 2020"),
fileName: "javascript.png",
issuer: "freeCodeCamp",
date: new Date("April 24, 2020")
issuer: "freeCodeCamp",
name: "JavaScript Algorithms and Data Structures",
},
{
name: "Front End Libraries",
date: new Date("April 28, 2020"),
fileName: "front-end-libs.png",
issuer: "freeCodeCamp",
date: new Date("April 28, 2020")
issuer: "freeCodeCamp",
name: "Front End Libraries",
},
{
name: "Information Security and Quality Assurance",
date: new Date("May 19, 2020"),
fileName: "infosec.png",
issuer: "freeCodeCamp",
date: new Date("May 19, 2020")
issuer: "freeCodeCamp",
name: "Information Security and Quality Assurance",
},
{
name: "Full Stack Developer",
date: new Date("May 25, 2020"),
fileName: "legacy-full-stack.png",
issuer: "freeCodeCamp",
date: new Date("May 25, 2020")
issuer: "freeCodeCamp",
name: "Full Stack Developer",
},
{
name: "Data Visualisation",
date: new Date("May 25, 2020"),
fileName: "data-visualisation.png",
issuer: "freeCodeCamp",
date: new Date("May 25, 2020")
issuer: "freeCodeCamp",
name: "Data Visualisation",
},
{
name: "Back End Development and APIs",
date: new Date("May 8 2020"),
fileName: "back-end-dev.png",
issuer: "freeCodeCamp",
date: new Date("May 8 2020")
issuer: "freeCodeCamp",
name: "Back End Development and APIs",
},
{
name: "Quality Assurance",
date: new Date("July 3 2020"),
fileName: "quality-assurance.png",
issuer: "freeCodeCamp",
date: new Date("July 3 2020")
issuer: "freeCodeCamp",
name: "Quality Assurance",
},
{
name: "Legacy Front End",
date: new Date("August 14 2020"),
fileName: "legacy-front-end.png",
issuer: "freeCodeCamp",
date: new Date("August 14 2020")
issuer: "freeCodeCamp",
name: "Legacy Front End",
},
{
name: "Scientific Computing with Python",
date: new Date("July 3, 2020"),
fileName: "scientific-computing.png",
issuer: "freeCodeCamp",
date: new Date("July 3, 2020")
issuer: "freeCodeCamp",
name: "Scientific Computing with Python",
},
{
name: "Node.js Developer",
date: new Date("May 9 2024"),
fileName: "mongo-nodejs.jpg",
issuer: "MongoDB",
date: new Date("May 9 2024")
issuer: "MongoDB",
name: "Node.js Developer",
},
{
name: "Self-Managed Database Admin",
date: new Date("May 9 2024"),
fileName: "mongo-self-admin.jpg",
issuer: "MongoDB",
date: new Date("May 9 2024")
issuer: "MongoDB",
name: "Self-Managed Database Admin",
},
{
name: "Data Modelling",
date: new Date("May 9 2024"),
fileName: "mongo-data-model.jpg",
issuer: "MongoDB",
date: new Date("May 9 2024")
issuer: "MongoDB",
name: "Data Modelling",
},
{
name: "Atlas Database Admin",
date: new Date("May 9 2024"),
fileName: "mongo-atlas-admin.jpg",
issuer: "MongoDB",
date: new Date("May 9 2024")
issuer: "MongoDB",
name: "Atlas Database Admin",
},
{
name: "E-Commerce Modernisation and Personalisation",
date: new Date("May 9 2024"),
fileName: "ecommerce-retail.jpg",
issuer: "MongoDB",
date: new Date("May 9 2024")
}
issuer: "MongoDB",
name: "E-Commerce Modernisation and Personalisation",
},
];

View File

@ -1,109 +1,118 @@
export const Games: {
/**
* @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;
}[] = [
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",
img: "bloody-spell.jpg",
url: "https://store.steampowered.com/app/992300/_Bloody_Spell/",
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."
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",
img: "dw-9.jpg",
url: "https://store.steampowered.com/app/1341200/DYNASTY_WARRIORS_9_Empires/",
alt: "Animated character in a purple traditional Chinese robe wielding a large sword in a wooden temple-like structure with ornate pillars and lanterns."
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",
img: "soul-calibur.jpg",
url: "https://store.steampowered.com/app/544750/SOULCALIBUR_VI/",
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."
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",
img: "swtor.jpg",
url: "https://store.steampowered.com/app/1286830/STAR_WARS_The_Old_Republic/",
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."
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",
img: "idling-god.png",
url: "https://store.steampowered.com/app/466170/Idling_to_Rule_the_Gods/",
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."
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",
img: "nioh-2.jpg",
url: "https://store.steampowered.com/app/1325200/Nioh_2__The_Complete_Edition/",
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."
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",
img: "deathly-stillness.jpg",
url: "https://store.steampowered.com/app/1727650/Deathly_Stillness/",
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."
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",
img: "once-human.jpg",
url: "https://store.steampowered.com/app/2139460/Once_Human/",
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."
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",
img: "fallout-4.jpg",
url: "https://store.steampowered.com/app/377160/Fallout_4/",
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."
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",
img: "gw2.png",
url: "https://store.steampowered.com/app/1284210/Guild_Wars_2/",
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."
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",
img: "nwn.jpg",
url: "https://store.steampowered.com/app/704450/Neverwinter_Nights_Enhanced_Edition/",
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."
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",
img: "lords-of-the-fallen.jpg",
url: "https://store.steampowered.com/app/1501750/Lords_of_the_Fallen/",
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."
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",
img: "vroid.png",
url: "https://store.steampowered.com/app/1486350/VRoid_Studio_v1294/",
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."
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",
img: "koikatsu.png",
url: "https://store.steampowered.com/app/1073440/__Koikatsu_Party/",
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."
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",
img: "amalur.jpg",
url: "https://store.steampowered.com/app/1041720/Kingdoms_of_Amalur_ReReckoning/",
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."
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",
img: "encased.jpg",
url: "https://store.steampowered.com/app/921800/Encased_A_SciFi_PostApocalyptic_RPG/",
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."
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",
img: "demonologist.jpg",
url: "https://store.steampowered.com/app/1929610/Demonologist/",
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."
}
url: "https://store.steampowered.com/app/1929610/Demonologist/",
},
];

View File

@ -1,75 +1,81 @@
export const Jobs: {
title: string;
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/**
* List of professional work to render.
*/
export const Jobs: Array<{
title: string;
company: string;
/**
* Set ALL dates to the 5th. These are not exact days, but intended to avoid month discrepancies
* when dealing with timezones.
*/
start: Date;
/**
* Set ALL dates to the 5th. These are not exact days, but intended to avoid month discrepancies
* when dealing with timezones.
*/
end: Date | null;
link: string;
type: "volunteer" | "fixed" | "project" | "hypothetical";
end: Date | null;
link: string;
type: "volunteer" | "fixed" | "project" | "hypothetical";
description: string;
/**
* File name of logo.
*/
logo: string;
}[] = [
}> = [
{
title: "Consultant",
company: "Your Company!",
start: new Date(Date.now()),
end: null,
link: "https://topmate.io/nhcarrigan/913920",
type: "hypothetical",
logo: "future.jpeg",
company: "Your Company!",
description: `You could be our next client! Hit the "Contact" link and use the "Hire Us" button to get in touch!`,
end: null,
link: "https://topmate.io/nhcarrigan/913920",
logo: "future.jpeg",
start: new Date(Date.now()),
title: "Consultant",
type: "hypothetical",
},
{
title: "Started Journey",
company: "nhcarrigan",
start: new Date("April 5 2020"),
end: null,
link: "https://naomi.lgbt",
type: "hypothetical",
logo: "nhcarrigan.jpeg",
description:
"Began my journey learning to code, starting from the top of the freeCodeCamp curriculum.",
end: null,
link: "https://naomi.lgbt",
logo: "nhcarrigan.jpeg",
start: new Date("April 5 2020"),
title: "Started Journey",
type: "hypothetical",
},
{
title: "Development Lead",
company: "Artists For Palestine",
start: new Date("November 5 2023"),
end: null,
link: "https://art4palestine.org",
type: "volunteer",
logo: "a4p.jpeg",
company: "Artists For Palestine",
description: `As a Development Lead at Art 4 Palestine, I developed a bot that efficiently manages integrations between Airtable forms, Trello boards, and Discord, streamlining workflow and improving productivity. The bot also pulls news articles from reputable sources and cross-posts them into the server, keeping the community informed with reliable updates.
Additionally, I guided and mentored other developers in the design and coding of the website, ensuring high-quality output and consistent alignment with project goals. My contributions have been instrumental in supporting the organization's initiatives and fostering collaboration within the development team.`,
end: null,
link: "https://art4palestine.org",
logo: "a4p.jpeg",
start: new Date("November 5 2023"),
title: "Development Lead",
type: "volunteer",
},
{
title: "Community Moderator",
company: "AngelRose",
start: new Date("September 5 2023"),
end: null,
link: "https://discord.gg/kYpjgEB",
type: "volunteer",
logo: "angel.png",
company: "AngelRose",
description: `As a Discord Moderator at AngelRose, I played a key role in maintaining a safe, respectful, and engaging environment for community members. I monitored conversations, enforcing community guidelines to prevent disruptive behavior and protect users. Additionally, I addressed and resolved conflicts, providing support and guidance to members to promote a positive experience. My contributions helped uphold the integrity of the community and foster an inclusive and welcoming space for all participants.`,
end: null,
link: "https://discord.gg/kYpjgEB",
logo: "angel.png",
start: new Date("September 5 2023"),
title: "Community Moderator",
type: "volunteer",
},
{
title: "Community Bot Engineer",
company: "Deepgram",
start: new Date("July 5 2023"),
end: new Date("June 4 2024"),
link: "https://deepgram.com",
type: "project",
logo: "deepgram.jpeg",
company: "Deepgram",
description: `<div class="pl-4">
<p>As a Discord Bot Developer, I spearheaded the creation and implementation of an advanced bot designed to revolutionize community management and user interactions. My primary focus involved developing a comprehensive suite of features to streamline communication, enhance user engagement, and provide valuable insights into community health.</p>
@ -112,15 +118,15 @@ Additionally, I guided and mentored other developers in the design and coding of
</li>
</ul>
</div>`,
end: new Date("June 4 2024"),
link: "https://deepgram.com",
logo: "deepgram.jpeg",
start: new Date("July 5 2023"),
title: "Community Bot Engineer",
type: "project",
},
{
title: "Twitch Integration Engineer",
company: "BigBadBeaver Productions",
start: new Date("May 5 2023"),
end: new Date("January 5 2024"),
link: "https://linktr.ee/bigbadbeaver",
type: "project",
logo: "beaver.png",
company: "BigBadBeaver Productions",
description: `<div class="pl-4">
<p>As a Twitch Bot Developer, I spearheaded the creation and deployment of "PrivateTwigs," a custom chat bot designed to revolutionize stream management and viewer engagement. My role involved engineering sophisticated features, implementing data analysis tools, and creating user-friendly interfaces to empower streamers with valuable insights and enhanced interaction capabilities.</p>
@ -160,15 +166,15 @@ Additionally, I guided and mentored other developers in the design and coding of
</li>
</ul>
</div>`,
end: new Date("January 5 2024"),
link: "https://linktr.ee/bigbadbeaver",
logo: "beaver.png",
start: new Date("May 5 2023"),
title: "Twitch Integration Engineer",
type: "project",
},
{
title: "Community Manager and Open-Source Engineer",
company: "Sema Software",
start: new Date("May 5 2022"),
end: new Date("September 5 2022"),
link: "https://www.semasoftware.com",
type: "fixed",
logo: "sema.jpeg",
company: "Sema Software",
description: `<div class="pl-4">
<p>As an Open Source Community Manager, I successfully spearheaded the growth of our Discord community from 300 to 1,000 members, achieving a remarkable 233% increase. My role involved implementing strategic engagement initiatives, creating an inclusive environment, and leading multiple open source projects that significantly enhanced developer productivity and community engagement.</p>
@ -207,15 +213,15 @@ Additionally, I guided and mentored other developers in the design and coding of
</li>
</ul>
</div>`,
end: new Date("September 5 2022"),
link: "https://www.semasoftware.com",
logo: "sema.jpeg",
start: new Date("May 5 2022"),
title: "Community Manager and Open-Source Engineer",
type: "fixed",
},
{
title: "Community Manager",
company: "4C",
start: new Date("May 5 2022"),
end: new Date("November 5 2022"),
link: "https://discord.com/invite/ns5x8bTz25",
type: "fixed",
logo: "4c.png",
company: "4C",
description: `<div class="pl-4">
<p>As a Discord Community Growth Manager, I successfully orchestrated the exponential growth of our Discord community, expanding it from 1,100 to 3,350 members a remarkable 205% increase. My role encompassed implementing strategic engagement initiatives, developing community events, and creating a safe, inclusive environment that fostered active participation and meaningful interactions among members.</p>
@ -248,15 +254,15 @@ Additionally, I guided and mentored other developers in the design and coding of
</li>
</ul>
</div>`,
end: new Date("November 5 2022"),
link: "https://discord.com/invite/ns5x8bTz25",
logo: "4c.png",
start: new Date("May 5 2022"),
title: "Community Manager",
type: "fixed",
},
{
title: "Community Manager",
company: "TweetShift",
start: new Date("January 5 2022"),
end: new Date("May 5 2023"),
link: "https://tweetshift.com",
type: "fixed",
logo: "tweetshift.png",
company: "TweetShift",
description: `<div class="pl-4">
<p>As the Community Manager, I successfully managed and supported a vast user community spanning over 230,000 Discord servers, demonstrating my ability to operate effectively at scale. My role encompassed providing comprehensive support, implementing and enforcing community guidelines, and fostering high levels of engagement across this extensive user base.</p>
@ -297,14 +303,15 @@ Additionally, I guided and mentored other developers in the design and coding of
</li>
</ul>
</div>`,
end: new Date("May 5 2023"),
link: "https://tweetshift.com",
logo: "tweetshift.png",
start: new Date("January 5 2022"),
title: "Community Manager",
type: "fixed",
},
{
title: "Senior Integrations Engineer",
company: "Rythm",
start: new Date("April 5 2022"),
end: new Date("November 5, 2024"),
link: "https://rythm.fm",
type: "fixed",
company: "Rythm",
description: `<div class="pl-4">
<p>As a Discord Community Manager specializing in bot development, I spearheaded the creation and implementation of advanced management tools for a thriving community of over 300,000 members. My primary focus involved developing a comprehensive suite of bots and systems to streamline moderation, enhance user engagement, and provide valuable insights into community health and staff performance.</p>
@ -346,16 +353,15 @@ Additionally, I guided and mentored other developers in the design and coding of
</li>
</ul>
</div>`,
logo: "rythm.jpeg",
end: new Date("November 5, 2024"),
link: "https://rythm.fm",
logo: "rythm.jpeg",
start: new Date("April 5 2022"),
title: "Senior Integrations Engineer",
type: "fixed",
},
{
title: "Community Manager and Infrastructure Engineer",
company: "Streamcord",
start: new Date("August 5 2021"),
end: null,
link: "https://streamcord.io",
type: "fixed",
logo: "streamcord.jpeg",
company: "Streamcord",
description: `<div class="pl-4">
<p>As a Community Manager, I played a pivotal role in nurturing and expanding a vibrant community of over 50,000 members. My responsibilities encompassed a wide range of areas, including community engagement, technical support, human resources management, and bot operations. I was instrumental in fostering a thriving ecosystem around the Streamcord bot, which is utilized by over 1 million communities.</p>
@ -396,89 +402,89 @@ Additionally, I guided and mentored other developers in the design and coding of
</li>
</ul>
</div>`,
end: null,
link: "https://streamcord.io",
logo: "streamcord.jpeg",
start: new Date("August 5 2021"),
title: "Community Manager and Infrastructure Engineer",
type: "fixed",
},
{
title: "Community Moderator",
company: "Battlesnake",
start: new Date("June 5 2021"),
end: new Date("November 5 2022"),
link: "https://play.battlesnake.com",
type: "volunteer",
logo: "battlesnake.jpeg",
company: "Battlesnake",
description: `As a Community Moderator at Battlesnake, I played a vital role in ensuring a safe and welcoming environment for players. My focus was on promoting a positive experience for all members, allowing them to enjoy the game and interact with one another respectfully.
Additionally, I contributed to driving engagement for the Summer League 2021 and the Caster House system, fostering enthusiasm and participation in these events. My efforts helped create an inclusive and vibrant community for Battlesnake enthusiasts.`,
end: new Date("November 5 2022"),
link: "https://play.battlesnake.com",
logo: "battlesnake.jpeg",
start: new Date("June 5 2021"),
title: "Community Moderator",
type: "volunteer",
},
{
title: "Discord Administrator and Platform Engineering Manager",
company: "Caylus Crew",
start: new Date("June 5 2021"),
end: null,
link: "https://discord.gg/infinite",
type: "volunteer",
logo: "caylus.png",
company: "Caylus Crew",
description: `As the Discord Administrator and Platform Engineering Manager at Caylus Crew, I developed custom bots that enhanced the community experience and streamlined operations. One such bot posted daily messages wishing happy birthday to members, fostering a sense of connection and camaraderie. I also created a bot to manage sponsor perks, ensuring efficient and accurate distribution of benefits to eligible members.
In addition to technical contributions, I coached and trained moderators, providing quarterly staff reviews to support their professional growth and improve performance. My work played a pivotal role in maintaining a vibrant, well-managed community and ensuring an exceptional experience for all participants.`,
end: null,
link: "https://discord.gg/infinite",
logo: "caylus.png",
start: new Date("June 5 2021"),
title: "Discord Administrator and Platform Engineering Manager",
type: "volunteer",
},
{
title: "Integrations Engineer",
company: "Xcentric Collective",
start: new Date("April 5 2021"),
end: new Date("July 5 2023"),
link: "http://discord.gg/U3jQVYNbJt",
type: "volunteer",
logo: "xcentric.jpg",
company: "Xcentric Collective",
description: `As an Integrations Engineer at Xcentric Collective, I developed a custom Discord bot that incorporated a unique Matchmaking Rating (MMR) system to calculate Rocket League proficiency. This system enabled users to track and monitor their skill levels accurately.
The bot also featured the ability to define teams and find matches with opponents of similar skill levels, facilitating balanced and fair gameplay. Additionally, the bot allowed users to schedule matches seamlessly, providing a streamlined and efficient experience for the community. My work enhanced the overall gaming experience and engagement within the Xcentric Collective community. As an Integrations Engineer at Xcentric Collective, I developed a custom Discord bot that incorporated a unique Matchmaking Rating (MMR) system to calculate Rocket League proficiency. This system enabled users to track and monitor their skill levels accurately. The bot also featured the ability to define teams and find matches with opponents of similar skill levels, facilitating balanced and fair gameplay. Additionally, the bot allowed users to schedule matches seamlessly, providing a streamlined and efficient experience for the community. My work enhanced the overall gaming experience and engagement within the Xcentric Collective community.`,
end: new Date("July 5 2023"),
link: "http://discord.gg/U3jQVYNbJt",
logo: "xcentric.jpg",
start: new Date("April 5 2021"),
title: "Integrations Engineer",
type: "volunteer",
},
{
title: "Hacktoberfest Community Moderator",
company: "DigitalOcean",
start: new Date("April 5 2021"),
end: new Date("November 5 2024"),
link: "https://hacktoberfest.com",
type: "volunteer",
logo: "digitalocean.jpeg",
company: "DigitalOcean",
description: `As a Hacktoberfest Community Moderator at DigitalOcean, I supported the community by building a custom bot to ensure repository links were correctly included in promotional channel messages, streamlining interactions, and providing automated responses for frequently asked questions. This contributed to a smoother experience for participants and enhanced community engagement.
In addition to managing the bot, I answered queries and guided developers in their open-source contributions, helping them navigate the Hacktoberfest event and maximize their impact. By maintaining a safe and welcoming community space, I fostered an inclusive environment for developers of all skill levels to collaborate and grow. My efforts played a crucial role in the success of the event and the satisfaction of its participants.`,
end: new Date("November 5 2024"),
link: "https://hacktoberfest.com",
logo: "digitalocean.jpeg",
start: new Date("April 5 2021"),
title: "Hacktoberfest Community Moderator",
type: "volunteer",
},
{
title: "Discord Administrator",
company: "EddieHub",
start: new Date("January 5 2021"),
end: new Date("May 5 2023"),
link: "https://www.eddiehub.org",
type: "volunteer",
logo: "eddiehub.jpeg",
company: "EddieHub",
description: `As a Discord Administrator for EddieHub, I played a key role in moderating the community and fostering an open and inclusive environment. My focus was on upholding community guidelines and ensuring a positive experience for all members.
I provided support and encouragement to members on their development path, offering guidance and resources to help them grow and succeed. Additionally, I planned and managed community events and initiatives, creating opportunities for members to engage, learn, and collaborate.
My efforts contributed to the vibrancy and success of the EddieHub community, helping to create a welcoming space for developers to thrive.`,
end: new Date("May 5 2023"),
link: "https://www.eddiehub.org",
logo: "eddiehub.jpeg",
start: new Date("January 5 2021"),
title: "Discord Administrator",
type: "volunteer",
},
{
title: "Discord Administrator and Lead Integrations Engineer",
company: "Commit Your Code",
start: new Date("December 5 2020"),
end: null,
link: "https://discord.gg/StwJYeq",
type: "volunteer",
logo: "cyc.jpeg",
company: "Commit Your Code",
description: `As the Discord Administrator and Lead Integrations Engineer at Commit Your Code, I played a key role in fostering a supportive and friendly community environment by moderating interactions and ensuring a positive space for members. I provided guidance to individuals on their development path, offering support as they prepared for their first job or encountered coding challenges.
To enhance community security, I built a robust verification system that significantly reduced the number of compromised accounts, safeguarding members' data and promoting a safe space for collaboration. My contributions have been instrumental in supporting members' growth and maintaining a secure, welcoming atmosphere within the community.`,
end: null,
link: "https://discord.gg/StwJYeq",
logo: "cyc.jpeg",
start: new Date("December 5 2020"),
title: "Discord Administrator and Lead Integrations Engineer",
type: "volunteer",
},
{
title: "Educational Developer and Community Manager",
company: "freeCodeCamp",
start: new Date("December 5, 2020"),
end: null,
link: "https://freecodecamp.org",
type: "fixed",
logo: "fcc.jpeg",
company: "freeCodeCamp",
description: `<div class="pl-4">
<p>As a Community Manager at freeCodeCamp, I played a crucial role in maintaining and enhancing an open-source curriculum used by millions of developers worldwide. My responsibilities encompassed curriculum development, technical solution implementation, community management across multiple platforms, and contributing to the overall growth of the freeCodeCamp ecosystem.</p>
@ -518,119 +524,119 @@ To enhance community security, I built a robust verification system that signifi
</li>
</ul>
</div>`,
end: null,
link: "https://freecodecamp.org",
logo: "fcc.jpeg",
start: new Date("December 5, 2020"),
title: "Educational Developer and Community Manager",
type: "fixed",
},
{
title: "Discord Moderator",
company: "Virtual Insanity",
start: new Date("May 5 2024"),
end: null,
link: "https://discord.com/invite/GDYNGnrGUs",
type: "volunteer",
logo: "troopy.png",
company: "Virtual Insanity",
description: `As a Discord Moderator for Virtual Insanity, an adult-only community, I was instrumental in cultivating a secure, respectful, and interactive atmosphere for our diverse members. My responsibilities encompassed vigilant monitoring of discussions, ensuring compliance with established guidelines to deter any disruptive conduct, and safeguarding our users. Alongside conflict resolution, I provided empathetic support and guidance, fostering a harmonious environment conducive to positive interactions. Moreover, I actively assisted in verifying identification documents, reinforcing our commitment to maintaining a safe and authentic community experience. Through these efforts, I contributed to upholding the community's integrity and nurturing an inclusive space for all participants to thrive.`,
end: null,
link: "https://discord.com/invite/GDYNGnrGUs",
logo: "troopy.png",
start: new Date("May 5 2024"),
title: "Discord Moderator",
type: "volunteer",
},
{
title: "Discord Administrator and Integrations Engineer",
company: "Azuliah (VTuber)",
start: new Date("December 5 2023"),
end: new Date("April 5 2024"),
link: "https://discord.com/invite/XNSy8PMvyy",
type: "volunteer",
logo: "azuliah.jpg",
company: "Azuliah (VTuber)",
description: `As a Discord Administrator and Integrations Engineer at Azuliah, I established custom integrations to streamline moderation efforts and enhance community management. These integrations supported the moderation team in maintaining a safe and welcoming space for all members.
In addition to technical contributions, I trained the owner and moderation team on best practices for running a successful community. My guidance included techniques for efficient moderation, conflict resolution, and fostering positive interactions among members. Through my efforts, I played a key role in ensuring the community's smooth operation and promoting a vibrant, supportive environment.`,
end: new Date("April 5 2024"),
link: "https://discord.com/invite/XNSy8PMvyy",
logo: "azuliah.jpg",
start: new Date("December 5 2023"),
title: "Discord Administrator and Integrations Engineer",
type: "volunteer",
},
{
title: "Discord Moderator",
company: "Rion Kuroko (VTuber)",
start: new Date("Nov 5 2023"),
end: new Date("Jan 5 2024"),
link: "https://discord.com",
type: "volunteer",
logo: "rion.jpg",
company: "Rion Kuroko (VTuber)",
description: `As a Discord Moderator for Rion Kuroko, I constructed the server almost entirely from scratch to tailor it to the specific needs of the community. This involved designing and implementing structures, channels, and rules that supported a smooth and organized environment.
I also provided guidance to the owner on the technical aspects of Discord moderation, sharing best practices and offering solutions to effectively manage the server. My efforts played a key role in establishing a functional, user-friendly community space and empowering the owner with the knowledge needed to maintain and grow the server.`,
end: new Date("Jan 5 2024"),
link: "https://discord.com",
logo: "rion.jpg",
start: new Date("Nov 5 2023"),
title: "Discord Moderator",
type: "volunteer",
},
{
title: "Senior Discord Moderator",
company: "Rythm",
start: new Date("Feb 5 2022"),
end: new Date("July 5 2022"),
link: "https://discord.com/invite/rythm",
type: "volunteer",
logo: "rythm.jpeg",
company: "Rythm",
description: `As a Senior Discord Moderator for Rythm, I played a dual role in overseeing community moderation and mentoring the moderation team. My responsibilities included tracking weekly staff activity to monitor performance and identify areas for improvement, ensuring the team remained effective and engaged.
Additionally, I guided and supported moderators, helping them develop their skills and excel in their roles. By identifying opportunities to enhance staff activity, I contributed to the overall health and vibrancy of the community. My efforts were instrumental in maintaining a positive, safe, and well-managed space for all members.`,
end: new Date("July 5 2022"),
link: "https://discord.com/invite/rythm",
logo: "rythm.jpeg",
start: new Date("Feb 5 2022"),
title: "Senior Discord Moderator",
type: "volunteer",
},
{
title: "Technical Support Staff",
company: "TweetShift",
start: new Date("Oct 5 2021"),
end: new Date("Jan 5 2022"),
link: "https://discord.com/invite/zdfQhjc",
type: "volunteer",
logo: "tweetshift.png",
company: "TweetShift",
description: `As Technical Support Staff for TweetShift, I responded promptly to user queries, addressing issues and bugs within the bot to ensure a smooth user experience. I provided clear guidance on how to use the bot's features effectively, helping users maximize its capabilities.
My role involved troubleshooting technical challenges and offering solutions to enhance user satisfaction. By delivering efficient support and sharing helpful tips, I contributed to the overall success and reliability of the bot for the TweetShift community.`,
end: new Date("Jan 5 2022"),
link: "https://discord.com/invite/zdfQhjc",
logo: "tweetshift.png",
start: new Date("Oct 5 2021"),
title: "Technical Support Staff",
type: "volunteer",
},
{
title: "Discord Moderator",
company: "Rythm",
start: new Date("Sept 5 2021"),
end: new Date("Feb 5 2022"),
link: "https://discord.com/invite/rythm",
type: "volunteer",
logo: "rythm.jpeg",
company: "Rythm",
description: `As a Discord Moderator for Rythm, I played a central role in maintaining a safe, respectful, and welcoming environment for community members. I monitored conversations to ensure compliance with community guidelines and addressed any disruptive behavior promptly.
My efforts helped foster a positive and inclusive space for all members, contributing to the overall health and vibrancy of the Rythm community.`,
end: new Date("Feb 5 2022"),
link: "https://discord.com/invite/rythm",
logo: "rythm.jpeg",
start: new Date("Sept 5 2021"),
title: "Discord Moderator",
type: "volunteer",
},
{
title: "Technical Support Staff",
company: "Streamcord",
start: new Date("Mar 5 2021"),
end: new Date("Aug 5 2021"),
link: "https://discord.com/invite/streamcord",
type: "volunteer",
logo: "streamcord.jpeg",
company: "Streamcord",
description: `As Technical Support Staff for Streamcord, I provided essential support to users by triaging and debugging issues related to the Streamcord Discord bot. My role involved investigating and resolving technical challenges to ensure a seamless user experience.
Acting as a liaison between users and developers, I facilitated clear communication and reported user feedback to the development team for continuous improvement. Additionally, I moderated and engaged with the community, fostering a positive and inclusive environment for all participants. My efforts contributed to the overall success and satisfaction of the Streamcord community.`,
end: new Date("Aug 5 2021"),
link: "https://discord.com/invite/streamcord",
logo: "streamcord.jpeg",
start: new Date("Mar 5 2021"),
title: "Technical Support Staff",
type: "volunteer",
},
{
title: "Community Moderator",
company: "freeCodeCamp",
start: new Date("Jun 5 2020"),
end: new Date("Dec 5 2020"),
link: "https://discord.com/invite/freecodecamp-org-official-fi-fo-692816967895220344",
type: "volunteer",
logo: "fcc.jpeg",
company: "freeCodeCamp",
description: `As a Community Moderator for freeCodeCamp, I provided vital support to users (campers) as they navigated the freeCodeCamp curriculum. My role included assisting users in debugging their code and answering questions, ensuring they received the guidance they needed to progress in their learning journey.
I engaged with and moderated the community on the forum and Discord server, fostering a positive, supportive, and inclusive environment for all members. Additionally, I assisted with issue triage and pull request review on GitHub, contributing to the ongoing improvement and development of freeCodeCamp's open-source projects. My efforts played a key role in maintaining the quality of the community and empowering users to achieve their learning goals.`,
},
{
end: new Date("Dec 5 2020"),
link: "https://discord.com/invite/freecodecamp-org-official-fi-fo-692816967895220344",
logo: "fcc.jpeg",
start: new Date("Jun 5 2020"),
title: "Community Moderator",
company: "FruitPursuits",
start: new Date("March 5 2024"),
end: null,
link: "https://discord.gg/xcy2fRsC5K",
type: "volunteer",
logo: "fruit.png",
description: `As a Discord Moderator for FruitPursuits, I provide crucial support to members, ensuring they have a seamless experience within our community. I troubleshoot technical issues, enforce community guidelines, and foster engagement among members. Collaborating with fellow moderators, I contribute to the ongoing improvement of our Discord server, empowering users to fully enjoy their fruit-loving journey.`,
type: "volunteer",
},
{
title: "Developer Experience Consultant",
company: "Deepgram",
start: new Date("June 5 2024"),
end: null,
link: "https://deepgram.com",
type: "fixed",
logo: "deepgram.jpeg",
company: "FruitPursuits",
description: `As a Discord Moderator for FruitPursuits, I provide crucial support to members, ensuring they have a seamless experience within our community. I troubleshoot technical issues, enforce community guidelines, and foster engagement among members. Collaborating with fellow moderators, I contribute to the ongoing improvement of our Discord server, empowering users to fully enjoy their fruit-loving journey.`,
end: null,
link: "https://discord.gg/xcy2fRsC5K",
logo: "fruit.png",
start: new Date("March 5 2024"),
title: "Community Moderator",
type: "volunteer",
},
{
company: "Deepgram",
description: `
<div class="pl-4">
<p>As a DX Consultant at Deepgram, I spearhead efforts to enhance community engagement and streamline developer experiences. Leveraging my expertise, I optimize community management workflows on platforms like Slack, GitHub Discussions, and Discord. My primary focus involves acting as a Community Manager, where I strive to answer approximately 60% of community inquiries, foster engagement, and ensure valuable feedback reaches the Product Board for consideration.</p>
@ -672,15 +678,15 @@ I engaged with and moderated the community on the forum and Discord server, fost
</li>
</ul>
</div>`,
end: null,
link: "https://deepgram.com",
logo: "deepgram.jpeg",
start: new Date("June 5 2024"),
title: "Developer Experience Consultant",
type: "fixed",
},
{
title: "Service Operations Manager",
company: "Safeway",
start: new Date("5 August 2009"),
end: new Date("5 April 2020"),
link: "https://www.safeway.com",
type: "fixed",
logo: "safeway.png",
company: "Safeway",
description: `<div class="pl-4">
<p>As the Service Operations Manager at Safeway, I played a pivotal role in overseeing the store's day-to-day operations and maintaining exceptional service standards. My responsibilities encompassed a wide range of critical functions, from personnel management and training to ensuring compliance with safety regulations and company policies.</p>
@ -719,15 +725,15 @@ I engaged with and moderated the community on the forum and Discord server, fost
</li>
</ul>
</div>`,
end: new Date("5 April 2020"),
link: "https://www.safeway.com",
logo: "safeway.png",
start: new Date("5 August 2009"),
title: "Service Operations Manager",
type: "fixed",
},
{
title: "Student",
company: "Vancouver School of Arts and Academics",
start: new Date("5 September 2001"),
end: new Date("5 June 2009"),
link: "https://arts.vansd.org/",
type: "volunteer",
logo: "vsaa.png",
company: "Vancouver School of Arts and Academics",
description: `<div class="pl-4">
<p>As a student at the Vancouver School of Arts and Academics, I immersed myself in a diverse and enriching artistic environment, with a primary focus on music and a secondary emphasis on theatre. This multidisciplinary approach allowed me to develop a broad range of skills across various art forms, fostering my creativity and artistic expression.</p>
@ -761,5 +767,11 @@ I engaged with and moderated the community on the forum and Discord server, fost
</li>
</ul>
</div>`,
end: new Date("5 June 2009"),
link: "https://arts.vansd.org/",
logo: "vsaa.png",
start: new Date("5 September 2001"),
title: "Student",
type: "volunteer",
},
];

View File

@ -1,3 +1,13 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/**
* Navigation items to render on home page and
* on main navbar.
*/
export const NavItems = [
{ href: "/about", text: "About" },
{ href: "/manual", text: "User Manual" },
@ -5,11 +15,13 @@ export const NavItems = [
{ href: "/contact", text: "Contact" },
{ href: "/certs", text: "Certifications" },
{ href: "/reviews", text: "Reviews" },
{ href: "/games", text: "Games"},
{ href: "/games", text: "Games" },
{ href: "/team", text: "Our Team" },
{ href: "/polycule", text: "Polycule"},
{ href: "/activity", text: "Activity"},
{ href: "/art", text: "Art"},
{ href: "https://nhcarrigan.creator-spring.com/", text: "Merch"},
{ href: "/manifesto", text: "Transfemme Manifesto"},
].sort((a, b) => a.text.localeCompare(b.text));
{ href: "/polycule", text: "Polycule" },
{ href: "/activity", text: "Activity" },
{ href: "/art", text: "Art" },
{ href: "https://nhcarrigan.creator-spring.com/", text: "Merch" },
{ href: "/manifesto", text: "Transfemme Manifesto" },
].sort((a, b) => {
return a.text.localeCompare(b.text);
});

View File

@ -1,29 +1,40 @@
export const Partners: {
name: string;
avatar: string;
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/**
* List of polycule members to render.
*/
export const Partners: Array<{
name: string;
avatar: string;
relationship: string;
url: string;
anniversary: Date;
}[] = [
{
name: "Sadashi",
avatar: "dashi.png",
relationship: "Romantic Partner",
url: "https://chat.nhcarrigan.com",
anniversary: new Date("January 1, 2023")
},
{
name: "Rain",
avatar: "estel.png",
relationship: "Fiancée",
url: "https://chat.nhcarrigan.com",
anniversary: new Date("November 17, 2023")
},
{
name: "Kaitlyn",
avatar: "fruit.png",
relationship: "Queer-Platonic Partner",
url: "https://kaitlyn.nhcarrigan.com",
anniversary: new Date("October 1, 2023")
},
].sort((a, b) => a.anniversary.getTime() - b.anniversary.getTime())
url: string;
anniversary: Date;
}> = [
{
anniversary: new Date("January 1, 2023"),
avatar: "dashi.png",
name: "Sadashi",
relationship: "Romantic Partner",
url: "https://chat.nhcarrigan.com",
},
{
anniversary: new Date("November 17, 2023"),
avatar: "estel.png",
name: "Rain",
relationship: "Fiancée",
url: "https://chat.nhcarrigan.com",
},
{
anniversary: new Date("October 1, 2023"),
avatar: "fruit.png",
name: "Kaitlyn",
relationship: "Queer-Platonic Partner",
url: "https://kaitlyn.nhcarrigan.com",
},
].sort((a, b) => {
return a.anniversary.getTime() - b.anniversary.getTime();
});

View File

@ -1,17 +1,8 @@
import { Codeberg } from "@/icons/Codeberg";
import { Fiverr } from "@/icons/Fiverr";
import { Gather } from "@/icons/Gather";
import { Gog } from "@/icons/Gog";
import { Kofi } from "@/icons/KoFi";
import { Matrix } from "@/icons/Matrix";
import { Peerlist } from "@/icons/Peerlist";
import { Pixiv } from "@/icons/Pixiv";
import { Polywork } from "@/icons/Polywork";
import { Saylor } from "@/icons/Saylor";
import { TeeSpring } from "@/icons/TeeSpring";
import { TreeNation } from "@/icons/TreeNation";
import { VRoid } from "@/icons/VRoid";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import {
faAngellist,
faBluesky,
@ -56,20 +47,33 @@ import {
faMoneyBill,
faUniversity,
} from "@fortawesome/free-solid-svg-icons";
import { Codeberg } from "../icons/Codeberg";
import { Fiverr } from "../icons/Fiverr";
import { Gather } from "../icons/Gather";
import { Gog } from "../icons/Gog";
import { Kofi } from "../icons/KoFi";
import { Matrix } from "../icons/Matrix";
import { Peerlist } from "../icons/Peerlist";
import { Pixiv } from "../icons/Pixiv";
import { Polywork } from "../icons/Polywork";
import { Saylor } from "../icons/Saylor";
import { TeeSpring } from "../icons/TeeSpring";
import { TreeNation } from "../icons/TreeNation";
import { VRoid } from "../icons/VRoid";
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
export const HireMe: {
icon: IconDefinition;
link: string;
label: string;
alt: string;
color: string;
/**
* Custom button with gradient for hire CTA.
*/
const HireMe: {
icon: IconDefinition;
link: string;
label: string;
alt: string;
color: string;
background: string;
} = {
icon: faBriefcase,
link: "https://docs.nhcarrigan.com/#/hire",
label: "Hire Us!",
alt: "Briefcase Icon",
color: "#003600",
alt: "Briefcase Icon",
background: `linear-gradient(
90deg,
#5bcefa,
@ -78,21 +82,24 @@ export const HireMe: {
#f5a9b8,
#5bcefa
)`,
color: "#003600",
icon: faBriefcase,
label: "Hire Us!",
link: "https://docs.nhcarrigan.com/#/hire",
};
export const Donate: {
icon: IconDefinition;
link: string;
label: string;
alt: string;
color: string;
/**
* Custom button with gradient for Donate CTA.
*/
const Donate: {
icon: IconDefinition;
link: string;
label: string;
alt: string;
color: string;
background: string;
} = {
icon: faMoneyBill,
link: "https://docs.nhcarrigan.com/#/donate",
label: "Donate 💜",
alt: "Money Icon",
color: "#003600",
alt: "Money Icon",
background: `linear-gradient(
90deg,
rgba(255, 0, 0, 1) 0%,
@ -107,430 +114,439 @@ export const Donate: {
rgba(255, 154, 0, 1) 90%,
rgba(255, 0, 0, 1) 100%
)`,
color: "#003600",
icon: faMoneyBill,
label: "Donate 💜",
link: "https://docs.nhcarrigan.com/#/donate",
};
export const Socials: {
icon: IconDefinition;
link: string;
label: string;
alt: string;
color: string;
/**
* List of social media buttons to render.
*/
const Socials: Array<{
icon: IconDefinition;
link: string;
label: string;
alt: string;
color: string;
background: string;
}[] = [
}> = [
{
label: "Codeberg",
link: "https://codeberg.org/naomi-lgbt",
alt: "Codeberg Logo",
icon: Codeberg,
alt: "Codeberg Logo",
background: "#0B3049",
color: "#B5DDFF",
color: "#B5DDFF",
icon: Codeberg,
label: "Codeberg",
link: "https://codeberg.org/naomi-lgbt",
},
{
label: "GitHub",
link: "https://github.com/nhcarrigan",
alt: "GitHub Logo",
icon: faGithub,
alt: "GitHub Logo",
background: "#333",
color: "#FFF",
color: "#FFF",
icon: faGithub,
label: "GitHub",
link: "https://github.com/nhcarrigan",
},
{
label: "Discord",
link: "https://chat.naomi.lgbt",
alt: "Discord Logo",
icon: faDiscord,
alt: "Discord Logo",
background: "#7289DA",
color: "#FFF",
color: "#FFF",
icon: faDiscord,
label: "Discord",
link: "https://chat.naomi.lgbt",
},
{
label: "Matrix",
link: "https://matrix.to/#/#naomi:matrix.org",
alt: "Element Logo",
icon: Matrix,
alt: "Element Logo",
background: "#29B6F6",
color: "#FFF",
color: "#FFF",
icon: Matrix,
label: "Matrix",
link: "https://matrix.to/#/#naomi:matrix.org",
},
{
label: "IRC",
link: "https://docs.nhcarrigan.com/about/contact/#33-irc-channels",
alt: "Hash symbol",
icon: faHashtag,
alt: "Hash symbol",
background: "#000",
color: "#FFF",
color: "#FFF",
icon: faHashtag,
label: "IRC",
link: "https://docs.nhcarrigan.com/about/contact/#33-irc-channels",
},
{
label: "Slack",
link: "https://join.slack.com/t/naomi-lgbt/signup",
alt: "Slack Logo",
icon: faSlack,
alt: "Slack Logo",
background: "#4A154B",
color: "#FFF",
color: "#FFF",
icon: faSlack,
label: "Slack",
link: "https://join.slack.com/t/naomi-lgbt/signup",
},
{
label: "Reddit",
link: "https://reddit.com/r/nhcarrigan",
alt: "Reddit Logo",
icon: faReddit,
alt: "Reddit Logo",
background: "#FF4500",
color: "#FFF",
color: "#FFF",
icon: faReddit,
label: "Reddit",
link: "https://reddit.com/r/nhcarrigan",
},
{
label: "Blog",
link: "https://blog.nhcarrigan.com",
alt: "Hashnode Logo",
icon: faHashnode,
alt: "Hashnode Logo",
background: "#2962FF",
color: "#FFF",
color: "#FFF",
icon: faHashnode,
label: "Blog",
link: "https://blog.nhcarrigan.com",
},
{
label: "LinkedIn",
link: "https://linkedin.com/in/naomi-lgbt",
alt: "LinkedIn Logo",
icon: faLinkedinIn,
alt: "LinkedIn Logo",
background: "#0077B5",
color: "#FFF",
color: "#FFF",
icon: faLinkedinIn,
label: "LinkedIn",
link: "https://linkedin.com/in/naomi-lgbt",
},
{
label: "Peerlist",
link: "https://resume.nhcarrigan.com",
alt: "Peerlist Logo",
icon: Peerlist,
alt: "Peerlist Logo",
background: "rgb(33, 150, 83)",
color: "#FFF",
color: "#FFF",
icon: Peerlist,
label: "Peerlist",
link: "https://resume.nhcarrigan.com",
},
{
label: "Polywork",
link: "https://polywork.nhcarrigan.com/",
alt: "Polywork Logo",
icon: Polywork,
alt: "Polywork Logo",
background: "#7C3AED",
color: "#FFF",
color: "#FFF",
icon: Polywork,
label: "Polywork",
link: "https://polywork.nhcarrigan.com/",
},
{
label: "Fiverr",
link: "https://www.fiverr.com/nhcarrigan",
alt: "Fiverr Logo",
icon: Fiverr,
alt: "Fiverr Logo",
background: "#1DBF73",
color: "#FFF",
color: "#FFF",
icon: Fiverr,
label: "Fiverr",
link: "https://www.fiverr.com/nhcarrigan",
},
{
label: "Mastodon",
link: "https://mastodon.social/@naomi_lgbt",
alt: "Mastodon Logo",
icon: faMastodon,
alt: "Mastodon Logo",
background: "#2B90D9",
color: "#FFF",
color: "#FFF",
icon: faMastodon,
label: "Mastodon",
link: "https://mastodon.social/@naomi_lgbt",
},
{
label: "X (Twitter)",
link: "https://x.com/naomi_lgbt",
alt: "X Logo",
icon: faXTwitter,
alt: "X Logo",
background: "#1DA1F2",
color: "#FFF",
color: "#FFF",
icon: faXTwitter,
label: "X (Twitter)",
link: "https://x.com/naomi_lgbt",
},
{
label: "Steam",
link: "https://steamcommunity.com/id/naomi-lgbt/",
alt: "Steam Logo",
icon: faSteam,
alt: "Steam Logo",
background: "rgb(27, 40, 56)",
color: "rgb(199, 213, 224)",
color: "rgb(199, 213, 224)",
icon: faSteam,
label: "Steam",
link: "https://steamcommunity.com/id/naomi-lgbt/",
},
{
label: "Twitch",
link: "https://www.twitch.tv/naomilgbt/",
alt: "Twitch Logo",
icon: faTwitch,
alt: "Twitch Logo",
background: "#6441A4",
color: "#FFF",
color: "#FFF",
icon: faTwitch,
label: "Twitch",
link: "https://www.twitch.tv/naomilgbt/",
},
{
label: "HLTB",
link: "https://howlongtobeat.com/user/naomi_lgbt",
alt: "How Long to Beat Logo",
icon: faGamepad,
alt: "How Long to Beat Logo",
background: "#333",
color: "#FFF",
color: "#FFF",
icon: faGamepad,
label: "HLTB",
link: "https://howlongtobeat.com/user/naomi_lgbt",
},
{
label: "Snapchat",
link: "https://www.snapchat.com/add/naomi-lgbt",
alt: "Snapchat Logo",
icon: faSnapchat,
alt: "Snapchat Logo",
background: "#FFFC00",
color: "#000",
color: "#000",
icon: faSnapchat,
label: "Snapchat",
link: "https://www.snapchat.com/add/naomi-lgbt",
},
{
label: "GOG",
link: "https://www.gog.com/u/naomi-lgbt",
alt: "GOG Logo",
icon: Gog,
alt: "GOG Logo",
background: "#863A3A",
color: "#FFF",
color: "#FFF",
icon: Gog,
label: "GOG",
link: "https://www.gog.com/u/naomi-lgbt",
},
{
label: "Itch.io",
link: "https://nhcarrigan.itch.io/",
alt: "Itch.io Logo",
icon: faItchIo,
alt: "Itch.io Logo",
background: "#FA5C5C",
color: "#FFF",
color: "#FFF",
icon: faItchIo,
label: "Itch.io",
link: "https://nhcarrigan.itch.io/",
},
{
label: "Lichess",
link: "https://lichess.org/@/naomi-lgbt",
alt: "Lichess Logo",
icon: faChessKnight,
alt: "Lichess Logo",
background: "#000",
color: "#FFF",
color: "#FFF",
icon: faChessKnight,
label: "Lichess",
link: "https://lichess.org/../naomi-lgbt",
},
{
label: "Saylor Academy",
link: "https://learn.saylor.org/user/profile.php?id=2074619",
alt: "Saylor Academy Logo",
icon: Saylor,
alt: "Saylor Academy Logo",
background: "#469dcc",
color: "#183140",
color: "#183140",
icon: Saylor,
label: "Saylor Academy",
link: "https://learn.saylor.org/user/profile.php?id=2074619",
},
{
label: "Paypal",
link: "https://paypal.me/nhcarrigan",
alt: "Paypal Logo",
icon: faPaypal,
alt: "Paypal Logo",
background: "#003087",
color: "#FFF",
color: "#FFF",
icon: faPaypal,
label: "Paypal",
link: "https://paypal.me/nhcarrigan",
},
{
label: "Ko-Fi",
link: "https://ko-fi.com/nhcarrigan",
alt: "Ko-Fi Logo",
icon: Kofi,
alt: "Ko-Fi Logo",
background: "#FF5E5B",
color: "#FFF",
color: "#FFF",
icon: Kofi,
label: "Ko-Fi",
link: "https://ko-fi.com/nhcarrigan",
},
{
label: "Patreon",
link: "https://patreon.com/nhcarrigan",
alt: "Patreon Logo",
icon: faPatreon,
alt: "Patreon Logo",
background: "#F96854",
color: "#FFF",
color: "#FFF",
icon: faPatreon,
label: "Patreon",
link: "https://patreon.com/nhcarrigan",
},
{
label: "freeCodeCamp",
link: "https://forum.freecodecamp.org/u/nhcarrigan/summary",
alt: "freeCodeCamp Logo",
icon: faFreeCodeCamp,
alt: "freeCodeCamp Logo",
background: "#0a0a23",
color: "#FFF",
color: "#FFF",
icon: faFreeCodeCamp,
label: "freeCodeCamp",
link: "https://forum.freecodecamp.org/u/nhcarrigan/summary",
},
{
label: "NPM",
link: "https://www.npmjs.com/~nhcarrigan",
alt: "NPM Logo",
icon: faNpm,
alt: "NPM Logo",
background: "#CB3837",
color: "#FFF",
color: "#FFF",
icon: faNpm,
label: "NPM",
link: "https://www.npmjs.com/~nhcarrigan",
},
{
label: "GatherTown",
link: "https://app.gather.town/invite?token=CiIvbSnrQiW-akXFSPL_",
alt: "GatherTown Logo",
icon: Gather,
alt: "GatherTown Logo",
background: "#7B68EE",
color: "#FFF",
color: "#FFF",
icon: Gather,
label: "GatherTown",
link: "https://app.gather.town/invite?token=CiIvbSnrQiW-akXFSPL_",
},
{
label: "VRoid",
link: "https://hub.vroid.com/en/characters/6033404747153826650/models/3483506204509065121",
alt: "VRoid Logo",
icon: VRoid,
color: "#000",
alt: "VRoid Logo",
background: "#ffe100",
color: "#000",
icon: VRoid,
label: "VRoid",
link: "https://hub.vroid.com/en/characters/6033404747153826650/models/3483506204509065121",
},
{
label: "Pixiv",
link: "https://www.pixiv.net/en/users/77818154",
alt: "Pixiv Logo",
icon: Pixiv,
alt: "Pixiv Logo",
background: "#0096FA",
color: "#FFF",
color: "#FFF",
icon: Pixiv,
label: "Pixiv",
link: "https://www.pixiv.net/en/users/77818154",
},
{
label: "Email",
link: "https://docs.nhcarrigan.com/about/contact/#7-email-communication",
alt: "Email Icon",
icon: faEnvelope,
alt: "Email Icon",
background: "#000000",
color: "#FFF",
color: "#FFF",
icon: faEnvelope,
label: "Email",
link: "https://docs.nhcarrigan.com/about/contact/#7-email-communication",
},
{
label: "Coursera",
link: "https://www.coursera.org/learner/naomi-lgbt",
alt: "University Icon",
icon: faUniversity,
alt: "University Icon",
background: "#0056D2",
color: "#FFF",
color: "#FFF",
icon: faUniversity,
label: "Coursera",
link: "https://www.coursera.org/learner/naomi-lgbt",
},
{
label: "Udemy",
link: "https://www.udemy.com/user/naomi-carrigan/",
alt: "University Icon",
icon: faUniversity,
alt: "University Icon",
background: "#EC5252",
color: "#FFF",
color: "#FFF",
icon: faUniversity,
label: "Udemy",
link: "https://www.udemy.com/user/naomi-carrigan/",
},
{
label: "Gravatar",
link: "https://gravatar.com/nhcarrigan",
alt: "Wordpress Logo",
icon: faWordpress,
alt: "Wordpress Logo",
background: "#1E8EDE",
color: "#FFF",
color: "#FFF",
icon: faWordpress,
label: "Gravatar",
link: "https://gravatar.com/nhcarrigan",
},
{
label: "PC Part Picker",
link: "https://pcpartpicker.com/user/nhcarrigan/",
alt: "Computer Icon",
icon: faComputer,
alt: "Computer Icon",
background: "#000",
color: "#FFF",
color: "#FFF",
icon: faComputer,
label: "PC Part Picker",
link: "https://pcpartpicker.com/user/nhcarrigan/",
},
{
label: "Throne",
link: "https://throne.com/naomilgbt",
alt: "Gift Icon",
icon: faGift,
alt: "Gift Icon",
background: "#000",
color: "#FFF",
color: "#FFF",
icon: faGift,
label: "Throne",
link: "https://throne.com/naomilgbt",
},
{
label: "Stripe",
link: "https://buy.stripe.com/cN24iTfqu1j6b3afZ2",
alt: "Stripe Logo",
icon: faStripe,
alt: "Stripe Logo",
background: "#6772E5",
color: "#FFF",
color: "#FFF",
icon: faStripe,
label: "Stripe",
link: "https://buy.stripe.com/cN24iTfqu1j6b3afZ2",
},
{
label: "Signal",
link: "https://signal.me/#eu/YGo6ag7kAwkdfkw863-J4qLK3VyKn8uWLhvyX60Jx5J0qrDZnz1B_aLrjM9yAMe6",
alt: "Signal Logo",
icon: faSignalMessenger,
alt: "Signal Logo",
background: "#FFF",
color: "#3A76F0",
color: "#3A76F0",
icon: faSignalMessenger,
label: "Signal",
link: "https://signal.me/#eu/YGo6ag7kAwkdfkw863-J4qLK3VyKn8uWLhvyX60Jx5J0qrDZnz1B_aLrjM9yAMe6",
},
{
label: "WellFound",
link: "https://wellfound.com/u/naomi-h-carrigan",
alt: "Angellist Logo",
icon: faAngellist,
alt: "Angellist Logo",
background: "#000",
color: "#FFF",
color: "#FFF",
icon: faAngellist,
label: "WellFound",
link: "https://wellfound.com/u/naomi-h-carrigan",
},
{
label: "ProtonDB",
link: "https://www.protondb.com/users/1717755560",
alt: "ProtonDB Logo",
icon: faAtom,
alt: "ProtonDB Logo",
background: "#000",
color: "#FFF",
color: "#FFF",
icon: faAtom,
label: "ProtonDB",
link: "https://www.protondb.com/users/1717755560",
},
{
label: "Merch",
link: "https://nhcarrigan.creator-spring.com/",
alt: "TeeSpring Logo",
icon: TeeSpring,
alt: "TeeSpring Logo",
background: "#000",
color: "#FFF",
color: "#FFF",
icon: TeeSpring,
label: "Merch",
link: "https://nhcarrigan.creator-spring.com/",
},
{
label: "BlueSky",
link: "https://bsky.app/profile/naomi-lgbt.bsky.social",
alt: "BlueSky Logo",
icon: faBluesky,
alt: "BlueSky Logo",
background: "#1DA1F2",
color: "#FFF",
color: "#FFF",
icon: faBluesky,
label: "BlueSky",
link: "https://bsky.app/profile/naomi-lgbt.bsky.social",
},
{
label: "Telegram",
link: "https://t.me/naomi_lgbt",
alt: "Telegram Logo",
icon: faTelegramPlane,
alt: "Telegram Logo",
background: "#0088cc",
color: "#FFF",
color: "#FFF",
icon: faTelegramPlane,
label: "Telegram",
link: "https://t.me/naomi_lgbt",
},
{
label: "WhatsApp",
link: "https://wa.me/qr/WCGJIID7UKWIE1",
alt: "WhatsApp Logo",
icon: faWhatsapp,
alt: "WhatsApp Logo",
background: "#25D366",
color: "#FFF",
color: "#FFF",
icon: faWhatsapp,
label: "WhatsApp",
link: "https://wa.me/qr/WCGJIID7UKWIE1",
},
{
label: "Google Chat",
link: "https://chat.google.com/room/AAAAK270m3M",
alt: "Google Chat Logo",
icon: faGooglePlus,
alt: "Google Chat Logo",
background: "#DB4437",
color: "#FFF",
color: "#FFF",
icon: faGooglePlus,
label: "Google Chat",
link: "https://chat.google.com/room/AAAAK270m3M",
},
{
label: "Tree Nation",
link: "https://tree-nation.com/profile/naomi-carrigan",
alt: "Tree Nation Logo",
icon: TreeNation,
alt: "Tree Nation Logo",
background: "#FFF",
color: "#55C1A8",
color: "#55C1A8",
icon: TreeNation,
label: "Tree Nation",
link: "https://tree-nation.com/profile/naomi-carrigan",
},
{
label: "Meetup",
link: "https://www.meetup.com/members/437962584/",
alt: "Meetup Logo",
icon: faMeetup,
alt: "Meetup Logo",
background: "#FF6D00",
color: "#FFF",
color: "#FFF",
icon: faMeetup,
label: "Meetup",
link: "https://www.meetup.com/members/437962584/",
},
{
label: "Tumblr",
link: "https://www.tumblr.com/naomi-lgbt",
alt: "Tumblr Logo",
icon: faTumblr,
alt: "Tumblr Logo",
background: "#36465D",
color: "#FFF",
color: "#FFF",
icon: faTumblr,
label: "Tumblr",
link: "https://www.tumblr.com/naomi-lgbt",
},
{
label: "YouTube",
link: "https://www.youtube.com/@naomilgbt",
alt: "YouTube Logo",
icon: faYoutube,
alt: "YouTube Logo",
background: "#FF0000",
color: "#FFF",
color: "#FFF",
icon: faYoutube,
label: "YouTube",
link: "https://www.youtube.com/@naomilgbt",
},
{
label: "Tiktok",
link: "https://www.tiktok.com/@naomilgbt",
alt: "Tiktok Logo",
icon: faTiktok,
alt: "Tiktok Logo",
background: "#000",
color: "#FFF",
color: "#FFF",
icon: faTiktok,
label: "Tiktok",
link: "https://www.tiktok.com/@naomilgbt",
},
{
label: "Threads",
link: "https://www.threads.net/@naomi.lgbt",
alt: "Threads Logo",
icon: faThreads,
alt: "Threads Logo",
background: "#000",
color: "#FFF",
color: "#FFF",
icon: faThreads,
label: "Threads",
link: "https://www.threads.net/@naomi.lgbt",
},
{
label: "Instagram",
link: "https://www.instagram.com/naomi.lgbt/",
alt: "Instagram Logo",
icon: faInstagram,
alt: "Instagram Logo",
background: "#C13584",
color: "#FFF",
}
color: "#FFF",
icon: faInstagram,
label: "Instagram",
link: "https://www.instagram.com/naomi.lgbt/",
},
];
export { HireMe, Donate, Socials };

View File

@ -1,36 +1,45 @@
export const TeamMembers: {
name: string;
avatar: string;
role: string;
url: string;
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/**
* List of nhcarrigan team members to render.
*/
export const TeamMembers: Array<{
name: string;
avatar: string;
role: string;
url: string;
joinDate: Date;
}[] = [
{
name: "Naomi Carrigan",
avatar: "naomi.png",
role: "Founder / CEO",
url: "https://chat.nhcarrigan.com",
joinDate: new Date("April 1, 2020")
},
{
name: "Denna",
avatar: "denna.png",
role: "Chief Financial Officer",
url: "https://denna.nhcarrigan.com",
joinDate: new Date("April 2, 2020")
},
{
name: "Tim",
avatar: "tim.png",
role: "Chief Technical Officer",
url: "https://chat.nhcarrigan.com",
joinDate: new Date("April 2, 2020")
},
{
name: "Anna",
avatar: "anna.png",
role: "Software Engineering Intern",
url: "https://chat.nhcarrigan.com",
joinDate: new Date("August 29, 2023")
}
]
}> = [
{
avatar: "naomi.png",
joinDate: new Date("April 1, 2020"),
name: "Naomi Carrigan",
role: "Founder / CEO",
url: "https://chat.nhcarrigan.com",
},
{
avatar: "denna.png",
joinDate: new Date("April 2, 2020"),
name: "Denna",
role: "Chief Financial Officer",
url: "https://denna.nhcarrigan.com",
},
{
avatar: "tim.png",
joinDate: new Date("April 2, 2020"),
name: "Tim",
role: "Chief Technical Officer",
url: "https://chat.nhcarrigan.com",
},
{
avatar: "anna.png",
joinDate: new Date("August 29, 2023"),
name: "Anna",
role: "Software Engineering Intern",
url: "https://chat.nhcarrigan.com",
},
];

View File

@ -1,82 +1,90 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { faLinkedin } from "@fortawesome/free-brands-svg-icons";
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
export const Testimonials: {
name: string;
date: Date;
content: string;
/**
* List of reviews to render.
*/
export const Testimonials: Array<{
name: string;
date: Date;
content: string;
sourceIcon: IconDefinition;
sourceUrl: string;
sourceUrl: string;
sourceName: string;
}[] = [
}> = [
{
name: "Eddie Jaoude",
date: new Date("June 30, 2023"),
content:
"Naomi has done a fantastic job in creating Becca Bot, which is an integral part in managing the EddieHub Discord Community. As founder of EddieHub, Naomi is super helpful to all Community members and an excellent moderator, from our text channels to audio calls and live streams. Naomi demonstrates an excellent technical knowledge and is always keen to share this with the community.",
date: new Date("June 30, 2023"),
name: "Eddie Jaoude",
sourceIcon: faLinkedin,
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
sourceName: "LinkedIn"
sourceName: "LinkedIn",
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
},
{
name: "Danny Thompson",
date: new Date("July 6 2023"),
content:
"If you need a problem solver, look at Naomi. Naomi is a fantastic part of the online tech community by teaching and offering help to beginners on their journeys into tech. She has created some great solutions and is a consistent learner. Naomi has led initiatives using Javascript and front-end technologies to produce finished products within a volunteer position. Highly recommend Naomi to any team.",
date: new Date("July 6 2023"),
name: "Danny Thompson",
sourceIcon: faLinkedin,
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
sourceName: "LinkedIn"
sourceName: "LinkedIn",
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
},
{
name: "Francez Urmatan",
date: new Date("May 2 2024"),
content:
"Naomi is an absolute trailblazer, and is an amazing person to work with! Naomi is humorous and also has an amazing attitude to work with. Her ability to solve complex problems efficiently astounds me. Not only does she demonstrate outstanding technical knowledge, but also does an amazing job at elucidating her needs as an engineer. She is a very warm person and quite easy to work with. Naomi is immensely perceptive and very calculated with what she does. Naomi would make an excellent addition to any company that is lucky enough to hire her!",
date: new Date("May 2 2024"),
name: "Francez Urmatan",
sourceIcon: faLinkedin,
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
sourceName: "LinkedIn"
sourceName: "LinkedIn",
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
},
{
name: "Katey Berry",
date: new Date("May 14 2024"),
content:
"I've worked alongside Naomi on a number of projects, and it is always a blessing to have her on the team. She is knowledgable, reliable, and always willing to jump in with creative and efficient engineering solutions to complex workflow problems. Naomi is also such a patient teacher, effectively explaining how things work and enabling others to become more independent. I always look forward to working with Naomi, and recommend you work with her if you have the opportunity!",
date: new Date("May 14 2024"),
name: "Katey Berry",
sourceIcon: faLinkedin,
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
sourceName: "LinkedIn"
sourceName: "LinkedIn",
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
},
{
name: "Kaitlyn Nichols",
date: new Date("May 30, 2024"),
content:
"She is a dedicated programmer and spends most of her time either making bots, or coding. She has an excellent work ethic and goes until she is satisfied with the end product.",
date: new Date("May 30, 2024"),
name: "Kaitlyn Nichols",
sourceIcon: faLinkedin,
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
sourceName: "LinkedIn"
sourceName: "LinkedIn",
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
},
{
name: "Alix Takada Sharp",
date: new Date("July 15, 2024"),
content:
"Naomi is an incredibly dynamic and resourceful professional, always ready with a multitude of solutions for any challenge. Her problem-solving skills are like watching a masterful sequence unfold, earning her the well-deserved nickname 'the technomancer.' Naomi's strong educational background is evident in her clear and concise explanations, making complex concepts easily understandable. She excels in communication, ensuring that everyone feels valued and integral to the team. Naomi is truly the glue that holds any team together. Adding her to your team will be a decision you won't regret.",
date: new Date("July 15, 2024"),
name: "Alix Takada Sharp",
sourceIcon: faLinkedin,
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
sourceName: "LinkedIn"
sourceName: "LinkedIn",
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
},
{
name: "Chris Ohman",
date: new Date("September 27, 2024"),
content: "Naomi's erudition on just about every development topic under the sun, expediency at completing massive technical undertakings solo, and undeniable passion for the work she does is an ever-present joy and boon for any organization lucky enough to work with her. As an example, Naomi composed an extensive and accessible DB comprising all available traits and behaviors of our Users, whipped up an extremely user-friendly API that our team of end-users could utilize seamlessly, and regularly developed additional clever dataflows for very particular asks. Every aspect of this work was done impeccably and frictionlessly, and that's just one of the dozens of projects she developed with our team during the time I worked alongside her. As brilliant as Naomi is, she's also humble, hilarious, and an all-around aspirational figure to all people looking to find true passion for themselves alongside professional success. I strive to be more like her, and I know I'm not alone in that.",
content: "Naomi's erudition on just about every development topic under the sun, expediency at completing massive technical undertakings solo, and undeniable passion for the work she does is an ever-present joy and boon for any organization lucky enough to work with her. As an example, Naomi composed an extensive and accessible DB comprising all available traits and behaviors of our Users, whipped up an extremely user-friendly API that our team of end-users could utilize seamlessly, and regularly developed additional clever dataflows for very particular asks. Every aspect of this work was done impeccably and frictionlessly, and that's just one of the dozens of projects she developed with our team during the time I worked alongside her. As brilliant as Naomi is, she's also humble, hilarious, and an all-around aspirational figure to all people looking to find true passion for themselves alongside professional success. I strive to be more like her, and I know I'm not alone in that.",
date: new Date("September 27, 2024"),
name: "Chris Ohman",
sourceIcon: faLinkedin,
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
sourceName: "LinkedIn"
sourceName: "LinkedIn",
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
},
{
name: "Alexis Madsen",
date: new Date("October 28 2024"),
content: "Naomi always had the knowledge and information for nearly every question I ever had. It was such an honor to work with her, and anyone would be lucky to have the chance for her to work with you or your team.",
content: "Naomi always had the knowledge and information for nearly every question I ever had. It was such an honor to work with her, and anyone would be lucky to have the chance for her to work with you or your team.",
date: new Date("October 28 2024"),
name: "Alexis Madsen",
sourceIcon: faLinkedin,
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
sourceName: "LinkedIn"
}
sourceName: "LinkedIn",
sourceUrl: "https://www.linkedin.com/in/naomi-lgbt/details/recommendations/",
},
];

View File

@ -1,8 +1,15 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* Custom FontAwesome icon definition for the
* Codeberg logo.
*/
export const Codeberg: IconDefinition = {
prefix: "xxx",
iconName: "yyy",
icon: [
474,
474,
@ -59,6 +66,8 @@ export const Codeberg: IconDefinition = {
331.00,465.00 315.85,409.00 315.85,409.00
315.85,409.00 283.85,289.00 283.85,289.00
283.85,289.00 257.12,189.00 257.12,189.00
257.12,189.00 244.00,138.00 244.00,138.00 Z`
]
257.12,189.00 244.00,138.00 244.00,138.00 Z`,
],
iconName: "yyy",
prefix: "xxx",
} as never;

View File

@ -1,8 +1,15 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* Custom FontAwesome icon definition for the
* Fiverr logo.
*/
export const Fiverr: IconDefinition = {
prefix: "xxx",
iconName: "yyy",
icon: [
300,
300,
@ -49,6 +56,8 @@ export const Fiverr: IconDefinition = {
138.00,106.78 137.73,101.98 139.34,98.01
143.35,88.13 153.31,89.00 162.00,89.00
162.00,89.00 176.00,89.00 176.00,89.00
176.00,89.00 176.00,58.00 176.00,58.00 Z`
]
176.00,89.00 176.00,58.00 176.00,58.00 Z`,
],
iconName: "yyy",
prefix: "xxx",
} as never;

View File

@ -1,8 +1,15 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* Custom FontAwesome icon definition for the
* GatherTown logo.
*/
export const Gather: IconDefinition = {
prefix: "xxx",
iconName: "yyy",
icon: [
38,
42,
@ -69,6 +76,8 @@ export const Gather: IconDefinition = {
25.00,39.00 27.00,39.00 27.00,39.00
27.00,39.00 28.00,38.00 28.00,38.00
28.00,38.00 28.00,36.00 28.00,36.00
28.00,36.00 27.00,35.00 27.00,35.00 Z`
]
28.00,36.00 27.00,35.00 27.00,35.00 Z`,
],
iconName: "yyy",
prefix: "xxx",
} as never;

View File

@ -1,13 +1,22 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* Custom FontAwesome icon definition for the
* Good Ol' Games logo.
*/
export const Gog: IconDefinition = {
prefix: "xxx",
iconName: "yyy",
icon: [
34,
31,
[],
"U+E002",
`M31,31H3a3,3,0,0,1-3-3V3A3,3,0,0,1,3,0H31a3,3,0,0,1,3,3V28A3,3,0,0,1,31,31ZM4,24.5A1.5,1.5,0,0,0,5.5,26H11V24H6.5a.5.5,0,0,1-.5-.5v-3a.5.5,0,0,1,.5-.5H11V18H5.5A1.5,1.5,0,0,0,4,19.5Zm8-18A1.5,1.5,0,0,0,10.5,5h-5A1.5,1.5,0,0,0,4,6.5v5A1.5,1.5,0,0,0,5.5,13H9V11H6.5a.5.5,0,0,1-.5-.5v-3A.5.5,0,0,1,6.5,7h3a.5.5,0,0,1,.5.5v6a.5.5,0,0,1-.5.5H4v2h6.5A1.5,1.5,0,0,0,12,14.5Zm0,13v5A1.5,1.5,0,0,0,13.5,26h5A1.5,1.5,0,0,0,20,24.5v-5A1.5,1.5,0,0,0,18.5,18h-5A1.5,1.5,0,0,0,12,19.5Zm9-13A1.5,1.5,0,0,0,19.5,5h-5A1.5,1.5,0,0,0,13,6.5v5A1.5,1.5,0,0,0,14.5,13h5A1.5,1.5,0,0,0,21,11.5Zm9,0A1.5,1.5,0,0,0,28.5,5h-5A1.5,1.5,0,0,0,22,6.5v5A1.5,1.5,0,0,0,23.5,13H27V11H24.5a.5.5,0,0,1-.5-.5v-3a.5.5,0,0,1,.5-.5h3a.5.5,0,0,1,.5.5v6a.5.5,0,0,1-.5.5H22v2h6.5A1.5,1.5,0,0,0,30,14.5ZM30,18H22.5A1.5,1.5,0,0,0,21,19.5V26h2V20.5a.5.5,0,0,1,.5-.5h1v6h2V20H28v6h2ZM18.5,11h-3a.5.5,0,0,1-.5-.5v-3a.5.5,0,0,1,.5-.5h3a.5.5,0,0,1,.5.5v3A.5.5,0,0,1,18.5,11Zm-4,9h3a.5.5,0,0,1,.5.5v3a.5.5,0,0,1-.5.5h-3a.5.5,0,0,1-.5-.5v-3A.5.5,0,0,1,14.5,20Z`
]
`M31,31H3a3,3,0,0,1-3-3V3A3,3,0,0,1,3,0H31a3,3,0,0,1,3,3V28A3,3,0,0,1,31,31ZM4,24.5A1.5,1.5,0,0,0,5.5,26H11V24H6.5a.5.5,0,0,1-.5-.5v-3a.5.5,0,0,1,.5-.5H11V18H5.5A1.5,1.5,0,0,0,4,19.5Zm8-18A1.5,1.5,0,0,0,10.5,5h-5A1.5,1.5,0,0,0,4,6.5v5A1.5,1.5,0,0,0,5.5,13H9V11H6.5a.5.5,0,0,1-.5-.5v-3A.5.5,0,0,1,6.5,7h3a.5.5,0,0,1,.5.5v6a.5.5,0,0,1-.5.5H4v2h6.5A1.5,1.5,0,0,0,12,14.5Zm0,13v5A1.5,1.5,0,0,0,13.5,26h5A1.5,1.5,0,0,0,20,24.5v-5A1.5,1.5,0,0,0,18.5,18h-5A1.5,1.5,0,0,0,12,19.5Zm9-13A1.5,1.5,0,0,0,19.5,5h-5A1.5,1.5,0,0,0,13,6.5v5A1.5,1.5,0,0,0,14.5,13h5A1.5,1.5,0,0,0,21,11.5Zm9,0A1.5,1.5,0,0,0,28.5,5h-5A1.5,1.5,0,0,0,22,6.5v5A1.5,1.5,0,0,0,23.5,13H27V11H24.5a.5.5,0,0,1-.5-.5v-3a.5.5,0,0,1,.5-.5h3a.5.5,0,0,1,.5.5v6a.5.5,0,0,1-.5.5H22v2h6.5A1.5,1.5,0,0,0,30,14.5ZM30,18H22.5A1.5,1.5,0,0,0,21,19.5V26h2V20.5a.5.5,0,0,1,.5-.5h1v6h2V20H28v6h2ZM18.5,11h-3a.5.5,0,0,1-.5-.5v-3a.5.5,0,0,1,.5-.5h3a.5.5,0,0,1,.5.5v3A.5.5,0,0,1,18.5,11Zm-4,9h3a.5.5,0,0,1,.5.5v3a.5.5,0,0,1-.5.5h-3a.5.5,0,0,1-.5-.5v-3A.5.5,0,0,1,14.5,20Z`,
],
iconName: "yyy",
prefix: "xxx",
} as never;

View File

@ -1,8 +1,15 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* Custom FontAwesome icon definition for the
* Ko-Fi logo.
*/
export const Kofi: IconDefinition = {
prefix: "xxx",
iconName: "yyy",
icon: [
245,
162,
@ -63,6 +70,8 @@ export const Kofi: IconDefinition = {
C 186.51,82.86 192.10,81.90 196.79,76.90
204.64,68.51 202.92,52.32 192.96,46.23
188.79,43.68 184.66,44.00 180.00,44.00
180.00,44.00 180.00,83.00 180.00,83.00 Z`
]
180.00,44.00 180.00,83.00 180.00,83.00 Z`,
],
iconName: "yyy",
prefix: "xxx",
} as never;

View File

@ -1,8 +1,15 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* Custom FontAwesome icon definition for the
* Matrix.org logo.
*/
export const Matrix: IconDefinition = {
prefix: "xxx",
iconName: "yyy",
icon: [
342,
342,
@ -56,6 +63,8 @@ export const Matrix: IconDefinition = {
119.62,325.74 86.75,295.86 70.60,256.00
70.60,256.00 66.29,243.00 66.29,243.00
63.87,234.58 62.76,227.67 61.84,219.00
60.14,203.20 59.71,189.94 78.00,184.53 Z`
]
60.14,203.20 59.71,189.94 78.00,184.53 Z`,
],
iconName: "yyy",
prefix: "xxx",
} as never;

View File

@ -1,8 +1,15 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* Custom FontAwesome icon definition for the
* Peerlist logo.
*/
export const Peerlist: IconDefinition = {
prefix: "xxx",
iconName: "yyy",
icon: [
512,
512,
@ -122,6 +129,8 @@ export const Peerlist: IconDefinition = {
258.53,264.02 277.96,264.47 284.58,259.01
289.25,255.16 289.06,244.57 289.00,239.00
288.79,221.67 275.23,207.53 259.00,203.16
253.08,201.56 247.07,202.00 241.00,202.00 Z`
]
253.08,201.56 247.07,202.00 241.00,202.00 Z`,
],
iconName: "yyy",
prefix: "xxx",
} as never;

View File

@ -1,8 +1,15 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* Custom FontAwesome icon definition for the
* Pixiv logo.
*/
export const Pixiv: IconDefinition = {
prefix: "xxx",
iconName: "yyy",
icon: [
900,
900,
@ -90,6 +97,8 @@ export const Pixiv: IconDefinition = {
320.00,248.81 342.00,237.22 342.00,237.22
367.65,225.16 397.00,216.77 425.00,212.42
425.00,212.42 450.00,209.21 450.00,209.21 Z
`
]
`,
],
iconName: "yyy",
prefix: "xxx",
} as never;

View File

@ -1,13 +1,20 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* Custom FontAwesome icon definition for the
* Polywork logo.
*/
export const Polywork: IconDefinition = {
prefix: "xxx",
iconName: "yyy",
icon: [
225, // SVG view box width
225, // SVG view box height
225,
225,
[],
"U+E002", // probably not important for SVG and JS approach
"U+E002",
`M 153.00,155.00
C 153.00,177.49 155.16,201.18 134.00,215.78
117.90,226.88 101.50,224.00 83.00,224.00
@ -71,6 +78,8 @@ export const Polywork: IconDefinition = {
83.00,158.00 83.00,213.00 83.00,213.00
83.00,213.00 106.00,213.00 106.00,213.00
110.59,212.99 113.55,213.02 118.00,211.45
143.85,202.34 141.00,180.01 141.00,158.00 Z`
]
143.85,202.34 141.00,180.01 141.00,158.00 Z`,
],
iconName: "yyy",
prefix: "xxx",
} as never;

View File

@ -1,8 +1,15 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* Custom FontAwesome icon definition for the
* Saylor Academy logo.
*/
export const Saylor: IconDefinition = {
prefix: "xxx",
iconName: "yyy",
icon: [
171,
134,
@ -56,6 +63,8 @@ export const Saylor: IconDefinition = {
M 99.00,105.00
C 99.00,105.00 98.00,105.00 98.00,105.00
98.00,105.00 99.00,106.00 99.00,106.00
99.00,106.00 99.00,105.00 99.00,105.00 Z`
]
99.00,106.00 99.00,105.00 99.00,105.00 Z`,
],
iconName: "yyy",
prefix: "xxx",
} as never;

View File

@ -1,8 +1,15 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* Custom FontAwesome icon definition for the
* TeeSpring logo.
*/
export const TeeSpring: IconDefinition = {
prefix: "xxx",
iconName: "yyy",
icon: [
475,
545,
@ -62,4 +69,6 @@ export const TeeSpring: IconDefinition = {
151.90,337.62 201.72,367.04 243.00,373.56
254.27,375.34 265.63,375.00 277.00,375.00 Z`,
],
iconName: "yyy",
prefix: "xxx",
} as never;

View File

@ -1,8 +1,15 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* Custom FontAwesome icon definition for the
* Tree Nation logo.
*/
export const TreeNation: IconDefinition = {
prefix: "xxx",
iconName: "yyy",
icon: [
52,
52,
@ -134,6 +141,8 @@ export const TreeNation: IconDefinition = {
M 23.00,34.00
C 23.00,34.00 22.00,34.00 22.00,34.00
22.00,34.00 23.00,35.00 23.00,35.00
23.00,35.00 23.00,34.00 23.00,34.00 Z`
]
23.00,35.00 23.00,34.00 23.00,34.00 Z`,
],
iconName: "yyy",
prefix: "xxx",
} as never;

View File

@ -1,8 +1,15 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* Custom FontAwesome icon definition for the
* VRoid Hub logo.
*/
export const VRoid: IconDefinition = {
prefix: "xxx",
iconName: "yyy",
icon: [
280,
280,
@ -341,6 +348,8 @@ export const VRoid: IconDefinition = {
86.00,263.00 72.00,264.00 72.00,264.00
72.00,264.00 167.00,264.00 167.00,264.00
167.00,264.00 194.00,264.00 194.00,264.00
194.00,264.00 208.00,263.00 208.00,263.00 Z`
]
194.00,264.00 208.00,263.00 208.00,263.00 Z`,
],
iconName: "yyy",
prefix: "xxx",
} as never;

View File

@ -1,8 +1,15 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
/**
* @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 = {
prefix: "xxx",
iconName: "yyy",
icon: [
800,
798,
@ -178,6 +185,8 @@ export const Volunteer: IconDefinition = {
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`
]
410.00,559.00 410.00,545.00 410.00,545.00 Z`,
],
iconName: "yyy",
prefix: "xxx",
} as never;

View File

@ -1,190 +1,204 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
interface ActivityData {
id: number
user_id: number
op_type: string
act_user_id: number
act_user: ActUser
repo_id: number
repo: Repo
comment_id: number
comment?: Comment
ref_name: string
is_private: boolean
content: string
created: string
id: number;
user_id: number;
op_type: string;
act_user_id: number;
act_user: ActUser;
repo_id: number;
repo: Repo;
comment_id: number;
comment?: Comment;
ref_name: string;
is_private: boolean;
content: string;
created: string;
}
interface ActUser {
id: number
login: string
login_name: string
source_id: number
full_name: string
email: string
avatar_url: string
html_url: string
language: string
is_admin: boolean
last_login: string
created: string
restricted: boolean
active: boolean
prohibit_login: boolean
location: string
pronouns: string
website: string
description: string
visibility: string
followers_count: number
following_count: number
starred_repos_count: number
username: string
id: number;
login: string;
login_name: string;
source_id: number;
full_name: string;
email: string;
avatar_url: string;
html_url: string;
language: string;
is_admin: boolean;
last_login: string;
created: string;
restricted: boolean;
active: boolean;
prohibit_login: boolean;
location: string;
pronouns: string;
website: string;
description: string;
visibility: string;
followers_count: number;
following_count: number;
starred_repos_count: number;
username: string;
}
interface Repo {
id: number
owner: Owner
name: string
full_name: string
description: string
empty: boolean
private: boolean
fork: boolean
template: boolean
parent: any
mirror: boolean
size: number
language: string
languages_url: string
html_url: string
url: string
link: string
ssh_url: string
clone_url: string
original_url: string
website: string
stars_count: number
forks_count: number
watchers_count: number
open_issues_count: number
open_pr_counter: number
release_counter: number
default_branch: string
archived: boolean
created_at: string
updated_at: string
archived_at: string
permissions: Permissions
has_issues: boolean
external_tracker: ExternalTracker
has_wiki: boolean
wiki_branch: string
globally_editable_wiki: boolean
has_pull_requests: boolean
has_projects: boolean
has_releases: boolean
has_packages: boolean
has_actions: boolean
ignore_whitespace_conflicts: boolean
allow_merge_commits: boolean
allow_rebase: boolean
allow_rebase_explicit: boolean
allow_squash_merge: boolean
allow_fast_forward_only_merge: boolean
allow_rebase_update: boolean
default_delete_branch_after_merge: boolean
default_merge_style: string
default_allow_maintainer_edit: boolean
avatar_url: string
internal: boolean
mirror_interval: string
object_format_name: string
mirror_updated: string
repo_transfer: any
topics: any
id: number;
owner: Owner;
name: string;
full_name: string;
description: string;
empty: boolean;
private: boolean;
fork: boolean;
template: boolean;
parent: unknown;
mirror: boolean;
size: number;
language: string;
languages_url: string;
html_url: string;
url: string;
link: string;
ssh_url: string;
clone_url: string;
original_url: string;
website: string;
stars_count: number;
forks_count: number;
watchers_count: number;
open_issues_count: number;
open_pr_counter: number;
release_counter: number;
default_branch: string;
archived: boolean;
created_at: string;
updated_at: string;
archived_at: string;
permissions: Permissions;
has_issues: boolean;
external_tracker: ExternalTracker;
has_wiki: boolean;
wiki_branch: string;
globally_editable_wiki: boolean;
has_pull_requests: boolean;
has_projects: boolean;
has_releases: boolean;
has_packages: boolean;
has_actions: boolean;
ignore_whitespace_conflicts: boolean;
allow_merge_commits: boolean;
allow_rebase: boolean;
allow_rebase_explicit: boolean;
allow_squash_merge: boolean;
allow_fast_forward_only_merge: boolean;
allow_rebase_update: boolean;
default_delete_branch_after_merge: boolean;
default_merge_style: string;
default_allow_maintainer_edit: boolean;
avatar_url: string;
internal: boolean;
mirror_interval: string;
object_format_name: string;
mirror_updated: string;
repo_transfer: unknown;
topics: unknown;
}
interface Owner {
id: number
login: string
login_name: string
source_id: number
full_name: string
email: string
avatar_url: string
html_url: string
language: string
is_admin: boolean
last_login: string
created: string
restricted: boolean
active: boolean
prohibit_login: boolean
location: string
pronouns: string
website: string
description: string
visibility: string
followers_count: number
following_count: number
starred_repos_count: number
username: string
id: number;
login: string;
login_name: string;
source_id: number;
full_name: string;
email: string;
avatar_url: string;
html_url: string;
language: string;
is_admin: boolean;
last_login: string;
created: string;
restricted: boolean;
active: boolean;
prohibit_login: boolean;
location: string;
pronouns: string;
website: string;
description: string;
visibility: string;
followers_count: number;
following_count: number;
starred_repos_count: number;
username: string;
}
interface Permissions {
admin: boolean
push: boolean
pull: boolean
admin: boolean;
push: boolean;
pull: boolean;
}
interface ExternalTracker {
external_tracker_url: string
external_tracker_format: string
external_tracker_style: string
external_tracker_regexp_pattern: string
external_tracker_url: string;
external_tracker_format: string;
external_tracker_style: string;
external_tracker_regexp_pattern: string;
}
interface Comment {
id: number
html_url: string
pull_request_url: string
issue_url: string
user: any
original_author: string
original_author_id: number
body: string
assets: any[]
created_at: string
updated_at: string
id: number;
html_url: string;
pull_request_url: string;
issue_url: string;
user: unknown;
original_author: string;
original_author_id: number;
body: string;
assets: Array<unknown>;
created_at: string;
updated_at: string;
}
class Codeberg {
private cache: ActivityData[];
private cache: Array<ActivityData>;
private lastUpdate: Date | null = null;
constructor() {
public constructor() {
this.cache = [];
}
public async getActivities(): Promise<Array<ActivityData>> {
// Stale after 6 hours
if (this.lastUpdate && Date.now()
- this.lastUpdate.getTime() < 6 * 60 * 60 * 1000) {
return this.cache;
}
return await this.refreshData().then(() => {
return this.cache;
});
}
private async refreshData(): Promise<Codeberg> {
const response = await fetch("https://codeberg.org/api/v1/users/naomi-lgbt/activities/feeds?only-performed-by=true");
const data = await response.json() as ActivityData[];
const response = await
// eslint-disable-next-line stylistic/max-len
fetch("https://codeberg.org/api/v1/users/naomi-lgbt/activities/feeds?only-performed-by=true");
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const data = await response.json() as Array<ActivityData>;
this.cache = data;
this.lastUpdate = new Date();
return this;
}
public async getActivities(): Promise<ActivityData[]> {
// Stale after 6 hours
if (this.lastUpdate && (new Date().getTime() - this.lastUpdate.getTime()) < 6 * 60 * 60 * 1000) {
return this.cache;
}
return this.refreshData().then(() => this.cache);
}
}
const instantiated = new Codeberg();
export const getCodebergData = async (): Promise<ActivityData[]> => {
/**
* Fetches and caches the Codeberg activity.
* @returns An array of activity objects.
*/
export const getCodebergData = async(): Promise<Array<ActivityData>> => {
return await instantiated.getActivities();
}
};

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,12 @@
{
"extends": "@nhcarrigan/typescript-config",
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
@ -18,9 +19,19 @@
}
],
"paths": {
"@/*": ["./src/*"]
}
"@/*": [
"./src/*"
]
},
"allowJs": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}