diff --git a/api/src/app/plugins/helmet.ts b/api/src/app/plugins/helmet.ts
index 5ded151..1d2db76 100644
--- a/api/src/app/plugins/helmet.ts
+++ b/api/src/app/plugins/helmet.ts
@@ -13,9 +13,8 @@ const helmetPlugin: FastifyPluginAsync = async (app) => {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
- // Remove unsafe-inline for better security
- // Angular uses inline styles in development, but production builds should use external CSS
- styleSrc: ["'self'", process.env.NODE_ENV === "production" ? "'self'" : "'unsafe-inline'"],
+ // Angular uses inline styles for component encapsulation, so we need to allow them
+ styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
scriptSrc: ["'self'"],
connectSrc: ["'self'", process.env.FRONTEND_URL ?? "http://localhost:4200"],
diff --git a/apps/frontend-e2e/src/e2e/app.cy.ts b/apps/frontend-e2e/src/e2e/app.cy.ts
index 7423a9b..00df1bf 100644
--- a/apps/frontend-e2e/src/e2e/app.cy.ts
+++ b/apps/frontend-e2e/src/e2e/app.cy.ts
@@ -1,7 +1,7 @@
/**
- * @copyright 2026 NHCarrigan
* @copyright 2026 NHCarrigan
* @license Naomi's Public License
+ * @author Naomi Carrigan
*/
import { getGreeting } from "../support/app.po";
diff --git a/apps/frontend-e2e/src/support/app.po.ts b/apps/frontend-e2e/src/support/app.po.ts
index 66ec6f2..764baff 100644
--- a/apps/frontend-e2e/src/support/app.po.ts
+++ b/apps/frontend-e2e/src/support/app.po.ts
@@ -1,11 +1,12 @@
/**
- * @copyright 2026 NHCarrigan
* @copyright 2026 NHCarrigan
* @license Naomi's Public License
+ * @author Naomi Carrigan
*/
/**
- *
+ * Gets the greeting element from the page.
+ * @returns A Cypress chainable to the h1 element.
*/
export const getGreeting = (): Cypress.Chainable => {
return cy.get("h1");
diff --git a/apps/frontend-e2e/src/support/commands.ts b/apps/frontend-e2e/src/support/commands.ts
index ccfbaae..8fe6300 100644
--- a/apps/frontend-e2e/src/support/commands.ts
+++ b/apps/frontend-e2e/src/support/commands.ts
@@ -1,7 +1,7 @@
/**
- * @copyright 2026 NHCarrigan
* @copyright 2026 NHCarrigan
* @license Naomi's Public License
+ * @author Naomi Carrigan
*/
///
diff --git a/apps/frontend-e2e/src/support/e2e.ts b/apps/frontend-e2e/src/support/e2e.ts
index bb04d5c..a38dbe3 100644
--- a/apps/frontend-e2e/src/support/e2e.ts
+++ b/apps/frontend-e2e/src/support/e2e.ts
@@ -3,6 +3,7 @@
* @license Naomi's Public License
* @author Naomi Carrigan
*/
+/* eslint-disable unicorn/prevent-abbreviations -- e2e is a standard Cypress filename */
/**
* This example support/e2e.ts is processed and
diff --git a/apps/frontend/src/app/app.html b/apps/frontend/src/app/app.html
index 03c3155..278e406 100644
--- a/apps/frontend/src/app/app.html
+++ b/apps/frontend/src/app/app.html
@@ -1,4 +1,4 @@
-Skip to main content
+Skip to main content
diff --git a/apps/frontend/src/app/app.scss b/apps/frontend/src/app/app.scss
index 410e935..1f963b3 100644
--- a/apps/frontend/src/app/app.scss
+++ b/apps/frontend/src/app/app.scss
@@ -1,3 +1,35 @@
+// Skip to main content link - visually hidden but accessible to screen readers
+.skip-link {
+ position: absolute !important;
+ width: 1px !important;
+ height: 1px !important;
+ padding: 0 !important;
+ margin: -1px !important;
+ overflow: hidden !important;
+ clip: rect(0, 0, 0, 0) !important;
+ white-space: nowrap !important;
+ border: 0 !important;
+}
+
+.skip-link:focus {
+ position: fixed !important;
+ top: 0 !important;
+ left: 0 !important;
+ width: auto !important;
+ height: auto !important;
+ padding: 0.75rem 1.5rem !important;
+ margin: 0 !important;
+ overflow: visible !important;
+ clip: auto !important;
+ white-space: normal !important;
+ background: var(--witch-rose) !important;
+ color: var(--witch-moon) !important;
+ text-decoration: none !important;
+ font-weight: 600 !important;
+ border-radius: 0 0 4px 0 !important;
+ z-index: 10000 !important;
+}
+
.main-content {
min-height: calc(100vh - 60px); // Assuming header is ~60px
background-color: transparent; // Let the body background show through
diff --git a/apps/frontend/src/app/app.ts b/apps/frontend/src/app/app.ts
index 7826492..493a7fb 100644
--- a/apps/frontend/src/app/app.ts
+++ b/apps/frontend/src/app/app.ts
@@ -18,4 +18,13 @@ export class App implements OnInit {
ngOnInit(): void {
this.analytics.initialise();
}
+
+ skipToMainContent(event: Event): void {
+ event.preventDefault();
+ const mainContent = document.getElementById('main-content');
+ if (mainContent) {
+ mainContent.focus();
+ mainContent.scrollIntoView({ behavior: 'smooth' });
+ }
+ }
}
diff --git a/apps/frontend/src/styles.scss b/apps/frontend/src/styles.scss
index d5d1249..074f3db 100644
--- a/apps/frontend/src/styles.scss
+++ b/apps/frontend/src/styles.scss
@@ -143,23 +143,36 @@ button {
background: var(--witch-plum);
}
-// Skip to main content link
+// Skip to main content link - visually hidden but accessible to screen readers
.skip-link {
position: absolute;
- top: -40px;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
+.skip-link:focus {
+ position: fixed;
+ top: 0;
left: 0;
+ width: auto;
+ height: auto;
+ padding: 0.75rem 1.5rem;
+ margin: 0;
+ overflow: visible;
+ clip: auto;
+ white-space: normal;
background: var(--witch-rose);
color: var(--witch-moon);
- padding: 0.75rem 1.5rem;
text-decoration: none;
font-weight: 600;
border-radius: 0 0 4px 0;
z-index: 10000;
- transition: top 0.3s;
-}
-
-.skip-link:focus {
- top: 0;
}
// Enhanced focus styles for keyboard navigation
diff --git a/shared-types/src/lib/achievement.constants.ts b/shared-types/src/lib/achievement.constants.ts
index fcbf485..ae5003f 100644
--- a/shared-types/src/lib/achievement.constants.ts
+++ b/shared-types/src/lib/achievement.constants.ts
@@ -3,6 +3,11 @@
* @license Naomi's Public License
* @author Naomi Carrigan
*/
+/* eslint-disable @typescript-eslint/naming-convention -- Achievement keys use snake_case for database identifiers */
+/* eslint-disable sort-keys -- Achievements organized by category, not alphabetically */
+/* eslint-disable sort-keys-fix/sort-keys-fix -- Achievements organized by category, not alphabetically */
+/* eslint-disable stylistic/key-spacing -- Aligned for readability */
+/* eslint-disable import/group-exports -- Single export at end of file */
import {
AchievementCategory,
type AchievementDefinition,
@@ -11,6 +16,61 @@ import {
export const ACHIEVEMENTS: Record = {
+ like_enthusiast: {
+ category: AchievementCategory.Like,
+ description: "Like 25 items in the library",
+ icon: "💕",
+ key: "like_enthusiast",
+ points: 50,
+ title: "Enthusiast",
+ requirements: { count: 25 },
+ tier: AchievementTier.Bronze,
+ },
+
+ like_fan: {
+ category: AchievementCategory.Like,
+ description: "Like 100 items in the library",
+ icon: "💖",
+ key: "like_fan",
+ points: 100,
+ title: "Fan",
+ requirements: { count: 100 },
+ tier: AchievementTier.Silver,
+ },
+
+ suggestion_acceptance_100: {
+ category: AchievementCategory.Suggestion,
+ description: "Achieve 100% acceptance rate (minimum 10 suggestions)",
+ icon: "✨",
+ key: "suggestion_acceptance_100",
+ points: 500,
+ requirements: { count: 10, rate: 1 },
+ tier: AchievementTier.Platinum,
+ title: "Perfect Record",
+ },
+
+ like_book_lover: {
+ description: "Like 50 books",
+ key: "like_book_lover",
+ category: AchievementCategory.Like,
+ title: "Book Lover",
+ icon: "📚",
+ points: 100,
+ tier: AchievementTier.Silver,
+ requirements: { count: 50 },
+ },
+
+ suggestion_acceptance_75: {
+ category: AchievementCategory.Suggestion,
+ description: "Maintain a 75%+ acceptance rate (minimum 20 suggestions)",
+ icon: "🎯",
+ key: "suggestion_acceptance_75",
+ points: 300,
+ requirements: { count: 20, rate: 0.75 },
+ title: "Quality Over Quantity",
+ tier: AchievementTier.Gold,
+ },
+
suggestion_approved: {
category: AchievementCategory.Suggestion,
description: "Have your first suggestion accepted",
@@ -18,8 +78,19 @@ export const ACHIEVEMENTS: Record = {
key: "suggestion_approved",
points: 50,
requirements: { count: 1 },
- title: "Approved!",
tier: AchievementTier.Bronze,
+ title: "Approved!",
+ },
+
+ like_cinephile: {
+ description: "Like 50 shows/films",
+ category: AchievementCategory.Like,
+ key: "like_cinephile",
+ icon: "🎬",
+ title: "Cinephile",
+ points: 100,
+ requirements: { count: 50 },
+ tier: AchievementTier.Silver,
},
suggestion_contributor: {
@@ -33,6 +104,17 @@ export const ACHIEVEMENTS: Record = {
title: "Contributor",
},
+ like_diverse: {
+ description: "Like items from all 6 media types",
+ category: AchievementCategory.Like,
+ key: "like_diverse",
+ icon: "🌍",
+ title: "Diverse Taste",
+ points: 200,
+ requirements: { diversity: true },
+ tier: AchievementTier.Gold,
+ },
+
suggestion_dedicated: {
category: AchievementCategory.Suggestion,
description: "Submit 100 suggestions to the library",
@@ -43,19 +125,43 @@ export const ACHIEVEMENTS: Record = {
tier: AchievementTier.Gold,
title: "Dedicated",
},
-
- suggestion_acceptance_100: {
- key: "suggestion_acceptance_100",
- description: "Achieve 100% acceptance rate (minimum 10 suggestions)",
- title: "Perfect Record",
- category: AchievementCategory.Suggestion,
- icon: "✨",
- tier: AchievementTier.Platinum,
- points: 500,
- requirements: { count: 10, rate: 1.0 },
+
+ // ========== COMMENT ACHIEVEMENTS (12) ==========
+ comment_first: {
+ description: "Leave your first comment in the library",
+ category: AchievementCategory.Comment,
+ key: "comment_first",
+ icon: "💬",
+ title: "First Thoughts",
+ points: 25,
+ requirements: { count: 1 },
+ tier: AchievementTier.Bronze,
},
+
+ suggestion_enthusiast: {
+ category: AchievementCategory.Suggestion,
+ description: "Submit 5 suggestions in one day",
+ icon: "🔥",
+ key: "suggestion_enthusiast",
+ points: 150,
+ requirements: { count: 5, dayRange: 1 },
+ title: "Suggestion Enthusiast",
+ tier: AchievementTier.Silver,
+ },
+
+ comment_critic: {
+ description: "Leave 50 comments in the library",
+ category: AchievementCategory.Comment,
+ key: "comment_critic",
+ icon: "🗣️",
+ title: "Critic",
+ points: 100,
+ requirements: { count: 50 },
+ tier: AchievementTier.Silver,
+ },
+
// ========== SUGGESTION ACHIEVEMENTS (15) ==========
-suggestion_first_steps: {
+ suggestion_first_steps: {
category: AchievementCategory.Suggestion,
description: "Submit your first 10 suggestions to the library",
icon: "🌱",
@@ -63,18 +169,20 @@ suggestion_first_steps: {
points: 50,
requirements: { count: 10 },
tier: AchievementTier.Bronze,
- title: "First Steps",
+ title: "First Steps",
},
- suggestion_acceptance_75: {
- description: "Maintain a 75%+ acceptance rate (minimum 20 suggestions)",
- key: "suggestion_acceptance_75",
- category: AchievementCategory.Suggestion,
- title: "Quality Over Quantity",
- icon: "🎯",
- points: 300,
- tier: AchievementTier.Gold,
- requirements: { count: 20, rate: 0.75 },
+
+ comment_expert: {
+ category: AchievementCategory.Comment,
+ description: "Leave 100 comments in the library",
+ icon: "🎭",
+ key: "comment_expert",
+ points: 250,
+ title: "Expert Critic",
+ requirements: { count: 100 },
+ tier: AchievementTier.Gold,
},
+
suggestion_legend: {
category: AchievementCategory.Suggestion,
description: "Submit 500 suggestions to the library",
@@ -83,18 +191,20 @@ suggestion_first_steps: {
points: 1000,
requirements: { count: 500 },
tier: AchievementTier.Diamond,
- title: "Legend",
+ title: "Legend",
},
- like_enthusiast: {
- key: "like_enthusiast",
- description: "Like 25 items in the library",
- title: "Enthusiast",
- category: AchievementCategory.Like,
- icon: "💕",
- tier: AchievementTier.Bronze,
- points: 50,
- requirements: { count: 25 },
+
+ comment_legend: {
+ category: AchievementCategory.Comment,
+ description: "Leave 500 comments in the library",
+ icon: "🏅",
+ key: "comment_legend",
+ points: 1000,
+ title: "Review Legend",
+ requirements: { count: 500 },
+ tier: AchievementTier.Diamond,
},
+
suggestion_master: {
category: AchievementCategory.Suggestion,
description: "Submit 250 suggestions to the library",
@@ -102,19 +212,21 @@ suggestion_first_steps: {
key: "suggestion_master",
points: 500,
requirements: { count: 250 },
- title: "Master Curator",
tier: AchievementTier.Platinum,
+ title: "Master Curator",
},
- like_fan: {
- description: "Like 100 items in the library",
- key: "like_fan",
- category: AchievementCategory.Like,
- title: "Fan",
- icon: "💖",
- points: 100,
- tier: AchievementTier.Silver,
- requirements: { count: 100 },
+
+ comment_detailed: {
+ category: AchievementCategory.Comment,
+ description: "Write 10 comments with 500+ characters",
+ icon: "📝",
+ key: "comment_detailed",
+ points: 150,
+ requirements: { count: 10 },
+ title: "Detailed Critic",
+ tier: AchievementTier.Silver,
},
+
suggestion_quality_10: {
category: AchievementCategory.Suggestion,
description: "Have 10 suggestions accepted",
@@ -122,59 +234,74 @@ suggestion_first_steps: {
key: "suggestion_quality_10",
points: 200,
requirements: { count: 10 },
- title: "Elite Curator",
tier: AchievementTier.Gold,
- },
- suggestion_enthusiast: {
- description: "Submit 5 suggestions in one day",
- category: AchievementCategory.Suggestion,
- key: "suggestion_enthusiast",
- icon: "🔥",
- title: "Suggestion Enthusiast",
- points: 150,
- requirements: { count: 5, dayRange: 1 },
- tier: AchievementTier.Silver,
- },
- suggestion_quality_100: {
- description: "Have 100 suggestions accepted",
- key: "suggestion_quality_100",
- category: AchievementCategory.Suggestion,
- title: "Ultimate Curator",
- icon: "👸",
- points: 1500,
- tier: AchievementTier.Diamond,
- requirements: { count: 100 },
- },
- like_book_lover: {
- description: "Like 50 books",
- key: "like_book_lover",
- category: AchievementCategory.Like,
- title: "Book Lover",
- icon: "📚",
- points: 100,
- tier: AchievementTier.Silver,
- requirements: { count: 50 },
- },
- suggestion_quality_25: {
- description: "Have 25 suggestions accepted",
- key: "suggestion_quality_25",
- category: AchievementCategory.Suggestion,
- title: "Master Curator",
- icon: "💫",
- points: 400,
- tier: AchievementTier.Platinum,
- requirements: { count: 25 },
+ title: "Elite Curator",
},
- like_cinephile: {
- description: "Like 50 shows/films",
- key: "like_cinephile",
- category: AchievementCategory.Like,
- title: "Cinephile",
- icon: "🎬",
- points: 100,
- tier: AchievementTier.Silver,
- requirements: { count: 50 },
+ comment_essay: {
+ category: AchievementCategory.Comment,
+ description: "Write 5 comments with 1000+ characters",
+ icon: "📄",
+ key: "comment_essay",
+ points: 300,
+ requirements: { count: 5 },
+ tier: AchievementTier.Gold,
+ title: "Essay Writer",
+ },
+
+ suggestion_quality_100: {
+ description: "Have 100 suggestions accepted",
+ category: AchievementCategory.Suggestion,
+ key: "suggestion_quality_100",
+ icon: "👸",
+ title: "Ultimate Curator",
+ points: 1500,
+ requirements: { count: 100 },
+ tier: AchievementTier.Diamond,
+ },
+
+ comment_diverse: {
+ category: AchievementCategory.Comment,
+ description: "Comment on all 6 media types",
+ icon: "🎨",
+ key: "comment_diverse",
+ points: 250,
+ requirements: { diversity: true },
+ title: "Well-Rounded Critic",
+ tier: AchievementTier.Gold,
+ },
+
+ suggestion_quality_25: {
+ description: "Have 25 suggestions accepted",
+ category: AchievementCategory.Suggestion,
+ key: "suggestion_quality_25",
+ icon: "💫",
+ title: "Master Curator",
+ points: 400,
+ requirements: { count: 25 },
+ tier: AchievementTier.Platinum,
+ },
+
+ comment_first_to_comment: {
+ category: AchievementCategory.Comment,
+ description: "Be the first to comment on 10 items",
+ icon: "🗨️",
+ key: "comment_first_to_comment",
+ points: 200,
+ requirements: { count: 10 },
+ tier: AchievementTier.Silver,
+ title: "Discussion Starter",
+ },
+
+ comment_master: {
+ category: AchievementCategory.Comment,
+ description: "Leave 250 comments in the library",
+ icon: "📖",
+ key: "comment_master",
+ points: 500,
+ title: "Master Reviewer",
+ requirements: { count: 250 },
+ tier: AchievementTier.Platinum,
},
suggestion_quality_5: {
@@ -188,201 +315,6 @@ suggestion_first_steps: {
requirements: { count: 5 },
},
- like_diverse: {
- description: "Like items from all 6 media types",
- key: "like_diverse",
- category: AchievementCategory.Like,
- title: "Diverse Taste",
- icon: "🌍",
- points: 200,
- tier: AchievementTier.Gold,
- requirements: { diversity: true },
- },
-
- // ========== COMMENT ACHIEVEMENTS (12) ==========
-comment_first: {
- key: "comment_first",
- description: "Leave your first comment in the library",
- title: "First Thoughts",
- category: AchievementCategory.Comment,
- icon: "💬",
- tier: AchievementTier.Bronze,
- points: 25,
- requirements: { count: 1 },
- },
-
-
-suggestion_quality_50: {
- key: "suggestion_quality_50",
- title: "Grand Master",
- description: "Have 50 suggestions accepted",
- category: AchievementCategory.Suggestion,
- tier: AchievementTier.Platinum,
- icon: "🎖️",
- points: 700,
- requirements: { count: 50 },
- },
-
- comment_critic: {
- key: "comment_critic",
- description: "Leave 50 comments in the library",
- title: "Critic",
- category: AchievementCategory.Comment,
- icon: "🗣️",
- tier: AchievementTier.Silver,
- points: 100,
- requirements: { count: 50 },
- },
-
-
- suggestion_renaissance: {
- description: "Submit accepted suggestions across all 6 media types",
- key: "suggestion_renaissance",
- category: AchievementCategory.Suggestion,
- title: "Renaissance Person",
- icon: "🌈",
- points: 400,
- tier: AchievementTier.Gold,
- requirements: { diversity: true },
- },
-
-
-comment_expert: {
- description: "Leave 100 comments in the library",
- key: "comment_expert",
- category: AchievementCategory.Comment,
- title: "Expert Critic",
- icon: "🎭",
- points: 250,
- tier: AchievementTier.Gold,
- requirements: { count: 100 },
- },
-
- // ========== LIKE ACHIEVEMENTS (12) ==========
-like_first: {
- description: "Like your first item in the library",
- key: "like_first",
- category: AchievementCategory.Like,
- title: "First Like",
- icon: "❤️",
- points: 25,
- tier: AchievementTier.Bronze,
- requirements: { count: 1 },
- },
-
- comment_legend: {
- description: "Leave 500 comments in the library",
- key: "comment_legend",
- category: AchievementCategory.Comment,
- title: "Review Legend",
- icon: "🏅",
- points: 1000,
- tier: AchievementTier.Diamond,
- requirements: { count: 500 },
- },
-
- like_mega_fan: {
- description: "Like 500 items in the library",
- key: "like_mega_fan",
- category: AchievementCategory.Like,
- title: "Mega Fan",
- icon: "💗",
- points: 500,
- tier: AchievementTier.Platinum,
- requirements: { count: 500 },
- },
-
- comment_detailed: {
- description: "Write 10 comments with 500+ characters",
- category: AchievementCategory.Comment,
- key: "comment_detailed",
- icon: "📝",
- title: "Detailed Critic",
- points: 150,
- requirements: { count: 10 },
- tier: AchievementTier.Silver,
- },
-
- like_super_fan: {
- key: "like_super_fan",
- description: "Like 250 items in the library",
- title: "Super Fan",
- category: AchievementCategory.Like,
- icon: "💝",
- tier: AchievementTier.Gold,
- points: 250,
- requirements: { count: 250 },
- },
- comment_essay: {
- category: AchievementCategory.Comment,
- description: "Write 5 comments with 1000+ characters",
- icon: "📄",
- key: "comment_essay",
- points: 300,
- title: "Essay Writer",
- requirements: { count: 5 },
- tier: AchievementTier.Gold,
- },
- like_legendary: {
- key: "like_legendary",
- description: "Like 1000 items in the library",
- title: "Legendary Fan",
- category: AchievementCategory.Like,
- icon: "💞",
- tier: AchievementTier.Diamond,
- points: 1000,
- requirements: { count: 1000 },
- },
- comment_diverse: {
- description: "Comment on all 6 media types",
- category: AchievementCategory.Comment,
- key: "comment_diverse",
- icon: "🎨",
- title: "Well-Rounded Critic",
- points: 250,
- requirements: { diversity: true },
- tier: AchievementTier.Gold,
- },
- like_gamer: {
- key: "like_gamer",
- description: "Like 50 games",
- title: "Gamer",
- category: AchievementCategory.Like,
- icon: "🎮",
- tier: AchievementTier.Silver,
- points: 100,
- requirements: { count: 50 },
- },
- comment_first_to_comment: {
- category: AchievementCategory.Comment,
- description: "Be the first to comment on 10 items",
- icon: "🗨️",
- key: "comment_first_to_comment",
- points: 200,
- title: "Discussion Starter",
- requirements: { count: 10 },
- tier: AchievementTier.Silver,
- },
- like_music: {
- key: "like_music",
- description: "Like 50 music albums",
- title: "Music Enthusiast",
- category: AchievementCategory.Like,
- icon: "🎵",
- tier: AchievementTier.Silver,
- points: 100,
- requirements: { count: 50 },
- },
- comment_master: {
- description: "Leave 250 comments in the library",
- category: AchievementCategory.Comment,
- key: "comment_master",
- icon: "📖",
- title: "Master Reviewer",
- points: 500,
- requirements: { count: 250 },
- tier: AchievementTier.Platinum,
- },
comment_novel: {
category: AchievementCategory.Comment,
description: "Write 3 comments with 2000+ characters",
@@ -391,28 +323,42 @@ like_first: {
points: 500,
requirements: { count: 3 },
tier: AchievementTier.Platinum,
- title: "Novel Writer",
+ title: "Novel Writer",
},
- like_binge: {
- key: "like_binge",
- title: "Binge Liker",
- description: "Like 20+ items in one day",
- category: AchievementCategory.Like,
- tier: AchievementTier.Silver,
- icon: "⚡",
- points: 150,
- requirements: { count: 20, dayRange: 1 },
+
+ suggestion_quality_50: {
+ key: "suggestion_quality_50",
+ description: "Have 50 suggestions accepted",
+ title: "Grand Master",
+ category: AchievementCategory.Suggestion,
+ icon: "🎖️",
+ tier: AchievementTier.Platinum,
+ points: 700,
+ requirements: { count: 50 },
},
+
comment_reviewer: {
- key: "comment_reviewer",
- description: "Leave 10 comments in the library",
- title: "Reviewer",
+ description: "Leave 10 comments in the library",
category: AchievementCategory.Comment,
- icon: "✍️",
- tier: AchievementTier.Bronze,
+ key: "comment_reviewer",
+ icon: "✍️",
+ title: "Reviewer",
points: 50,
requirements: { count: 10 },
+ tier: AchievementTier.Bronze,
},
+
+ suggestion_renaissance: {
+ description: "Submit accepted suggestions across all 6 media types",
+ category: AchievementCategory.Suggestion,
+ key: "suggestion_renaissance",
+ icon: "🌈",
+ title: "Renaissance Person",
+ points: 400,
+ requirements: { diversity: true },
+ tier: AchievementTier.Gold,
+ },
+
comment_thoughtful: {
category: AchievementCategory.Comment,
description: "Comment on 50 different items",
@@ -425,36 +371,86 @@ like_first: {
},
engagement_early_adopter: {
- description: "Be among the first 10 users to join",
category: AchievementCategory.Engagement,
- key: "engagement_early_adopter",
+ description: "Be among the first 10 users to join",
icon: "🌟",
- title: "Early Adopter",
+ key: "engagement_early_adopter",
points: 1000,
requirements: {},
- tier: AchievementTier.Diamond,
+ tier: AchievementTier.Diamond,
+ title: "Early Adopter",
},
-
- engagement_power_user: {
- description: "Achieve Triple Threat 10 times",
- category: AchievementCategory.Engagement,
- key: "engagement_power_user",
- icon: "💫",
- title: "Power User",
- points: 750,
- requirements: { count: 10 },
- tier: AchievementTier.Platinum,
+ // ========== LIKE ACHIEVEMENTS (12) ==========
+ like_first: {
+ description: "Like your first item in the library",
+ key: "like_first",
+ category: AchievementCategory.Like,
+ title: "First Like",
+ icon: "❤️",
+ points: 25,
+ tier: AchievementTier.Bronze,
+ requirements: { count: 1 },
},
-
engagement_founding_100: {
category: AchievementCategory.Engagement,
description: "Be among the first 100 users to join",
icon: "🎖️",
key: "engagement_founding_100",
points: 500,
- title: "Founding Member",
requirements: {},
tier: AchievementTier.Gold,
+ title: "Founding Member",
+ },
+ like_mega_fan: {
+ description: "Like 500 items in the library",
+ key: "like_mega_fan",
+ category: AchievementCategory.Like,
+ title: "Mega Fan",
+ icon: "💗",
+ points: 500,
+ tier: AchievementTier.Platinum,
+ requirements: { count: 500 },
+ },
+ engagement_founding_1000: {
+ category: AchievementCategory.Engagement,
+ description: "Be among the first 1000 users to join",
+ icon: "🚀",
+ key: "engagement_founding_1000",
+ points: 200,
+ requirements: {},
+ tier: AchievementTier.Silver,
+ title: "Pioneer",
+ },
+ like_super_fan: {
+ key: "like_super_fan",
+ description: "Like 250 items in the library",
+ title: "Super Fan",
+ category: AchievementCategory.Like,
+ icon: "💝",
+ tier: AchievementTier.Gold,
+ points: 250,
+ requirements: { count: 250 },
+ },
+ engagement_power_user: {
+ category: AchievementCategory.Engagement,
+ description: "Achieve Triple Threat 10 times",
+ icon: "💫",
+ key: "engagement_power_user",
+ points: 750,
+ requirements: { count: 10 },
+ tier: AchievementTier.Platinum,
+ title: "Power User",
+ },
+
+ like_legendary: {
+ key: "like_legendary",
+ description: "Like 1000 items in the library",
+ title: "Legendary Fan",
+ category: AchievementCategory.Like,
+ icon: "💞",
+ tier: AchievementTier.Diamond,
+ points: 1000,
+ requirements: { count: 1000 },
},
engagement_streak_100: {
@@ -463,31 +459,42 @@ like_first: {
icon: "🏆",
key: "engagement_streak_100",
points: 500,
- title: "Committed",
requirements: { streak: 100 },
tier: AchievementTier.Gold,
+ title: "Committed",
},
- engagement_founding_1000: {
- category: AchievementCategory.Engagement,
- description: "Be among the first 1000 users to join",
- icon: "🚀",
- key: "engagement_founding_1000",
- points: 200,
- requirements: {},
- title: "Pioneer",
- tier: AchievementTier.Silver,
+ like_gamer: {
+ key: "like_gamer",
+ description: "Like 50 games",
+ title: "Gamer",
+ category: AchievementCategory.Like,
+ icon: "🎮",
+ tier: AchievementTier.Silver,
+ points: 100,
+ requirements: { count: 50 },
},
engagement_streak_30: {
- description: "Login for 30 days in a row",
category: AchievementCategory.Engagement,
- key: "engagement_streak_30",
+ description: "Login for 30 days in a row",
icon: "💪",
- title: "Dedicated",
+ key: "engagement_streak_30",
points: 250,
requirements: { streak: 30 },
+ tier: AchievementTier.Silver,
+ title: "Dedicated",
+ },
+
+ like_music: {
+ key: "like_music",
+ description: "Like 50 music albums",
+ title: "Music Enthusiast",
+ category: AchievementCategory.Like,
+ icon: "🎵",
tier: AchievementTier.Silver,
+ points: 100,
+ requirements: { count: 50 },
},
engagement_streak_365: {
@@ -496,20 +503,31 @@ like_first: {
icon: "⚡",
key: "engagement_streak_365",
points: 2000,
- title: "Unstoppable",
requirements: { streak: 365 },
tier: AchievementTier.Diamond,
+ title: "Unstoppable",
},
engagement_streak_7: {
- description: "Login for 7 days in a row",
- key: "engagement_streak_7",
category: AchievementCategory.Engagement,
- title: "Daily Visitor",
+ description: "Login for 7 days in a row",
icon: "🔥",
+ key: "engagement_streak_7",
points: 100,
- tier: AchievementTier.Bronze,
requirements: { streak: 7 },
+ tier: AchievementTier.Bronze,
+ title: "Daily Visitor",
+ },
+
+ like_binge: {
+ key: "like_binge",
+ title: "Binge Liker",
+ description: "Like 20+ items in one day",
+ category: AchievementCategory.Like,
+ tier: AchievementTier.Silver,
+ icon: "⚡",
+ points: 150,
+ requirements: { count: 20, dayRange: 1 },
},
engagement_triple_threat: {
@@ -519,11 +537,11 @@ like_first: {
key: "engagement_triple_threat",
points: 200,
requirements: { dayRange: 1 },
- title: "Triple Threat",
tier: AchievementTier.Silver,
+ title: "Triple Threat",
},
-
-engagement_veteran_180: {
+
+ engagement_veteran_180: {
category: AchievementCategory.Engagement,
description: "Have an account for 6 months",
icon: "📅",
@@ -533,17 +551,7 @@ engagement_veteran_180: {
tier: AchievementTier.Silver,
title: "Seasoned",
},
- // ========== ENGAGEMENT ACHIEVEMENTS (15) ==========
-engagement_welcome: {
- key: "engagement_welcome",
- title: "Welcome!",
- description: "Complete your profile setup",
- category: AchievementCategory.Engagement,
- tier: AchievementTier.Bronze,
- icon: "👋",
- points: 25,
- requirements: {},
- },
+
engagement_veteran_1825: {
category: AchievementCategory.Engagement,
description: "Have an account for 5 years",
@@ -554,6 +562,7 @@ engagement_welcome: {
tier: AchievementTier.Diamond,
title: "Legend",
},
+
engagement_veteran_30: {
category: AchievementCategory.Engagement,
description: "Have an account for 30 days",
@@ -564,6 +573,7 @@ engagement_welcome: {
tier: AchievementTier.Bronze,
title: "Veteran",
},
+
engagement_veteran_365: {
category: AchievementCategory.Engagement,
description: "Have an account for 1 year",
@@ -574,6 +584,7 @@ engagement_welcome: {
tier: AchievementTier.Gold,
title: "Longstanding",
},
+
engagement_veteran_730: {
category: AchievementCategory.Engagement,
description: "Have an account for 2 years",
@@ -584,6 +595,17 @@ engagement_welcome: {
tier: AchievementTier.Platinum,
title: "Elder",
},
+ // ========== ENGAGEMENT ACHIEVEMENTS (15) ==========
+ engagement_welcome: {
+ category: AchievementCategory.Engagement,
+ description: "Complete your profile setup",
+ icon: "👋",
+ key: "engagement_welcome",
+ points: 25,
+ requirements: {},
+ tier: AchievementTier.Bronze,
+ title: "Welcome!",
+ },
report_accuracy_80: {
category: AchievementCategory.Report,
@@ -593,7 +615,7 @@ engagement_welcome: {
points: 300,
requirements: { count: 10, rate: 0.8 },
tier: AchievementTier.Gold,
- title: "Sharp Eye",
+ title: "Sharp Eye",
},
report_accuracy_90: {
@@ -604,7 +626,7 @@ engagement_welcome: {
points: 500,
requirements: { count: 10, rate: 0.9 },
tier: AchievementTier.Platinum,
- title: "Eagle Eye",
+ title: "Eagle Eye",
},
report_consistent: {
@@ -615,7 +637,7 @@ engagement_welcome: {
points: 200,
requirements: { count: 4 },
tier: AchievementTier.Silver,
- title: "Consistent Guardian",
+ title: "Consistent Guardian",
},
report_guardian: {
@@ -626,7 +648,7 @@ engagement_welcome: {
points: 100,
requirements: { count: 5 },
tier: AchievementTier.Silver,
- title: "Guardian",
+ title: "Guardian",
},
report_protector: {
@@ -637,7 +659,7 @@ engagement_welcome: {
points: 250,
requirements: { count: 10 },
tier: AchievementTier.Gold,
- title: "Protector",
+ title: "Protector",
},
report_vigilant: {
@@ -648,7 +670,7 @@ engagement_welcome: {
points: 500,
requirements: { count: 25 },
tier: AchievementTier.Platinum,
- title: "Vigilant Protector",
+ title: "Vigilant Protector",
},
report_volume: {
@@ -663,14 +685,14 @@ engagement_welcome: {
},
// ========== REPORT ACHIEVEMENTS (8) ==========
report_watchful: {
- description: "Submit your first valid report (ACTION_TAKEN)",
category: AchievementCategory.Report,
- key: "report_watchful",
+ description: "Submit your first valid report (ACTION_TAKEN)",
icon: "👀",
- title: "Watchful Eye",
+ key: "report_watchful",
points: 50,
requirements: { count: 1 },
- tier: AchievementTier.Bronze,
+ tier: AchievementTier.Bronze,
+ title: "Watchful Eye",
},
};
diff --git a/shared-types/src/lib/achievement.types.ts b/shared-types/src/lib/achievement.types.ts
index 7d08de0..7abc562 100644
--- a/shared-types/src/lib/achievement.types.ts
+++ b/shared-types/src/lib/achievement.types.ts
@@ -3,6 +3,8 @@
* @license Naomi's Public License
* @author Naomi Carrigan
*/
+/* eslint-disable @typescript-eslint/naming-convention -- Enum members use PascalCase for display values */
+/* eslint-disable import/group-exports -- Multiple exports for better organization */
export enum AchievementCategory {
Suggestion = "SUGGESTION",
diff --git a/shared-types/src/lib/report.types.ts b/shared-types/src/lib/report.types.ts
index b25e449..1340425 100644
--- a/shared-types/src/lib/report.types.ts
+++ b/shared-types/src/lib/report.types.ts
@@ -1,3 +1,11 @@
+/**
+ * @copyright NHCarrigan
+ * @license Naomi's Public License
+ * @author Naomi Carrigan
+ */
+/* eslint-disable @typescript-eslint/naming-convention -- Enum members use UPPER_CASE for database values */
+/* eslint-disable import/group-exports -- Multiple exports for better organization */
+
export enum ReportReason {
INAPPROPRIATE_CONTENT = "INAPPROPRIATE_CONTENT",
HARASSMENT = "HARASSMENT",