feat : add elemental ickn

This commit is contained in:
2026-09-04 18:30:22 +07:00 Unverified
parent 9a7954fa36
commit 9d3717d0f3
7 changed files with 129 additions and 25 deletions
+14
View File
@@ -8,6 +8,20 @@ const elementBackgroundColors: Record<string, string> = {
Geo: "#0F0D00",
};
const elementIconFilters: Record<string, string> = {
Anemo: "brightness(0) saturate(100%) invert(76%) sepia(19%) saturate(867%) hue-rotate(113deg) brightness(92%) contrast(86%)",
Geo: "brightness(0) saturate(100%) invert(77%) sepia(80%) saturate(1159%) hue-rotate(337deg) brightness(99%) contrast(90%)",
Electro: "brightness(0) saturate(100%) invert(66%) sepia(17%) saturate(1525%) hue-rotate(225deg) brightness(91%) contrast(89%)",
Dendro: "brightness(0) saturate(100%) invert(70%) sepia(39%) saturate(713%) hue-rotate(32deg) brightness(93%) contrast(87%)",
Hydro: "brightness(0) saturate(100%) invert(71%) sepia(68%) saturate(1329%) hue-rotate(164deg) brightness(97%) contrast(95%)",
Pyro: "brightness(0) saturate(100%) invert(54%) sepia(72%) saturate(1836%) hue-rotate(339deg) brightness(99%) contrast(89%)",
Cryo: "brightness(0) saturate(100%) invert(87%) sepia(20%) saturate(952%) hue-rotate(167deg) brightness(93%) contrast(89%)",
};
export function elementBackgroundColor(element: string | null | undefined): string {
return (element && elementBackgroundColors[element]) || "#17383d";
}
export function elementIconFilter(element: string): string | undefined {
return elementIconFilters[element];
}
+16
View File
@@ -104,6 +104,21 @@ export async function getPublicGuide(slug: string) {
getDb().select().from(teamCompositions).where(eq(teamCompositions.guideId, row.guide.id)).orderBy(asc(teamCompositions.sortOrder)),
]);
const elementIconUrls = Object.fromEntries(
(await getDb()
.select({
element: catalogCharacters.element,
elementIconKey: catalogCharacters.elementIconKey,
})
.from(catalogCharacters)
.where(isNotNull(catalogCharacters.elementIconKey)))
.flatMap((character) =>
character.element && character.elementIconKey
? [[character.element, publicMediaUrl(character.elementIconKey)] as const]
: [],
),
);
const weaponRows = weaponConditionsRows.length ? await getDb().select({ recommendation: weaponRecommendations, weapon: catalogWeapons }).from(weaponRecommendations).innerJoin(catalogWeapons, eq(catalogWeapons.key, weaponRecommendations.weaponKey)).where(inArray(weaponRecommendations.conditionId, weaponConditionsRows.map((condition) => condition.id))).orderBy(asc(weaponRecommendations.sortOrder)) : [];
const constellations = constellationGroups.length ? await getDb().select().from(constellationRecommendations).where(inArray(constellationRecommendations.conditionId, constellationGroups.map((group) => group.id))).orderBy(asc(constellationRecommendations.level)) : [];
@@ -124,6 +139,7 @@ export async function getPublicGuide(slug: string) {
...row.guide,
coverUrl: publicMediaUrl(row.cover.objectKey),
character: catalogCharacterWithUrls(row.character),
elementIconUrls,
sections,
weapons: weaponRows.map(({ recommendation, weapon }) => ({ ...recommendation, weapon: { ...weapon, imageUrl: publicMediaUrl(weapon.imageKey) } })),
weaponGroups: weaponConditionsRows.map((condition) => ({ ...condition, rows: weaponRows.filter(({ recommendation }) => recommendation.conditionId === condition.id).map(({ recommendation, weapon }) => ({ ...recommendation, weapon: { ...weapon, imageUrl: publicMediaUrl(weapon.imageKey) } })) })),
+11 -1
View File
@@ -2,7 +2,11 @@ import { describe, expect, it } from "vitest";
import { calculateTeamMetrics } from "@/lib/guides/calculations";
import { artifactSectionSchema, constellationSectionSchema, createGuideSchema, teamSectionSchema, updateGuideSchema, weaponSectionSchema } from "@/lib/guides/schemas";
import { recommendedStatIconName, targetStatIconName } from "@/lib/guides/stats";
import {
recommendedStatElement,
recommendedStatIconName,
targetStatIconName,
} from "@/lib/guides/stats";
describe("structured guide validation", () => {
const artifactGroups = [
@@ -63,6 +67,12 @@ describe("structured guide validation", () => {
expect(targetStatIconName("DEF")).toBe(recommendedStatIconName("DEF%"));
});
it("maps elemental damage recommendations to Lunaris element icons", () => {
expect(recommendedStatElement("Pyro DMG%")).toBe("Pyro");
expect(recommendedStatElement("Hydro DMG%")).toBe("Hydro");
expect(recommendedStatElement("Physical DMG%")).toBeNull();
});
it("limits an optional note on each artifact recommendation", () => {
const row = { firstArtifactKey: "1", secondArtifactKey: null, percent: 100 };
const base = { expectedVersion: 1, sands: [], goblet: [], circlet: [], substats: [], targets: [], talents: [], note: null };
+5
View File
@@ -51,6 +51,11 @@ export function recommendedStatIconName(value: string): string | null {
return ["hp", "atk", "def", "em", "er"].includes(base) ? base : null;
}
export function recommendedStatElement(value: string): string | null {
const match = /^(Anemo|Geo|Electro|Dendro|Hydro|Pyro|Cryo) DMG%$/u.exec(value);
return match?.[1] ?? null;
}
export function statPriorityColorClass(index: number): string {
return index === 0
? "text-tier-gold"