import { LitElement, html } from "lit"; import { customElement, property } from "lit/decorators.js"; import playerAchievementMetadataJson from "../../../../../resources/playerAchievementMetadata.json" with { type: "json" }; import type { AchievementsResponse, PlayerAchievementJson, } from "../../../../core/ApiSchemas"; import type { Difficulty } from "../../../../core/game/Game"; import { translateText } from "../../../Utils"; type PlayerAchievementMetadata = { difficulty: Difficulty; }; type PlayerAchievementCard = { achievement: string; achievedAt: string | null; isUnlocked: boolean; }; const playerAchievementMetadata = playerAchievementMetadataJson as Record< string, PlayerAchievementMetadata >; @customElement("player-achievements") export class PlayerAchievements extends LitElement { createRenderRoot() { return this; } @property({ attribute: false }) achievementGroups: AchievementsResponse = []; private get unlockedAchievements(): PlayerAchievementJson[] { return this.achievementGroups .flatMap((group) => (group.type === "player" ? group.data : [])) .slice() .sort( (a, b) => new Date(b.achievedAt).getTime() - new Date(a.achievedAt).getTime(), ); } private get achievements(): PlayerAchievementCard[] { const unlockedByKey = new Map( this.unlockedAchievements.map((achievement) => [ achievement.achievement, achievement, ]), ); const knownKeys = Object.keys(playerAchievementMetadata); const achievementKeys = [ ...knownKeys, ...this.unlockedAchievements .map((achievement) => achievement.achievement) .filter((achievement) => !knownKeys.includes(achievement)), ]; const originalOrder = new Map( achievementKeys.map((achievement, index) => [achievement, index]), ); return achievementKeys .map((achievement) => { const unlockedAchievement = unlockedByKey.get(achievement); return { achievement, achievedAt: unlockedAchievement?.achievedAt ?? null, isUnlocked: unlockedAchievement !== undefined, }; }) .sort((a, b) => { if (a.isUnlocked !== b.isUnlocked) { return Number(b.isUnlocked) - Number(a.isUnlocked); } if (a.achievedAt && b.achievedAt) { return ( new Date(b.achievedAt).getTime() - new Date(a.achievedAt).getTime() ); } return ( (originalOrder.get(a.achievement) ?? 0) - (originalOrder.get(b.achievement) ?? 0) ); }); } private formatDate(achievedAt: string): string { const date = new Date(achievedAt); if (Number.isNaN(date.getTime())) { return achievedAt; } return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", }).format(date); } private resolveTitle(achievementKey: string): string { const translationKey = `achivements.${achievementKey}`; const translated = translateText(translationKey); return translated === translationKey ? achievementKey : translated; } private resolveDescription(achievementKey: string): string | null { const translationKey = `achivements.${achievementKey}_desc`; const translated = translateText(translationKey); return translated === translationKey ? null : translated; } private resolveDifficulty(achievementKey: string): Difficulty | null { return playerAchievementMetadata[achievementKey]?.difficulty ?? null; } private difficultyClasses(difficulty: Difficulty): string { switch (difficulty) { case "Easy": return "bg-emerald-500/15 text-emerald-300 border-emerald-400/25"; case "Medium": return "bg-amber-500/15 text-amber-200 border-amber-400/25"; case "Hard": return "bg-rose-500/15 text-rose-200 border-rose-400/25"; case "Impossible": return "bg-violet-500/15 text-violet-200 border-violet-400/25"; default: return "bg-white/5 text-white/60 border-white/10"; } } private renderDifficultyBadge(difficulty: Difficulty | null) { if (!difficulty) { return html` ${translateText("account_modal.unknown_difficulty")} `; } const translationKey = `difficulty.${difficulty.toLowerCase()}`; const translated = translateText(translationKey); const label = translated === translationKey ? difficulty : translated; return html` ${label} `; } private renderAchievementCard(achievement: PlayerAchievementCard) { const difficulty = this.resolveDifficulty(achievement.achievement); const description = this.resolveDescription(achievement.achievement); const cardClasses = achievement.isUnlocked ? "border-white/10 bg-gradient-to-br from-slate-900/70 via-slate-900/40 to-black/20" : "border-white/6 bg-gradient-to-br from-slate-900/40 via-slate-900/20 to-black/10 opacity-80"; return html`
${translateText("account_modal.achievement_label")}

${this.resolveTitle(achievement.achievement)}

${description ? html`

${description}

` : null}
${this.renderDifficultyBadge(difficulty)}
${achievement.isUnlocked ? translateText("account_modal.achieved_on") : translateText("account_modal.status")}
${achievement.isUnlocked && achievement.achievedAt ? html` ` : html`
${translateText("account_modal.not_unlocked_yet")}
`}
`; } render() { if (this.achievements.length === 0) { return html`
${translateText("account_modal.no_achievements")}
`; } return html`
${this.achievements.map((achievement) => this.renderAchievementCard(achievement), )}
`; } }