${this.resolveTitle(achievement.achievement)}
${description ? html`${description}
` : null}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`
${description}
${this.resolveTitle(achievement.achievement)}
${description
? html`