export const overviewRoles = ["Main DPS", "Sub DPS", "Support"] as const; export type OverviewRole = (typeof overviewRoles)[number]; export const elementOrder = [ "Pyro", "Hydro", "Anemo", "Electro", "Dendro", "Cryo", "Geo", ] as const; const weaponIconNames: Record = { WEAPON_SWORD_ONE_HAND: "sword.png", WEAPON_CLAYMORE: "claymore.png", WEAPON_POLE: "polearm.png", WEAPON_BOW: "bow.png", WEAPON_CATALYST: "catalyst.png", }; export function normalizeHex(value: string): string | null { const trimmed = value.trim(); if (!/^#[\da-f]{6}$/iu.test(trimmed)) return null; return trimmed.toUpperCase(); } function mixHex(hex: string, target: number, amount: number): string { const normalized = normalizeHex(hex); if (!normalized) return hex; const channels = [1, 3, 5].map((offset) => Number.parseInt(normalized.slice(offset, offset + 2), 16), ); return `#${channels .map((channel) => Math.round(channel + (target - channel) * amount) .toString(16) .padStart(2, "0"), ) .join("")}`.toUpperCase(); } function mixColors(left: string, right: string): string { const leftHex = normalizeHex(left) ?? "#E9A51A"; const rightHex = normalizeHex(right) ?? leftHex; const channels = [1, 3, 5].map((offset) => Math.round( (Number.parseInt(leftHex.slice(offset, offset + 2), 16) + Number.parseInt(rightHex.slice(offset, offset + 2), 16)) / 2, ), ); return `#${channels.map((channel) => channel.toString(16).padStart(2, "0")).join("")}`.toUpperCase(); } export function accentGradient(start: string, end = start): string { const normalizedStart = normalizeHex(start) ?? "#E9A51A"; const normalizedEnd = normalizeHex(end) ?? normalizedStart; const startEdge = mixHex(normalizedStart, 0, 0.12); const endEdge = mixHex(normalizedEnd, 0, 0.12); const highlight = mixHex(mixColors(normalizedStart, normalizedEnd), 255, 0.35); return `linear-gradient(90deg, ${startEdge} 0%, ${normalizedStart} 25%, ${highlight} 50%, ${normalizedEnd} 75%, ${endEdge} 100%)`; } export function weaponTypeIconPath(weaponType: string | null): string | null { const fileName = weaponType ? weaponIconNames[weaponType] : undefined; return fileName ? `/icon/weapon-types/${fileName}` : null; } export function overviewFileName(characterKey: string, role: OverviewRole): string { const safeKey = characterKey.replace(/[^\p{L}\p{N}._-]+/gu, "-"); const safeRole = role.toLowerCase().replaceAll(" ", "-"); return `overview-${safeKey}-${safeRole}.png`; }