UPDATE: add more ui for showcase card, fix some bug
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 2m1s
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 2m1s
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
import Image from "next/image"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useEffect } from "react"
|
||||
import CharacterCard from "../card/characterCard"
|
||||
import useLocaleStore from "@/stores/localeStore"
|
||||
import useAvatarStore from "@/stores/avatarStore"
|
||||
@@ -8,9 +8,17 @@ import { useTranslations } from "next-intl"
|
||||
|
||||
|
||||
export default function AvatarBar({ onClose }: { onClose?: () => void }) {
|
||||
const [listElement, setListElement] = useState<Record<string, boolean>>({ "fire": false, "ice": false, "imaginary": false, "physical": false, "quantum": false, "thunder": false, "wind": false })
|
||||
const [listPath, setListPath] = useState<Record<string, boolean>>({ "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false })
|
||||
const { listAvatar, setAvatarSelected, setSkillSelected, setFilter, filter } = useAvatarStore()
|
||||
const {
|
||||
listAvatar,
|
||||
setAvatarSelected,
|
||||
setSkillSelected,
|
||||
setFilter,
|
||||
filter,
|
||||
listElement,
|
||||
listPath,
|
||||
setListElement,
|
||||
setListPath
|
||||
} = useAvatarStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const { locale } = useLocaleStore()
|
||||
|
||||
@@ -40,7 +48,7 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setListElement((prev) => ({ ...prev, [key]: !prev[key] }))
|
||||
setListElement({ ...listElement, [key]: !listElement[key] })
|
||||
}}
|
||||
className="hover:bg-gray-600 grid items-center justify-items-center cursor-pointer rounded-md shadow-lg"
|
||||
style={{
|
||||
@@ -60,7 +68,7 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setListPath((prev) => ({ ...prev, [key]: !prev[key] }))
|
||||
setListPath({ ...listPath, [key]: !listPath[key] })
|
||||
}}
|
||||
className="hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-lg cursor-pointer"
|
||||
style={{
|
||||
|
||||
@@ -13,7 +13,7 @@ import SelectCustomImage from "../select/customSelectImage";
|
||||
|
||||
export default function CopyImport() {
|
||||
const { avatars, setAvatar } = useUserDataStore();
|
||||
const {avatarSelected} = useListAvatarStore()
|
||||
const { avatarSelected } = useListAvatarStore()
|
||||
const { locale } = useLocaleStore()
|
||||
const {
|
||||
selectedProfiles,
|
||||
@@ -22,12 +22,16 @@ export default function CopyImport() {
|
||||
setSelectedProfiles,
|
||||
filterCopy,
|
||||
setFilterCopy,
|
||||
setAvatarCopySelected
|
||||
setAvatarCopySelected,
|
||||
listElement,
|
||||
listPath,
|
||||
listRank,
|
||||
setListElement,
|
||||
setListPath,
|
||||
setListRank
|
||||
} = useCopyProfileStore()
|
||||
const transI18n = useTranslations("DataPage")
|
||||
const [listPath, setListPath] = useState<Record<string, boolean>>({ "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false })
|
||||
const [listElement, setListElement] = useState<Record<string, boolean>>({ "fire": false, "ice": false, "imaginary": false, "physical": false, "quantum": false, "thunder": false, "wind": false })
|
||||
const [listRank, setListRank] = useState<Record<string, boolean>>({ "4": false, "5": false })
|
||||
|
||||
const [message, setMessage] = useState({
|
||||
type: "",
|
||||
text: ""
|
||||
@@ -49,8 +53,9 @@ export default function CopyImport() {
|
||||
element: Object.keys(listElement).filter((key) => listElement[key]),
|
||||
rarity: Object.keys(listRank).filter((key) => listRank[key])
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [listPath, listRank, locale])
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [listPath, listRank, listElement, locale, setFilterCopy])
|
||||
|
||||
const clearSelection = () => {
|
||||
setSelectedProfiles([]);
|
||||
@@ -142,7 +147,7 @@ export default function CopyImport() {
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setListPath((prev) => ({ ...prev, [key]: !prev[key] }))
|
||||
setListPath({ ...listPath, [key]: !listPath[key] })
|
||||
}}
|
||||
className="w-[50px] h-[50px] hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer"
|
||||
style={{
|
||||
@@ -167,7 +172,7 @@ export default function CopyImport() {
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setListElement((prev) => ({ ...prev, [key]: !prev[key] }))
|
||||
setListElement({ ...listElement, [key]: !listElement[key] })
|
||||
}}
|
||||
className="w-[50px] h-[50px] hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer"
|
||||
style={{
|
||||
@@ -192,7 +197,7 @@ export default function CopyImport() {
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setListRank((prev) => ({ ...prev, [key]: !prev[key] }))
|
||||
setListRank({ ...listRank, [key]: !listRank[key] })
|
||||
}}
|
||||
className="w-[50px] h-[50px] hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer"
|
||||
style={{
|
||||
@@ -207,9 +212,10 @@ export default function CopyImport() {
|
||||
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
<div>
|
||||
<div>{transI18n("characterName")}</div>
|
||||
{listCopyAvatar.length > 0 && (
|
||||
|
||||
{listCopyAvatar.length > 0 && (
|
||||
<div>
|
||||
<div>{transI18n("characterName")}</div>
|
||||
<SelectCustomImage
|
||||
customSet={listCopyAvatar.map((avatar) => ({
|
||||
value: avatar.id.toString(),
|
||||
@@ -221,8 +227,9 @@ export default function CopyImport() {
|
||||
placeholder="Character Select"
|
||||
setSelectedCustomSet={(value) => setAvatarCopySelected(listCopyAvatar.find((avatar) => avatar.id.toString() === value) || null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -240,7 +247,7 @@ export default function CopyImport() {
|
||||
{transI18n("clearAll")}
|
||||
</button>
|
||||
<button
|
||||
onClick={selectAll}
|
||||
onClick={selectAll}
|
||||
className="text-primary/75 hover:text-primary text-sm font-medium px-3 py-1 border border-primary/75 rounded hover:bg-primary/10 transition-colors cursor-pointer">
|
||||
{transI18n("selectAll")}
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { useEffect } from "react"
|
||||
import Image from "next/image";
|
||||
import useLocaleStore from "@/stores/localeStore"
|
||||
import useLightconeStore from "@/stores/lightconeStore";
|
||||
@@ -11,10 +11,17 @@ import useModelStore from "@/stores/modelStore";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function LightconeBar() {
|
||||
const [listPath, setListPath] = useState<Record<string, boolean>>({ "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false })
|
||||
const [listRank, setListRank] = useState<Record<string, boolean>>({ "3": false, "4": false, "5": false })
|
||||
const { locale } = useLocaleStore()
|
||||
const { listLightcone, filter, setFilter, defaultFilter } = useLightconeStore()
|
||||
const {
|
||||
listLightcone,
|
||||
filter,
|
||||
setFilter,
|
||||
defaultFilter,
|
||||
listPath,
|
||||
listRank,
|
||||
setListPath,
|
||||
setListRank
|
||||
} = useLightconeStore()
|
||||
const { setAvatar, avatars } = useUserDataStore()
|
||||
const { avatarSelected } = useAvatarStore()
|
||||
const { setIsOpenLightcone } = useModelStore()
|
||||
@@ -35,7 +42,7 @@ export default function LightconeBar() {
|
||||
}
|
||||
setListPath(newListPath)
|
||||
setListRank(newListRank)
|
||||
}, [defaultFilter])
|
||||
}, [defaultFilter, setListPath, setListRank])
|
||||
|
||||
useEffect(() => {
|
||||
setFilter({
|
||||
@@ -72,7 +79,7 @@ export default function LightconeBar() {
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setListPath((prev) => ({ ...prev, [key]: !prev[key] }))
|
||||
setListPath({ ...listPath, [key]: !listPath[key] })
|
||||
}}
|
||||
className="h-[38px] w-[38px] md:h-[50px] md:w-[50px] hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer"
|
||||
style={{
|
||||
@@ -96,7 +103,7 @@ export default function LightconeBar() {
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setListRank((prev) => ({ ...prev, [key]: !prev[key] }))
|
||||
setListRank({ ...listRank, [key]: !listRank[key] })
|
||||
}}
|
||||
className="h-[38px] w-[38px] md:h-[50px] md:w-[50px] hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer"
|
||||
style={{
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useMemo } from "react";
|
||||
import useAvatarStore from "@/stores/avatarStore";
|
||||
import { calcAffixBonus, calcBaseStatRaw, calcBonusStatRaw, calcMainAffixBonus, calcMainAffixBonusRaw, calcPromotion, calcSubAffixBonusRaw, replaceByParam } from "@/helper";
|
||||
import { mappingStats } from "@/constant/constant";
|
||||
import RelicShowcase from "../showcaseCard/relicShowcase";
|
||||
export default function QuickView() {
|
||||
const { avatarSelected, mapAvatarInfo } = useAvatarStore()
|
||||
const { mapLightconeInfo } = useLightconeStore()
|
||||
@@ -80,7 +81,9 @@ export default function QuickView() {
|
||||
return {
|
||||
property: subAffixMap?.[subValue?.sub_affix_id]?.property,
|
||||
valueAffix: calcAffixBonus(subAffixMap?.[subValue?.sub_affix_id], subValue?.step, subValue?.count),
|
||||
detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.property]
|
||||
detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.property],
|
||||
step: subValue?.step,
|
||||
count: subValue?.count
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -398,7 +401,7 @@ export default function QuickView() {
|
||||
<div key={index} className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<NextImage src={stat?.icon || ""} alt="Stat Icon" width={40} height={40} className="h-auto w-10 p-1 mx-1 bg-black/20 rounded-full" />
|
||||
<span className="font-bold">{stat.name}</span>
|
||||
<div className="font-bold">{stat.name}</div>
|
||||
</div>
|
||||
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
|
||||
<div className="flex cursor-default flex-col text-right font-bold">{
|
||||
@@ -439,63 +442,79 @@ export default function QuickView() {
|
||||
|
||||
<div className="grid grid-cols-1 gap-2 justify-between py-3 text-lg">
|
||||
|
||||
{relicStats?.map((relic, index) => {
|
||||
{relicStats?.map((relic, index) => {
|
||||
if (!relic) return null
|
||||
return (
|
||||
<div key={index} className="black-blur relative flex flex-row items-center rounded-lg border-2 p-1 border-yellow-600">
|
||||
<div className="flex">
|
||||
<NextImage src={relic?.img || ""} width={80} height={80} alt="Relic Icon" className="h-auto w-20" />
|
||||
<div
|
||||
key={index}
|
||||
className="relative w-full flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600/60 bg-gradient-to-r from-yellow-600/20 to-transparent"
|
||||
>
|
||||
{/* Subtle glow overlay */}
|
||||
<div className="absolute inset-0 rounded-s-lg pointer-events-none"></div>
|
||||
|
||||
<div
|
||||
className="absolute text-yellow-500 font-bold z-10"
|
||||
style={{
|
||||
left: '0.7rem',
|
||||
bottom: '-0.5rem',
|
||||
fontSize: '1.1rem',
|
||||
letterSpacing: '-0.1em',
|
||||
textShadow: `
|
||||
0 0 0.2em #f59e0b,
|
||||
0 0 0.4em #f59e0b,
|
||||
0 0 0.8em #f59e0b,
|
||||
-0.05em -0.05em 0.05em rgba(0,0,0,0.7),
|
||||
0.05em 0.05em 0.05em rgba(0,0,0,0.7)
|
||||
`
|
||||
}}
|
||||
>
|
||||
✦✦✦✦✦
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-1 flex w-1/6 flex-col items-center justify-center">
|
||||
<NextImage src={relic?.mainAffix?.detail?.icon || ""} width={36} height={36} alt="Main Affix Icon" className="h-auto w-9 bg-black/20 rounded-full" />
|
||||
<span className="text-base text-[#f1a23c]">{relic?.mainAffix?.valueAffix + relic?.mainAffix?.detail?.unit}</span>
|
||||
<span className="black-blur rounded px-1 text-xs">+{relic?.mainAffix?.level}</span>
|
||||
</div>
|
||||
<div style={{ opacity: 0.5, height: '80px', borderLeftWidth: '1px' }}></div>
|
||||
<div className="m-auto grid w-1/2 grid-cols-2 gap-2">
|
||||
{relic?.subAffix?.map((subAffix, index) => {
|
||||
if (!subAffix) return null
|
||||
return (
|
||||
<div key={index} className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
{subAffix?.detail?.icon ? (
|
||||
<NextImage src={subAffix?.detail?.icon || ""} width={36} height={36} alt="Sub Affix Icon" className="h-auto w-9 bg-black/20 rounded-full" />
|
||||
) : (
|
||||
<div className="h-9 w-9 bg-black/20 rounded flex items-center justify-center">
|
||||
<span className="text-xs text-white">?</span>
|
||||
</div>
|
||||
)}
|
||||
<span className="text-sm">+{subAffix?.valueAffix + subAffix?.detail?.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="flex relative">
|
||||
<div className="absolute inset-0 rounded-lg blur-lg -z-10"></div>
|
||||
<NextImage
|
||||
src={relic?.img || ""}
|
||||
width={78}
|
||||
height={78}
|
||||
alt="Relic Icon"
|
||||
className="h-auto w-[78px] rounded-lg"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="absolute text-yellow-400 font-bold z-10 drop-shadow-[0_0_6px_rgba(251,191,36,0.8)]"
|
||||
style={{
|
||||
left: '0.65rem',
|
||||
bottom: '-0.45rem',
|
||||
fontSize: '1.05rem',
|
||||
letterSpacing: '-0.1em',
|
||||
}}
|
||||
>
|
||||
✦✦✦✦✦
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className=" flex w-1/6 flex-col items-center justify-center">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 bg-yellow-500/15 rounded-full blur-md -z-10"></div>
|
||||
<NextImage
|
||||
src={relic?.mainAffix?.detail?.icon || ""}
|
||||
width={35}
|
||||
height={35}
|
||||
alt="Main Affix Icon"
|
||||
className="h-auto w-[35px]"
|
||||
/>
|
||||
</div>
|
||||
<span className="text-base text-yellow-400 font-semibold drop-shadow-[0_0_4px_rgba(251,191,36,0.5)]">
|
||||
{relic?.mainAffix?.valueAffix + relic?.mainAffix?.detail?.unit}
|
||||
</span>
|
||||
<span className="bg-black/20 backdrop-blur-sm rounded px-1.5 py-0.5 text-xs border border-white/10">
|
||||
+{relic?.mainAffix?.level}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style={{ opacity: 0.3, height: '78px', borderLeftWidth: '1px' }}></div>
|
||||
|
||||
<div className="grid w-[65%] m-2 grid-cols-2 gap-1">
|
||||
{relic?.subAffix?.map((subAffix, index) => {
|
||||
if (!subAffix) return null
|
||||
return (
|
||||
<RelicShowcase key={index} relic={relic} />
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{(!relicStats || !relicStats?.length) && <div className="flex flex-col items-center justify-center ">
|
||||
<span className="text-lg">{transI18n("noRelicEquipped")}</span>
|
||||
</div>}
|
||||
})}
|
||||
|
||||
{(!relicStats || !relicStats?.length) && (
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="text-center p-6 rounded-lg bg-black/40 backdrop-blur-sm border border-white/10">
|
||||
<span className="text-lg text-gray-400">{transI18n("noRelicEquipped")}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -16,6 +16,8 @@ import { useTranslations } from 'next-intl';
|
||||
import useAffixStore from '@/stores/affixStore';
|
||||
import useRelicStore from '@/stores/relicStore';
|
||||
import { toast } from 'react-toastify';
|
||||
import RelicShowcase from './relicShowcase';
|
||||
|
||||
|
||||
export default function ShowCaseInfo() {
|
||||
const { avatarSelected, mapAvatarInfo } = useAvatarStore()
|
||||
@@ -37,9 +39,9 @@ export default function ShowCaseInfo() {
|
||||
|
||||
import("html2canvas-pro")
|
||||
.then(({ default: html2canvas }) =>
|
||||
html2canvas(cardRef.current!, {
|
||||
scale: 2,
|
||||
backgroundColor: "#000000",
|
||||
html2canvas(cardRef.current!, {
|
||||
scale: 2,
|
||||
backgroundColor: "#000000",
|
||||
logging: false,
|
||||
proxy: '/api/proxy/',
|
||||
imageTimeout: 30000,
|
||||
@@ -162,7 +164,9 @@ export default function ShowCaseInfo() {
|
||||
return {
|
||||
property: subAffixMap?.[subValue?.sub_affix_id]?.property,
|
||||
valueAffix: calcAffixBonus(subAffixMap?.[subValue?.sub_affix_id], subValue?.step, subValue?.count),
|
||||
detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.property]
|
||||
detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.property],
|
||||
step: subValue?.step,
|
||||
count: subValue?.count
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -495,7 +499,7 @@ export default function ShowCaseInfo() {
|
||||
}, [avatarSelected])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-start m-2 text-white">
|
||||
<div className="flex flex-col justify-start m-1 text-white">
|
||||
<div className="flex items-center justify-start mt-4 mb-4">
|
||||
<button className="btn btn-success w-24 text-sm" onClick={handleSaveImage}>Save Img</button>
|
||||
</div>
|
||||
@@ -503,7 +507,7 @@ export default function ShowCaseInfo() {
|
||||
<div className="overflow-auto">
|
||||
<div
|
||||
ref={cardRef}
|
||||
className=" relative min-h-[650px] w-[1400px] rounded-3xl transition-all duration-500"
|
||||
className=" relative min-h-[650px] w-[1600px] rounded-3xl transition-all duration-500 overflow-hidden"
|
||||
style={{
|
||||
backgroundColor: `${applyBrightness(avgColor, 0.3)}`,
|
||||
backdropFilter: "blur(50px)",
|
||||
@@ -522,7 +526,7 @@ export default function ShowCaseInfo() {
|
||||
<NextImage
|
||||
ref={imgRef}
|
||||
src={`https://api.hakush.in/hsr/UI/avatardrawcard/${avatarSelected?.id}.webp`}
|
||||
className="object-cover scale-[2]"
|
||||
className="object-cover scale-[2] overflow-hidden"
|
||||
alt="Character Preview"
|
||||
width={1024}
|
||||
height={1024}
|
||||
@@ -607,7 +611,6 @@ export default function ShowCaseInfo() {
|
||||
{avatarData && avatarInfo && avatarSkillTree && traceShowCaseMap[avatarSelected?.baseType || ""]
|
||||
&& Object.values(traceShowCaseMap[avatarSelected?.baseType || ""] || []).map((item, index) => {
|
||||
|
||||
|
||||
return (
|
||||
<div key={`row-${index}`} className="flex flex-row items-center">
|
||||
{item.map((btn, idx) => {
|
||||
@@ -803,7 +806,6 @@ export default function ShowCaseInfo() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chỉ số */}
|
||||
<div className="flex justify-center items-center flex-col gap-1 mt-1 ">
|
||||
<div className="flex gap-1 text-sm ">
|
||||
<div className="flex items-center gap-1 rounded bg-black/30 px-1 w-fit py-1">
|
||||
@@ -872,7 +874,7 @@ export default function ShowCaseInfo() {
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<span className="black-blur bg-black/50 flex w-5 justify-center rounded px-1.5 py-0.5">{setEffect.count}</span>
|
||||
<span className="black-blur bg-black/30 flex w-5 justify-center rounded px-1.5 py-0.5">{setEffect.count}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -881,65 +883,22 @@ export default function ShowCaseInfo() {
|
||||
</div>
|
||||
|
||||
<div className="w-1/3 z-10">
|
||||
<div className="flex h-[650px] flex-col justify-between py-3 text-lg">
|
||||
<div className="flex h-[650px] flex-col justify-between py-3 mr-1 text-lg w-full" >
|
||||
|
||||
{relicStats?.map((relic, index) => {
|
||||
if (!relic) return null
|
||||
return (
|
||||
<div key={index} className="black-blur relative flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600">
|
||||
<div className="flex">
|
||||
<NextImage src={relic?.img || ""} width={80} height={80} alt="Relic Icon" className="h-auto w-20" />
|
||||
|
||||
<div
|
||||
className="absolute text-yellow-500 font-bold z-10"
|
||||
style={{
|
||||
left: '0.7rem',
|
||||
bottom: '-0.5rem',
|
||||
fontSize: '1.1rem',
|
||||
letterSpacing: '-0.1em',
|
||||
textShadow: `
|
||||
0 0 0.2em #f59e0b,
|
||||
0 0 0.4em #f59e0b,
|
||||
0 0 0.8em #f59e0b,
|
||||
-0.05em -0.05em 0.05em rgba(0,0,0,0.7),
|
||||
0.05em 0.05em 0.05em rgba(0,0,0,0.7)
|
||||
`
|
||||
}}
|
||||
>
|
||||
✦✦✦✦✦
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-1 flex w-1/6 flex-col items-center justify-center">
|
||||
<NextImage src={relic?.mainAffix?.detail?.icon || ""} width={36} height={36} alt="Main Affix Icon" className="h-auto w-9" />
|
||||
<span className="text-base text-[#f1a23c]">{relic?.mainAffix?.valueAffix + relic?.mainAffix?.detail?.unit}</span>
|
||||
<span className="black-blur rounded px-1 text-xs">+{relic?.mainAffix?.level}</span>
|
||||
</div>
|
||||
<div style={{ opacity: 0.5, height: '80px', borderLeftWidth: '1px' }}></div>
|
||||
<div className="m-auto grid w-1/2 grid-cols-2 gap-2">
|
||||
{relic?.subAffix?.map((subAffix, index) => {
|
||||
if (!subAffix) return null
|
||||
return (
|
||||
<div key={index} className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
{subAffix?.detail?.icon ? (
|
||||
<NextImage src={subAffix?.detail?.icon || ""} width={36} height={36} alt="Sub Affix Icon" className="h-auto w-9" />
|
||||
) : (
|
||||
<div className="h-9 w-9 bg-black/50 rounded flex items-center justify-center">
|
||||
<span className="text-xs text-white">?</span>
|
||||
</div>
|
||||
)}
|
||||
<span className="text-sm">+{subAffix?.valueAffix + subAffix?.detail?.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<RelicShowcase key={index} relic={relic} />
|
||||
)
|
||||
})}
|
||||
{(!relicStats || !relicStats?.length) && <div className="flex flex-col items-center justify-center ">
|
||||
<span className="text-lg">{transI18n("noRelicEquipped")}</span>
|
||||
</div>}
|
||||
|
||||
{(!relicStats || !relicStats?.length) && (
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="text-center p-6">
|
||||
<span className="text-lg text-white">{transI18n("noRelicEquipped")}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
99
src/components/showcaseCard/relicShowcase.tsx
Normal file
99
src/components/showcaseCard/relicShowcase.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
|
||||
"use client"
|
||||
|
||||
import NextImage from "next/image"
|
||||
import { RelicShowcaseType } from "@/types";
|
||||
|
||||
export default function RelicShowcase({
|
||||
relic,
|
||||
}: {
|
||||
relic: RelicShowcaseType;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="relative w-full flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600/60 bg-gradient-to-r from-yellow-600/20 to-transparent"
|
||||
>
|
||||
{/* Subtle glow overlay */}
|
||||
<div className="absolute inset-0 rounded-s-lg pointer-events-none"></div>
|
||||
|
||||
<div className="flex relative">
|
||||
<div className="absolute inset-0 rounded-lg blur-lg -z-10"></div>
|
||||
<NextImage
|
||||
src={relic?.img || ""}
|
||||
width={78}
|
||||
height={78}
|
||||
alt="Relic Icon"
|
||||
className="h-auto w-[78px] rounded-lg"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="absolute text-yellow-400 font-bold z-10 drop-shadow-[0_0_6px_rgba(251,191,36,0.8)]"
|
||||
style={{
|
||||
left: '0.65rem',
|
||||
bottom: '-0.45rem',
|
||||
fontSize: '1.05rem',
|
||||
letterSpacing: '-0.1em',
|
||||
}}
|
||||
>
|
||||
✦✦✦✦✦
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className=" flex w-1/6 flex-col items-center justify-center">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 bg-yellow-500/15 rounded-full blur-md -z-10"></div>
|
||||
<NextImage
|
||||
src={relic?.mainAffix?.detail?.icon || ""}
|
||||
width={35}
|
||||
height={35}
|
||||
alt="Main Affix Icon"
|
||||
className="h-auto w-[35px]"
|
||||
/>
|
||||
</div>
|
||||
<span className="text-base text-yellow-400 font-semibold drop-shadow-[0_0_4px_rgba(251,191,36,0.5)]">
|
||||
{relic?.mainAffix?.valueAffix + relic?.mainAffix?.detail?.unit}
|
||||
</span>
|
||||
<span className="bg-black/20 backdrop-blur-sm rounded px-1.5 py-0.5 text-xs border border-white/10">
|
||||
+{relic?.mainAffix?.level}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style={{ opacity: 0.3, height: '78px', borderLeftWidth: '1px' }}></div>
|
||||
|
||||
<div className="grid w-[65%] m-2 grid-cols-2 gap-1">
|
||||
{relic?.subAffix?.map((subAffix, index) => {
|
||||
if (!subAffix) return null
|
||||
return (
|
||||
<div key={index} className="flex flex-col">
|
||||
<div className="relative flex flex-row items-center bg-black/20 backdrop-blur-sm rounded-md p-1 border border-white/5 min-w-0">
|
||||
{subAffix?.detail?.icon ? (
|
||||
<NextImage
|
||||
src={subAffix?.detail?.icon || ""}
|
||||
width={32}
|
||||
height={32}
|
||||
alt="Sub Affix Icon"
|
||||
className="h-auto w-6 flex-shrink-0"
|
||||
/>
|
||||
) : (
|
||||
<div className="h-6 w-6 bg-black/60 rounded flex items-center justify-center border border-white/10 flex-shrink-0">
|
||||
<span className="text-xs text-white/50">?</span>
|
||||
</div>
|
||||
)}
|
||||
<span className="text-xs text-gray-200 ml-0.5 truncate flex-1 min-w-0">
|
||||
+{subAffix?.valueAffix + subAffix?.detail?.unit}
|
||||
</span>
|
||||
{subAffix.step > 1 && (
|
||||
<span className="ml-1 bg-yellow-600/20 text-yellow-400 rounded-full px-1 py-0.5 text-[10px] font-semibold border border-yellow-600/30 flex-shrink-0 leading-none">
|
||||
{subAffix?.step}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -9,6 +9,10 @@ interface AvatarState {
|
||||
avatarSelected: CharacterBasic | null;
|
||||
mapAvatarInfo: Record<string, CharacterDetail>;
|
||||
skillSelected: string | null;
|
||||
listElement: Record<string, boolean>;
|
||||
listPath: Record<string, boolean>;
|
||||
setListElement: (newListElement: Record<string, boolean>) => void;
|
||||
setListPath: (newListPath: Record<string, boolean>) => void;
|
||||
setListAvatar: (newListAvatar: CharacterBasic[]) => void;
|
||||
setAvatarSelected: (newAvatarSelected: CharacterBasic) => void;
|
||||
setFilter: (newFilter: FilterAvatarType) => void;
|
||||
@@ -30,6 +34,10 @@ const useAvatarStore = create<AvatarState>((set, get) => ({
|
||||
avatarSelected: null,
|
||||
skillSelected: null,
|
||||
mapAvatarInfo: {},
|
||||
listElement: { "fire": false, "ice": false, "imaginary": false, "physical": false, "quantum": false, "thunder": false, "wind": false },
|
||||
listPath: { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false },
|
||||
setListElement: (newListElement: Record<string, boolean>) => set({ listElement: newListElement }),
|
||||
setListPath: (newListPath: Record<string, boolean>) => set({ listPath: newListPath }),
|
||||
setSkillSelected: (newSkillSelected: string | null) => set({ skillSelected: newSkillSelected }),
|
||||
setListAvatar: (newListAvatar: CharacterBasic[]) => set({ listAvatar: newListAvatar, listRawAvatar: newListAvatar }),
|
||||
setAvatarSelected: (newAvatarSelected: CharacterBasic) => set({ avatarSelected: newAvatarSelected }),
|
||||
|
||||
@@ -7,6 +7,12 @@ interface CopyProfileState {
|
||||
listRawCopyAvatar: CharacterBasic[];
|
||||
filterCopy: FilterAvatarType;
|
||||
avatarCopySelected: CharacterBasic | null;
|
||||
listElement: Record<string, boolean>;
|
||||
listPath: Record<string, boolean>;
|
||||
listRank: Record<string, boolean>;
|
||||
setListElement: (newListElement: Record<string, boolean>) => void;
|
||||
setListPath: (newListPath: Record<string, boolean>) => void;
|
||||
setListRank: (newListRank: Record<string, boolean>) => void;
|
||||
setSelectedProfiles: (newListAvatar: AvatarProfileCardType[]) => void;
|
||||
setAvatarCopySelected: (newAvatarSelected: CharacterBasic | null) => void;
|
||||
setFilterCopy: (newFilter: FilterAvatarType) => void;
|
||||
@@ -25,6 +31,12 @@ const useCopyProfileStore = create<CopyProfileState>((set, get) => ({
|
||||
locale: "",
|
||||
},
|
||||
avatarCopySelected: null,
|
||||
listElement: { "fire": false, "ice": false, "imaginary": false, "physical": false, "quantum": false, "thunder": false, "wind": false },
|
||||
listPath: { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false },
|
||||
listRank: { "4": false, "5": false },
|
||||
setListElement: (newListElement: Record<string, boolean>) => set({ listElement: newListElement }),
|
||||
setListPath: (newListPath: Record<string, boolean>) => set({ listPath: newListPath }),
|
||||
setListRank: (newListRank: Record<string, boolean>) => set({ listRank: newListRank }),
|
||||
setListCopyAvatar: (newListAvatar: CharacterBasic[]) => set({ listCopyAvatar: newListAvatar, listRawCopyAvatar: newListAvatar }),
|
||||
setAvatarCopySelected: (newAvatarSelected: CharacterBasic | null) => set({ avatarCopySelected: newAvatarSelected }),
|
||||
setFilterCopy: (newFilter: FilterAvatarType) => {
|
||||
|
||||
@@ -4,9 +4,13 @@ import { create } from 'zustand'
|
||||
interface LightconeState {
|
||||
listLightcone: LightConeBasic[];
|
||||
listRawLightcone: LightConeBasic[];
|
||||
listPath: Record<string, boolean>;
|
||||
listRank: Record<string, boolean>;
|
||||
filter: FilterLightconeType;
|
||||
defaultFilter: { path: string[], rarity: string[] };
|
||||
mapLightconeInfo: Record<string, LightConeDetail>;
|
||||
setListPath: (newListPath: Record<string, boolean>) => void;
|
||||
setListRank: (newListRank: Record<string, boolean>) => void;
|
||||
setDefaultFilter: (newDefaultFilter: { path: string[], rarity: string[] }) => void;
|
||||
setListLightcone: (newListLightcone: LightConeBasic[]) => void;
|
||||
setFilter: (newFilter: FilterLightconeType) => void;
|
||||
@@ -18,6 +22,7 @@ const useLightconeStore = create<LightconeState>((set, get) => ({
|
||||
listLightcone: [],
|
||||
listRawLightcone: [],
|
||||
mapLightconeInfo: {},
|
||||
|
||||
filter: {
|
||||
name: "",
|
||||
path: [],
|
||||
@@ -25,6 +30,10 @@ const useLightconeStore = create<LightconeState>((set, get) => ({
|
||||
rarity: [],
|
||||
},
|
||||
defaultFilter: { path: [], rarity: [] },
|
||||
listPath: { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false },
|
||||
listRank: { "3": false, "4": false, "5": false },
|
||||
setListPath: (newListPath: Record<string, boolean>) => set({ listPath: newListPath }),
|
||||
setListRank: (newListRank: Record<string, boolean>) => set({ listRank: newListRank }),
|
||||
setDefaultFilter: (newDefaultFilter: { path: string[], rarity: string[] }) => set({ defaultFilter: newDefaultFilter }),
|
||||
setListLightcone: (newListLightcone: LightConeBasic[]) => set({ listLightcone: newListLightcone, listRawLightcone: newListLightcone }),
|
||||
setFilter: (newFilter: FilterLightconeType) => {
|
||||
|
||||
@@ -20,3 +20,4 @@ export * from "./monsterValue"
|
||||
export * from "./peakDetail"
|
||||
export * from "./monsterDetail"
|
||||
export * from "./extraData"
|
||||
export * from "./showcase"
|
||||
|
||||
26
src/types/showcase.tsx
Normal file
26
src/types/showcase.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
export type RelicShowcaseType = {
|
||||
img: string;
|
||||
mainAffix: {
|
||||
property: string;
|
||||
level: number;
|
||||
valueAffix: string;
|
||||
detail: {
|
||||
name: string;
|
||||
icon: string;
|
||||
unit: string;
|
||||
baseStat: string;
|
||||
};
|
||||
};
|
||||
subAffix: {
|
||||
property: string;
|
||||
valueAffix: string;
|
||||
detail: {
|
||||
name: string;
|
||||
icon: string;
|
||||
unit: string;
|
||||
baseStat: string;
|
||||
};
|
||||
step: number;
|
||||
count: number;
|
||||
}[]
|
||||
}
|
||||
Reference in New Issue
Block a user