UPDATE: Update readme and Next 16.07 (CVE-2025-66478)
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import { getNameChar } from '@/helper';
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import { CharacterBasic } from '@/types';
|
||||
import ParseText from '../parseText';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface CharacterCardProps {
|
||||
data: CharacterBasic
|
||||
}
|
||||
|
||||
export default function CharacterCard({ data }: CharacterCardProps) {
|
||||
const { locale } = useLocaleStore();
|
||||
const text = getNameChar(locale, data)
|
||||
return (
|
||||
<li
|
||||
className="z-10 flex flex-col items-center rounded-xl shadow-xl
|
||||
bg-linear-to-br from-base-300 via-base-100 to-warning/70
|
||||
transform transition-transform duration-300 ease-in-out
|
||||
hover:scale-105 cursor-pointer min-h-[170px] sm:min-h-[180px] md:min-h-[210px] lg:min-h-[220px] xl:min-h-[240px] 2xl:min-h-[340px]"
|
||||
>
|
||||
<div
|
||||
className={`w-full rounded-md bg-linear-to-br ${data.rank === "CombatPowerAvatarRarityType5"
|
||||
? "from-yellow-400 via-yellow-600/70 to-yellow-800/50"
|
||||
: "from-purple-400 via-purple-600/70 to-purple-800/50"
|
||||
}`}
|
||||
>
|
||||
|
||||
<div className="relative w-full h-full">
|
||||
<Image
|
||||
width={376}
|
||||
height={512}
|
||||
src={`https://api.hakush.in/hsr/UI/avatarshopicon/${data.id}.webp`}
|
||||
priority={true}
|
||||
className="w-full h-full rounded-md object-cover"
|
||||
alt="ALT"
|
||||
/>
|
||||
<Image
|
||||
width={32}
|
||||
height={32}
|
||||
|
||||
src={`/icon/${data.damageType.toLowerCase()}.webp`}
|
||||
className="absolute top-0 left-0 w-6 h-6 rounded-full"
|
||||
alt={data.damageType.toLowerCase()}
|
||||
/>
|
||||
<Image
|
||||
width={32}
|
||||
height={32}
|
||||
src={`/icon/${data.baseType.toLowerCase()}.webp`}
|
||||
className="absolute top-0 right-0 w-6 h-6 rounded-full"
|
||||
alt={data.baseType.toLowerCase()}
|
||||
style={{
|
||||
boxShadow: "inset 0 0 8px 4px #9CA3AF"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ParseText
|
||||
locale={locale}
|
||||
text={text}
|
||||
className="mt-2 px-1 text-center text-shadow-white font-bold leading-tight text-sm sm:text-base 2xl:text-lg"
|
||||
/>
|
||||
</li>
|
||||
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import { CharacterInfoCardType } from '@/types';
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import useAvatarStore from '@/stores/avatarStore';
|
||||
import useLightconeStore from '@/stores/lightconeStore';
|
||||
import Image from 'next/image';
|
||||
import ParseText from '../parseText';
|
||||
|
||||
|
||||
export default function CharacterInfoCard({ character, selectedCharacters, onCharacterToggle }: { character: CharacterInfoCardType, selectedCharacters: CharacterInfoCardType[], onCharacterToggle: (characterId: CharacterInfoCardType) => void }) {
|
||||
const isSelected = selectedCharacters.some((selectedCharacter) => selectedCharacter.avatar_id === character.avatar_id);
|
||||
const { mapAvatarInfo } = useAvatarStore();
|
||||
const { mapLightconeInfo } = useLightconeStore();
|
||||
const { locale } = useLocaleStore();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`bg-base-200/60 rounded-xl p-4 border cursor-pointer transition-all duration-200 ${isSelected
|
||||
? 'border-blue-400 ring-2 ring-blue-400/50'
|
||||
: 'border-base-300/50 hover:border-base-300 opacity-75'
|
||||
}`}
|
||||
|
||||
onClick={() => onCharacterToggle(character)}
|
||||
>
|
||||
|
||||
{/* Character Portrait */}
|
||||
<div className="relative mb-4">
|
||||
<div className="w-full h-48 rounded-lg overflow-hidden relative">
|
||||
<Image
|
||||
src={`https://api.hakush.in/hsr/UI/avatarshopicon/${character.avatar_id}.webp`}
|
||||
alt={mapAvatarInfo[character.avatar_id.toString()]?.Name || ""}
|
||||
width={376}
|
||||
height={512}
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
<Image
|
||||
width={48}
|
||||
height={48}
|
||||
src={`/icon/${mapAvatarInfo[character.avatar_id.toString()]?.DamageType.toLowerCase()}.webp`}
|
||||
className="absolute top-0 left-0 w-10 h-10 rounded-full"
|
||||
alt={mapAvatarInfo[character.avatar_id.toString()]?.DamageType.toLowerCase()}
|
||||
/>
|
||||
<Image
|
||||
width={48}
|
||||
height={48}
|
||||
src={`/icon/${mapAvatarInfo[character.avatar_id.toString()]?.BaseType.toLowerCase()}.webp`}
|
||||
className="absolute top-0 right-0 w-10 h-10 rounded-full"
|
||||
alt={mapAvatarInfo[character.avatar_id.toString()]?.BaseType.toLowerCase()}
|
||||
style={{
|
||||
boxShadow: "inset 0 0 8px 4px #9CA3AF"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Character Name and Level */}
|
||||
<div className="w-full rounded-lg flex items-center justify-center mb-2">
|
||||
<div className="text-center">
|
||||
<ParseText className="text-lg font-bold"
|
||||
text={mapAvatarInfo[character.avatar_id.toString()]?.Name}
|
||||
locale={locale}
|
||||
/>
|
||||
<div className="text-base mb-1">Lv.{character.level} E{character.rank}</div>
|
||||
</div>
|
||||
</div>
|
||||
{character.relics.length > 0 && (
|
||||
<div className="flex flex-wrap items-center justify-center gap-2 mb-4">
|
||||
{character.relics.map((relic, index) => (
|
||||
<div key={index} className="relative">
|
||||
<div className="w-9 h-9 rounded-lg flex items-center justify-center border border-amber-500/50">
|
||||
<Image
|
||||
src={`https://api.hakush.in/hsr/UI/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.webp`}
|
||||
alt="Relic"
|
||||
width={124}
|
||||
height={124}
|
||||
className="w-14 h-14 object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute -bottom-2 left-1/2 transform -translate-x-1/2 bg-slate-800 text-white text-xs px-1 rounded">
|
||||
+{relic.level}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Light Cone */}
|
||||
{character.lightcone.item_id && (
|
||||
<div className="">
|
||||
<div className="rounded-lg h-42 flex items-center justify-center">
|
||||
<Image
|
||||
src={`https://api.hakush.in/hsr/UI/lightconemediumicon/${character.lightcone.item_id}.webp`}
|
||||
alt={mapLightconeInfo[character.lightcone.item_id.toString()]?.Name}
|
||||
width={348}
|
||||
height={408}
|
||||
className="w-full h-full object-contain rounded-lg"
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div className="w-full h-full rounded-lg flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-bold">
|
||||
<ParseText
|
||||
text={mapLightconeInfo[character.lightcone.item_id.toString()]?.Name}
|
||||
locale={locale}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-base mb-1">Lv.{character.lightcone.level} S{character.lightcone.rank}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
"use client";
|
||||
|
||||
import { getLocaleName } from '@/helper';
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import { LightConeBasic } from '@/types';
|
||||
import ParseText from '../parseText';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface LightconeCardProps {
|
||||
data: LightConeBasic
|
||||
}
|
||||
|
||||
export default function LightconeCard({ data }: LightconeCardProps) {
|
||||
|
||||
const { locale } = useLocaleStore();
|
||||
const text = getLocaleName(locale, data)
|
||||
return (
|
||||
<li className="z-10 flex flex-col items-center rounded-md shadow-lg
|
||||
bg-linear-to-b from-customStart to-customEnd transform transition-transform duration-300
|
||||
hover:scale-105 cursor-pointer min-h-[220px]"
|
||||
>
|
||||
<div
|
||||
className={`w-full rounded-md bg-linear-to-br ${data.rank === "CombatPowerLightconeRarity5"
|
||||
? "from-yellow-400 via-yellow-600/70 to-yellow-800/50"
|
||||
: data.rank === "CombatPowerLightconeRarity4" ? "from-purple-400 via-purple-600/70 to-purple-800/50" :
|
||||
"from-blue-400 via-blue-600/70 to-blue-800/50"
|
||||
}`}
|
||||
>
|
||||
|
||||
<div className="relative w-full h-full">
|
||||
<Image
|
||||
loading="lazy"
|
||||
src={`https://api.hakush.in/hsr/UI/lightconemediumicon/${data.id}.webp`}
|
||||
width={348}
|
||||
height={408}
|
||||
className="w-full h-full rounded-md object-cover"
|
||||
alt="ALT"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ParseText
|
||||
locale={locale}
|
||||
text={text}
|
||||
className="mt-2 px-1 text-center text-sm sm:text-base font-bold leading-tight"
|
||||
/>
|
||||
</li>
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import { AvatarProfileCardType } from '@/types';
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import useLightconeStore from '@/stores/lightconeStore';
|
||||
import Image from 'next/image';
|
||||
import ParseText from '../parseText';
|
||||
|
||||
|
||||
export default function ProfileCard({ profile, selectedProfile, onProfileToggle }: { profile: AvatarProfileCardType, selectedProfile: AvatarProfileCardType[], onProfileToggle: (profileId: AvatarProfileCardType) => void }) {
|
||||
const isSelected = selectedProfile.some((selectedProfile) => selectedProfile.key === profile.key);
|
||||
const { mapLightconeInfo } = useLightconeStore();
|
||||
const { locale } = useLocaleStore();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`bg-base-200/60 rounded-xl p-4 border cursor-pointer transition-all duration-200 ${isSelected
|
||||
? 'border-blue-400 ring-2 ring-blue-400/50'
|
||||
: 'border-base-300/50 hover:border-base-300 opacity-75'
|
||||
}`}
|
||||
|
||||
onClick={() => onProfileToggle(profile)}
|
||||
>
|
||||
{/* Light Cone */}
|
||||
{profile.lightcone && (
|
||||
<div className="">
|
||||
<div className="rounded-lg h-42 flex items-center justify-center">
|
||||
<Image
|
||||
src={`https://api.hakush.in/hsr/UI/lightconemediumicon/${profile.lightcone.item_id}.webp`}
|
||||
alt={mapLightconeInfo[profile.lightcone.item_id.toString()]?.Name}
|
||||
width={348}
|
||||
height={408}
|
||||
className="w-full h-full object-contain rounded-lg"
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div className="w-full h-full rounded-lg flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-bold">
|
||||
<ParseText
|
||||
text={mapLightconeInfo[profile.lightcone.item_id.toString()]?.Name}
|
||||
locale={locale}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-base mb-1">Lv.{profile.lightcone.level} S{profile.lightcone.rank}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{Object.keys(profile.relics).length > 0 && (
|
||||
<div className="flex flex-wrap items-center justify-center gap-2 mb-4">
|
||||
{Object.values(profile.relics).map((relic, index) => (
|
||||
<div key={index} className="relative">
|
||||
<div className="w-9 h-9 rounded-lg flex items-center justify-center border border-amber-500/50">
|
||||
<Image
|
||||
src={`https://api.hakush.in/hsr/UI/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.webp`}
|
||||
alt="Relic"
|
||||
width={124}
|
||||
height={124}
|
||||
className="w-14 h-14 object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute -bottom-2 left-1/2 transform -translate-x-1/2 bg-slate-800 text-white text-xs px-1 rounded">
|
||||
+{relic.level}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,182 @@
|
||||
"use client";
|
||||
|
||||
import useRelicMakerStore from "@/stores/relicMakerStore";
|
||||
import useUserDataStore from "@/stores/userDataStore";
|
||||
import Image from "next/image";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface RelicCardProps {
|
||||
slot: string
|
||||
avatarId: string
|
||||
}
|
||||
const getRarityColor = (rarity: string) => {
|
||||
switch (rarity) {
|
||||
case '3': return 'border-green-500 shadow-green-500/50 bg-linear-to-br from-green-700 via-green-400 to-green-500';
|
||||
case '4': return 'border-blue-500 shadow-blue-500/50 bg-linear-to-br from-blue-700 via-blue-400 to-blue-500';
|
||||
case '5': return 'border-purple-500 shadow-purple-500/50 bg-linear-to-br from-purple-700 via-purple-400 to-purple-500';
|
||||
case '6': return 'border-yellow-500 shadow-yellow-500/50 bg-linear-to-br from-yellow-700 via-yellow-400 to-yellow-500';
|
||||
default: return 'border-gray-500 shadow-gray-500/50';
|
||||
}
|
||||
};
|
||||
|
||||
const getRarityName = (slot: string) => {
|
||||
switch (slot) {
|
||||
case '1': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image
|
||||
src="/relics/HEAD.png"
|
||||
alt="Head"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Head</h2>
|
||||
</div>
|
||||
);
|
||||
case '2': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image
|
||||
src="/relics/HAND.png"
|
||||
alt="Hand"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Hands</h2>
|
||||
</div>
|
||||
);
|
||||
case '3': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image
|
||||
src="/relics/BODY.png"
|
||||
alt="Body"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Body</h2>
|
||||
</div>
|
||||
);
|
||||
case '4': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image
|
||||
src="/relics/FOOT.png"
|
||||
alt="Foot"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Feet</h2>
|
||||
</div>
|
||||
);
|
||||
case '5': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image
|
||||
src="/relics/NECK.png"
|
||||
alt="Neck"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Planar sphere</h2>
|
||||
</div>
|
||||
);
|
||||
case '6': return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Image
|
||||
src="/relics/OBJECT.png"
|
||||
alt="Object"
|
||||
width={20}
|
||||
height={20}
|
||||
className="bg-black/50 rounded-full"
|
||||
/>
|
||||
<h2>Link rope</h2>
|
||||
</div>
|
||||
);
|
||||
default: return '';
|
||||
}
|
||||
};
|
||||
export default function RelicCard({ slot, avatarId }: RelicCardProps) {
|
||||
const { avatars } = useUserDataStore()
|
||||
const { selectedRelicSlot } = useRelicMakerStore()
|
||||
|
||||
const relicDetail = useMemo(() => {
|
||||
const avatar = avatars[avatarId];
|
||||
if (avatar) {
|
||||
if (avatar.profileList[avatar.profileSelect].relics[slot]) {
|
||||
return avatar.profileList[avatar.profileSelect].relics[slot];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}, [avatars, avatarId, slot]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{relicDetail ? (
|
||||
<div
|
||||
className="flex flex-col items-center cursor-pointer ">
|
||||
<div
|
||||
className={`
|
||||
relative w-24 h-24 rounded-full
|
||||
${getRarityColor(relicDetail.relic_id.toString()[0])}
|
||||
shadow-xl
|
||||
flex items-center justify-center
|
||||
cursor-pointer transition-transform
|
||||
${selectedRelicSlot === slot ? 'ring-5 ring-success scale-105' : 'ring-3 ring-primary'}
|
||||
`}
|
||||
>
|
||||
<span>
|
||||
<Image
|
||||
src={`https://api.hakush.in/hsr/UI/relicfigures/IconRelic_${relicDetail.relic_set_id}_${slot}.webp`}
|
||||
alt="Relic"
|
||||
width={124}
|
||||
height={124}
|
||||
className="w-14 h-14 object-contain"
|
||||
/>
|
||||
</span>
|
||||
|
||||
{/* Level Badge */}
|
||||
<div className="absolute -bottom-2 bg-base-100 border-2 border-base-300 rounded-full px-2 py-1">
|
||||
<span className="text-sm font-bold text-primary">+{relicDetail.level}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 text-center">
|
||||
<div className="text-sm font-medium text-base-content">{getRarityName(slot)}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="flex flex-col items-center cursor-pointer">
|
||||
<div
|
||||
className={`
|
||||
relative w-24 h-24 rounded-full border-4
|
||||
${getRarityColor("None")}
|
||||
bg-base-300 shadow-xl
|
||||
flex items-center justify-center
|
||||
cursor-pointer hover:scale-105 transition-transform
|
||||
ring-4 ring-primary
|
||||
`}
|
||||
>
|
||||
<span className="text-3xl">
|
||||
<svg className="w-12 h-12 text-base-content/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
{/* Level Badge */}
|
||||
<div className="absolute -bottom-2 bg-base-100 border-2 border-base-300 rounded-full px-2 py-1">
|
||||
<span className="text-sm font-bold text-primary">+{0}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 text-center">
|
||||
<div>{getRarityName(slot)}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user