feat(catalog): color equipment by rarity
CI / Verify (push) Successful in 1m0s
CI / Build immutable images and deploy (push) Successful in 3m26s

This commit is contained in:
2026-08-30 06:09:19 +00:00 Unverified
parent 890fb9385a
commit d0e2a95350
10 changed files with 3544 additions and 20 deletions
+18
View File
@@ -0,0 +1,18 @@
import { describe, expect, it } from "vitest";
import { rarityGradient, rarityGradientClass } from "@/lib/catalog/rarity";
describe("catalog rarity backgrounds", () => {
it("maps every weapon and artifact rarity to its Tailwind gradient", () => {
expect(rarityGradientClass(1)).toContain("rgba(23,25,28,1)");
expect(rarityGradientClass(2)).toContain("rgba(11,43,24,1)");
expect(rarityGradientClass(3)).toContain("rgba(0,160,181,1)");
expect(rarityGradientClass(4)).toContain("from-[#a300b8]");
expect(rarityGradientClass(5)).toContain("rgba(138,90,0,1)");
});
it("uses the neutral equipment background when rarity is unavailable", () => {
expect(rarityGradient(null)).toBe("#17383d");
expect(rarityGradientClass(undefined)).toBeUndefined();
});
});
+23
View File
@@ -0,0 +1,23 @@
const rarityGradientClasses: Record<number, string> = {
1: "bg-[linear-gradient(90deg,rgba(23,25,28,1)_0%,rgba(19,20,23,1)_100%)]",
2: "bg-[linear-gradient(90deg,rgba(11,43,24,1)_0%,rgba(7,18,13,1)_100%)]",
3: "bg-[linear-gradient(90deg,rgba(0,160,181,1)_0%,rgba(0,0,0,1)_100%)]",
4: "bg-gradient-to-r from-[#a300b8] to-black",
5: "bg-[linear-gradient(90deg,rgba(138,90,0,1)_0%,rgba(15,8,0,1)_100%)]",
};
const rarityGradients: Record<number, string> = {
1: "linear-gradient(90deg, rgba(23, 25, 28, 1) 0%, rgba(19, 20, 23, 1) 100%)",
2: "linear-gradient(90deg, rgba(11, 43, 24, 1) 0%, rgba(7, 18, 13, 1) 100%)",
3: "linear-gradient(90deg, rgba(0, 160, 181, 1) 0%, rgba(0, 0, 0, 1) 100%)",
4: "linear-gradient(90deg, rgba(163, 0, 184, 1) 0%, rgba(0, 0, 0, 1) 100%)",
5: "linear-gradient(90deg, rgba(138, 90, 0, 1) 0%, rgba(15, 8, 0, 1) 100%)",
};
export function rarityGradientClass(rarity: number | null | undefined): string | undefined {
return rarity ? rarityGradientClasses[rarity] : undefined;
}
export function rarityGradient(rarity: number | null | undefined): string {
return (rarity && rarityGradients[rarity]) || "#17383d";
}
+4 -3
View File
@@ -39,6 +39,7 @@ const weaponSchema = z.record(z.string(), z.object({
const artifactSchema = z.record(z.string(), z.object({
enName: z.string(),
setIcon: z.string(),
qualityType: z.string().optional(),
}));
async function getJson(url: string): Promise<unknown> {
@@ -145,7 +146,7 @@ export async function processCatalogSyncJob(jobId: string): Promise<void> {
sourceImage: `${API_ROOT}/assets/weaponicon/${encodeURIComponent(webpName(value.weaponIcon))}`,
}));
let artifacts = Object.entries(artifactData).filter(([, value]) => value.enName && value.enName !== "???" && value.setIcon).map(([key, value]) => ({
version, key, name: value.enName,
version, key, name: value.enName, rarity: rarity(value.qualityType),
imageKey: `catalog/${version}/artifacts/${webpName(value.setIcon)}`,
sourceImage: `${API_ROOT}/assets/artifacts/${encodeURIComponent(webpName(value.setIcon))}`,
}));
@@ -176,7 +177,7 @@ export async function processCatalogSyncJob(jobId: string): Promise<void> {
await Promise.all([
storage.write(`catalog/${version}/characters.json`, JSON.stringify(characters.map((item) => ({ version: item.version, key: item.key, name: item.name, element: item.element, rarity: item.rarity, weaponType: item.weaponType, imageKey: item.imageKey, elementIconKey: item.elementIconKey, constellations: item.constellations }))), { type: "application/json", acl: "public-read" }),
storage.write(`catalog/${version}/weapons.json`, JSON.stringify(weapons.map((item) => ({ version: item.version, key: item.key, name: item.name, rarity: item.rarity, weaponType: item.weaponType, imageKey: item.imageKey }))), { type: "application/json", acl: "public-read" }),
storage.write(`catalog/${version}/artifacts.json`, JSON.stringify(artifacts.map((item) => ({ version: item.version, key: item.key, name: item.name, imageKey: item.imageKey }))), { type: "application/json", acl: "public-read" }),
storage.write(`catalog/${version}/artifacts.json`, JSON.stringify(artifacts.map((item) => ({ version: item.version, key: item.key, name: item.name, rarity: item.rarity, imageKey: item.imageKey }))), { type: "application/json", acl: "public-read" }),
]);
await getDb().transaction(async (tx) => {
@@ -186,7 +187,7 @@ export async function processCatalogSyncJob(jobId: string): Promise<void> {
await tx.delete(catalogArtifacts).where(eq(catalogArtifacts.version, version));
if (characters.length) await tx.insert(catalogCharacters).values(characters.map((item) => ({ version: item.version, key: item.key, name: item.name, element: item.element, rarity: item.rarity, weaponType: item.weaponType, imageKey: item.imageKey, elementIconKey: item.elementIconKey, constellations: item.constellations })));
if (weapons.length) await tx.insert(catalogWeapons).values(weapons.map((item) => ({ version: item.version, key: item.key, name: item.name, rarity: item.rarity, weaponType: item.weaponType, imageKey: item.imageKey })));
if (artifacts.length) await tx.insert(catalogArtifacts).values(artifacts.map((item) => ({ version: item.version, key: item.key, name: item.name, imageKey: item.imageKey })));
if (artifacts.length) await tx.insert(catalogArtifacts).values(artifacts.map((item) => ({ version: item.version, key: item.key, name: item.name, rarity: item.rarity, imageKey: item.imageKey })));
await tx.update(catalogVersions).set({ active: false });
await tx.update(catalogVersions).set({ active: true, syncedAt: new Date() }).where(eq(catalogVersions.version, version));
await tx.update(catalogSyncJobs).set({ status: "completed", completedAt: new Date(), progress: uniqueAssets.size, total: uniqueAssets.size }).where(eq(catalogSyncJobs.id, jobId));