feat: site-wide content and feature updates (#2)
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 2m7s

A wide-ranging set of updates across multiple pages that accumulated over time.

## Tarot

- Expanded spread descriptions and added "good for" guidance to help users pick the right spread
- Cards now start face-down and flip individually on click, with a synthesised flip sound and text fade-in
- "Click card to reveal" hint shown on each unflipped card
- Spread selection and draw button are locked until all cards in the current reading have been revealed

## Nocturne

- Added dedicated sacred scriptures showcase section
- Added patron saints section with avatar images
- Added Naomi's Prayer
- Added sacred practices section (expanded to six cards)
- Added clergy and hierarchy section
- Added titles of address section
- Added testimonial and expanded FAQ
- Capitalised She/Her pronouns referring to Naomi

## Scripture

- Converted page to interactive book with page-turning
- Expanded canon through the Fourth Edition across multiple updates
- Synced the Ten Commandments with Nocturne
- Fixed meta description to reflect fourteen books
- Capitalised She/Her pronouns referring to Naomi

## Other Pages

- Books, games, and music pages now redirect to `library.nhcarrigan.com`
- Updated user manual with additional context
- Added 404 error page

## Chore / Fixes

- Updated sitemap with new and corrected entries
- Fixed null-safe while loop for filename handling in scripts
- Updated scripts to use correct Windows paths via WSL
- Cleaned up data files and added JSON to `.gitignore`
- Added secret #13

 This PR was created with help from Hikari~ 🌸

Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Reviewed-on: #2
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #2.
This commit is contained in:
2026-03-18 12:31:50 -07:00
committed by Naomi Carrigan
parent b799dae225
commit 8c958a2f71
16 changed files with 5218 additions and 84331 deletions
+3
View File
@@ -0,0 +1,3 @@
*.json
tarot/generate.py
tarot/.venv/
+289
View File
@@ -0,0 +1,289 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>404 — Lost in the Void</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
name="description"
content="The page you sought has slipped into the void. Even a 525-year-old vampire cannot find it."
/>
<script
src="https://cdn.nhcarrigan.com/headers/index.js"
async
defer
></script>
<style>
/* ========== LAYOUT ========== */
main {
z-index: 1;
}
footer {
z-index: 2;
}
/* ========== HERO ========== */
.error-hero {
text-align: center;
padding: 2em 1em 2.5em;
border-bottom: 2px solid var(--witch-plum);
margin-bottom: 2.5em;
}
.error-code {
font-family: 'Griffy', cursive;
font-size: 7rem;
line-height: 1;
color: var(--witch-plum);
letter-spacing: 0.1em;
margin: 0 0 0.15em;
text-shadow: 0 0 32px rgba(130, 80, 120, 0.35);
}
.error-seal {
font-size: 3.5rem;
display: block;
margin: 0 auto 0.5em;
animation: floatSeal 4s ease-in-out infinite;
}
@keyframes floatSeal {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
.error-hero h1 {
font-size: 2rem;
letter-spacing: 0.06em;
margin-bottom: 0.4em;
}
.error-tagline {
font-size: 1.05rem;
color: var(--witch-plum);
font-style: italic;
max-width: 560px;
margin: 0 auto;
line-height: 1.6;
}
/* ========== MESSAGE SCROLL ========== */
.void-section {
margin: 0 auto 3em;
max-width: 680px;
text-align: center;
}
.void-section > h2 {
border-bottom: 2px solid var(--witch-plum);
padding-bottom: 0.4em;
margin-bottom: 1em;
letter-spacing: 0.05em;
}
.void-scroll {
background: rgba(212, 165, 199, 0.08);
border: 1px solid var(--witch-plum);
border-radius: 15px;
padding: 1.75em 2em;
font-size: 1rem;
line-height: 1.75;
text-align: left;
}
.void-scroll p + p {
margin-top: 0.75em;
}
.void-scroll .accent {
color: var(--witch-plum);
font-weight: 600;
}
/* ========== NAVIGATION CARDS ========== */
.nav-section {
margin: 0 auto 3em;
max-width: 900px;
}
.nav-section > h2 {
border-bottom: 2px solid var(--witch-plum);
padding-bottom: 0.4em;
margin-bottom: 1.25em;
letter-spacing: 0.05em;
text-align: center;
}
.nav-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 1.25em;
}
.nav-card {
background: rgba(212, 165, 199, 0.08);
border: 1px solid var(--witch-plum);
border-radius: 15px;
padding: 1.25em 1.5em;
text-align: center;
text-decoration: none;
color: inherit;
transition:
background 0.2s ease,
transform 0.2s ease,
box-shadow 0.2s ease;
cursor: url('https://cdn.nhcarrigan.com/cursors/pointer.cur'),
pointer;
display: block;
}
.nav-card:hover {
background: rgba(212, 165, 199, 0.2);
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(130, 80, 120, 0.2);
text-decoration: none;
}
.nav-card-icon {
font-size: 2rem;
display: block;
margin-bottom: 0.4em;
}
.nav-card h3 {
font-family: 'Griffy', cursive;
font-size: 1.1rem;
margin: 0 0 0.3em;
letter-spacing: 0.04em;
color: var(--witch-plum);
}
.nav-card p {
font-size: 0.88rem;
margin: 0;
line-height: 1.5;
opacity: 0.85;
}
/* ========== RESPONSIVE ========== */
@media (max-width: 600px) {
.error-code {
font-size: 5rem;
}
.error-hero h1 {
font-size: 1.5rem;
}
.void-scroll {
padding: 1.25em 1.25em;
}
}
</style>
</head>
<body>
<main>
<!-- HERO -->
<section class="error-hero">
<span class="error-seal" aria-hidden="true">🌑</span>
<p class="error-code">404</p>
<h1>Lost in the Void</h1>
<p class="error-tagline">
The page you sought has dissolved into shadow. Even after 525 years,
some things cannot be found.
</p>
</section>
<!-- MESSAGE -->
<section class="void-section">
<h2>✦ What Happened? ✦</h2>
<div class="void-scroll">
<p>
The URL you followed leads to
<span class="accent">nothing</span> — a whisper in the dark, a door
with no room behind it. This could mean a few things:
</p>
<p>
The page may have been <span class="accent">moved</span>, renamed,
or quietly retired into the archives of history (much like certain
empires I have personally watched crumble).
</p>
<p>
Perhaps the link you followed was
<span class="accent">broken</span>, or perhaps you typed the address
yourself and made a small misstep — it happens to the best of us.
Even vampires miscount sometimes.
</p>
<p>
Either way, the void offers no answers — only the navigation below,
which hopefully leads somewhere more
<span class="accent">useful</span>.
</p>
</div>
</section>
<!-- NAVIGATION -->
<section class="nav-section">
<h2>✦ Find Your Way ✦</h2>
<nav class="nav-grid" aria-label="Site navigation">
<a href="https://nhcarrigan.com" class="nav-card">
<span class="nav-card-icon" aria-hidden="true">🏠</span>
<h3>Home</h3>
<p>Return to the main site and start fresh.</p>
</a>
<a href="https://chat.nhcarrigan.com" class="nav-card">
<span class="nav-card-icon" aria-hidden="true">💬</span>
<h3>Chat</h3>
<p>Join the community and say hello.</p>
</a>
<a href="https://support.nhcarrigan.com" class="nav-card">
<span class="nav-card-icon" aria-hidden="true">🛟</span>
<h3>Support</h3>
<p>Need help? Find support resources here.</p>
</a>
<a href="https://donate.nhcarrigan.com" class="nav-card">
<span class="nav-card-icon" aria-hidden="true">💜</span>
<h3>Donate</h3>
<p>Support the work if you find it valuable.</p>
</a>
<a href="https://socials.nhcarrigan.com" class="nav-card">
<span class="nav-card-icon" aria-hidden="true">🌐</span>
<h3>Socials</h3>
<p>Find Naomi across the many corners of the internet.</p>
</a>
<a href="https://git.nhcarrigan.com" class="nav-card">
<span class="nav-card-icon" aria-hidden="true">💻</span>
<h3>Code</h3>
<p>Browse the source code and open-source projects.</p>
</a>
<a href="https://library.nhcarrigan.com" class="nav-card">
<span class="nav-card-icon" aria-hidden="true">📚</span>
<h3>Library</h3>
<p>Explore the ever-growing collection of books.</p>
</a>
<a href="https://nocturne.nhcarrigan.com" class="nav-card">
<span class="nav-card-icon" aria-hidden="true">🧛</span>
<h3>Nocturne</h3>
<p>The sacred faith of Naomi's Nocturne. No garlic bread.</p>
</a>
<a href="https://sitemap.nhcarrigan.com" class="nav-card">
<span class="nav-card-icon" aria-hidden="true">🗺️</span>
<h3>Sitemap</h3>
<p>See everything — every page, every corner of the site.</p>
</a>
</nav>
</section>
</main>
</body>
</html>
-36
View File
@@ -1,36 +0,0 @@
IFS=$'\n'
# Initialize an empty string to hold the list of books in JSON-like format
books=""
filecount=$(find /home/naomi/cloud/Books -type f | wc -l)
echo "Found $filecount files."
current=0
# Loop over each file found by find
for file in $(find /home/naomi/cloud/Books -type f -print0 | tr '\0' '\n'); do
current=$((current + 1))
echo -ne "Processing $current/$filecount\r"
title=$(exiftool "$file" | grep "^Title\s*:" | cut -d ":" -f 2 | sed -e 's/^[[:space:]]*//')
author=$(exiftool "$file" | grep "^Creator\s*:" | cut -d ":" -f 2 | sed -e 's/^[[:space:]]*//')
if [ -z "$title" ]; then
# remove .mp3 from the title
title=$(basename "$file" | sed -e 's/\.*//g')
fi
if [ -z "$author" ]; then
author=$(exiftool "$file" | grep "^Author\s*:" | cut -d ":" -f 2 | sed -e 's/^[[:space:]]*//')
fi
if [ -z "$author" ]; then
author="Unknown Author"
fi
# use jq to add the book to the list
books="$books$(jq -n --arg title "$title" --arg author "$author" '{title: $title, author: $author}'),"
done
# Remove trailing comma and add square brackets to complete the list
books="[${books%,}]"
# Write to ./books/books.json
echo "$books" > ./books/books.json
echo -ne "Done!\r"
-1210
View File
File diff suppressed because it is too large Load Diff
+6 -105
View File
@@ -4,114 +4,15 @@
<title>Naomi's Book Library</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="An interactive explorer for the books Naomi reads." />
<meta name="description" content="Naomi's book library has moved to library.nhcarrigan.com." />
<script src="https://cdn.nhcarrigan.com/headers/index.js" async defer></script>
</head>
<body>
<main>
<h1>Naomi's Book Library</h1>
<section>
<p>An interactive explorer for the books Naomi reads.</p>
<p id="count">Loading library...</p>
</section>
<div style="display: none;">
<span>Search Authors: </span>
<input type="text" id="author" />
</div>
<div style="display: none;">
<span>Search Titles: </span>
<input type="text" id="title" />
</div>
<div style="display: none;">
<button type="button" id="clear">Clear Filters</button>
</div>
<table id="books">
</table>
<h1>Naomi's Book Library</h1>
<section>
<p>This page has moved! You can find Naomi's book library at <a href="https://library.nhcarrigan.com">library.nhcarrigan.com</a>.</p>
</section>
</main>
</body>
<script>
const authorQuery = document.getElementById('author');
const titleQuery = document.getElementById('title');
const resetButton = document.getElementById('clear');
const bookTable = document.getElementById('books');
const filterBooks = (author, title) => {
let result = [...bookList];
if(author) {
result = result.filter(book => book.author.toLowerCase().includes(author.toLowerCase()));
}
if(title) {
result = result.filter(book => book.title.toLowerCase().includes(title.toLowerCase()));
}
resetButton.parentElement.style.display = author || title ? "block" : "none";
document.getElementById('count').innerText = author || title ? `Filtered to ${result.length} books from ${bookList.length}.` : `Naomi currently has ${bookList.length} books.`;
updateTable(result);
}
const loadBooks = (books) => {
bookList.push(...books);
authorQuery.value = "";
titleQuery.value = "";
authorQuery.parentElement.style.display = "block";
titleQuery.parentElement.style.display = "block";
document.getElementById('count').innerText = `Naomi currently has ${books.length} books.`;
updateTable(books);
}
const updateTable = (books) => {
books = books.sort((a, b) => a.title.localeCompare(b.title));
bookTable.innerHTML = "";
const header = document.createElement('tr');
const authorHeader = document.createElement('th');
authorHeader.innerText = "Author";
const titleHeader = document.createElement('th');
titleHeader.innerText = "Title";
header.appendChild(titleHeader);
header.appendChild(authorHeader);
bookTable.appendChild(header);
books.forEach(book => {
const row = document.createElement('tr');
const author = document.createElement('td');
author.innerText = book.author;
const title = document.createElement('td');
title.innerText = book.title;
row.appendChild(title);
row.appendChild(author);
bookTable.appendChild(row);
});
}
const bookList = [];
fetch("./books.json").then(res => res.json()).then(data => loadBooks(data))
authorQuery?.addEventListener("input", (e) => filterBooks(e.target.value, titleQuery.value));
titleQuery?.addEventListener("input", (e) => filterBooks(authorQuery.value, e.target.value));
resetButton?.addEventListener("click", () => {
authorQuery.value = "";
titleQuery.value = "";
filterBooks("", "");
});
</script>
<style>
table {
width: 100%;
border-collapse: collapse;
}
tr:nth-of-type(even) {
background-color: #db7093dd;
color: #ffefef;
}
input {
background:var(--foreground);
color:var(--background);
border:1px solid white;
border-radius:10px;
padding:.25rem
}
button {
background:var(--foreground);
color:var(--background);
border:1px solid white;
border-radius:10px;
padding:.25rem;
cursor:url('https://cdn.nhcarrigan.com/cursors/pointer.cur'), pointer;
}
</style>
</html>
</html>
+3 -24
View File
@@ -4,36 +4,15 @@
<title>Games</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="These are the various games we have developed!" />
<meta name="description" content="Naomi's game library has moved to library.nhcarrigan.com." />
<script src="https://cdn.nhcarrigan.com/headers/index.js" async defer></script>
</head>
<body>
<main>
<h1>Games</h1>
<section>
<p>These are the various games we have developed.</p>
</section>
<section>
<h2>Links</h2>
<p>
<a href="https://beccalia.nhcarrigan.com/">
🩵 Beccalia Series
</a>
</p>
<p>
<a href="https://goblin.nhcarrigan.com">
🩷 Ruu's Goblin Quest
</a>
</p>
<p>
<a href="https://loan.nhcarrigan.com">
🩵 Life of a Naomi
</a>
</p>
<p>
We are currently on a hiatus from game development. But we may return in the future - be sure to join our Discord server so you don't miss any updates~!
</p>
<p>This page has moved! You can find Naomi's game library at <a href="https://library.nhcarrigan.com">library.nhcarrigan.com</a>.</p>
</section>
</main>
</body>
</html>
</html>
+31 -9
View File
@@ -51,9 +51,10 @@
<li>
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.
aspects of your life. And I expect the same courtesy in return. I am
a transgender woman and a proud member of the LGBTQ+ community. You
do not have to agree with 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>
@@ -64,6 +65,19 @@
with pale imitations, or harass/target someone, or restrict access
to information.
</li>
<li>
Third, technology should be inclusive, ethical, and sustainable.
These are not buzzwords to me — they are the foundation of every
project I take on. I learned to code through a free, open curriculum
during the pandemic, and that experience shaped everything. Knowledge
hoarded helps no one. Knowledge shared changes lives. I build with
that in mind.
</li>
<li>
Finally, autonomy is sacred. I will never build something designed
to manipulate, coerce, or remove agency from the people using it.
If a project asks me to do that, the answer is no.
</li>
</ul>
<hr />
</section>
@@ -102,6 +116,12 @@
hours are the best. Too early and I'll likely fail to wake up. Too
late and I might be incoherent.
</li>
<li>
I have nerve damage and lumbar spine degeneration, which means I
need to step away from the screen and move around regularly. If I go
quiet for a few minutes mid-conversation, I haven't disappeared —
I'm just stretching. Please do not interpret this as disengagement.
</li>
<li>
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
@@ -126,12 +146,14 @@
it all at once, or save the ping for the last part.
</li>
<li>
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.
I have generalised anxiety disorder and depression. 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. Likewise, if I've done something well,
please tell me — words of encouragement genuinely land for me and
will make me work even harder for you.
</li>
<li>
I dunno if you noticed the tone changed in this document about a
+6 -114
View File
@@ -4,123 +4,15 @@
<title>Naomi's Music Library</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="An interactive explorer for the music Naomi listens to." />
<meta name="description" content="Naomi's music library has moved to library.nhcarrigan.com." />
<script src="https://cdn.nhcarrigan.com/headers/index.js" async defer></script>
</head>
<body>
<main>
<h1>Naomi's Music Library</h1>
<section>
<p>An interactive explorer for the music Naomi listens to.</p>
<p id="count">Loading library...</p>
</section>
<div style="display: none;">
<span>Search Artists: </span>
<input type="text" id="artist" />
</div>
<div style="display: none;">
<span>Search Titles: </span>
<input type="text" id="title" />
</div>
<div style="display: none;">
<button type="button" id="clear">Clear Filters</button>
</div>
<table id="songs">
</table>
<h1>Naomi's Music Library</h1>
<section>
<p>This page has moved! You can find Naomi's music library at <a href="https://library.nhcarrigan.com">library.nhcarrigan.com</a>.</p>
</section>
</main>
</body>
<script>
const artistQuery = document.getElementById('artist');
const titleQuery = document.getElementById('title');
const resetButton = document.getElementById('clear');
const songTable = document.getElementById('songs');
const filterSongs = (artist, title) => {
let result = [...songList];
if(artist) {
result = result.filter(song => song.artist.toLowerCase().includes(artist.toLowerCase()));
}
if(title) {
result = result.filter(song => song.title.toLowerCase().includes(title.toLowerCase()));
}
resetButton.parentElement.style.display = artist || title ? "block" : "none";
document.getElementById('count').innerText = artist || title ? `Filtered to ${result.length} songs from ${songList.length}.` : `Naomi currently has ${songList.length} songs.`;
updateTable(result);
}
const loadSongs = (songs) => {
songList.push(...songs);
artistQuery.value = "";
titleQuery.value = "";
artistQuery.parentElement.style.display = "block";
titleQuery.parentElement.style.display = "block";
document.getElementById('count').innerText = `Naomi currently has ${songs.length} songs.`;
updateTable(songs);
}
const updateTable = (songs) => {
songs = songs.sort((a, b) => a.title.localeCompare(b.title));
songTable.innerHTML = "";
const header = document.createElement('tr');
const artistHeader = document.createElement('th');
artistHeader.innerText = "Artist";
const titleHeader = document.createElement('th');
titleHeader.innerText = "Title";
header.appendChild(titleHeader);
header.appendChild(artistHeader);
songTable.appendChild(header);
songs.forEach(song => {
const row = document.createElement('tr');
const artist = document.createElement('td');
artist.innerText = song.artist;
const title = document.createElement('td');
title.innerText = song.title;
row.appendChild(title);
row.appendChild(artist);
songTable.appendChild(row);
});
}
const songList = [];
fetch("./songs.json").then(res => res.json()).then(data => loadSongs(data))
artistQuery?.addEventListener("input", (e) => filterSongs(e.target.value, titleQuery.value));
titleQuery?.addEventListener("input", (e) => filterSongs(artistQuery.value, e.target.value));
resetButton?.addEventListener("click", () => {
artistQuery.value = "";
titleQuery.value = "";
filterSongs("", "");
});
</script>
<style>
table {
width: 100%;
max-width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
tr:nth-of-type(even) {
background-color: var(--foreground);
color: var(--background);
}
input {
background:var(--foreground);
color:var(--background);
border:1px solid white;
border-radius:10px;
padding:.25rem
}
button {
background:var(--foreground);
color:var(--background);
border:1px solid white;
border-radius:10px;
padding:.25rem;
cursor:url('https://cdn.nhcarrigan.com/cursors/pointer.cur'), pointer;
}
tr {
max-width: 100%;
}
td {
max-width: 50%;
word-wrap: break-word;
}
</style>
</html>
</html>
-82528
View File
File diff suppressed because it is too large Load Diff
+1312 -11
View File
File diff suppressed because it is too large Load Diff
-260
View File
@@ -1,260 +0,0 @@
[
{
"name": "mod-logs",
"description": "Logs for moderation actions taken on our platforms.",
"url": "",
"category": "archived"
},
{
"name": "tingle-bot",
"description": "Bot for my friend Ruu's server",
"url": "",
"category": "archived"
},
{
"name": "announcements",
"description": "Repository for our announcements page.",
"url": "",
"category": "archived"
},
{
"name": "forms",
"description": "Client and server monorepo for our various webforms",
"url": "",
"category": "archived"
},
{
"name": "notes",
"description": "",
"url": "",
"category": "private"
},
{
"name": "status",
"description": "Status updates for our client work.",
"url": "",
"category": "private"
},
{
"name": "beaver-twitch",
"description": "Twitch bot for BigBadBeaver",
"url": "",
"category": "private"
},
{
"name": "obsidian",
"description": "",
"url": "",
"category": "private"
},
{
"name": "insomnium",
"description": "Our Insomnium requests",
"url": "",
"category": "private"
},
{
"name": "life-of-a-naomi",
"description": "A little game",
"url": "",
"category": "games"
},
{
"name": "naomis-adventure-1",
"description": "Our first full-length, paid game!",
"url": "",
"category": "games"
},
{
"name": "beccalia-origins",
"description": "The story of how Becca and Rosalia first met, and how they came to be. Never moved past a demo state.",
"url": "https://beccalia.nhcarrigan.com/origins",
"category": "games"
},
{
"name": "beccalia-prologue",
"description": "A short adventure to introduce our characters Becca and Rosalia. Our first attempt at game development!",
"url": "https://beccalia.nhcarrigan.com/prologue",
"category": "games"
},
{
"name": "ruu-goblin-quest",
"description": "A quick game we made about our friend Ruu, as part of a game jam she was hosting.",
"url": "https://goblin.nhcarrigan.com",
"category": "games"
},
{
"name": "template",
"description": "",
"url": "",
"category": "public"
},
{
"name": "a4p-bot",
"description": "Bot for the Art 4 Palestine charity initiative.",
"url": "https://a4p.nhcarrigan.com",
"category": "public"
},
{
"name": "boost-monitor",
"description": "Discord bot that monitors boost status in the Caylus Crew server.",
"url": "https://oogie.nhcarrigan.com/",
"category": "public"
},
{
"name": "docs",
"description": "Our documentation site.",
"url": "https://docs.nhcarrigan.com",
"category": "public"
},
{
"name": "eslint-config",
"description": "Our custom linter rules for our various TypeScript products.",
"url": "https://www.npmjs.com/package/@nhcarrigan/eslint-config",
"category": "public"
},
{
"name": "espanso",
"description": "Our shortcuts for Espanso.",
"url": "",
"category": "public"
},
{
"name": "celestine",
"description": "Moderation bot for Discord.",
"url": "https://hooks.nhcarrigan.com/",
"category": "public"
},
{
"name": "portfolio",
"description": "Our main homepage",
"url": "https://nhcarrigan.com",
"category": "public"
},
{
"name": "rig-task-bot",
"description": "Task bot for a friend's organisation.",
"url": "",
"category": "public"
},
{
"name": "security",
"description": "A quick tool to scan our projects for security concerns.",
"url": "https://security.nhcarrigan.com",
"category": "public"
},
{
"name": "website-headers",
"description": "Our global styling and scripts for all of our pages.",
"url": "https://cdn.nhcarrigan.com/headers/index.js",
"category": "public"
},
{
"name": "typescript-config",
"description": "Our global TypeScript configuration.",
"url": "https://www.npmjs.com/package/@nhcarrigan/typescript-config",
"category": "public"
},
{
"name": "blog",
"description": "Naomi's personal musings.",
"url": "https://blog.nhcarrigan.com",
"category": "public"
},
{
"name": "nginx-configs",
"description": "A version controlled backup of our servers' NGINX configurations.",
"url": "",
"category": "public"
},
{
"name": "vscode-themes",
"description": "Custom colour schemes for VSCode.",
"url": "https://marketplace.visualstudio.com/items?itemName=nhcarrigan.naomis-themes",
"category": "public"
},
{
"name": ".gitea",
"description": "This repository contains the files that customise our Gitea instance!",
"url": "https://git.nhcarrigan.com",
"category": "public"
},
{
"name": "aria-iuvo",
"description": "A user-installable translation application for Discord. Now you can translate messages directly on the platform!",
"url": "https://trans-bot.nhcarrigan.com/",
"category": "public"
},
{
"name": "cordelia-taryne",
"description": "AI-powered virtual assistant for Discord",
"url": "https://assistant.nhcarrigan.com/",
"category": "public"
},
{
"name": "rosalia-nightsong",
"description": "A webserver to handle alerting us to application logs and errors.",
"url": "https://alerts.nhcarrigan.com/",
"category": "public"
},
{
"name": "logger",
"description": "Our custom logging package, which pipes logs to our alerts server.",
"url": "https://www.npmjs.com/package/@nhcarrigan/logger",
"category": "public"
},
{
"name": "melody-iuvo",
"description": "Task management bot for Discord",
"url": "https://tasks.nhcarrigan.com/",
"category": "public"
},
{
"name": "static-pages",
"description": "The raw HTML pages served via our production box.",
"url": "",
"category": "public"
},
{
"name": "becca-lyria",
"description": "An AI-powered Discord bot that allows you to play an RPG without any friends!",
"url": "https://becca.nhcarrigan.com/",
"category": "public"
},
{
"name": "maylin-taryne",
"description": "An AI-powered companion to help you through the tough times.",
"url": "https://maylin.nhcarrigan.com/",
"category": "public"
},
{
"name": "gwen-abalise",
"description": "A ticket system for Discord!",
"url": "https://gwen.nhcarrigan.com/",
"category": "public"
},
{
"name": "nails",
"description": "Nail polish tracker for my sister",
"url": "",
"category": "public"
},
{
"name": "maribelle",
"description": "",
"url": "",
"category": "public"
},
{
"name": "mommy",
"description": "Mommy loves you~!",
"url": "https://mommy.nhcarrigan.com",
"category": "public"
},
{
"name": "mommy-bot",
"description": "Mommy loves you everywhere~!",
"url": "https://mommy-bot.nhcarrigan.com/",
"category": "public"
}
]
+2345
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -281,7 +281,7 @@
<div class="secret">
<div class="secret-number">Secret #13</div>
<div class="unknown">??????</div>
<div class="secret-text">Every choice you make leads you somewhere in life. So long as you find happiness, there are no wrong choices.</div>
</div>
<div class="secret">
+7 -1
View File
@@ -38,7 +38,7 @@
<li><a href="https://amari.nhcarrigan.com" target="_blank"><code>amari</code> - Naomi's personal assistant bot, notifies her when people use her name</a></li>
<li><a href="https://aria.nhcarrigan.com" target="_blank"><code>aria</code> - Translation bot</a></li>
<li><a href="https://assistant.nhcarrigan.com" target="_blank"><code>assistant</code> - Cordelia Taryne</a></li>
<li><a href="https://analytics.nhcarrigan.com" target="_blank"><code>becca</code> - Becca Lyria</a></li>
<li><a href="https://becca.nhcarrigan.com" target="_blank"><code>becca</code> - Becca Lyria</a></li>
<li><a href="https://caelia.nhcarrigan.com" target="_blank"><code>caelia</code> - Discord bot that reminds people to use inclusive language</a></li>
<li><a href="https://callista.nhcarrigan.com" target="_blank"><code>callista</code> - Discord bot that allows you to "bookmark" messages</a></li>
<li><a href="https://camperchan.nhcarrigan.com" target="_blank"><code>camperchan</code> - CamperChan (freeCodeCamp bot)</a></li>
@@ -72,6 +72,7 @@
<ul>
<li><a href="https://beccalia.nhcarrigan.com" target="_blank"><code>beccalia</code> - Beccalia landing Page</a></li>
<li><a href="https://games.nhcarrigan.com" target="_blank"><code>games</code> - Landing Page for All Games</a></li>
<li><a href="https://elysium.nhcarrigan.com" target="_blank"><code>elysium</code> - Browser-based idle game</a></li>
<li><a href="https://goblin.nhcarrigan.com" target="_blank"><code>goblin</code> - Ruu's Goblin Quest</a></li>
<li><a href="https://loan.nhcarrigan.com" target="_blank"><code>loan</code> - Life of a Naomi</a></li>
</ul>
@@ -92,19 +93,23 @@
<li><a href="https://git.nhcarrigan.com" target="_blank"><code>git</code> - Gitea Instance</a></li>
<li><a href="https://hikari.nhcarrigan.com" target="_blank"><code>hikari</code> - Our unified dashboard (currently shows announcements, sanction logs, and product lists)</a></li>
<li><a href="https://incidents.nhcarrigan.com" target="_blank"><code>incidents</code> - Uptime Kuma Instance</a></li>
<li><a href="https://library.nhcarrigan.com" target="_blank"><code>library</code> - Naomi's media library (books, games, music, and more) with community suggestions</a></li>
<li><a href="https://lore.nhcarrigan.com" target="_blank"><code>lore</code> - A fun li'l site offering the "lore" behind NHCarrigan</a></li>
<li><a href="https://manual.nhcarrigan.com" target="_blank"><code>manual</code> - User Manual</a></li>
<li><a href="https://moderation.nhcarrigan.com" target="_blank"><code>moderation</code> - Mod Logs</a></li>
<li><a href="https://mommy.nhcarrigan.com" target="_blank"><code>mommy</code> - A site to get some motherly love</a></li>
<li><a href="https://music.nhcarrigan.com" target="_blank"><code>music</code> - Music Library</a></li>
<li><a href="https://nocturne.nhcarrigan.com" target="_blank"><code>nocturne</code> - Naomi's Nocturne (the official religion)</a></li>
<li><a href="https://nails.nhcarrigan.com" target="_blank"><code>nails</code> - Nail Polish Tracker</a></li>
<li><a href="https://notes.nhcarrigan.com" target="_blank"><code>notes</code> - Private Notes for Sponsors</a></li>
<li><a href="https://quality.nhcarrigan.com" target="_blank"><code>quality</code> - SonarQube Instance</a></li>
<li><a href="https://resume.nhcarrigan.com" target="_blank"><code>resume</code> - Web-based Resume</a></li>
<li><a href="https://scripture.nhcarrigan.com" target="_blank"><code>scripture</code> - The Nocturne Scriptures</a></li>
<li><a href="https://secrets.nhcarrigan.com" target="_blank"><code>secrets</code> - Naomi's Secrets to Success™</a></li>
<li><a href="https://security.nhcarrigan.com" target="_blank"><code>security</code> - Automated Code Scanning Results</a></li>
<li><a href="https://silly.nhcarrigan.com" target="_blank"><code>silly</code> - A collection of dumb little websites Naomi has built (usually as part of an event)</a></li>
<li><a href="https://sitemap.nhcarrigan.com" target="_blank"><code>sitemap</code> - This page!</a></li>
<li><a href="https://style.nhcarrigan.com" target="_blank"><code>style</code> - NHCarrigan Style &amp; Branding Guide</a></li>
<li><a href="https://socials.nhcarrigan.com" target="_blank"><code>socials</code> - Naomi's social media account list</a></li>
<li><a href="https://support.nhcarrigan.com" target="_blank"><code>support</code> - Our product and community support forum</a></li>
<li><a href="https://telemetry.nhcarrigan.com" target="_blank"><code>telemetry</code> - Our metrics dashboard</a></li>
@@ -140,6 +145,7 @@
<ul>
<li><a href="https://naomi.lgbt" target="_blank"><code>naomi.lgbt</code> - Our Portfolio</a></li>
<li><a href="https://naomi.party" target="_blank"><code>naomi.party</code> - BlueSky Username Service</a></li>
<li><a href="https://lynira.link" target="_blank"><code>lynira.link</code> - Public paid link shortener service</a></li>
<li><a href="https://nhcarrigan.link" target="_blank"><code>nhcarrigan.link</code> - Link Redirection Server</a></li>
</ul>
</section>
-32
View File
@@ -1,32 +0,0 @@
IFS=$'\n'
# Initialize an empty string to hold the list of songs in JSON-like format
songs=""
filecount=$(find /home/naomi/music -type f | wc -l)
echo "Found $filecount files."
current=0
# Loop over each file found by find
for file in $(find /home/naomi/music -type f -print0 | tr '\0' '\n'); do
current=$((current + 1))
echo -ne "Processing $current/$filecount\r"
title=$(mid3v2 -l "$file" | grep "TIT2\|TT2" | cut -d "=" -f 2)
artist=$(mid3v2 -l "$file" | grep "TPE1\|TP1" | cut -d "=" -f 2)
if [ -z "$title" ]; then
# remove .mp3 from the title
title=$(basename "$file" | sed -e 's/\.mp3//g')
fi
if [ -z "$artist" ]; then
artist="Unknown Artist"
fi
# use jq to add the song to the list
songs="$songs$(jq -n --arg title "$title" --arg artist "$artist" '{title: $title, artist: $artist}'),"
done
# Remove trailing comma and add square brackets to complete the list
songs="[${songs%,}]"
# Write to ./music/songs.json
echo "$songs" > ./music/songs.json
echo -ne "Done!\r"
+1215
View File
File diff suppressed because it is too large Load Diff