update wave, cycle, damage type ...
This commit is contained in:
@@ -2,18 +2,19 @@
|
||||
import useAvatarDataStore from "@/stores/avatarDataStore";
|
||||
import useBattleDataStore from "@/stores/battleDataStore";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import { AvatarType } from "@/types";
|
||||
import { TurnBattleInfo } from "@/types/mics";
|
||||
import {attackTypeToString, AvatarHakushiType} from "@/types";
|
||||
import { SkillBattleInfo } from "@/types/mics";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { motion } from "framer-motion";
|
||||
import { getNameChar } from "@/helper";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function ActionBar() {
|
||||
const [selectTurn, setSelectTurn] = useState<TurnBattleInfo | null>(null);
|
||||
const [selectAvatar, setSelectAvatar] = useState<AvatarType | null>(null);
|
||||
const [selectTurn, setSelectTurn] = useState<SkillBattleInfo | null>(null);
|
||||
const [selectAvatar, setSelectAvatar] = useState<AvatarHakushiType | null>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const { turnHistory } = useBattleDataStore();
|
||||
const { skillHistory, turnHistory, cycleIndex, waveIndex, maxWave } = useBattleDataStore();
|
||||
const { listAvatar } = useAvatarDataStore();
|
||||
const { locale } = useLocaleStore();
|
||||
const transI18n = useTranslations("DataAnalysisPage");
|
||||
@@ -21,13 +22,13 @@ export default function ActionBar() {
|
||||
|
||||
|
||||
const contentStyle: React.CSSProperties = {
|
||||
|
||||
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
};
|
||||
|
||||
const handleShow = (modalId: string, avatar: AvatarType, turn: TurnBattleInfo) => {
|
||||
const handleShow = (modalId: string, avatar: AvatarHakushiType, turn: SkillBattleInfo) => {
|
||||
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
||||
if (modal) {
|
||||
setSelectAvatar(avatar);
|
||||
@@ -61,10 +62,10 @@ export default function ActionBar() {
|
||||
|
||||
// Scroll to the bottom when new turns are added
|
||||
useEffect(() => {
|
||||
if (turnListRef.current && turnHistory.length > 0) {
|
||||
if (turnListRef.current && skillHistory.length > 0) {
|
||||
turnListRef.current.scrollTop = turnListRef.current.scrollHeight;
|
||||
}
|
||||
}, [turnHistory.length]);
|
||||
}, [skillHistory.length]);
|
||||
|
||||
return (
|
||||
<div className="p-4 md:p-1 rounded-lg shadow-lg w-full h-full">
|
||||
@@ -76,49 +77,31 @@ export default function ActionBar() {
|
||||
>
|
||||
{transI18n("actionBar")}
|
||||
</motion.h2>
|
||||
<div className="grid grid-cols-2 md:grid-cols-1 xl:grid-cols-2 items-center justify-items-center w-full gap-2 mb-2 mx-2">
|
||||
<div className="badge badge-soft badge-info gap-2 text-lg py-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="size-6">
|
||||
<path fillRule="evenodd" d="M3 2.25a.75.75 0 0 1 .75.75v.54l1.838-.46a9.75 9.75 0 0 1 6.725.738l.108.054A8.25 8.25 0 0 0 18 4.524l3.11-.732a.75.75 0 0 1 .917.81 47.784 47.784 0 0 0 .005 10.337.75.75 0 0 1-.574.812l-3.114.733a9.75 9.75 0 0 1-6.594-.77l-.108-.054a8.25 8.25 0 0 0-5.69-.625l-2.202.55V21a.75.75 0 0 1-1.5 0V3A.75.75 0 0 1 3 2.25Z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span>{waveIndex}/{maxWave}</span>
|
||||
</div>
|
||||
<div className="badge badge-soft badge-error gap-2 text-lg py-3 items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" fill="currentColor" className="size-6">
|
||||
<path d="M0 32C0 14.3 14.3 0 32 0L64 0 320 0l32 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l0 11c0 42.4-16.9 83.1-46.9 113.1L237.3 256l67.9 67.9c30 30 46.9 70.7 46.9 113.1l0 11c17.7 0 32 14.3 32 32s-14.3 32-32 32l-32 0L64 512l-32 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l0-11c0-42.4 16.9-83.1 46.9-113.1L146.7 256 78.9 188.1C48.9 158.1 32 117.4 32 75l0-11C14.3 64 0 49.7 0 32zM96 64l0 11c0 25.5 10.1 49.9 28.1 67.9L192 210.7l67.9-67.9c18-18 28.1-42.4 28.1-67.9l0-11L96 64zm0 384l192 0 0-11c0-25.5-10.1-49.9-28.1-67.9L192 301.3l-67.9 67.9c-18 18-28.1 42.4-28.1 67.9l0 11z" />
|
||||
</svg>
|
||||
<span>{cycleIndex}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref={turnListRef}
|
||||
className="flex md:block px-2 md:px-0 w-full pt-2 border-t-2 border-accent overflow-x-auto md:overflow-x-hidden md:overflow-y-auto max-h-[90vh] custom-scrollbar"
|
||||
className="flex md:block px-2 md:px-0 w-full pt-2 border-t-2 border-accent overflow-x-auto md:overflow-x-hidden md:overflow-y-auto max-h-[90vh] lg:max-h-[80vh]"
|
||||
>
|
||||
<style jsx>{`
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: hsl(var(--p)) hsl(var(--b3));
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: hsl(var(--b3));
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: hsl(var(--p));
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: hsl(var(--pf));
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-button {
|
||||
display: none;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div className="flex flex-nowrap md:grid md:grid-cols-1 gap-2 w-fit md:w-full">
|
||||
{turnHistory.length === 0 ? (
|
||||
{skillHistory.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<p className="text-base-content opacity-50">{transI18n("noTurns")}</p>
|
||||
</div>
|
||||
) : (
|
||||
turnHistory.map((turn, index) => {
|
||||
skillHistory.map((turn, index) => {
|
||||
const data = listAvatar.find(it => it.id === turn.avatarId.toString());
|
||||
if (!data) return null;
|
||||
const text = getNameChar(locale, data);
|
||||
@@ -135,20 +118,22 @@ export default function ActionBar() {
|
||||
>
|
||||
<div className="avatar">
|
||||
<div className="w-12 h-12 rounded-full border-2 flex items-center justify-center bg-base-300 border-cyan-400 border-l-4">
|
||||
<img
|
||||
|
||||
<Image
|
||||
src={`https://api.hakush.in/hsr/UI/avatarshopicon/${data.id}.webp`}
|
||||
alt={text}
|
||||
loading="lazy"
|
||||
width={32}
|
||||
height={32}
|
||||
className="w-8 h-8 object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="text-base-content text-center text-sm mt-1 font-medium">{getNameChar(locale, data)}</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 justify-center gap-2 py-2 w-full">
|
||||
<div className="bg-local text-primary text-xs max-w-full">
|
||||
{`${transI18n("useSkill")}: ${transI18n(turn.skillType.toLowerCase())}`}
|
||||
{`${transI18n("useSkill")}: ${transI18n(attackTypeToString(turn.skillType).toLowerCase())}`}
|
||||
</div>
|
||||
<div className="text-primary text-xs max-w-full">
|
||||
{`${transI18n("totalDamage")}: ${turn.totalDamage.toFixed(2)}`}
|
||||
@@ -157,7 +142,7 @@ export default function ActionBar() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
@@ -189,7 +174,7 @@ export default function ActionBar() {
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="mt-4 w-full custom-scrollbar"
|
||||
className="mt-4 w-full"
|
||||
>
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md mb-4">
|
||||
<h4 className="text-lg font-semibold mb-2 text-pink-500">{transI18n("characterInformation")}</h4>
|
||||
@@ -205,10 +190,11 @@ export default function ActionBar() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center items-center">
|
||||
<img
|
||||
loading="lazy"
|
||||
<Image
|
||||
src={`https://api.hakush.in/hsr/UI/avatarshopicon/${selectAvatar.id}.webp`}
|
||||
alt={getNameChar(locale, selectAvatar)}
|
||||
width={80}
|
||||
height={80}
|
||||
className="h-20 w-20 object-cover rounded-full border-2 border-purple-500 shadow-lg shadow-purple-500/20"
|
||||
/>
|
||||
</div>
|
||||
@@ -218,7 +204,7 @@ export default function ActionBar() {
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
||||
<h4 className="text-lg font-semibold mb-2 text-cyan-500 border-b border-cyan-300/30 pb-1">{transI18n("skillType")}</h4>
|
||||
<p className="mt-2">{transI18n(selectTurn?.skillType.toLowerCase())}</p>
|
||||
<p className="mt-2">{transI18n(attackTypeToString(selectTurn?.skillType).toLowerCase())}</p>
|
||||
</div>
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
||||
<h4 className="text-lg font-semibold mb-2 text-cyan-500 border-b border-cyan-300/30 pb-1">{transI18n("skillName")}</h4>
|
||||
@@ -226,10 +212,21 @@ export default function ActionBar() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
||||
<h4 className="text-lg font-semibold mb-2 text-cyan-500 border-b border-cyan-300/30 pb-1">{transI18n("cycle")}</h4>
|
||||
<p className="mt-2">{turnHistory[selectTurn?.turnBattleId].cycleIndex}</p>
|
||||
</div>
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
||||
<h4 className="text-lg font-semibold mb-2 text-cyan-500 border-b border-cyan-300/30 pb-1">{transI18n("wave")}</h4>
|
||||
<p className="mt-2">{turnHistory[selectTurn?.turnBattleId].waveIndex}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
||||
<h4 className="text-lg font-semibold mb-2 text-cyan-500 border-b border-cyan-300/30 pb-1">{transI18n("actionValue")}</h4>
|
||||
<p className="mt-2">{selectTurn?.actionValue.toFixed(2)}</p>
|
||||
<p className="mt-2">{turnHistory[selectTurn?.turnBattleId].actionValue.toFixed(2)}</p>
|
||||
</div>
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
||||
<h4 className="text-lg font-semibold mb-2 text-purple-500 border-b border-purple-300/30 pb-1">{transI18n("totalDamage")}</h4>
|
||||
@@ -239,15 +236,26 @@ export default function ActionBar() {
|
||||
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md mb-4">
|
||||
<h4 className="text-lg font-semibold mb-2 text-cyan-500 border-b border-cyan-300/30 pb-1">{transI18n("damageDetails")}</h4>
|
||||
{selectTurn?.damageDetail && selectTurn.damageDetail.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2 mt-3">
|
||||
{selectTurn?.damageDetail?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3 mt-3">
|
||||
{selectTurn.damageDetail.map((detail, idx) => (
|
||||
<p key={idx} className="py-1 px-2 rounded bg-base-300 text-sm">{detail.toFixed(2)}</p>
|
||||
<div
|
||||
key={idx}
|
||||
className="flex flex-col items-start gap-1 p-3 rounded-lg shadow bg-base-200"
|
||||
>
|
||||
<span className="text-lg font-semibold text-primary">
|
||||
{detail.damage.toFixed(2)}
|
||||
</span>
|
||||
<span className="text-xs uppercase text-gray-500 tracking-wide">
|
||||
{transI18n(attackTypeToString(detail?.damage_type).toLowerCase())}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="mt-2 italic opacity-70">{transI18n("noDamageDetail")}</p>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import { getNameChar } from '@/helper';
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import { AvatarType } from '@/types';
|
||||
import { AvatarHakushiType } from '@/types';
|
||||
|
||||
interface CharacterCardProps {
|
||||
data: AvatarType
|
||||
data: AvatarHakushiType
|
||||
}
|
||||
|
||||
export function parseRuby(text: string): string {
|
||||
|
||||
@@ -0,0 +1,665 @@
|
||||
"use client";
|
||||
import React, { useState, useRef} from 'react';
|
||||
|
||||
export default function ShowCaseInfo() {
|
||||
const [imageUrl, setImageUrl] = useState<string>('https://api.hakush.in/hsr/UI/avatardrawcard/1001.webp');
|
||||
const [position, setPosition] = useState<{ x: number; y: number }>({ x: 0, y: 100 });
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-start m-2">
|
||||
|
||||
<div className="overflow-auto">
|
||||
<div ref={ref} className="relative min-h-[650px] w-[1400px] rounded-3xl overflow-hidden">
|
||||
<img
|
||||
src="https://cdn.jsdelivr.net/gh/picklejason/hsr-showcase@main/public/blur-bg.png"
|
||||
alt="Showcase Background"
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute bottom-2 left-4 z-10">
|
||||
<span className="shadow-black [text-shadow:1px_1px_2px_var(--tw-shadow-color)]"></span>
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<div
|
||||
className="relative min-h-[650px] w-[28%]"
|
||||
|
||||
>
|
||||
<div
|
||||
className="flex h-[650px] items-center"
|
||||
style={{ cursor: 'grab' }}
|
||||
>
|
||||
<div className="overflow-hidden w-full h-full"
|
||||
>
|
||||
<img
|
||||
src={imageUrl}
|
||||
className="scale-[1.8] cursor-pointer object-cover"
|
||||
alt="Character Preview"
|
||||
style={{
|
||||
position: 'relative',
|
||||
top: `${position.y}px`,
|
||||
left: `${position.x}px`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute right-0 top-0 mr-[-15px] pt-3">
|
||||
<div className="flex flex-col">
|
||||
{[
|
||||
"https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/memory-of-you-eidolon_icon_small.webp",
|
||||
"https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/memory-of-it-eidolon_icon_small.webp",
|
||||
"https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/memory-of-everything-eidolon_icon_small.webp",
|
||||
"https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/never-forfeit-again-eidolon_icon_small.webp",
|
||||
"https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/never-forget-again-eidolon_icon_small.webp",
|
||||
"https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/just-like-this-always-eidolon_icon_small.webp"
|
||||
].map((src, index) => (
|
||||
<div key={index} className="relative my-1 flex rounded-full border-2 border-neutral-300 bg-neutral-800">
|
||||
<img src={src} alt="Rank Icon" className="h-auto w-10" />
|
||||
<div className="absolute flex h-full w-full items-center justify-center rounded-full bg-neutral-800/70">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" className="size-5">
|
||||
<path fillRule="evenodd" d="M10 1a4.5 4.5 0 0 0-4.5 4.5V9H5a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2h-.5V5.5A4.5 4.5 0 0 0 10 1Zm3 8V5.5a3 3 0 1 0-6 0V9h6Z" clipRule="evenodd"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex min-h-[650px] w-[72%] flex-row items-center gap-3.5 rounded-r-3xl pl-10">
|
||||
<img
|
||||
src="https://cdn.jsdelivr.net/gh/picklejason/hsr-showcase@main/public/fade-bg.png"
|
||||
alt="Background"
|
||||
className="absolute inset-0 w-full h-full object-cover rounded-r-3xl"
|
||||
style={{ zIndex: -1 }}
|
||||
/>
|
||||
|
||||
<div className="flex h-[650px] w-1/3 flex-col justify-between py-3">
|
||||
<div className="flex h-full flex-col justify-between">
|
||||
<div>
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<span className="text-4xl">March 7th</span>
|
||||
<img src="https://api.hakush.in/hsr/UI/element/ice.webp" alt="Element Icon" className="h-auto w-14" />
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<img src="https://api.hakush.in/hsr/UI/pathicon/knight.webp" alt="Path Icon" className="h-auto w-8" />
|
||||
<span className="text-xl">Preservation</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-2xl">Lv. 80</span>
|
||||
<span className="text-xl"> / </span>
|
||||
<span className="text-xl text-neutral-400">80</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mx-4 flex h-[225px] w-auto flex-row items-center justify-evenly">
|
||||
<div className="absolute mb-5">
|
||||
<img src="https://api.hakush.in/hsr/UI/pathicon/knight.webp" alt="Path Icon" className="h-40 w-40 opacity-20" />
|
||||
</div>
|
||||
<div className="flex h-full w-1/3 flex-col justify-center gap-8">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="relative flex flex-col items-center">
|
||||
<img src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/skill/frigid-cold-arrow-skill_icon.webp" alt="Skill Icon" className="h-auto w-12 rounded-full border-2 border-neutral-500 bg-neutral-800" />
|
||||
<span className="black-blur absolute bottom-4 text-sm">6 / 6</span>
|
||||
<span className="z-10 mt-1.5 truncate text-sm">Basic ATK</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="relative flex flex-col items-center">
|
||||
<img src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/skill/the-power-of-cuteness-skill_icon.webp" alt="Skill Icon" className="h-auto w-12 rounded-full border-2 border-neutral-500 bg-neutral-800" />
|
||||
<span className="black-blur absolute bottom-4 text-sm">10 / 10</span>
|
||||
<span className="z-10 mt-1.5 truncate text-sm">Skill</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-1/3 justify-center">
|
||||
<div className="relative flex flex-col items-center">
|
||||
<img src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/skill/glacial-cascade-skill_icon.webp" alt="Skill Icon" className="h-auto w-12 rounded-full border-2 border-neutral-500 bg-neutral-800" />
|
||||
<span className="black-blur absolute bottom-4 text-sm">10 / 10</span>
|
||||
<span className="z-10 mt-1.5 truncate text-sm">Ultimate</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full w-1/3 flex-col justify-center gap-8">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="relative flex flex-col items-center">
|
||||
<img src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/skill/girl-power-skill_icon.webp" alt="Skill Icon" className="h-auto w-12 rounded-full border-2 border-neutral-500 bg-neutral-800" />
|
||||
<span className="black-blur absolute bottom-4 text-sm">8 / 8</span>
|
||||
<span className="z-10 mt-1.5 truncate text-sm">Talent</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="relative flex flex-col items-center">
|
||||
<img src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/skill/freezing-beauty-skill_icon.webp" alt="Skill Icon" className="h-auto w-12 rounded-full border-2 border-neutral-500 bg-neutral-800" />
|
||||
<span className="black-blur absolute bottom-4 text-sm">5 / 5</span>
|
||||
<span className="z-10 mt-1.5 truncate text-sm">Technique</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="flex w-full flex-row justify-evenly">
|
||||
{/* First Column */}
|
||||
<div className="flex flex-col justify-center items-center" style={{ gap: '0.25rem' }}>
|
||||
<img
|
||||
src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/trace/purify-trace_icon.webp"
|
||||
alt="Icon 1001101"
|
||||
className="bg-neutral-800 h-auto w-5 rounded-full"
|
||||
/>
|
||||
<div className="flex flex-row justify-center items-center" style={{ gap: '0.25rem' }}>
|
||||
<img
|
||||
src="https://api.hakush.in/hsr/UI/trace/IconDefence.webp"
|
||||
alt="Icon 1001101"
|
||||
className="bg-neutral-800 h-auto w-3 rounded-full"
|
||||
/>
|
||||
<img
|
||||
src="https://api.hakush.in/hsr/UI/trace/IconIceAddedRatio.webp"
|
||||
alt="Icon 1001101"
|
||||
className="bg-neutral-800 h-auto w-3 rounded-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Second Column */}
|
||||
<div className="flex flex-col justify-center items-center" style={{ gap: '0.25rem' }}>
|
||||
<img
|
||||
src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/trace/reinforce-trace_icon.webp"
|
||||
alt="Icon 1001102"
|
||||
className="bg-neutral-800 h-auto w-5 rounded-full"
|
||||
/>
|
||||
<div className="flex flex-row justify-center items-center" style={{ gap: '0.25rem' }}>
|
||||
<img
|
||||
src="https://api.hakush.in/hsr/UI/trace/IconIceAddedRatio.webp"
|
||||
alt="Icon 1001102"
|
||||
className="bg-neutral-800 h-auto w-3 rounded-full"
|
||||
/>
|
||||
<img
|
||||
src="https://api.hakush.in/hsr/UI/trace/IconStatusResistance.webp"
|
||||
alt="Icon 1001102"
|
||||
className="bg-neutral-800 h-auto w-3 rounded-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Third Column */}
|
||||
<div className="flex flex-col justify-center items-center" style={{ gap: '0.25rem' }}>
|
||||
<img
|
||||
src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/trace/ice-spell-trace_icon.webp"
|
||||
alt="Icon 1001103"
|
||||
className="bg-neutral-800 h-auto w-5 rounded-full"
|
||||
/>
|
||||
<div className="flex flex-row justify-center items-center" style={{ gap: '0.25rem' }}>
|
||||
<img
|
||||
src="https://api.hakush.in/hsr/UI/trace/IconIceAddedRatio.webp"
|
||||
alt="Icon 1001103"
|
||||
className="bg-neutral-800 h-auto w-3 rounded-full"
|
||||
/>
|
||||
<img
|
||||
src="https://api.hakush.in/hsr/UI/trace/IconIceAddedRatio.webp"
|
||||
alt="Icon 1001103"
|
||||
className="bg-neutral-800 h-auto w-3 rounded-full"
|
||||
/>
|
||||
<img
|
||||
src="https://api.hakush.in/hsr/UI/trace/IconDefence.webp"
|
||||
alt="Icon 1001103"
|
||||
className="bg-neutral-800 h-auto w-3 rounded-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Fourth Column */}
|
||||
<div className="flex flex-col justify-center items-center" style={{ gap: '0.25rem' }}>
|
||||
<div className="flex flex-row justify-center items-center" style={{ gap: '0.25rem' }}>
|
||||
<img
|
||||
src="https://api.hakush.in/hsr/UI/trace/IconIceAddedRatio.webp"
|
||||
alt="Icon 1001201"
|
||||
className="bg-neutral-800 h-auto w-3 rounded-full"
|
||||
/>
|
||||
<img
|
||||
src="https://api.hakush.in/hsr/UI/trace/IconStatusResistance.webp"
|
||||
alt="Icon 1001201"
|
||||
className="bg-neutral-800 h-auto w-3 rounded-full"
|
||||
/>
|
||||
<img
|
||||
src="https://api.hakush.in/hsr/UI/trace/IconDefence.webp"
|
||||
alt="Icon 1001201"
|
||||
className="bg-neutral-800 h-auto w-3 rounded-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center justify-center">
|
||||
<div className="relative flex flex-col items-center">
|
||||
<img
|
||||
src="https://api.hakush.in/hsr/UI/lightconemaxfigures/24000.webp"
|
||||
alt="Light Cone Preview"
|
||||
className="h-auto w-32"
|
||||
/>
|
||||
<img
|
||||
src="https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/deco/Star5.png"
|
||||
alt="Light Cone Rarity Icon"
|
||||
className="absolute bottom-0 left-1 h-auto w-36"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-3/5 flex-col items-center gap-2 text-center">
|
||||
<span className="text-xl">On the Fall of an Aeon</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-6 w-6 rounded-full font-normal bg-[#f6ce71] text-black">
|
||||
V
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-lg">Lv. 80</span>
|
||||
<span> / </span>
|
||||
<span className="text-neutral-400">80</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row gap-1.5">
|
||||
<div className="black-blur flex flex-row items-center rounded pr-1">
|
||||
<img
|
||||
src="https://srtools.pages.dev/icon/property/IconAttack.png"
|
||||
alt="Attribute Icon"
|
||||
className="h-auto w-6 p-1"
|
||||
/>
|
||||
<span className="text-sm">529</span>
|
||||
</div>
|
||||
<div className="black-blur flex flex-row items-center rounded pr-1">
|
||||
<img
|
||||
src="https://srtools.pages.dev/icon/property/IconDefence.png"
|
||||
alt="Attribute Icon"
|
||||
className="h-auto w-6 p-1"
|
||||
/>
|
||||
<span className="text-sm">397</span>
|
||||
</div>
|
||||
<div className="black-blur flex flex-row items-center rounded pr-1">
|
||||
<img
|
||||
src="https://srtools.pages.dev/icon/property/IconMaxHP.png"
|
||||
alt="Attribute Icon"
|
||||
className="h-auto w-6 p-1"
|
||||
/>
|
||||
<span className="text-sm">1058</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex h-[650px] w-1/3 flex-col justify-between py-3">
|
||||
<div className="flex w-full flex-col justify-between gap-y-0.5 text-base h-[500px]">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconMaxHP.png" alt="Stat Icon" className="h-auto w-10 p-2" />
|
||||
<span className="font-bold">HP</span>
|
||||
</div>
|
||||
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
|
||||
<div className="flex cursor-default flex-col text-right font-bold">2942</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Stat Icon" className="h-auto w-10 p-2" />
|
||||
<span className="font-bold">ATK</span>
|
||||
</div>
|
||||
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
|
||||
<div className="flex cursor-default flex-col text-right font-bold">3212</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconDefence.png" alt="Stat Icon" className="h-auto w-10 p-2" />
|
||||
<span className="font-bold">DEF</span>
|
||||
</div>
|
||||
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
|
||||
<div className="flex cursor-default flex-col text-right font-bold">1255</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconSpeed.png" alt="Stat Icon" className="h-auto w-10 p-2" />
|
||||
<span className="font-bold">SPD</span>
|
||||
</div>
|
||||
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
|
||||
<div className="flex cursor-default flex-col text-right font-bold">106</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconCriticalChance.png" alt="Stat Icon" className="h-auto w-10 p-2" />
|
||||
<span className="font-bold">CRIT Rate</span>
|
||||
</div>
|
||||
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
|
||||
<div className="flex cursor-default flex-col text-right font-bold">16.7%</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconCriticalDamage.png" alt="Stat Icon" className="h-auto w-10 p-2" />
|
||||
<span className="font-bold">CRIT DMG</span>
|
||||
</div>
|
||||
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
|
||||
<div className="flex cursor-default flex-col text-right font-bold">79.2%</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconBreakUp.png" alt="Stat Icon" className="h-auto w-10 p-2" />
|
||||
<span className="font-bold">Break Effect</span>
|
||||
</div>
|
||||
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
|
||||
<div className="flex cursor-default flex-col text-right font-bold">186.9%</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconStatusResistance.png" alt="Stat Icon" className="h-auto w-10 p-2" />
|
||||
<span className="font-bold">Effect RES</span>
|
||||
</div>
|
||||
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
|
||||
<div className="flex cursor-default flex-col text-right font-bold">4.0%</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconEnergyRecovery.png" alt="Stat Icon" className="h-auto w-10 p-2" />
|
||||
<span className="font-bold">Energy Regeneration Rate</span>
|
||||
</div>
|
||||
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
|
||||
<div className="flex cursor-default flex-col text-right font-bold">0.0%</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconStatusProbability.png" alt="Stat Icon" className="h-auto w-10 p-2" />
|
||||
<span className="font-bold">Effect Hit Rate</span>
|
||||
</div>
|
||||
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
|
||||
<div className="flex cursor-default flex-col text-right font-bold">20.3%</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconHealRatio.png" alt="Stat Icon" className="h-auto w-10 p-2" />
|
||||
<span className="font-bold">Outgoing Healing Boost</span>
|
||||
</div>
|
||||
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
|
||||
<div className="flex cursor-default flex-col text-right font-bold">0.0%</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconIceAddedRatio.png" alt="Stat Icon" className="h-auto w-10 p-2" />
|
||||
<span className="font-bold">Ice DMG Boost</span>
|
||||
</div>
|
||||
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
|
||||
<div className="flex cursor-default flex-col text-right font-bold">6.4%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<div className="flex w-full flex-row justify-between text-left">
|
||||
<span>Prisoner in Deep Confinement</span>
|
||||
<div>
|
||||
<span className="black-blur bg-black flex w-5 justify-center rounded px-1.5 py-0.5">2</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full flex-row justify-between text-left">
|
||||
<span>Watchmaker, Master of Dream Machinations</span>
|
||||
<div>
|
||||
<span className="black-blur bg-black flex w-5 justify-center rounded px-1.5 py-0.5">2</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full flex-row justify-between text-left">
|
||||
<span>Talia: Kingdom of Banditry</span>
|
||||
<div>
|
||||
<span className="black-blur bg-black flex w-5 justify-center rounded px-1.5 py-0.5">2</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-1/3">
|
||||
<div className="flex h-[650px] flex-col justify-between py-3 text-lg">
|
||||
<div className="black-blur relative flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600">
|
||||
<div className="flex">
|
||||
<img src="https://api.hakush.in/hsr/UI/relicfigures/IconRelic_116_1.webp" alt="Relic Icon" className="h-auto w-20" />
|
||||
<img src="https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/deco/Star5.png" alt="Relic Rarity Icon" className="absolute bottom-1 h-auto w-20" />
|
||||
</div>
|
||||
<div className="mx-1 flex w-1/6 flex-col items-center justify-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconMaxHP.png" alt="Main Affix Icon" className="h-auto w-9" />
|
||||
<span className="text-base text-[#f1a23c]">705</span>
|
||||
<span className="black-blur rounded px-1 text-xs">+15</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">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+7.8%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconCriticalChance.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+3.2%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconCriticalDamage.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+17.5%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconBreakUp.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+15.6%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="black-blur relative flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600">
|
||||
<div className="flex">
|
||||
<img src="https://api.hakush.in/hsr/UI/relicfigures/IconRelic_118_2.webp" alt="Relic Icon" className="h-auto w-20" />
|
||||
<img src="https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/deco/Star5.png" alt="Relic Rarity Icon" className="absolute bottom-1 h-auto w-20" />
|
||||
</div>
|
||||
<div className="mx-1 flex w-1/6 flex-col items-center justify-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Main Affix Icon" className="h-auto w-9" />
|
||||
<span className="text-base text-[#f1a23c]">352</span>
|
||||
<span className="black-blur rounded px-1 text-xs">+15</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">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+6.9%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconCriticalDamage.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+11.7%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconStatusProbability.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+3.9%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconBreakUp.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+23.3%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="black-blur relative flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600">
|
||||
<div className="flex">
|
||||
<img src="https://api.hakush.in/hsr/UI/relicfigures/IconRelic_116_3.webp" alt="Relic Icon" className="h-auto w-20" />
|
||||
<img src="https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/deco/Star5.png" alt="Relic Rarity Icon" className="absolute bottom-1 h-auto w-20" />
|
||||
</div>
|
||||
<div className="mx-1 flex w-1/6 flex-col items-center justify-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Main Affix Icon" className="h-auto w-9" />
|
||||
<span className="text-base text-[#f1a23c]">43.2%</span>
|
||||
<span className="black-blur rounded px-1 text-xs">+15</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">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconDefence.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+57</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconMaxHP.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+3.9%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconCriticalChance.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+2.6%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconBreakUp.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+19.4%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="black-blur relative flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600">
|
||||
<div className="flex">
|
||||
<img src="https://api.hakush.in/hsr/UI/relicfigures/IconRelic_118_4.webp" alt="Relic Icon" className="h-auto w-20" />
|
||||
<img src="https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/deco/Star5.png" alt="Relic Rarity Icon" className="absolute bottom-1 h-auto w-20" />
|
||||
</div>
|
||||
<div className="mx-1 flex w-1/6 flex-col items-center justify-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Main Affix Icon" className="h-auto w-9" />
|
||||
<span className="text-base text-[#f1a23c]">18.9%</span>
|
||||
<span className="black-blur rounded px-1 text-xs">+15</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">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconMaxHP.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+5.2%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconCriticalDamage.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+4.3%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconCriticalChance.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+2.7%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconDefence.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+34</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="black-blur relative flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600">
|
||||
<div className="flex">
|
||||
<img src="https://api.hakush.in/hsr/UI/relicfigures/IconRelic_118_4.webp" alt="Relic Icon" className="h-auto w-20" />
|
||||
<img src="https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/deco/Star5.png" alt="Relic Rarity Icon" className="absolute bottom-1 h-auto w-20" />
|
||||
</div>
|
||||
<div className="mx-1 flex w-1/6 flex-col items-center justify-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Main Affix Icon" className="h-auto w-9" />
|
||||
<span className="text-base text-[#f1a23c]">18.9%</span>
|
||||
<span className="black-blur rounded px-1 text-xs">+15</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">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconMaxHP.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+5.2%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconCriticalDamage.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+4.3%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconCriticalChance.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+2.7%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconDefence.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+34</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="black-blur relative flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600">
|
||||
<div className="flex">
|
||||
<img src="https://api.hakush.in/hsr/UI/relicfigures/IconRelic_118_4.webp" alt="Relic Icon" className="h-auto w-20" />
|
||||
<img src="https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/deco/Star5.png" alt="Relic Rarity Icon" className="absolute bottom-1 h-auto w-20" />
|
||||
</div>
|
||||
<div className="mx-1 flex w-1/6 flex-col items-center justify-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Main Affix Icon" className="h-auto w-9" />
|
||||
<span className="text-base text-[#f1a23c]">18.9%</span>
|
||||
<span className="black-blur rounded px-1 text-xs">+15</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">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconMaxHP.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+5.2%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconCriticalDamage.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+4.3%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconCriticalChance.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+2.7%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="https://srtools.pages.dev/icon/property/IconDefence.png" alt="Sub Affix Icon" className="h-auto w-7" />
|
||||
<span className="text-sm">+34</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -13,24 +13,29 @@ import { useDamageLinesForAll } from '@/hooks/useDamageLinesForAll';
|
||||
import useAvatarDataStore from '@/stores/avatarDataStore';
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import { getNameChar } from '@/helper';
|
||||
import { useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement, Tooltip, Legend);
|
||||
|
||||
const colors = ['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c'];
|
||||
|
||||
export default function MultiCharLineChart({ mode = 0 }: { mode?: 0 | 1 }) {
|
||||
export default function MultiCharLineChart() {
|
||||
const [mode, setMode] = useState<1 | 2>(2);
|
||||
const dataByAvatar = useDamageLinesForAll(mode);
|
||||
const avatarIds = Object.keys(dataByAvatar).map(Number);
|
||||
const { listAvatar } = useAvatarDataStore()
|
||||
const { locale } = useLocaleStore();
|
||||
|
||||
const transI18n = useTranslations("DataAnalysisPage")
|
||||
|
||||
|
||||
const data = {
|
||||
datasets: avatarIds.map((id, idx) => ({
|
||||
label: getNameChar(locale, listAvatar.find(it => it.id == id.toString())),
|
||||
data: dataByAvatar[id].map(({ x, y }: { x: number; y: number }) => {
|
||||
return {
|
||||
x: x.toFixed(2),
|
||||
y: y.toFixed(2)
|
||||
x: x.toFixed(2),
|
||||
y: y.toFixed(2)
|
||||
}
|
||||
}),
|
||||
borderColor: colors[idx % colors.length],
|
||||
@@ -54,12 +59,27 @@ export default function MultiCharLineChart({ mode = 0 }: { mode?: 0 | 1 }) {
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
datalabels: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return <Line data={data} options={options} />;
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||
{[
|
||||
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||
].map(({ mode: m, label, className }) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setMode(m as 1 | 2)}
|
||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Line data={data} options={options} />
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { Line } from "react-chartjs-2";
|
||||
import { ChartOptions } from "chart.js";
|
||||
import { useDamageLineForOne } from "@/hooks/useDamageLineForOne";
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
LineElement,
|
||||
@@ -15,6 +14,7 @@ import {
|
||||
Filler,
|
||||
} from "chart.js";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
|
||||
ChartJS.register(
|
||||
LineElement,
|
||||
@@ -27,11 +27,9 @@ ChartJS.register(
|
||||
Filler
|
||||
);
|
||||
|
||||
ChartJS.defaults.set('plugins.datalabels', {
|
||||
color: '#FE777B'
|
||||
});
|
||||
|
||||
export function DamageLineForOne({ avatarId, mode = 0 }: { avatarId: number; mode?: 0 | 1 }) {
|
||||
export function DamageLineForOne({ avatarId }: { avatarId: number; }) {
|
||||
const [mode, setMode] = useState<0 | 1>(1);
|
||||
const dataRaw = useDamageLineForOne(avatarId, mode);
|
||||
const transI18n = useTranslations("DataAnalysisPage");
|
||||
const data = {
|
||||
@@ -52,9 +50,6 @@ export function DamageLineForOne({ avatarId, mode = 0 }: { avatarId: number; mod
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: true },
|
||||
datalabels: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: { title: { display: true, text: transI18n("actionValue") } },
|
||||
@@ -63,5 +58,23 @@ export function DamageLineForOne({ avatarId, mode = 0 }: { avatarId: number; mod
|
||||
|
||||
};
|
||||
|
||||
return <Line data={data} options={options} />;
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||
{[
|
||||
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||
].map(({ mode: m, label, className }) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setMode(m as 0 | 1)}
|
||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<Line data={data} options={options} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"use client";
|
||||
'use client';
|
||||
import { useState } from 'react';
|
||||
import { getNameChar } from '@/helper';
|
||||
import useAvatarDataStore from '@/stores/avatarDataStore';
|
||||
import useBattleDataStore from '@/stores/battleDataStore';
|
||||
@@ -9,11 +10,11 @@ import {
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
Tooltip,
|
||||
Legend,
|
||||
Legend
|
||||
} from 'chart.js';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import {attackTypeToString} from "@/types";
|
||||
|
||||
ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend);
|
||||
|
||||
@@ -25,56 +26,153 @@ const colorPalette = [
|
||||
'rgba(153, 102, 255, 0.6)',
|
||||
'rgba(255, 159, 64, 0.6)',
|
||||
'rgba(199, 199, 199, 0.6)',
|
||||
];
|
||||
|
||||
const borderPalette = colorPalette.map((color) => color.replace('0.6', '1'));
|
||||
'rgba(255, 99, 255, 0.6)',
|
||||
'rgba(0, 200, 83, 0.6)',
|
||||
'rgba(255, 112, 67, 0.6)',
|
||||
'rgba(63, 81, 181, 0.6)',
|
||||
'rgba(0, 188, 212, 0.6)',
|
||||
'rgba(233, 30, 99, 0.6)',
|
||||
'rgba(124, 179, 66, 0.6)',
|
||||
'rgba(255, 235, 59, 0.6)',
|
||||
'rgba(158, 158, 158, 0.6)',
|
||||
'rgba(121, 85, 72, 0.6)',
|
||||
'rgba(96, 125, 139, 0.6)',
|
||||
'rgba(100, 181, 246, 0.6)',
|
||||
'rgba(255, 171, 145, 0.6)',
|
||||
'rgba(174, 213, 129, 0.6)',
|
||||
'rgba(255, 245, 157, 0.6)'
|
||||
];
|
||||
|
||||
const borderPalette = colorPalette.map((c) => c.replace('0.6', '1'));
|
||||
|
||||
export default function DamagePerAvatarForAll() {
|
||||
const transI18n = useTranslations("DataAnalysisPage");
|
||||
const { lineup, turnHistory } = useBattleDataStore()
|
||||
const { listAvatar } = useAvatarDataStore()
|
||||
const { lineup, skillHistory } = useBattleDataStore();
|
||||
const { listAvatar } = useAvatarDataStore();
|
||||
const { locale } = useLocaleStore();
|
||||
const damageByAvatar = lineup.map((avatar) => {
|
||||
const turns = turnHistory.filter((turn) => turn.avatarId === avatar.avatarId);
|
||||
const totalDmg = turns.reduce((sum, turn) => sum + turn.totalDamage, 0);
|
||||
const char = listAvatar.find(it => it.id === avatar.avatarId.toString())
|
||||
if (!char) return
|
||||
|
||||
const [mode, setMode] = useState<number>(2);
|
||||
|
||||
const avatarMap = lineup.map((avatar) => {
|
||||
const char = listAvatar.find(it => it.id === avatar.avatarId.toString());
|
||||
if (!char) return undefined;
|
||||
return {
|
||||
avatarId: avatar.avatarId,
|
||||
damage: totalDmg,
|
||||
avatarName: getNameChar(locale, char)
|
||||
};
|
||||
});
|
||||
|
||||
const data = {
|
||||
labels: damageByAvatar.map((item) => item?.avatarName),
|
||||
datasets: [
|
||||
}).filter(Boolean) as { avatarId: number, avatarName: string }[];
|
||||
|
||||
const labels = avatarMap.map(a => a.avatarName);
|
||||
|
||||
let datasets: Array<{
|
||||
label: string;
|
||||
data: number[];
|
||||
backgroundColor: string | string[];
|
||||
borderColor: string | string[];
|
||||
borderWidth: number;
|
||||
stack?: string;
|
||||
}> = [];
|
||||
|
||||
if (mode === 1) {
|
||||
datasets = [
|
||||
{
|
||||
label: transI18n("totalDamage"),
|
||||
data: damageByAvatar.map((item) => item?.damage),
|
||||
backgroundColor: damageByAvatar.map((_, idx) => colorPalette[idx % colorPalette.length]),
|
||||
borderColor: damageByAvatar.map((_, idx) => borderPalette[idx % borderPalette.length]),
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
data: avatarMap.map(({ avatarId }) => {
|
||||
const total = skillHistory
|
||||
.filter(skill => skill.avatarId === avatarId)
|
||||
.reduce((sum, s) => sum + s.totalDamage, 0);
|
||||
return total;
|
||||
}),
|
||||
backgroundColor: avatarMap.map((_, i) => colorPalette[i % colorPalette.length]),
|
||||
borderColor: avatarMap.map((_, i) => borderPalette[i % borderPalette.length]),
|
||||
borderWidth: 1
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
if (mode === 2) {
|
||||
const damageTypesSet = new Set<string>();
|
||||
avatarMap.forEach(({ avatarId }) => {
|
||||
skillHistory.filter(s => s.avatarId === avatarId).forEach(s =>
|
||||
s.damageDetail?.forEach(d => d.damage_type && damageTypesSet.add(transI18n(attackTypeToString(d?.damage_type).toLowerCase())))
|
||||
);
|
||||
});
|
||||
const damageTypes = Array.from(damageTypesSet);
|
||||
|
||||
datasets = damageTypes.map((dt, i) => ({
|
||||
label: dt,
|
||||
data: avatarMap.map(({ avatarId }) => {
|
||||
const total = skillHistory
|
||||
.filter(skill => skill.avatarId === avatarId)
|
||||
.flatMap(s => s.damageDetail || [])
|
||||
.filter(d => transI18n(attackTypeToString(d?.damage_type).toLowerCase()) === dt)
|
||||
.reduce((sum, d) => sum + d.damage, 0);
|
||||
return total;
|
||||
}),
|
||||
backgroundColor: colorPalette[i % colorPalette.length],
|
||||
borderColor: borderPalette[i % borderPalette.length],
|
||||
borderWidth: 1,
|
||||
stack: transI18n('damage')
|
||||
}));
|
||||
}
|
||||
|
||||
if (mode === 3) {
|
||||
const skillTypesSet = new Set<string>();
|
||||
skillHistory.forEach(s => skillTypesSet.add(transI18n(attackTypeToString(s?.skillType)?.toLowerCase())));
|
||||
const skillTypes = Array.from(skillTypesSet);
|
||||
|
||||
datasets = skillTypes.map((st, i) => ({
|
||||
label: st,
|
||||
data: avatarMap.map(({ avatarId }) => {
|
||||
const total = skillHistory
|
||||
.filter(skill => skill.avatarId === avatarId && transI18n(attackTypeToString(skill?.skillType).toLowerCase()) === st)
|
||||
.reduce((sum, s) => sum + s.totalDamage, 0);
|
||||
return total;
|
||||
}),
|
||||
backgroundColor: colorPalette[i % colorPalette.length],
|
||||
borderColor: borderPalette[i % borderPalette.length],
|
||||
borderWidth: 1,
|
||||
stack: transI18n('skill')
|
||||
}));
|
||||
}
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
stacked: mode !== 1,
|
||||
ticks: {
|
||||
precision: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
datalabels: {
|
||||
display: false,
|
||||
x: {
|
||||
stacked: mode !== 1,
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
return <Bar data={data} options={options} />;
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||
{[
|
||||
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||
{ mode: 3, label: `${transI18n("type")} 3`, className: "btn-accent" },
|
||||
].map(({ mode: m, label, className }) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setMode(m)}
|
||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Bar data={{ labels, datasets }} options={options}/>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,36 +2,33 @@
|
||||
import { Bar } from "react-chartjs-2";
|
||||
import { ChartOptions } from "chart.js";
|
||||
import { useDamagePerCycleForAll } from "@/hooks/useDamagePerCycle";
|
||||
|
||||
import { Chart as ChartJS, BarElement, ArcElement, RadialLinearScale } from "chart.js";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
|
||||
ChartJS.register(BarElement, ArcElement, RadialLinearScale);
|
||||
export default function DamagePerCycleForAll({
|
||||
mode,
|
||||
}: {
|
||||
mode: 0 | 1 | 2;
|
||||
}) {
|
||||
export default function DamagePerCycleForAll() {
|
||||
const [mode, setMode] = useState<0 | 1 | 2>(0);
|
||||
const dataRaw = useDamagePerCycleForAll(mode);
|
||||
const transI18n = useTranslations("DataAnalysisPage");
|
||||
const data = {
|
||||
labels: dataRaw.map(d => d.x),
|
||||
datasets: [
|
||||
{
|
||||
label: mode === 0 ? `${transI18n("damagerPerCycle")} (100av)` : mode === 1 ? `${transI18n("damagerPerCycle")} (150av | 100v)` : `${transI18n("damagerPerCycle")} (150av | 150av | 100v)`,
|
||||
label: mode === 0 ? `${transI18n("damagePerCycleAndWave")}` : mode === 1 ? `${transI18n("damagePerCycle")}` : `${transI18n("damagePerWave")}`,
|
||||
data: dataRaw.map(d => d.y),
|
||||
backgroundColor: "rgba(255,99,132,0.6)",
|
||||
backgroundColor: dataRaw.map((_, i) =>
|
||||
['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c', '#f472b6'][i % 7]
|
||||
),
|
||||
},
|
||||
],
|
||||
|
||||
};
|
||||
|
||||
const options: ChartOptions<"bar"> = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: true },
|
||||
datalabels: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: { title: { display: true, text: transI18n("cycleCount") } },
|
||||
@@ -40,5 +37,24 @@ export default function DamagePerCycleForAll({
|
||||
|
||||
};
|
||||
|
||||
return <Bar data={data} options={options} />;
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||
{[
|
||||
{ mode: 0, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||
{ mode: 1, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||
{ mode: 2, label: `${transI18n("type")} 3`, className: "btn-accent" },
|
||||
].map(({ mode: m, label, className }) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setMode(m as 0 | 1 | 2)}
|
||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<Bar data={data} options={options} />;
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,24 +5,26 @@ import { useDamagePerCycleForOne } from "@/hooks/useDamagePerCycle";
|
||||
|
||||
import { Chart as ChartJS, BarElement, ArcElement, RadialLinearScale } from "chart.js";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
|
||||
ChartJS.register(BarElement, ArcElement, RadialLinearScale);
|
||||
export function DamagePerCycleForOne({
|
||||
avatarId,
|
||||
mode,
|
||||
}: {
|
||||
avatarId: number;
|
||||
mode: 0 | 1 | 2;
|
||||
}) {
|
||||
const [mode, setMode] = useState<0 | 1 | 2>(0);
|
||||
const dataRaw = useDamagePerCycleForOne(avatarId, mode);
|
||||
const transI18n = useTranslations("DataAnalysisPage");
|
||||
const data = {
|
||||
labels: dataRaw.map(d => d.x),
|
||||
datasets: [
|
||||
{
|
||||
label: mode === 0 ? `${transI18n("damagerPerCycle")} (100av)` : mode === 1 ? `${transI18n("damagerPerCycle")} (150av | 100v)` : `${transI18n("damagerPerCycle")} (150av | 150av | 100v)`,
|
||||
label: mode === 0 ? `${transI18n("damagePerCycleAndWave")}` : mode === 1 ? `${transI18n("damagePerCycle")}` : `${transI18n("damagePerWave")}`,
|
||||
data: dataRaw.map(d => d.y),
|
||||
backgroundColor: "rgba(255,99,132,0.6)",
|
||||
backgroundColor: dataRaw.map((_, i) =>
|
||||
['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c', '#f472b6'][i % 7]
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -31,9 +33,6 @@ export function DamagePerCycleForOne({
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: true },
|
||||
datalabels: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: { title: { display: true, text: transI18n("cycleCount") } },
|
||||
@@ -42,5 +41,24 @@ export function DamagePerCycleForOne({
|
||||
|
||||
};
|
||||
|
||||
return <Bar data={data} options={options} />;
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||
{[
|
||||
{ mode: 0, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||
{ mode: 1, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||
{ mode: 2, label: `${transI18n("type")} 3`, className: "btn-warning" },
|
||||
].map(({ mode: m, label, className }) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setMode(m as 0 | 1 | 2)}
|
||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<Bar data={data} options={options} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,22 +11,40 @@ import useAvatarDataStore from '@/stores/avatarDataStore';
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import { getNameChar } from '@/helper';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useState } from 'react';
|
||||
import { useDamagePercentByType } from '@/hooks/useDamagePercentForDamgeType';
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip, Legend);
|
||||
|
||||
const colors = ['#f87171', '#34d399', '#60a5fa', '#facc15', '#a78bfa', '#fb923c', '#f472b6'];
|
||||
const colors = [
|
||||
'#f87171', '#34d399', '#60a5fa', '#facc15',
|
||||
'#a78bfa', '#fb923c', '#f472b6', '#10b981',
|
||||
'#fcd34d', '#c084fc', '#f97316', '#ec4899',
|
||||
'#4ade80', '#fbbf24', '#8b5cf6', '#f43f5e'
|
||||
];
|
||||
|
||||
export default function DamagePercentChartForAll() {
|
||||
const data = useDamagePercentPerAvatar();
|
||||
const { listAvatar } = useAvatarDataStore()
|
||||
const [mode, setMode] = useState<1 | 2>(1);
|
||||
const damageByAvatar = useDamagePercentPerAvatar();
|
||||
const damageByType = useDamagePercentByType();
|
||||
const { listAvatar } = useAvatarDataStore();
|
||||
const { locale } = useLocaleStore();
|
||||
const transI18n = useTranslations("DataAnalysisPage");
|
||||
|
||||
const chartData = {
|
||||
labels: data.map(d => getNameChar(locale, listAvatar.find(it => it.id == d.avatarId.toString()))),
|
||||
labels: (mode === 1
|
||||
? damageByAvatar.map(d =>
|
||||
getNameChar(locale, listAvatar.find(it => it.id == d.avatarId.toString()))
|
||||
)
|
||||
: damageByType.map(d => transI18n(d.type.toLowerCase()))
|
||||
),
|
||||
datasets: [
|
||||
{
|
||||
label: '% ' + transI18n("damage"),
|
||||
data: data.map(d => d.percent.toFixed(2)),
|
||||
data: (mode === 1
|
||||
? damageByAvatar.map(d => d.percent.toFixed(2))
|
||||
: damageByType.map(d => d.percent.toFixed(2))
|
||||
),
|
||||
backgroundColor: colors,
|
||||
borderWidth: 1,
|
||||
},
|
||||
@@ -47,5 +65,27 @@ export default function DamagePercentChartForAll() {
|
||||
},
|
||||
};
|
||||
|
||||
return <Pie data={chartData} options={options} />;
|
||||
return (
|
||||
<div className='w-full'>
|
||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||
{[
|
||||
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||
].map(({ mode: m, label, className }) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setMode(m as 1 | 2)}
|
||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Pie data={chartData} options={options} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,58 +1,70 @@
|
||||
'use client';
|
||||
import { getNameChar } from '@/helper';
|
||||
import { useSkillDamageByAvatar } from '@/hooks/useSkillDamageStats';
|
||||
import useAvatarDataStore from '@/stores/avatarDataStore';
|
||||
import useLocaleStore from '@/stores/localeStore';
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
BarElement,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useState } from 'react';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
import { Chart as ChartJS, BarElement, CategoryScale, LinearScale, Tooltip, Legend } from 'chart.js';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useSkillDamageForAvatar } from '@/hooks/useSkillDamageForAvatar';
|
||||
import { useDamageByTypeForAvatar } from '@/hooks/useDamageTypeForAvatar';
|
||||
|
||||
ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend, ChartDataLabels);
|
||||
|
||||
ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend);
|
||||
|
||||
export default function SkillBarChart({ avatarId }: { avatarId: number }) {
|
||||
const { labels, damageValues } = useSkillDamageByAvatar(avatarId);
|
||||
const transI18n = useTranslations("DataAnalysisPage");
|
||||
const colors = ['#f87171', '#facc15', '#34d399', '#60a5fa', '#a78bfa', '#fb923c'];
|
||||
const { listAvatar } = useAvatarDataStore()
|
||||
const { locale } = useLocaleStore();
|
||||
const data = {
|
||||
labels: labels.map(it => transI18n(it.toLowerCase())),
|
||||
datasets: [
|
||||
{
|
||||
label: getNameChar(locale, listAvatar.find(it => it.id === avatarId.toString())),
|
||||
data: damageValues,
|
||||
backgroundColor: colors.slice(0, labels.length),
|
||||
borderColor: '#111',
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
const [mode, setMode] = useState<1 | 2>(2);
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: { precision: 0 },
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
datalabels: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
const transI18n = useTranslations('DataAnalysisPage');
|
||||
const { labels: skillLabels, damageValues: skillDamageValues } = useSkillDamageForAvatar(avatarId);
|
||||
const { labels: typeLabels, damageValues: typeDamageValues } = useDamageByTypeForAvatar(avatarId);
|
||||
|
||||
return <Bar data={data} options={options} />;
|
||||
const colors = ['#f87171', '#facc15', '#34d399', '#60a5fa', '#a78bfa', '#fb923c'];
|
||||
|
||||
const data = {
|
||||
labels: mode === 1 ? skillLabels.map(it => transI18n(it)) : typeLabels.map(it => transI18n(it)),
|
||||
datasets: [
|
||||
{
|
||||
label: mode === 1
|
||||
? transI18n('skill')
|
||||
: transI18n('damage'),
|
||||
data: mode === 1 ? skillDamageValues : typeDamageValues,
|
||||
backgroundColor: colors.slice(0, (mode === 1 ? skillLabels : typeLabels).length),
|
||||
borderColor: '#111',
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: { precision: 0 },
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||
{[
|
||||
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||
].map(({ mode: m, label, className }) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setMode(m as 1 | 2)}
|
||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<Bar data={data} options={options} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,59 +1,75 @@
|
||||
"use client";
|
||||
import { useSkillDamageByAvatar } from '@/hooks/useSkillDamageStats';
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
ArcElement,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
'use client';
|
||||
import { useState } from 'react';
|
||||
import { Pie } from 'react-chartjs-2';
|
||||
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
ArcElement,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useSkillDamageForAvatar } from '@/hooks/useSkillDamageForAvatar';
|
||||
import { useDamageByTypeForAvatar } from '@/hooks/useDamageTypeForAvatar';
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip, Legend, ChartDataLabels);
|
||||
ChartJS.register(ArcElement, Tooltip, Legend);
|
||||
|
||||
const COLORS = [
|
||||
'#f87171', '#facc15', '#34d399', '#60a5fa', '#a78bfa', '#fb923c',
|
||||
'#f472b6', '#38bdf8', '#a3e635', '#fdba74', '#c084fc', '#4ade80',
|
||||
'#fcd34d', '#818cf8', '#f43f5e', '#14b8a6', '#e879f9', '#86efac',
|
||||
];
|
||||
|
||||
export default function SkillPieChart({ avatarId }: { avatarId: number }) {
|
||||
const { labels, damageValues } = useSkillDamageByAvatar(avatarId);
|
||||
const transI18n = useTranslations("DataAnalysisPage");
|
||||
const total = damageValues.reduce((sum, val) => sum + val, 0);
|
||||
const [mode, setMode] = useState<1 | 2>(2);
|
||||
const transI18n = useTranslations("DataAnalysisPage");
|
||||
|
||||
const labelsWithPercent = labels.map((label, index) => {
|
||||
const value = damageValues[index];
|
||||
const percent = total ? ((value / total) * 100).toFixed(1) : '0.0';
|
||||
return `${transI18n(label.toLowerCase())} (${percent}%)`;
|
||||
});
|
||||
const skillData = useSkillDamageForAvatar(avatarId);
|
||||
const typeData = useDamageByTypeForAvatar(avatarId);
|
||||
|
||||
const data = {
|
||||
labels: labelsWithPercent,
|
||||
datasets: [
|
||||
{
|
||||
data: damageValues,
|
||||
backgroundColor: [
|
||||
'#f87171', // red
|
||||
'#facc15', // yellow
|
||||
'#34d399', // green
|
||||
'#60a5fa', // blue
|
||||
'#a78bfa', // purple
|
||||
'#fb923c', // orange
|
||||
],
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
const { labels, damageValues } = mode === 1 ? skillData : typeData;
|
||||
const total = damageValues.reduce((sum, val) => sum + val, 0);
|
||||
|
||||
return (
|
||||
<Pie
|
||||
data={data}
|
||||
options={{
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { position: 'right' },
|
||||
datalabels: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
const labelsWithPercent = labels.map((label, index) => {
|
||||
const value = damageValues[index];
|
||||
const percent = total ? ((value / total) * 100).toFixed(1) : '0.0';
|
||||
return `${transI18n(label.toLowerCase())} (${percent}%)`;
|
||||
});
|
||||
|
||||
const data = {
|
||||
labels: labelsWithPercent,
|
||||
datasets: [
|
||||
{
|
||||
data: damageValues,
|
||||
backgroundColor: COLORS.slice(0, labels.length),
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full ">
|
||||
<div className="mb-4 flex items-start gap-2 justify-end">
|
||||
{[
|
||||
{ mode: 1, label: `${transI18n("type")} 1`, className: "btn-primary" },
|
||||
{ mode: 2, label: `${transI18n("type")} 2`, className: "btn-warning" },
|
||||
].map(({ mode: m, label, className }) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setMode(m as 1 | 2)}
|
||||
className={`btn btn-sm ${mode === m ? className : "btn-ghost"}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<Pie
|
||||
data={data}
|
||||
options={{
|
||||
responsive: true,
|
||||
cutout: '30%',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { connectSocket, disconnectSocket, getSocket, isSocketConnected } from "@
|
||||
import useBattleDataStore from "@/stores/battleDataStore";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import useSocketStore from "@/stores/socketSettingStore";
|
||||
import { BattleDataStateJson } from "@/types/mics";
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
@@ -24,15 +23,7 @@ const themes = [
|
||||
export default function Header() {
|
||||
const { changeTheme } = useChangeTheme()
|
||||
const { locale, setLocale } = useLocaleStore()
|
||||
const {
|
||||
totalAV,
|
||||
totalDamage,
|
||||
damagePerAV,
|
||||
turnHistory,
|
||||
lineup,
|
||||
loadBattleDataFromJSON,
|
||||
} = useBattleDataStore()
|
||||
|
||||
const { loadBattleDataFromJSON } = useBattleDataStore()
|
||||
const router = useRouter()
|
||||
const transI18n = useTranslations("DataAnalysisPage")
|
||||
const { host, port, status, connectionType, setHost, setPort, setStatus, setConnectionType } = useSocketStore();
|
||||
@@ -201,7 +192,7 @@ export default function Header() {
|
||||
<li>
|
||||
<button
|
||||
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
||||
onClick={() => exportBattleData({ totalAV, totalDamage, turnHistory, damagePerAV, lineup } as BattleDataStateJson)}
|
||||
onClick={() => exportBattleData()}
|
||||
>
|
||||
{transI18n("exportData")}
|
||||
</button>
|
||||
@@ -261,7 +252,7 @@ export default function Header() {
|
||||
<li>
|
||||
<button
|
||||
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
|
||||
onClick={() => exportBattleData({ totalAV, totalDamage, turnHistory, damagePerAV, lineup } as BattleDataStateJson)}
|
||||
onClick={() => exportBattleData()}
|
||||
>
|
||||
{transI18n("exportData")}
|
||||
</button>
|
||||
|
||||
+116
-107
@@ -4,7 +4,7 @@ import useBattleDataStore from "@/stores/battleDataStore";
|
||||
import CharacterCard from "../card/characterCard";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState, useEffect } from "react";
|
||||
import { AvatarType } from "@/types";
|
||||
import { AvatarHakushiType } from "@/types";
|
||||
import useLocaleStore from "@/stores/localeStore";
|
||||
import { getNameChar } from '@/helper/getNameChar';
|
||||
import SkillBarChart from "../chart/skillBarChart";
|
||||
@@ -13,19 +13,17 @@ import { motion } from "framer-motion";
|
||||
import { DamageLineForOne } from "../chart/damageLineForOne";
|
||||
import { DamagePerCycleForOne } from "../chart/damagePerCycleForOne";
|
||||
import { useCalcTotalDmgAvatar, useCalcTotalTurnAvatar } from "@/hooks/useCalcAvatarData";
|
||||
import Image from "next/image";
|
||||
// import ShowCaseInfo from "../card/showCaseCard";
|
||||
|
||||
export default function LineupBar() {
|
||||
const [selectedCharacter, setSelectedCharacter] = useState<AvatarType | null>(null);
|
||||
const [selectedCharacter, setSelectedCharacter] = useState<AvatarHakushiType | null>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const transI18n = useTranslations("DataAnalysisPage");
|
||||
const { lineup } = useBattleDataStore();
|
||||
const { lineup, turnHistory, dataAvatar } = useBattleDataStore();
|
||||
const { listAvatar } = useAvatarDataStore();
|
||||
const { locale } = useLocaleStore();
|
||||
const [modeBar, setModeBar] = useState<0 | 1 | 2>(1);
|
||||
const [modeLine, setModeLine] = useState<0 | 1>(1);
|
||||
|
||||
|
||||
const totalDamage = useCalcTotalDmgAvatar(selectedCharacter ? Number(selectedCharacter.id) : 0);
|
||||
const totalTurn = useCalcTotalTurnAvatar(selectedCharacter ? Number(selectedCharacter.id) : 0)
|
||||
|
||||
@@ -34,7 +32,7 @@ export default function LineupBar() {
|
||||
lineup.some(av => av.avatarId.toString() === item.id)
|
||||
);
|
||||
|
||||
const handleShow = (modalId: string, item: AvatarType) => {
|
||||
const handleShow = (modalId: string, item: AvatarHakushiType) => {
|
||||
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
|
||||
if (modal) {
|
||||
setSelectedCharacter(item);
|
||||
@@ -76,62 +74,47 @@ export default function LineupBar() {
|
||||
{transI18n("lineupInfo")}
|
||||
</motion.h2>
|
||||
|
||||
<div className="relative h-full pt-2 max-h-[90vh] border-t-2 border-accent">
|
||||
<div className="flex justify-center mb-2">
|
||||
<div className="badge badge-accent gap-2 text-lg items-center px-4 py-2 whitespace-nowrap max-w-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 shrink-0">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||
</svg>
|
||||
|
||||
<span className="text-sm truncate">{transI18n("lastTurn")}: {getNameChar(locale, listAvatar.find(it => it.id === turnHistory.findLast(i => i?.avatarId)?.avatarId?.toString()))}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="relative h-full pt-2 max-h-[90vh] lg:max-h-[80vh] border-t-2 border-accent">
|
||||
{lineupAvatars.length === 0 ? (
|
||||
<div className="h-full flex justify-center items-start">
|
||||
<p className="text-base-content opacity-50">{transI18n("noCharactersInLineup")}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-full w-full overflow-x-auto md:overflow-x-hidden md:overflow-y-auto custom-scrollbar rounded-lg">
|
||||
<style jsx>{`
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: hsl(var(--p)) hsl(var(--b3));
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: hsl(var(--b3));
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: hsl(var(--p));
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: hsl(var(--pf));
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-button {
|
||||
display: none;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div className="h-full w-full overflow-x-auto md:overflow-x-hidden md:overflow-y-auto rounded-lg">
|
||||
<div className="flex flex-nowrap md:grid md:grid-cols-1 w-fit md:w-full justify-items-center items-start gap-2">
|
||||
{lineupAvatars.map((item, index) => (
|
||||
<motion.div
|
||||
key={item.id}
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3, delay: index * 0.1 }}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
className="cursor-pointer flex-shrink-0 md:w-full justify-items-center"
|
||||
onClick={() => handleShow("character_detail_modal", item)}
|
||||
>
|
||||
<CharacterCard data={item} />
|
||||
</motion.div>
|
||||
))}
|
||||
{lineupAvatars.map((item, index) => {
|
||||
const lastTurnAvatarId = turnHistory.findLast(i => i?.avatarId)?.avatarId || -1;
|
||||
const isLastTurn = item.id === lastTurnAvatarId.toString();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={item.id}
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3, delay: index * 0.1 }}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
onClick={() => handleShow("character_detail_modal", item)}
|
||||
className={`cursor-pointer flex-shrink-0 justify-items-center ${isLastTurn ? "shadow-[inset_0_0_10px_2px_rgba(59,130,246,0.7),_0_0_20px_5px_rgba(59,130,246,0.3)]" : ""
|
||||
}`}
|
||||
>
|
||||
<CharacterCard data={item} />
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)}
|
||||
|
||||
{/* Character Detail Modal */}
|
||||
@@ -165,7 +148,7 @@ export default function LineupBar() {
|
||||
<h4 className="text-lg font-semibold mb-2 text-pink-500">{transI18n("characterInformation")}</h4>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2">
|
||||
<div className="flex flex-col space-y-2 relative">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 justify-items-start items-center gap-6">
|
||||
<p>
|
||||
{transI18n("id")}: <span className="font-bold">{selectedCharacter.id}</span>
|
||||
</p>
|
||||
@@ -173,11 +156,13 @@ export default function LineupBar() {
|
||||
<span>{transI18n("path")}:</span>
|
||||
<span className="font-bold">{transI18n(selectedCharacter.baseType.toLowerCase())}</span>
|
||||
{selectedCharacter.baseType && (
|
||||
<img
|
||||
loading="lazy"
|
||||
<Image
|
||||
|
||||
src={`https://api.hakush.in/hsr/UI/pathicon/${selectedCharacter.baseType.toLowerCase()}.webp`}
|
||||
className="w-6 h-6"
|
||||
alt={selectedCharacter.baseType.toLowerCase()}
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
@@ -186,31 +171,77 @@ export default function LineupBar() {
|
||||
</p>
|
||||
<p className="flex items-center space-x-2">
|
||||
<span>{transI18n("element")}:</span>
|
||||
<span className="font-bold">{transI18n(selectedCharacter.damageType.toLowerCase())}</span>
|
||||
{selectedCharacter.damageType && (
|
||||
<img
|
||||
loading="lazy"
|
||||
src={`https://api.hakush.in/hsr/UI/element/${selectedCharacter.damageType.toLowerCase()}.webp`}
|
||||
className="w-6 h-6"
|
||||
alt={selectedCharacter.damageType.toLowerCase()}
|
||||
/>
|
||||
)}
|
||||
<Image
|
||||
src={`https://api.hakush.in/hsr/UI/element/${selectedCharacter.damageType.toLowerCase()}.webp`}
|
||||
className="w-6 h-6"
|
||||
alt={selectedCharacter.damageType.toLowerCase()}
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
</p>
|
||||
|
||||
{(() => {
|
||||
const avatar = dataAvatar.find(it => it.avatar_id.toString() === selectedCharacter.id);
|
||||
if (!avatar) return null;
|
||||
const relicIds = avatar.relics.map(item => {
|
||||
const relicIdStr = item.relic_id.toString().slice(1);
|
||||
const slot = relicIdStr.slice(-1);
|
||||
return `${item.relic_set_id}_${slot}`;
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
{transI18n("level")}: <span className="font-bold">{avatar.level}</span>
|
||||
</p>
|
||||
<p>
|
||||
{transI18n("eidolons")}: <span className="font-bold">{avatar?.data?.rank}</span>
|
||||
</p>
|
||||
<p className="flex items-center space-x-2">
|
||||
<span>{transI18n("lightcones")}:</span>
|
||||
<Image
|
||||
src={`https://api.hakush.in/hsr/UI/lightconemediumicon/${avatar?.Lightcone?.item_id}.webp`}
|
||||
className="w-12 h-12"
|
||||
alt={avatar?.Lightcone?.item_id?.toString() || ""}
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
</p>
|
||||
<p className="flex items-center space-x-2 w-full">
|
||||
<span>{transI18n("relics")}:</span>
|
||||
<div className="grid grid-cols-3 md:flex md:flex-row w-full">
|
||||
{relicIds.map(it => (
|
||||
<Image
|
||||
key={it}
|
||||
src={`https://api.hakush.in/hsr/UI/relicfigures/IconRelic_${it}.webp`}
|
||||
className="w-12 h-12"
|
||||
alt={avatar?.Lightcone?.item_id?.toString() || ""}
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center items-center">
|
||||
{selectedCharacter && (
|
||||
<img
|
||||
src={`https://api.hakush.in/hsr/UI/avatarshopicon/${selectedCharacter.id}.webp`}
|
||||
alt={getNameChar(locale, selectedCharacter)}
|
||||
className="h-32 w-32 object-cover rounded-full border-2 border-purple-500"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Image
|
||||
src={`https://api.hakush.in/hsr/UI/avatarshopicon/${selectedCharacter.id}.webp`}
|
||||
alt={getNameChar(locale, selectedCharacter)}
|
||||
className="h-32 w-32 object-cover rounded-full border-2 border-purple-500"
|
||||
width={128}
|
||||
height={128}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="md:col-span-2 bg-base-200 rounded-lg p-4 shadow-md">
|
||||
<h4 className="text-lg font-semibold mb-2 text-pink-500">{transI18n("characterInformation")}</h4>
|
||||
<ShowCaseInfo></ShowCaseInfo>
|
||||
</div> */}
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
||||
<p className="mt-2 font-bold text-lg text-cyan-500">{transI18n("totalTurn")}: <span className="text-base-content">{totalTurn.toFixed(2)}</span></p>
|
||||
</div>
|
||||
@@ -218,52 +249,30 @@ export default function LineupBar() {
|
||||
<h4 className="text-lg font-semibold mb-2 text-purple-500">{transI18n("totalDamage")}: <span className="text-base-content">{totalDamage.toFixed(2)}</span></h4>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md max-h-11/12">
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
||||
<h4 className="text-lg font-semibold mb-4 text-purple-500">{transI18n("skillDamageBreakdown")}</h4>
|
||||
<SkillBarChart avatarId={Number(selectedCharacter.id) ?? 0} />
|
||||
</div>
|
||||
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md max-h-11/12">
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h4 className="text-lg font-semibold mb-4 text-cyan-500">{transI18n("damageOverTime")}</h4>
|
||||
<div className="flex gap-2">
|
||||
{[0, 1].map((m) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setModeLine(m as 0 | 1)}
|
||||
className={`btn btn-sm ${modeLine === m ? "btn-accent" : "btn-ghost"}`}
|
||||
>
|
||||
{transI18n("type")} {m}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<DamageLineForOne avatarId={Number(selectedCharacter.id) ?? 0} mode={modeLine} />
|
||||
<DamageLineForOne avatarId={Number(selectedCharacter.id) ?? 0} />
|
||||
</div>
|
||||
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md max-h-11/12">
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
||||
<h4 className="text-lg font-semibold mb-4 text-cyan-500">{transI18n("skillUsageDistribution")}</h4>
|
||||
<SkillPieChart avatarId={Number(selectedCharacter.id) ?? 0} />
|
||||
</div>
|
||||
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md max-h-11/12">
|
||||
<div className="bg-base-200 rounded-lg p-4 shadow-md">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h4 className="text-lg font-semibold text-purple-500">{transI18n("damagerPerCycle")}</h4>
|
||||
<div className="flex gap-2">
|
||||
{[0, 1, 2].map((m) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setModeBar(m as 0 | 1 | 2)}
|
||||
className={`btn btn-sm ${modeBar === m ? "btn-accent" : "btn-ghost"}`}
|
||||
>
|
||||
{transI18n("type")} {m}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<h4 className="text-lg font-semibold text-purple-500">{transI18n("damagePerCycle")}</h4>
|
||||
</div>
|
||||
<DamagePerCycleForOne avatarId={Number(selectedCharacter.id) ?? 0} mode={modeBar} />
|
||||
<DamagePerCycleForOne avatarId={Number(selectedCharacter.id) ?? 0} />
|
||||
</div>
|
||||
|
||||
</motion.div>
|
||||
|
||||
Reference in New Issue
Block a user