diff --git a/PLANS.md b/PLANS.md index 20332b3..206ffff 100644 --- a/PLANS.md +++ b/PLANS.md @@ -11,6 +11,7 @@ Keep this checklist aligned with repository behavior. Check off work in the same - [x] Show owned/total roster progress and disable additions when every character is owned. - [x] Highlight max-level skills in green so awakening styling never obscures a stored level 12. - [x] Place Characters first in the Inventory navigation section. +- [x] Add portrait-selectable Mina, Jiwon, and Ophelia costumes with hidden-catalog development navigation details. ## Monsterlings: Available @@ -128,7 +129,11 @@ Implementation is intentionally deferred. Keep every item unchecked until the co ## Chunk 2: Artifacts Inventory -- [x] Add Brisshell's Tier 5 Earth Assassin artifact, Monstrous Longing. +<<<<<<< HEAD + +- [x] # Add Brisshell's Tier 5 Earth Assassin artifact, Monstrous Longing. +- [x] Add Brisshell's Tier 5 Earth Assassin artifact with a temporary title pending its official name. + > > > > > > > 59afb26 (Prepare Brishell assets) - [x] Define typed artifact data, owned-artifact fields, images, and validation from the game source. - [x] Add owned-artifact create, edit, delete, reset, local persistence, and mutation timestamps. - [x] Build the Artifacts page with cards, search, filters, empty states, and accessible forms. diff --git a/assets/images-source/Character_Portrait/portrait_Jiwon_Costume_01.png b/assets/images-source/Character_Portrait/portrait_Jiwon_Costume_01.png new file mode 100644 index 0000000..223bbd5 Binary files /dev/null and b/assets/images-source/Character_Portrait/portrait_Jiwon_Costume_01.png differ diff --git a/assets/images-source/Character_Portrait/portrait_Mina_Costume_01.png b/assets/images-source/Character_Portrait/portrait_Mina_Costume_01.png new file mode 100644 index 0000000..08dac53 Binary files /dev/null and b/assets/images-source/Character_Portrait/portrait_Mina_Costume_01.png differ diff --git a/assets/images-source/Character_Portrait/portrait_Ophelia_Costume_01.png b/assets/images-source/Character_Portrait/portrait_Ophelia_Costume_01.png new file mode 100644 index 0000000..efdc272 Binary files /dev/null and b/assets/images-source/Character_Portrait/portrait_Ophelia_Costume_01.png differ diff --git a/assets/images-source/Character_Portrait/portrait_dimension_Maybell_01.png b/assets/images-source/Character_Portrait/portrait_dimension_Maybell_01.png new file mode 100644 index 0000000..b9b6c27 Binary files /dev/null and b/assets/images-source/Character_Portrait/portrait_dimension_Maybell_01.png differ diff --git a/public/images/Character_Portrait/portrait_Jiwon_Costume_01.webp b/public/images/Character_Portrait/portrait_Jiwon_Costume_01.webp new file mode 100644 index 0000000..b833221 Binary files /dev/null and b/public/images/Character_Portrait/portrait_Jiwon_Costume_01.webp differ diff --git a/public/images/Character_Portrait/portrait_Mina_Costume_01.webp b/public/images/Character_Portrait/portrait_Mina_Costume_01.webp new file mode 100644 index 0000000..df9d880 Binary files /dev/null and b/public/images/Character_Portrait/portrait_Mina_Costume_01.webp differ diff --git a/public/images/Character_Portrait/portrait_Ophelia_Costume_01.webp b/public/images/Character_Portrait/portrait_Ophelia_Costume_01.webp new file mode 100644 index 0000000..5bc31e6 Binary files /dev/null and b/public/images/Character_Portrait/portrait_Ophelia_Costume_01.webp differ diff --git a/public/images/Character_Portrait/portrait_dimension_Maybell_01.webp b/public/images/Character_Portrait/portrait_dimension_Maybell_01.webp new file mode 100644 index 0000000..ad3478c Binary files /dev/null and b/public/images/Character_Portrait/portrait_dimension_Maybell_01.webp differ diff --git a/src/components/artifacts/artifacts-page.tsx b/src/components/artifacts/artifacts-page.tsx index fbf172e..32dd852 100644 --- a/src/components/artifacts/artifacts-page.tsx +++ b/src/components/artifacts/artifacts-page.tsx @@ -17,11 +17,12 @@ import { useAppStore } from "@/stores/app-store"; export const ArtifactsPage = () => { const owned = useAppStore((s) => s.artifactsOwned); + const charactersOwned = useAppStore((s) => s.charactersOwned); const loadouts = useAppStore((s) => s.loadouts); const equippedCharacterUsage = getEquippedCharacterUsage( loadouts, undefined, - { artifactInstanceIds: Object.keys(owned) }, + { artifactInstanceIds: Object.keys(owned), charactersOwned }, ); const [filters, setFilters] = useState(emptyArtifactFilters); const [instanceIdToEdit, setInstanceIdToEdit] = useState(null); diff --git a/src/components/characters/characters-page.tsx b/src/components/characters/characters-page.tsx index 3d2e7aa..0ca32ea 100644 --- a/src/components/characters/characters-page.tsx +++ b/src/components/characters/characters-page.tsx @@ -4,6 +4,7 @@ import { CharacterFilter } from "@/components/characters/components/character-fi import { CharacterOwnedList } from "@/components/characters/components/character-owned-list"; import { CharacterSkillLevel } from "@/components/characters/components/character-skill-level"; import { useCharacterFilter } from "@/components/characters/store/characters-filter-store"; +import { resolveCharacterPortrait } from "@/components/characters/utils/character-costume"; import { matchesCharacterFilters } from "@/components/characters/utils/character-utils"; import { CollectionExportMenu } from "@/components/shared/collection-export-menu"; import { PageTitle } from "@/components/shared/page-title"; @@ -58,7 +59,10 @@ export const CharactersPage = () => { ; @@ -58,12 +60,13 @@ export const CharacterOwnedDetailsForm = ({ [CHARACTER_SKILLS.SPECIAL]: editCharacterData?.skills.special ?? 1, [CHARACTER_SKILLS.ULTIMATE]: editCharacterData?.skills.ultimate ?? 1, }, + costume_id: editCharacterData?.costume_id ?? null, }, mode: "onChange", }); const onSubmit = (data: CharacterOwned) => { - const { awakening, skills } = data; + const { awakening, skills, costume_id } = data; const object = { id, awakening: Number(awakening), @@ -73,6 +76,7 @@ export const CharacterOwnedDetailsForm = ({ [CHARACTER_SKILLS.SPECIAL]: Number(skills[CHARACTER_SKILLS.SPECIAL]), [CHARACTER_SKILLS.ULTIMATE]: Number(skills[CHARACTER_SKILLS.ULTIMATE]), }, + costume_id: costume_id ?? null, }; setCharacterOwned(object); @@ -91,6 +95,9 @@ export const CharacterOwnedDetailsForm = ({ control: form.control, name: "awakening", }); + const costumeId = useWatch({ control: form.control, name: "costume_id" }); + const character = CHARACTERS_DATA[id]; + const costumes = getVisibleCostumes(character); return (
@@ -101,6 +108,44 @@ export const CharacterOwnedDetailsForm = ({ className="gap-y-2 flex flex-col" id={FORM_ID} > + + {costumes.length > 0 && ( +
+ Costume +
+ + {costumes.map((costume) => ( + + ))} +
+
+ )} +
+ name="awakening" diff --git a/src/components/characters/components/character-owned-list.tsx b/src/components/characters/components/character-owned-list.tsx index ffa9c05..b8e3131 100644 --- a/src/components/characters/components/character-owned-list.tsx +++ b/src/components/characters/components/character-owned-list.tsx @@ -6,6 +6,7 @@ import { CHARACTER_SORTS, type CharacterFilters, } from "@/components/characters/store/characters-filter-store"; +import { resolveCharacterPortrait } from "@/components/characters/utils/character-costume"; import { matchesCharacterFilters } from "@/components/characters/utils/character-utils"; import { CollectionEmptyState } from "@/components/shared/collection-empty-state"; import { CHARACTERS_DATA } from "@/data/characters/CHARACTERS_DATA"; @@ -78,7 +79,10 @@ export const CharacterOwnedList = ({ filters }: CharacterOwnedListProps) => { key={charOwned.id} portraitSize={130} iconSize={30} - portraitImage={charOwned.info.portraitImage} + portraitImage={resolveCharacterPortrait( + charOwned.info, + charOwned, + )} name={charOwned.info.name} element_id={charOwned.info.element_id} class_id={charOwned.info.class_id} diff --git a/src/components/characters/components/edit-character-details-dialog.tsx b/src/components/characters/components/edit-character-details-dialog.tsx index d5ae0f9..ef785cb 100644 --- a/src/components/characters/components/edit-character-details-dialog.tsx +++ b/src/components/characters/components/edit-character-details-dialog.tsx @@ -1,5 +1,6 @@ import { useGoogleAnalytics } from "tanstack-router-ga4"; import { CharacterOwnedDetailsForm } from "@/components/characters/components/character-details-form"; +import { resolveCharacterPortrait } from "@/components/characters/utils/character-costume"; import { ConfirmDeleteButton } from "@/components/shared/confirm-delete-button"; import { TierPortrait } from "@/components/shared/tier-portrait"; import { @@ -59,7 +60,10 @@ export const EditCharacterDetailsDialog = ( aria-hidden="true" > { + afterEach(() => vi.unstubAllEnvs()); + + it("shows released costumes and safely falls back for unknown IDs", () => { + vi.stubEnv("VITE_NODE_ENV", "production"); + expect( + resolveCharacterPortrait(CHARACTERS_DATA[9], { costume_id: 1 }), + ).toBe(CHARACTERS_DATA[9].costumes?.[0]?.portraitImage); + expect( + resolveCharacterPortrait(CHARACTERS_DATA[9], { costume_id: 99 }), + ).toBe(CHARACTERS_DATA[9].portraitImage); + }); + + it("preserves hidden selections while rendering them only in development", () => { + vi.stubEnv("VITE_NODE_ENV", "production"); + expect( + resolveCharacterPortrait(CHARACTERS_DATA[3], { costume_id: 1 }), + ).toBe(CHARACTERS_DATA[3].portraitImage); + vi.stubEnv("VITE_NODE_ENV", "development"); + expect( + resolveCharacterPortrait(CHARACTERS_DATA[3], { costume_id: 1 }), + ).toBe(CHARACTERS_DATA[3].costumes?.[0]?.portraitImage); + }); +}); diff --git a/src/components/characters/utils/character-costume.ts b/src/components/characters/utils/character-costume.ts new file mode 100644 index 0000000..880cab4 --- /dev/null +++ b/src/components/characters/utils/character-costume.ts @@ -0,0 +1,20 @@ +import type { Character } from "@/data/characters/CHARACTERS_DATA"; + +export type CharacterOwnedCostume = { costume_id?: number | null }; + +export const getVisibleCostumes = (character: Character) => + (character.costumes ?? []).filter( + (costume) => + import.meta.env.VITE_NODE_ENV === "development" || !costume.is_hidden, + ); + +export const resolveCharacterPortrait = ( + character: Character, + owned?: CharacterOwnedCostume, +) => + getVisibleCostumes(character).find( + (costume) => costume.id === owned?.costume_id, + )?.portraitImage ?? character.portraitImage; + +export const getCatalogCostume = (character: Character, id?: number | null) => + (character.costumes ?? []).find((costume) => costume.id === id); diff --git a/src/components/loadouts/components/loadout-card-character-row.tsx b/src/components/loadouts/components/loadout-card-character-row.tsx index b3a3311..f43ba0f 100644 --- a/src/components/loadouts/components/loadout-card-character-row.tsx +++ b/src/components/loadouts/components/loadout-card-character-row.tsx @@ -1,4 +1,5 @@ import { isArtifactVisible } from "@/components/artifacts/utils/artifact-utils"; +import { resolveCharacterPortrait } from "@/components/characters/utils/character-costume"; import { isCharacterVisible } from "@/components/characters/utils/character-utils"; import { TierPortrait } from "@/components/shared/tier-portrait"; import { ARTIFACTS_DATA } from "@/data/artifacts/ARTIFACTS_DATA"; @@ -50,6 +51,13 @@ export const LoadoutCardCharacterRow = ({ : null; const characterOwned = slot.characterId !== null ? charactersOwned[slot.characterId] : null; + const resolvedCharacter = + character && characterOwned + ? { + ...character, + portraitImage: resolveCharacterPortrait(character, characterOwned), + } + : character; const artifactId = slot.artifactInstanceId; const artifactOwned = artifactId ? artifactsOwned[artifactId] : null; const catalogArtifact = artifactOwned @@ -62,7 +70,7 @@ export const LoadoutCardCharacterRow = ({ return (
diff --git a/src/components/loadouts/components/loadout-character-picker.tsx b/src/components/loadouts/components/loadout-character-picker.tsx index 47d65a3..d7d85ae 100644 --- a/src/components/loadouts/components/loadout-character-picker.tsx +++ b/src/components/loadouts/components/loadout-character-picker.tsx @@ -3,6 +3,7 @@ import type { CharacterOwned } from "@/components/characters/components/characte import { CharacterFilter } from "@/components/characters/components/character-filter"; import { CharacterSkillLevel } from "@/components/characters/components/character-skill-level"; import type { CharacterFilters } from "@/components/characters/store/characters-filter-store"; +import { resolveCharacterPortrait } from "@/components/characters/utils/character-costume"; import type { Character } from "@/data/characters/CHARACTERS_DATA"; import { cn } from "@/lib/utils"; @@ -48,7 +49,10 @@ export const LoadoutCharacterPicker = ({ ; activeTab: string; monsterlingsOwned: StoreState["monsterlingsOwned"]; + charactersOwned: StoreState["charactersOwned"]; artifactsOwned: StoreState["artifactsOwned"]; onNameChange: (name: string) => void; onNotesChange: (notes: string) => void; @@ -53,6 +55,7 @@ export const LoadoutEditor = ({ draft, activeTab, monsterlingsOwned, + charactersOwned, artifactsOwned, onNameChange, onNotesChange, @@ -103,7 +106,13 @@ export const LoadoutEditor = ({ > ; + costume_id?: number | null; }; type LoadoutPreviewCharacterProps = { character: PreviewCharacter; diff --git a/src/components/loadouts/components/loadout-preview-row.tsx b/src/components/loadouts/components/loadout-preview-row.tsx index 5a2a50b..18a3ed5 100644 --- a/src/components/loadouts/components/loadout-preview-row.tsx +++ b/src/components/loadouts/components/loadout-preview-row.tsx @@ -1,3 +1,4 @@ +import { resolveCharacterPortrait } from "@/components/characters/utils/character-costume"; import { isCharacterVisible } from "@/components/characters/utils/character-utils"; import { MONSTERLING_CARD_WIDTH, @@ -90,7 +91,15 @@ export const LoadoutPreviewRow = ({
{character && characterOwned ? ( state.charactersOwned); const title = controller.pickerTarget?.type === LOADOUT_TARGET_TYPES.CHARACTER ? "Select Character" @@ -157,6 +159,7 @@ export const LoadoutsDialog = ({ draft={controller.draft} activeTab={controller.activeTab} monsterlingsOwned={controller.monsterlingsOwned} + charactersOwned={charactersOwned} artifactsOwned={controller.artifactsOwned} onNameChange={controller.setName} onNotesChange={controller.setNotes} diff --git a/src/components/loadouts/utils/equipped-character-usage.test.ts b/src/components/loadouts/utils/equipped-character-usage.test.ts index 134480d..3317139 100644 --- a/src/components/loadouts/utils/equipped-character-usage.test.ts +++ b/src/components/loadouts/utils/equipped-character-usage.test.ts @@ -15,7 +15,12 @@ const character = (id: number, name: string, variant?: "Summer Dive!") => ({ describe("getEquippedCharacterUsage", () => { it("matches exact regular, legendary, and artifact owned instances", () => { const characters = { - 1: character(1, "Zoe"), + 1: { + ...character(1, "Zoe"), + costumes: [ + { id: 1, name: "Costume 1", portraitImage: "/costume.webp" }, + ], + }, 2: character(2, "Angel"), }; const usage = getEquippedCharacterUsage( @@ -41,6 +46,7 @@ describe("getEquippedCharacterUsage", () => { { monsterlingInstanceIds: ["regular", "legendary", "unused"], artifactInstanceIds: ["artifact-a", "artifact-b"], + charactersOwned: { 1: { costume_id: 1 } }, }, ); @@ -49,6 +55,9 @@ describe("getEquippedCharacterUsage", () => { expect(usage.monsterlings.unused.map(({ id }) => id)).toEqual([1]); expect(usage.artifacts["artifact-a"].map(({ id }) => id)).toEqual([1]); expect(usage.artifacts["artifact-b"].map(({ id }) => id)).toEqual([2]); + expect(usage.artifacts["artifact-a"][0].portraitImage).toBe( + "/costume.webp", + ); }); it("deduplicates by character ID, sorts display names, and ignores bad references", () => { diff --git a/src/components/loadouts/utils/equipped-character-usage.ts b/src/components/loadouts/utils/equipped-character-usage.ts index f26275a..b640604 100644 --- a/src/components/loadouts/utils/equipped-character-usage.ts +++ b/src/components/loadouts/utils/equipped-character-usage.ts @@ -1,3 +1,7 @@ +import { + type CharacterOwnedCostume, + resolveCharacterPortrait, +} from "@/components/characters/utils/character-costume"; import type { Character, CharId } from "@/data/characters/CHARACTERS_DATA"; import { CHARACTERS_DATA } from "@/data/characters/CHARACTERS_DATA"; import type { LoadoutCharacterSlot } from "@/stores/loadouts-slice"; @@ -15,6 +19,7 @@ export type EquippedCharacterUsage = { export type EquippedCharacterInventory = { monsterlingInstanceIds?: Iterable; artifactInstanceIds?: Iterable; + charactersOwned?: Record; }; type EquippedCharacterSlot = Pick< @@ -67,7 +72,10 @@ export const getEquippedCharacterUsage = ( const resolved = { id: character.id, name: character.name, - portraitImage: character.portraitImage, + portraitImage: resolveCharacterPortrait( + character, + inventory.charactersOwned?.[characterId], + ), ...(character.variant ? { variant: character.variant } : {}), } satisfies EquippedCharacter; const monsterlingIds = [ diff --git a/src/components/monsterlings/components/monsterlings-list.tsx b/src/components/monsterlings/components/monsterlings-list.tsx index 346f4c2..74b03ee 100644 --- a/src/components/monsterlings/components/monsterlings-list.tsx +++ b/src/components/monsterlings/components/monsterlings-list.tsx @@ -22,10 +22,11 @@ export const MonsterlingsList = ({ filters }: MonsterlingsListProps) => { (s) => s.monsterlingLinkChainLevels, ); const loadouts = useAppStore((s) => s.loadouts); + const charactersOwned = useAppStore((s) => s.charactersOwned); const equippedCharacterUsage = getEquippedCharacterUsage( loadouts, undefined, - { monsterlingInstanceIds: Object.keys(monsterlingsOwned) }, + { monsterlingInstanceIds: Object.keys(monsterlingsOwned), charactersOwned }, ); const sortedMonsterlings = Object.entries(monsterlingsOwned) .filter(([, monsterling]) => { diff --git a/src/components/navigation-hidden-info.tsx b/src/components/navigation-hidden-info.tsx new file mode 100644 index 0000000..36fafdd --- /dev/null +++ b/src/components/navigation-hidden-info.tsx @@ -0,0 +1,53 @@ +import { Info } from "lucide-react"; +import { Tooltip } from "radix-ui"; +import { ARTIFACTS_DATA } from "@/data/artifacts/ARTIFACTS_DATA"; +import { CHARACTERS_DATA } from "@/data/characters/CHARACTERS_DATA"; + +type NavigationHiddenInfoProps = { catalog: "characters" | "artifacts" }; + +export const NavigationHiddenInfo = ({ + catalog, +}: NavigationHiddenInfoProps) => { + const entries = + catalog === "characters" + ? Object.values(CHARACTERS_DATA).flatMap((character) => [ + ...(character.is_hidden ? [`Character: ${character.name}`] : []), + ...(character.costumes ?? []) + .filter(({ is_hidden }) => is_hidden) + .map(({ name }) => `Costume: ${character.name} — ${name}`), + ]) + : Object.values(ARTIFACTS_DATA) + .filter(({ is_hidden }) => is_hidden) + .map(({ name }) => `Artifact: ${name}`); + + return ( + + + + + + + +

Hidden in production

+
    + {entries.map((entry) => ( +
  • {entry}
  • + ))} +
+
+
+
+
+ ); +}; diff --git a/src/components/navigation.test.tsx b/src/components/navigation.test.tsx index e3e4182..a0537cf 100644 --- a/src/components/navigation.test.tsx +++ b/src/components/navigation.test.tsx @@ -1,5 +1,5 @@ // @vitest-environment jsdom -import { cleanup, render, screen } from "@testing-library/react"; +import { cleanup, fireEvent, render, screen } from "@testing-library/react"; import type { ComponentProps, ReactNode } from "react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { Nav } from "@/components/navigation"; @@ -29,11 +29,20 @@ describe("Nav", () => { expect(screen.queryByRole("link", { name: "Artifacts (1)" })).toBeNull(); }); - it("shows catalog hidden counts in local development labels", () => { + it("shows hidden catalog details on hover, not click, in local development", async () => { vi.stubEnv("VITE_NODE_ENV", "development"); render(
diff --git a/src/data/characters/CHARACTERS_DATA.test.ts b/src/data/characters/CHARACTERS_DATA.test.ts index 66fb262..9500fad 100644 --- a/src/data/characters/CHARACTERS_DATA.test.ts +++ b/src/data/characters/CHARACTERS_DATA.test.ts @@ -60,6 +60,20 @@ describe("CHARACTERS_DATA", () => { expect(existsSync(resolve("public", character.fullImage.slice(1)))).toBe( true, ); + for (const costume of character.costumes ?? []) { + expect(costume.name).toMatch(/^Costume \d+$/); + expect(costume.portraitImage).toMatch(/^\/images\/.+\.webp$/); + expect( + existsSync(resolve("public", costume.portraitImage.slice(1))), + ).toBe(true); + } } + const costumes = characters.flatMap(({ costumes = [] }) => costumes); + expect( + new Set(costumes.map(({ portraitImage }) => portraitImage)).size, + ).toBe(costumes.length); + expect(CHARACTERS_DATA[3].costumes?.[0]?.is_hidden).toBe(true); + expect(CHARACTERS_DATA[7].costumes?.[0]?.is_hidden).toBeUndefined(); + expect(CHARACTERS_DATA[9].costumes?.[0]?.is_hidden).toBeUndefined(); }); }); diff --git a/src/data/characters/CHARACTERS_DATA.ts b/src/data/characters/CHARACTERS_DATA.ts index 5f2b2a7..70fc8f6 100644 --- a/src/data/characters/CHARACTERS_DATA.ts +++ b/src/data/characters/CHARACTERS_DATA.ts @@ -9,6 +9,12 @@ import { import { TIER_ID_BY_TIER, type TierId } from "@/data/tiers/TIERS_DATA"; export type CharId = number; +export type Costume = { + id: number; + name: string; + portraitImage: string; + is_hidden?: boolean; +}; export type Character = { id: CharId; @@ -21,6 +27,7 @@ export type Character = { tier_id: TierId; variant?: "Summer Dive!"; is_hidden?: boolean; + costumes?: Costume[]; }; export const CHARACTERS_DATA: Record = { @@ -62,6 +69,15 @@ export const CHARACTERS_DATA: Record = { portraitImage: "/images/Character_Portrait/portrait_Mina_00.webp", fullImage: "/images/Character_Full/Img_CharacterIllust_Mina.webp", tier_id: TIER_ID_BY_TIER.PRIME_5, + costumes: [ + { + id: 1, + name: "Costume 1", + portraitImage: + "/images/Character_Portrait/portrait_Mina_Costume_01.webp", + is_hidden: true, + }, + ], }, 4: { id: 4, @@ -125,6 +141,14 @@ export const CHARACTERS_DATA: Record = { portraitImage: "/images/Character_Portrait/portrait_Ophelia_00.webp", fullImage: "/images/Character_Full/Img_CharacterIllust_Ophelia.webp", tier_id: TIER_ID_BY_TIER.PRIME_5, + costumes: [ + { + id: 1, + name: "Costume 1", + portraitImage: + "/images/Character_Portrait/portrait_Ophelia_Costume_01.webp", + }, + ], }, 8: { id: 8, @@ -151,6 +175,14 @@ export const CHARACTERS_DATA: Record = { portraitImage: "/images/Character_Portrait/portrait_Jiwon_00.webp", fullImage: "/images/Character_Full/Img_CharacterIllust_Jiwon.webp", tier_id: TIER_ID_BY_TIER.PRIME_5, + costumes: [ + { + id: 1, + name: "Costume 1", + portraitImage: + "/images/Character_Portrait/portrait_Jiwon_Costume_01.webp", + }, + ], }, 10: { id: 10,