From 6e8c048e259ea79dfec9b6bb5d1b8a229fb1aa1e Mon Sep 17 00:00:00 2001 From: Naomi Carrigan Date: Fri, 4 Jul 2025 20:05:20 -0700 Subject: [PATCH] feat: build out project dashboard (#2) ### Explanation This creates an interactive product directory to help potential consumers discover our works. ### Issue _No response_ ### 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 _No response_ Reviewed-on: https://git.nhcarrigan.com/nhcarrigan/hikari/pulls/2 Co-authored-by: Naomi Carrigan Co-committed-by: Naomi Carrigan --- client/src/app/app.html | 1 + client/src/app/app.routes.ts | 4 + client/src/app/app.ts | 3 +- client/src/app/config/products.ts | 473 ++++++++++++++++++++++++++ client/src/app/home/home.css | 94 +++++ client/src/app/home/home.html | 26 +- client/src/app/nav/nav.css | 65 ++++ client/src/app/nav/nav.html | 19 ++ client/src/app/nav/nav.ts | 28 ++ client/src/app/products/products.css | 85 +++++ client/src/app/products/products.html | 150 ++++++++ client/src/app/products/products.ts | 78 +++++ client/src/app/soon/soon.css | 15 + client/src/app/soon/soon.html | 16 + client/src/app/soon/soon.ts | 17 + client/src/index.html | 35 +- client/src/styles.css | 103 +++++- 17 files changed, 1190 insertions(+), 22 deletions(-) create mode 100644 client/src/app/config/products.ts create mode 100644 client/src/app/nav/nav.css create mode 100644 client/src/app/nav/nav.html create mode 100644 client/src/app/nav/nav.ts create mode 100644 client/src/app/products/products.css create mode 100644 client/src/app/products/products.html create mode 100644 client/src/app/products/products.ts create mode 100644 client/src/app/soon/soon.css create mode 100644 client/src/app/soon/soon.html create mode 100644 client/src/app/soon/soon.ts diff --git a/client/src/app/app.html b/client/src/app/app.html index b3cb235..2e222e4 100644 --- a/client/src/app/app.html +++ b/client/src/app/app.html @@ -1,3 +1,4 @@ +
diff --git a/client/src/app/app.routes.ts b/client/src/app/app.routes.ts index a8fd309..fd3e26b 100644 --- a/client/src/app/app.routes.ts +++ b/client/src/app/app.routes.ts @@ -6,7 +6,11 @@ import { Routes } from "@angular/router"; import { Home } from "./home/home.js"; +import { Products } from "./products/products.js"; +import { Soon } from "./soon/soon.js"; export const routes: Routes = [ { component: Home, path: "", pathMatch: "full" }, + { component: Products, path: "products" }, + { component: Soon, path: "**" }, ]; diff --git a/client/src/app/app.ts b/client/src/app/app.ts index f686f90..ba571f8 100644 --- a/client/src/app/app.ts +++ b/client/src/app/app.ts @@ -6,9 +6,10 @@ import { Component } from "@angular/core"; import { RouterOutlet } from "@angular/router"; +import { Nav } from "./nav/nav.js"; @Component({ - imports: [ RouterOutlet ], + imports: [ RouterOutlet, Nav ], selector: "app-root", styleUrl: "./app.css", templateUrl: "./app.html", diff --git a/client/src/app/config/products.ts b/client/src/app/config/products.ts new file mode 100644 index 0000000..6f288a9 --- /dev/null +++ b/client/src/app/config/products.ts @@ -0,0 +1,473 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ +/* eslint-disable stylistic/max-len -- we are going to have long descriptions here. */ +/* eslint-disable max-lines -- Big ol' config!*/ + +export const products: Array<{ + name: string; + description: string; + url: string | null; + wip: boolean; + category: "community" | "websites" | "apps"; + premium: boolean; + avatar: string | null; +}> = [ + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/rosalia.png", + category: "websites", + description: + "Our global logging server, which pipes logs from all of our apps into a Discord webhook and our email inbox.", + name: "Rosalia Nightsong", + premium: false, + url: "https://rosalia.nhcarrigan.com", + wip: false, + }, + { + avatar: null, + category: "websites", + description: + "Our self-hosted LibreTranslate instance, which powers some of our apps and is available for subscribers.", + name: "Translation Service", + premium: true, + url: "https://trans.nhcarrigan.com", + wip: false, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/aria.png", + category: "community", + description: + "A user-installable bot that allows you to translate any message into your preferred language.", + name: "Aria Iuvo", + premium: true, + url: "https://aria.nhcarrigan.com/", + wip: false, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/becca.png", + category: "community", + description: + "A user-installable Discord app that facilitates a solo Dungeons and Dragons experience in private messages.", + name: "Becca Lyria", + premium: true, + url: "https://becca.nhcarrigan.com", + wip: false, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/cordelia.png", + category: "community", + description: + "A user-installable Discord app that allows you to ask questions, generate alt text for images, evaluate code, and more.", + name: "Cordelia Taryne", + premium: true, + url: "https://cordelia.nhcarrigan.com/", + wip: false, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/gwen.png", + category: "community", + description: "A ticketing system for Discord servers.", + name: "Gwen Abalise", + premium: true, + url: "https://gwen.nhcarrigan.com/", + wip: false, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/maylin.png", + category: "community", + description: "A helpful and supportive Discord bot that allows you to have conversations with a virtual friend in private messages.", + name: "Maylin Taryne", + premium: true, + url: "https://maylin.nhcarrigan.com/", + wip: false, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/melody.png", + category: "community", + description: "A user-installable task management application for Discord.", + name: "Melody Iuvo", + premium: true, + url: "https://melody.nhcarrigan.com/", + wip: false, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/beccalia.png", + category: "apps", + description: "Originally planned as the story of Becca and Rosalia growing up, this game was only released as a demo.", + name: "Beccalia: Origins", + premium: false, + url: "https://beccalia.nhcarrigan.com/origins", + wip: false, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/beccalia.png", + category: "apps", + description: "An introductory story that sets the stage for the Beccalia universe, featuring Becca and Rosalia.", + name: "Beccalia: Prologue", + premium: false, + url: "https://beccalia.nhcarrigan.com/prologue", + wip: false, + }, + { + avatar: "https://cdn.nhcarrigan.com/profile.png", + category: "apps", + description: "A quick game that introduces who Naomi is, and provides a glimpse into her life.", + name: "Life of a Naomi", + premium: false, + url: "https://loan.nhcarrigan.com", + wip: false, + }, + { + avatar: null, + category: "apps", + description: "A game developed for our friend Ruu's game jam.", + name: "Ruu's Goblin Quest", + premium: false, + url: "https://goblin.nhcarrigan.com", + wip: false, + }, + { + avatar: "https://cdn.nhcarrigan.com/profile.png", + category: "websites", + description: "The personal musings of our founder, Naomi Carrigan.", + name: "Naomi's Blog", + premium: false, + url: "https://blog.nhcarrigan.com", + wip: false, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/nymira.png", + category: "websites", + description: "A service that allows you to claim a custom .naomi.party username for Bluesky.", + name: "Nymira", + premium: true, + url: "https://naomi.party", + wip: true, + }, + { + avatar: null, + category: "websites", + description: "A website outlining our policies, legal agreements, community rules, and product information.", + name: "NHCarrigan Documentation", + premium: false, + url: "https://docs.nhcarrigan.com", + wip: true, + }, + { + avatar: null, + category: "websites", + description: "A self-hosted Discourse instance for our community.", + name: "Fourm", + premium: false, + url: "https://forum.nhcarrigan.com", + wip: false, + }, + { + avatar: null, + category: "websites", + description: "A self-hosted Gitea instance to hold all of our source code.", + name: "Gitea", + premium: false, + url: "https://git.nhcarrigan.com", + wip: false, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/hikari.png", + category: "websites", + description: "This dashboard!", + name: "Hikari", + premium: false, + url: "https://hikari.nhcarrigan.com", + wip: true, + }, + { + avatar: null, + category: "community", + description: "A Discord, Slack, and Bluesky bot that provides you motherly love and encouragement.", + name: "Mommy Bot", + premium: false, + url: "https://mommy-bot.nhcarrigan.com", + wip: false, + }, + { + avatar: null, + category: "websites", + description: "A quick web app that provides you motherly love and encouragements.", + name: "Mommy", + premium: false, + url: "https://mommy.nhcarrigan.com", + wip: false, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/lucinda.png", + category: "websites", + description: "A kanban-style task management site.", + name: "Lucinda", + premium: false, + url: "https://lucinda.nhcarrigan.com", + wip: false, + }, + { + avatar: null, + category: "websites", + description: "Our homepage and marketing landing.", + name: "NHCarrigan", + premium: false, + url: "https://nhcarrigan.com", + wip: false, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/vitalia.png", + category: "websites", + description: "A full-featured nutrition tracker with community-driven nutrient data.", + name: "Vitalia", + premium: true, + url: "https://vitalia.nhcarrigan.com", + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/octavia.png", + category: "apps", + description: "Linux-native music player application with a focus on handling large libraries with minimal memory.", + name: "Octavia", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/maribelle.png", + category: "community", + description: "A Discord bot that allows you to configure daily progress huddle reminders for your server members.", + name: "Maribelle", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/sorielle.png", + category: "community", + description: "A Discord bot that allows servers to specify a venting channel for automatic deletion.", + name: "Sorielle", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/verena.png", + category: "community", + description: "A Discord bot that allows identity and age verification.", + name: "Verena", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/thalassa.png", + category: "apps", + description: "A rich presence application for Linux.", + name: "Thalassa", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/aeris.png", + category: "websites", + description: "An authentication service featuring magic links and support for multiple social media platforms", + name: "Aeris", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/liora.png", + category: "community", + description: "A Discord bot that allows your server members to specify 'highlight' words, which they'll get pinged on if a message contains that word.", + name: "Liora", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/thessalia.png", + category: "community", + description: "An RPG game on Discord", + name: "Thessalia", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/callista.png", + category: "community", + description: "A user-installable Discord bot that allows you to bookmark messages and save a link and copy in your DMs.", + name: "Callista", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/isolda.png", + category: "apps", + description: "Modern, sleek email client for the web or desktop", + name: "Isolda", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/meliora.png", + category: "websites", + description: "Embeddable chat widget, comment section, and full support flow utility.", + name: "Meliora", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/aurelia.png", + category: "websites", + description: "Blogging platform with markdown editor", + name: "Aurelia", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/eirene.png", + category: "community", + description: "Website and Discord activity that allows you to participate in code challenges competitively or collaboratively", + name: "Eirene", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/amirei.png", + category: "websites", + description: "A quick social link aggregator for 'link in bio' pages.", + name: "Amirei", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/zephra.png", + category: "websites", + description: "Microblogging social media platform.", + name: "Zephra", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/oriana.png", + category: "websites", + description: "Uptime monitoring tool with status pages", + name: "Oriana", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/lyra.png", + category: "websites", + description: "A web-based API mocking tool, allowing you to create temporary endpoints for a front-end to hit, test webhook payloads, and more!", + name: "Lyra", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/selene.png", + category: "apps", + description: "A local-only privacy-focused REST API client.", + name: "Selene", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/sybil.png", + category: "community", + description: "A Discord bot that syndicates forum threads to an indexable website and generates help articles based on resolved conversations.", + name: "Sybil", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/calenelle.png", + category: "websites", + description: "A group coordination app with event scheduling and such.", + name: "Calenelle", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/rowena.png", + category: "websites", + description: "Web app that allows you to create and share forms, and track responses in a user friendly table.", + name: "Rowena", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/alouette.png", + category: "websites", + description: "A web server that allows you to set up arbitrary webhooks and format them to post on Discord.", + name: "Alouette", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/clarion.png", + category: "community", + description: "A Discord bot with dashboard that allows server mangers to post and edit announcements, rules, and similar.", + name: "Clarion", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/elowyn.png", + category: "websites", + description: "A quick website that helps you format text.", + name: "Elowyn", + premium: false, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/evangeline.png", + category: "community", + description: "A Discord bot that allows you to configure canned replies, retrieve them anywhere on discord, and easily copy + paste them into chat.", + name: "Evangeline", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/theodora.png", + category: "community", + description: "A Discord bot that generates 100 days of code reminders.", + name: "Theodora", + premium: true, + url: null, + wip: true, + }, + { + avatar: "https://cdn.nhcarrigan.com/new-avatars/vivienne.png", + category: "websites", + description: "An RSS feed reader/management site.", + name: "Vivienne", + premium: true, + url: null, + wip: true, + }, +]; diff --git a/client/src/app/home/home.css b/client/src/app/home/home.css index e69de29..f54fb81 100644 --- a/client/src/app/home/home.css +++ b/client/src/app/home/home.css @@ -0,0 +1,94 @@ +ul { + list-style: none; + padding: 0; + margin: 0; +} + +#one { + transform: translateY(-200vh); + animation: slide-down 2s forwards; + font-size: 1.3rem; +} + +#two { + transform: translateY(200vh); + animation: slide-up 2s forwards 2s; +} + +#three { + transform: translateX(-200vw); + animation: slide-left 2s forwards 4s; +} + +#four { + transform: translateX(200vw); + animation: slide-right 2s forwards 6s; +} + +#five { + transform: translateX(-200vw); + animation: slide-left 2s forwards 8s; +} + +#six { + transform: translateX(200vw); + animation: slide-right 2s forwards 10s; +} + +#fade { + opacity: 0; + animation: fade-in 2s forwards 12s; + display: flex; + flex-direction: row; + justify-content: space-evenly; + align-items: center; + flex-wrap: wrap; +} + +.btn { + display: inline-block; + padding: 10px 20px; + background-color: var(--foreground); + color: var(--background); + text-decoration: none; + border-radius: 50px; + border: 2px solid white; +} + +.btn:hover { + background-color: var(--background); + color: var(--foreground); + transition: background-color 0.3s, color 0.3s; +} + +@keyframes slide-left { + 100% { transform: translateX(0%); } +} + +@keyframes slide-right { + 100% { transform: translateX(0%); } +} + +@keyframes slide-up { + 100% { transform: translateY(0%); } +} + +@keyframes slide-down { + 100% { transform: translateY(0%); } +} + +@keyframes fade-in { + 100% { opacity: 1; } +} + +@keyframes background-color { + 0% { background-color: var(--foreground); } + 100% { background-color: var(--background); } +} + +@media screen and (prefers-reduced-motion: reduce) { + * { + animation: none !important; + transform: none !important; + } +} diff --git a/client/src/app/home/home.html b/client/src/app/home/home.html index 2652c7b..63372e4 100644 --- a/client/src/app/home/home.html +++ b/client/src/app/home/home.html @@ -1,8 +1,20 @@

Hi there, I'm Hikari~!

-Hikari -

- I am here to help you with all of NHCarrigan's products, including things like managing your subscriptions and configuring applications! -

-

- Naomi is still hard at work bringing me to life! We would love to hear your thoughts in our community~! -

+Hikari +

How may I help you today?

+

I can assist you with:

+
    +
  • Finding a product to suit your needs
  • +
  • Manage your account, subscriptions, and licenses
  • +
  • Modifying settings for individual products
  • +
  • Answering your specific questions with a chat assistant
  • +
+ diff --git a/client/src/app/nav/nav.css b/client/src/app/nav/nav.css new file mode 100644 index 0000000..2bb2a1b --- /dev/null +++ b/client/src/app/nav/nav.css @@ -0,0 +1,65 @@ +nav { + position: fixed; + top: 0; + left: 0; + right: 0; + height: 40px; + color: var(--foreground); + background-color: var(--background); + display: flex; + align-items: center; + justify-content: space-between; + padding-left: 15px; + padding-right: 15px; +} + +nav a:not(#logo) { + text-decoration: none; + padding: 0 15px; + font-size: 1.2rem; +} + +nav a:hover:not(#logo) { + text-decoration: underline; + color: var(--background); + background-color: var(--foreground); +} + +img { + height: 30px; + width: auto; + margin-right: 15px; +} + +hr { + width: 100%; + border: none; + border-top: 1px solid var(--foreground); + margin: 0; +} + +.dropdown { + display: none; +} + +.dropdown.open { + display: flex; + flex-direction: column; + position: absolute; + right: 0; + top: 40px; + background-color: var(--background); + color: var(--foreground); + border: 1px solid var(--foreground); + border-radius: 5px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); +} + +#logo { + display: flex; + align-items: center; + justify-content: center; + cursor: url('https://cdn.nhcarrigan.com/cursors/pointer.cur'), pointer; + text-decoration: none; + font-size: 2rem; +} diff --git a/client/src/app/nav/nav.html b/client/src/app/nav/nav.html new file mode 100644 index 0000000..4170929 --- /dev/null +++ b/client/src/app/nav/nav.html @@ -0,0 +1,19 @@ + diff --git a/client/src/app/nav/nav.ts b/client/src/app/nav/nav.ts new file mode 100644 index 0000000..fdae55f --- /dev/null +++ b/client/src/app/nav/nav.ts @@ -0,0 +1,28 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; + +@Component({ + imports: [ CommonModule ], + selector: "app-nav", + styleUrl: "./nav.css", + templateUrl: "./nav.html", +}) +export class Nav { + public navOpen = false; + public dropdownClass = "dropdown"; + + public toggleNav(): void { + this.navOpen = !this.navOpen; + if (this.navOpen) { + this.dropdownClass = "dropdown open"; + } else { + this.dropdownClass = "dropdown"; + } + } +} diff --git a/client/src/app/products/products.css b/client/src/app/products/products.css new file mode 100644 index 0000000..3c78adc --- /dev/null +++ b/client/src/app/products/products.css @@ -0,0 +1,85 @@ +a.product { + text-decoration: none; +} + +a.product:hover { + background-color: var(--background); + color: var(--foreground); +} + +.product:not(a) { + cursor: default; + border: 2px dashed grey; +} + +.btn { + display: inline-block; + padding: 10px 20px; + background-color: var(--foreground); + color: var(--background); + text-decoration: none; + border-radius: 50px; + border: 2px solid white; + font-family: 'OpenDyslexic', monospace; +} + +.btn:disabled { + background-color: var(--background); + color: var(--foreground); +} + +.btn:hover { + background-color: var(--background); + color: var(--foreground); + transition: background-color 0.3s, color 0.3s; +} + +.product { + display: grid; + grid-template-areas: "logo title icon" "logo description icon"; + grid-template-columns: 100px 1fr auto; + background-color: var(--foreground); + color: var(--background); + border: 2px solid white; + border-radius: 50px; + margin-left: 10px; + margin-right: 10px; + margin-bottom: 10px; + padding-right: 20px; + align-items: center; +} + +.icons { + grid-area: icon; + font-size: 2rem; + display: grid; + grid-template-columns: repeat(2, auto); + gap: 10px; +} + +.title { + grid-area: title; + font-size: 1.5rem; + font-weight: bold; +} + +.description { + grid-area: description; + font-size: 1rem; + margin-top: 10px; +} + +.logo { + grid-area: logo; + width: 100px; + height: 100px; + border-radius: 50%; +} + +.row { + display: flex; + flex-direction: row; + justify-content: space-evenly; + align-items: center; + flex-wrap: wrap; +} diff --git a/client/src/app/products/products.html b/client/src/app/products/products.html new file mode 100644 index 0000000..bdb4e9e --- /dev/null +++ b/client/src/app/products/products.html @@ -0,0 +1,150 @@ +

Products

+Hikari +

Excellent! What sort of product are you looking for?

+
+ + + + +
+

And would you like to apply a filter?

+
+ + + + +
+
+

+ Oh dear, it appears there are no products in this category yet! Please check + back later. +

+
+
+ + +

{{ product.name }}

+ +

{{ product.description }}

+
+ + + + +
+
+ + +
+

{{ product.name }}

+ +

{{ product.description }}

+
+ + + + +
+
+
+
+
+I want something custom... diff --git a/client/src/app/products/products.ts b/client/src/app/products/products.ts new file mode 100644 index 0000000..d23e86e --- /dev/null +++ b/client/src/app/products/products.ts @@ -0,0 +1,78 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { products } from "../config/products.js"; + +@Component({ + imports: [ CommonModule ], + selector: "app-products", + styleUrl: "./products.css", + templateUrl: "./products.html", +}) +export class Products { + public view: (typeof products)[number]["category"] | "all" + = "all"; + public products: typeof products = []; + public readonly filters: { + wip: boolean; + prod: boolean; + paid: boolean; + free: boolean; + } = { + free: true, + paid: true, + prod: true, + wip: true, + }; + + public constructor() { + this.selectCategory("all"); + } + + public selectCategory( + category: (typeof products)[number]["category"] | "all", + ): void { + this.view = category; + const sortedProducts = products.sort((a, b) => { + return a.name.localeCompare(b.name); + }); + if (this.view === "all") { + this.products = sortedProducts; + return; + } + this.products = sortedProducts.filter((product) => { + return product.category === this.view; + }); + } + + public toggleFilter( + filter: "wip" | "prod" | "paid" | "free", + ): void { + this.filters[filter] = !this.filters[filter]; + this.applyFilters(); + } + + private applyFilters(): void { + this.selectCategory(this.view); + this.products = this.products.filter((product) => { + if (!this.filters.wip && product.wip) { + return false; + } + if (!this.filters.prod && !product.wip) { + return false; + } + if (!this.filters.paid && product.premium) { + return false; + } + if (!this.filters.free && !product.premium) { + return false; + } + return true; + }); + } +} diff --git a/client/src/app/soon/soon.css b/client/src/app/soon/soon.css new file mode 100644 index 0000000..fb675d8 --- /dev/null +++ b/client/src/app/soon/soon.css @@ -0,0 +1,15 @@ +.btn { + display: inline-block; + padding: 10px 20px; + background-color: var(--foreground); + color: var(--background); + text-decoration: none; + border-radius: 50px; + border: 2px solid white; +} + +.btn:hover { + background-color: var(--background); + color: var(--foreground); + transition: background-color 0.3s, color 0.3s; +} diff --git a/client/src/app/soon/soon.html b/client/src/app/soon/soon.html new file mode 100644 index 0000000..c83961b --- /dev/null +++ b/client/src/app/soon/soon.html @@ -0,0 +1,16 @@ +

Oh dear~!

+Hikari +

You appear to have become lost!

+

+ Either this feature is still under construction, or you have tried to go + somewhere that does not exist. +

+

Do not worry, I can guide you back. Where would you like to go?

+ diff --git a/client/src/app/soon/soon.ts b/client/src/app/soon/soon.ts new file mode 100644 index 0000000..1b82b5e --- /dev/null +++ b/client/src/app/soon/soon.ts @@ -0,0 +1,17 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { Component } from "@angular/core"; + +@Component({ + imports: [], + selector: "app-soon", + styleUrl: "./soon.css", + templateUrl: "./soon.html", +}) +export class Soon { + +} diff --git a/client/src/index.html b/client/src/index.html index 7955438..3ab1cd9 100644 --- a/client/src/index.html +++ b/client/src/index.html @@ -1,15 +1,24 @@ - + - - - Hikari - - - - - - - - - + + + Hikari + + + + + + + + + + diff --git a/client/src/styles.css b/client/src/styles.css index 90d4ee0..9bdbeee 100644 --- a/client/src/styles.css +++ b/client/src/styles.css @@ -1 +1,102 @@ -/* You can add global styles to this file, and also import other style files */ +@font-face { + font-family: 'OpenDyslexic'; + src: url('https://cdn.nhcarrigan.com/fonts/OpenDyslexicMono-Regular.otf') format('opentype'); +} + +:root { + --foreground: #2a0a18; + --background: #ffb6c1bb; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + font-family: 'OpenDyslexic', monospace; + cursor: url('https://cdn.nhcarrigan.com/cursors/cursor.cur'), auto; + min-height: 100vh; + min-width: 100vw; +} +body::before { + background: url(https://cdn.nhcarrigan.com/background.png); + background-size: cover; + background-position: center; + width: 100%; + height: 100%; + z-index: -1; + content: ""; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + opacity: 1; + pointer-events: none; +} +main { + color: var(--foreground); + background-color: var(--background); + text-align: center; + border-radius: 10px; + width: 100vw; + margin-bottom: 85px; + margin-top: 50px; + min-height: calc(100vh - 85px - 50px); +} +footer { + width: 100%; + color: var(--foreground); + background-color: var(--background); + position: fixed; + bottom: 0; + height: 75px; + padding: 0 10px; +} +#footer-inner-container { + display: flex; + align-items: center; + justify-content: space-between; + height: 75px; +} +#footer-badge-container { + display: grid; + grid-template-columns: repeat(8, 1fr); + align-items: center; + justify-content: space-around; +} +#audio-theme-button, #theme-select-button { + background: none; + border: none; + cursor: url('https://cdn.nhcarrigan.com/cursors/pointer.cur'), pointer; + color: var(--foreground); +} +a { + color: unset; + cursor: url('https://cdn.nhcarrigan.com/cursors/pointer.cur'), pointer; +} +.btn:not(:disabled) { + cursor: url('https://cdn.nhcarrigan.com/cursors/pointer.cur'), pointer; +} +#tree-nation-offset-website { + display: flex; + align-items: center; +} +.is-dark { + --foreground: #ffb6c1; + --background: #2a0a18bb; +} +@media screen and (max-width: 625px) { + #tree-nation-offset-website { + display: none; + } + footer, #footer-inner-container { + height: 50px; + justify-content: space-around; + } + main { + margin-bottom: 60px; + } +}