UPDATE: new data
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m8s
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m8s
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
"rogue": "狩猎",
|
||||
"shaman": "同谐",
|
||||
"warlock": "虚无",
|
||||
"elation": "欢愉",
|
||||
"memory": "记忆",
|
||||
"fire": "火",
|
||||
"ice": "冰",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"shaman": "Harmony",
|
||||
"warlock": "Nihility",
|
||||
"memory": "Remembrance",
|
||||
"elation": "The Elation",
|
||||
"fire": "Fire",
|
||||
"ice": "Ice",
|
||||
"imaginary": "Imaginary",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"shaman": "調和",
|
||||
"warlock": "虚無",
|
||||
"memory": "記憶",
|
||||
"elation": "愉悦",
|
||||
"fire": "火",
|
||||
"ice": "氷",
|
||||
"imaginary": "虚数",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"shaman": "조화",
|
||||
"warlock": "허무",
|
||||
"memory": "기억",
|
||||
"elation": "환락",
|
||||
"fire": "화염",
|
||||
"ice": "얼음",
|
||||
"imaginary": "환상",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"shaman": "Hòa Hợp",
|
||||
"warlock": "Hư Vô",
|
||||
"memory": "Ký Ức",
|
||||
"elation": "Vui vẻ",
|
||||
"fire": "Hỏa",
|
||||
"ice": "Băng",
|
||||
"imaginary": "Ảo Ảnh",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"shaman": "同谐",
|
||||
"warlock": "虚无",
|
||||
"memory": "记忆",
|
||||
"elation": "欢愉",
|
||||
"fire": "火",
|
||||
"ice": "冰",
|
||||
"imaginary": "虚数",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -3318,7 +3318,7 @@
|
||||
]
|
||||
},
|
||||
"23050": {
|
||||
"0": [
|
||||
"0": [
|
||||
{
|
||||
"type": "BreakDamageAddedRatioBase",
|
||||
"value": 0.6000000238418579
|
||||
@@ -3348,5 +3348,136 @@
|
||||
"value": 1.2
|
||||
}
|
||||
]
|
||||
},
|
||||
"20023": {},
|
||||
"20024": {},
|
||||
"21064": {
|
||||
"0": [
|
||||
{
|
||||
"type": "ElationDamageAddedRatioBase",
|
||||
"value": 0.12
|
||||
}
|
||||
],
|
||||
"1": [
|
||||
{
|
||||
"type": "ElationDamageAddedRatioBase",
|
||||
"value": 0.14
|
||||
}
|
||||
],
|
||||
"2": [
|
||||
{
|
||||
"type": "ElationDamageAddedRatioBase",
|
||||
"value": 0.16
|
||||
}
|
||||
],
|
||||
"3": [
|
||||
{
|
||||
"type": "ElationDamageAddedRatioBase",
|
||||
"value": 0.18
|
||||
}
|
||||
],
|
||||
"4": [
|
||||
{
|
||||
"type": "ElationDamageAddedRatioBase",
|
||||
"value": 0.2
|
||||
}
|
||||
]
|
||||
},
|
||||
"21065": {
|
||||
"0": [
|
||||
{
|
||||
"type": "CriticalChanceBase",
|
||||
"value": 0.12
|
||||
}
|
||||
],
|
||||
"1": [
|
||||
{
|
||||
"type": "CriticalChanceBase",
|
||||
"value": 0.14
|
||||
}
|
||||
],
|
||||
"2": [
|
||||
{
|
||||
"type": "CriticalChanceBase",
|
||||
"value": 0.16
|
||||
}
|
||||
],
|
||||
"3": [
|
||||
{
|
||||
"type": "CriticalChanceBase",
|
||||
"value": 0.18
|
||||
}
|
||||
],
|
||||
"4": [
|
||||
{
|
||||
"type": "CriticalChanceBase",
|
||||
"value": 0.2
|
||||
}
|
||||
]
|
||||
},
|
||||
"23053": {
|
||||
"0": [
|
||||
{
|
||||
"type": "CriticalDamageBase",
|
||||
"value": 0.36000001430511475
|
||||
}
|
||||
],
|
||||
"1": [
|
||||
{
|
||||
"type": "CriticalDamageBase",
|
||||
"value": 0.41999998688697815
|
||||
}
|
||||
],
|
||||
"2": [
|
||||
{
|
||||
"type": "CriticalDamageBase",
|
||||
"value": 0.47999998927116394
|
||||
}
|
||||
],
|
||||
"3": [
|
||||
{
|
||||
"type": "CriticalDamageBase",
|
||||
"value": 0.5400000214576721
|
||||
}
|
||||
],
|
||||
"4": [
|
||||
{
|
||||
"type": "CriticalDamageBase",
|
||||
"value": 0.6000000238418579
|
||||
}
|
||||
]
|
||||
},
|
||||
"23054": {
|
||||
"0": [
|
||||
{
|
||||
"type": "SpeedAddedRatio",
|
||||
"value": 0.18000000715255737
|
||||
}
|
||||
],
|
||||
"1": [
|
||||
{
|
||||
"type": "SpeedAddedRatio",
|
||||
"value": 0.20999999344348907
|
||||
}
|
||||
],
|
||||
"2": [
|
||||
{
|
||||
"type": "SpeedAddedRatio",
|
||||
"value": 0.23999999463558197
|
||||
}
|
||||
],
|
||||
"3": [
|
||||
{
|
||||
"type": "SpeedAddedRatio",
|
||||
"value": 0.27000001072883606
|
||||
}
|
||||
],
|
||||
"4": [
|
||||
{
|
||||
"type": "SpeedAddedRatio",
|
||||
"value": 0.30000001192092896
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -670,5 +670,21 @@
|
||||
"https://homdgcat.wiki/images/skillicons/avatar/1321/SkillIcon_1321_Rank4.png",
|
||||
"https://homdgcat.wiki/images/skillicons/avatar/1321/SkillIcon_1321_BP.png",
|
||||
"https://homdgcat.wiki/images/skillicons/avatar/1321/SkillIcon_1321_Rank6.png"
|
||||
],
|
||||
"1501": [
|
||||
"https://starrail.honeyhunterworld.com/img/eidolon/goingviral-whoisshe-eidolon_icon_small.webp",
|
||||
"https://starrail.honeyhunterworld.com/img/eidolon/audienceknows-eidolon_icon_small.webp",
|
||||
"https://starrail.honeyhunterworld.com/img/eidolon/linkup-heartskip-eidolon_icon_small.webp",
|
||||
"https://starrail.honeyhunterworld.com/img/eidolon/lockedin-facecard-eidolon_icon_small.webp",
|
||||
"https://starrail.honeyhunterworld.com/img/eidolon/healingtheworld-goodvibesonly-eidolon_icon_small.webp",
|
||||
"https://starrail.honeyhunterworld.com/img/eidolon/builtdifferent-goingextinct-eidolon_icon_small.webp"
|
||||
],
|
||||
"1502": [
|
||||
"https://starrail.honeyhunterworld.com/img/eidolon/chuckle-chimes-where-jade-falls-eidolon_icon_small.webp",
|
||||
"https://starrail.honeyhunterworld.com/img/eidolon/blind-arrows-guided-by-feathers-eidolon_icon_small.webp",
|
||||
"https://starrail.honeyhunterworld.com/img/eidolon/auspices-mirrored-in-decalight-eidolon_icon_small.webp",
|
||||
"https://starrail.honeyhunterworld.com/img/eidolon/threads-of-fate-colored-by-plumes-eidolon_icon_small.webp",
|
||||
"https://starrail.honeyhunterworld.com/img/eidolon/bejeweled-in-radiant-grace-eidolon_icon_small.webp",
|
||||
"https://starrail.honeyhunterworld.com/img/eidolon/ferried-along-the-astral-arc-eidolon_icon_small.webp"
|
||||
]
|
||||
}
|
||||
@@ -447,5 +447,21 @@
|
||||
"value": 0.1599999964237213
|
||||
}
|
||||
]
|
||||
},
|
||||
"130": {
|
||||
"2": [
|
||||
{
|
||||
"type": "SpeedAddedRatio",
|
||||
"value": 0.05999999865889549
|
||||
}
|
||||
]
|
||||
},
|
||||
"129": {
|
||||
"2": [
|
||||
{
|
||||
"type": "CriticalDamageBase",
|
||||
"value": 0.1599999964237213
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
public/icon/IconJoy.webp
Normal file
BIN
public/icon/IconJoy.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icon/elation.webp
Normal file
BIN
public/icon/elation.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -56,14 +56,14 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
|
||||
}}>
|
||||
<Image src={ `/icon/${key}.webp`}
|
||||
alt={key}
|
||||
className="h-[28px] w-[28px] 2xl:h-[40px] 2xl:w-[40px] object-contain rounded-md"
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md"
|
||||
width={200}
|
||||
height={200} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-8 sm:grid-cols-4 lg:grid-cols-8 mb-1 mx-1 gap-2 overflow-y-auto w-full max-h-[17vh] min-h-[5vh]">
|
||||
<div className="grid grid-cols-9 sm:grid-cols-5 lg:grid-cols-9 mb-1 mx-1 gap-2 overflow-y-auto w-full max-h-[17vh] min-h-[5vh]">
|
||||
{Object.keys(listPath).map((key, index) => (
|
||||
<div
|
||||
key={index}
|
||||
@@ -78,7 +78,7 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
|
||||
|
||||
<Image src={`/icon/${key}.webp`}
|
||||
alt={key}
|
||||
className="h-[28px] w-[28px] 2xl:h-[40px] 2xl:w-[40px] object-contain rounded-md"
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md"
|
||||
width={200}
|
||||
height={200} />
|
||||
</div>
|
||||
|
||||
@@ -149,14 +149,14 @@ export default function CopyImport() {
|
||||
onClick={() => {
|
||||
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"
|
||||
className="w-12.5 h-12.5 hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer"
|
||||
style={{
|
||||
backgroundColor: listPath[key] ? "#374151" : "#6B7280"
|
||||
}}>
|
||||
<Image
|
||||
src={`/icon/${key}.webp`}
|
||||
alt={key}
|
||||
className="h-[32px] w-[32px] object-contain rounded-md"
|
||||
className="h-8 w-8 object-contain rounded-md"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
@@ -174,14 +174,14 @@ export default function CopyImport() {
|
||||
onClick={() => {
|
||||
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"
|
||||
className="w-12.5 h-12.5 hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer"
|
||||
style={{
|
||||
backgroundColor: listElement[key] ? "#374151" : "#6B7280"
|
||||
}}>
|
||||
<Image
|
||||
src={`/icon/${key}.webp`}
|
||||
alt={key}
|
||||
className="h-[28px] w-[28px] 2xl:h-[40px] 2xl:w-[40px] object-contain rounded-md"
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
@@ -199,11 +199,11 @@ export default function CopyImport() {
|
||||
onClick={() => {
|
||||
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"
|
||||
className="w-12.5 h-12.5 hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer"
|
||||
style={{
|
||||
backgroundColor: listRank[key] ? "#374151" : "#6B7280"
|
||||
}}>
|
||||
<div className="font-bold text-white h-[32px] w-[32px] text-center flex items-center justify-center">{key}*</div>
|
||||
<div className="font-bold text-white h-8 w-8 text-center flex items-center justify-center">{key}*</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function LightconeBar() {
|
||||
const transI18n = useTranslations("DataPage")
|
||||
|
||||
useEffect(() => {
|
||||
const newListPath: Record<string, boolean> = { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false }
|
||||
const newListPath: Record<string, boolean> = { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false, "elation": false }
|
||||
const newListRank: Record<string, boolean> = { "3": false, "4": false, "5": false }
|
||||
for (const path of defaultFilter.path) {
|
||||
if (path in newListPath) {
|
||||
@@ -73,7 +73,6 @@ export default function LightconeBar() {
|
||||
<div className="flex items-start flex-col gap-2 mt-2 w-full">
|
||||
<div>Filter</div>
|
||||
<div className="flex flex-row flex-wrap justify-between mt-1 w-full">
|
||||
{/* Nhóm 1 - Path icons */}
|
||||
<div className="flex flex-wrap mb-1 mx-1 gap-2">
|
||||
{Object.keys(listPath).map((key, index) => (
|
||||
<div
|
||||
@@ -81,7 +80,7 @@ export default function LightconeBar() {
|
||||
onClick={() => {
|
||||
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"
|
||||
className="h-9.5 w-9.5 md:h-12.5 md:w-12.5 hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer"
|
||||
style={{
|
||||
backgroundColor: listPath[key] ? "#374151" : "#6B7280"
|
||||
}}
|
||||
@@ -89,7 +88,7 @@ export default function LightconeBar() {
|
||||
<Image
|
||||
src={`/icon/${key}.webp`}
|
||||
alt={key}
|
||||
className="h-[28px] w-[28px] md:h-[32px] md:w-[32px] object-contain rounded-md"
|
||||
className="h-7 w-7 md:h-8 md:w-8 object-contain rounded-md"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
@@ -97,7 +96,6 @@ export default function LightconeBar() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Nhóm 2 - Rank icons */}
|
||||
<div className="flex flex-wrap mb-1 mx-1 gap-2">
|
||||
{Object.keys(listRank).map((key, index) => (
|
||||
<div
|
||||
@@ -105,12 +103,12 @@ export default function LightconeBar() {
|
||||
onClick={() => {
|
||||
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"
|
||||
className="h-9.5 w-9.5 md:h-12.5 md:w-12.5 hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer"
|
||||
style={{
|
||||
backgroundColor: listRank[key] ? "#374151" : "#6B7280"
|
||||
}}
|
||||
>
|
||||
<div className="font-bold text-white h-[32px] w-[32px] text-center flex items-center justify-center">
|
||||
<div className="font-bold text-white h-8 w-8 text-center flex items-center justify-center">
|
||||
{key}*
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -258,7 +258,7 @@ export default function AsBar() {
|
||||
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden flex-shrink-0 border border-white/10 shadow-sm">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{listMonster.find((monster) => monster.child.includes(waveValue))?.icon && <Image
|
||||
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster) => monster.child.includes(waveValue))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
|
||||
alt="Enemy Icon"
|
||||
@@ -277,7 +277,7 @@ export default function AsBar() {
|
||||
<Image
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-[28px] w-[28px] 2xl:h-[40px] 2xl:w-[40px] object-contain rounded-md border border-white/20 shadow-sm"
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
@@ -308,7 +308,7 @@ export default function AsBar() {
|
||||
>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden flex-shrink-0 border border-white/10 shadow-sm">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{listMonster.find((monster) => monster.child.includes(waveValue))?.icon && <Image
|
||||
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster) => monster.child.includes(waveValue))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
|
||||
alt="Enemy Icon"
|
||||
@@ -327,7 +327,7 @@ export default function AsBar() {
|
||||
<Image
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-[28px] w-[28px] 2xl:h-[40px] 2xl:w-[40px] object-contain rounded-md border border-white/20 shadow-sm"
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
|
||||
@@ -355,7 +355,7 @@ export default function CeBar() {
|
||||
<Image
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-[28px] w-[28px] 2xl:h-[40px] 2xl:w-[40px] object-contain rounded-md border border-white/20 shadow-sm"
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
@@ -482,7 +482,7 @@ export default function CeBar() {
|
||||
setShowSearchWaveId(null)
|
||||
}}
|
||||
>
|
||||
<div className="relative w-8 h-8 rounded-full overflow-hidden flex-shrink-0 border border-white/10 shadow-sm">
|
||||
<div className="relative w-8 h-8 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{listMonsterDetail.find((monster2) => monster2.id === monster.id)?.icon?.split("/")?.pop()?.replace(".png", "") && (
|
||||
<Image
|
||||
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonsterDetail.find((monster2) => monster2.id === monster.id)?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
|
||||
|
||||
@@ -238,7 +238,7 @@ export default function MocBar() {
|
||||
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden flex-shrink-0 border border-white/10 shadow-sm">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{listMonster.find((monster) => monster.child.includes(waveValue))?.icon && <Image
|
||||
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster) => monster.child.includes(waveValue))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
|
||||
alt="Enemy Icon"
|
||||
@@ -257,7 +257,7 @@ export default function MocBar() {
|
||||
<Image
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-[28px] w-[28px] 2xl:h-[40px] 2xl:w-[40px] object-contain rounded-md border border-white/20 shadow-sm"
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
@@ -288,7 +288,7 @@ export default function MocBar() {
|
||||
>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden flex-shrink-0 border border-white/10 shadow-sm">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{listMonster.find((monster) => monster.child.includes(waveValue))?.icon && <Image
|
||||
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster) => monster.child.includes(waveValue))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
|
||||
alt="Enemy Icon"
|
||||
@@ -307,7 +307,7 @@ export default function MocBar() {
|
||||
<Image
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-[28px] w-[28px] 2xl:h-[40px] 2xl:w-[40px] object-contain rounded-md border border-white/20 shadow-sm"
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
|
||||
@@ -233,7 +233,7 @@ export default function PeakBar() {
|
||||
>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden flex-shrink-0 border border-white/10 shadow-sm">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{listMonster.find((monster) => monster.child.includes(monsterId))?.icon && <Image
|
||||
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster) => monster.child.includes(monsterId))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
|
||||
alt="Enemy Icon"
|
||||
@@ -252,7 +252,7 @@ export default function PeakBar() {
|
||||
<Image
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-[28px] w-[28px] 2xl:h-[40px] 2xl:w-[40px] object-contain rounded-md border border-white/20 shadow-sm"
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
|
||||
@@ -247,7 +247,7 @@ export default function PfBar() {
|
||||
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden flex-shrink-0 border border-white/10 shadow-sm">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{listMonster.find((monster) => monster.child.includes(monsterId))?.icon && <Image
|
||||
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster) => monster.child.includes(monsterId))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
|
||||
alt="Enemy Icon"
|
||||
@@ -266,7 +266,7 @@ export default function PfBar() {
|
||||
<Image
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-[28px] w-[28px] 2xl:h-[40px] 2xl:w-[40px] object-contain rounded-md border border-white/20 shadow-sm"
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
@@ -297,7 +297,7 @@ export default function PfBar() {
|
||||
>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden flex-shrink-0 border border-white/10 shadow-sm">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
|
||||
{listMonster.find((monster) => monster.child.includes(monsterId))?.icon && <Image
|
||||
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listMonster.find((monster) => monster.child.includes(monsterId))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
|
||||
alt="Enemy Icon"
|
||||
@@ -316,7 +316,7 @@ export default function PfBar() {
|
||||
<Image
|
||||
src={`/icon/${icon.toLowerCase()}.webp`}
|
||||
alt={icon}
|
||||
className="h-[28px] w-[28px] 2xl:h-[40px] 2xl:w-[40px] object-contain rounded-md border border-white/20 shadow-sm"
|
||||
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
|
||||
width={200}
|
||||
height={200}
|
||||
key={iconIndex}
|
||||
|
||||
@@ -403,7 +403,7 @@ export default function QuickView() {
|
||||
<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" />
|
||||
<div className="font-bold">{stat.name}</div>
|
||||
</div>
|
||||
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
|
||||
<div className="ml-3 mr-3 grow border rounded opacity-50" />
|
||||
<div className="flex cursor-default flex-col text-right font-bold">{
|
||||
stat.value ? stat.unit === "%" ? (stat.value * 100).toFixed(stat.round) : stat.value.toFixed(stat.round) : 0
|
||||
}{stat.unit}</div>
|
||||
|
||||
@@ -367,6 +367,14 @@ export default function ShowCaseInfo() {
|
||||
unit: "%",
|
||||
round: 1
|
||||
},
|
||||
ElationAdd: {
|
||||
value: 0,
|
||||
base: 0,
|
||||
name: "Elation Boost",
|
||||
icon: "/icon/IconJoy.webp",
|
||||
unit: "%",
|
||||
round: 1
|
||||
}
|
||||
}
|
||||
|
||||
if (avatarProfile?.lightcone && mapLightconeInfo[avatarProfile?.lightcone?.item_id]) {
|
||||
@@ -499,10 +507,7 @@ export default function ShowCaseInfo() {
|
||||
const getImageSkill = useCallback((icon: string | undefined, status: StatusAddType | undefined) => {
|
||||
if (!icon) return
|
||||
if (icon.startsWith("SkillIcon")) {
|
||||
if (Number(avatarSelected?.id) > 8000 && Number(avatarSelected?.id) % 2 === 0) {
|
||||
return `https://homdgcat.wiki/images/skillicons/avatar/${Number(avatarSelected?.id) - 1}/${icon.replaceAll(avatarSelected?.id || "", (Number(avatarSelected?.id) - 1).toString())}`
|
||||
}
|
||||
return `https://homdgcat.wiki/images/skillicons/avatar/${avatarSelected?.id}/${icon}`
|
||||
return `https://api.hakush.in/hsr/UI/skillicons/${icon.replace(".png", ".webp")}`
|
||||
} else if (status && mappingStats[status.PropertyType]) {
|
||||
return mappingStats[status.PropertyType].icon
|
||||
}
|
||||
@@ -510,7 +515,7 @@ export default function ShowCaseInfo() {
|
||||
return `https://api.hakush.in/hsr/UI/trace/${icon.replace(".png", ".webp")}`
|
||||
}
|
||||
return ""
|
||||
}, [avatarSelected])
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-start m-1 text-white">
|
||||
@@ -521,7 +526,7 @@ export default function ShowCaseInfo() {
|
||||
<div className="overflow-auto">
|
||||
<div
|
||||
ref={cardRef}
|
||||
className=" relative min-h-[650px] w-[1600px] rounded-3xl transition-all duration-500 overflow-hidden"
|
||||
className=" relative min-h-162.5 w-400 rounded-3xl transition-all duration-500 overflow-hidden"
|
||||
style={{
|
||||
backgroundColor: `${applyBrightness(avgColor, 0.3)}`,
|
||||
backdropFilter: "blur(50px)",
|
||||
@@ -533,7 +538,7 @@ export default function ShowCaseInfo() {
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<div
|
||||
className="relative min-h-[650px] w-[24%]"
|
||||
className="relative min-h-162.5 w-[24%]"
|
||||
>
|
||||
<div className="flex justify-center items-center w-full h-full overflow-hidden">
|
||||
{avatarSelected && (
|
||||
@@ -556,7 +561,7 @@ export default function ShowCaseInfo() {
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="relative flex min-h-[650px] w-[76%] flex-row items-center gap-3.5 rounded-3xl pl-10 z-10 transition-all duration-500"
|
||||
className="relative flex min-h-162.5 w-[76%] flex-row items-center gap-3.5 rounded-3xl pl-10 z-10 transition-all duration-500"
|
||||
style={{
|
||||
backgroundColor: `${applyBrightness(avgColor, 0.5)}`,
|
||||
backdropFilter: "blur(50px)",
|
||||
@@ -577,7 +582,7 @@ export default function ShowCaseInfo() {
|
||||
alt="Rank Icon"
|
||||
width={50}
|
||||
height={50}
|
||||
className="h-auto w-12 transition-all duration-300 ease-in-out p-[1px] rounded-full"
|
||||
className="h-auto w-12 transition-all duration-300 ease-in-out p-px rounded-full"
|
||||
style={{
|
||||
opacity: isActive ? 1 : 0.6,
|
||||
filter: isActive
|
||||
@@ -596,7 +601,7 @@ export default function ShowCaseInfo() {
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex h-[650px] w-1/3 flex-col justify-between py-3 pl-8">
|
||||
<div className="flex h-162.5 w-1/3 flex-col justify-between py-3 pl-8">
|
||||
<div className="flex h-full flex-col justify-between">
|
||||
<div>
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
@@ -619,7 +624,7 @@ export default function ShowCaseInfo() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative flex h-[225px] w-auto flex-row items-center">
|
||||
<div className="relative flex h-56.25 w-auto flex-row items-center">
|
||||
{avatarSelected && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<NextImage src={`/icon/${avatarSelected?.baseType.toLowerCase()}.webp`} alt="Path Icon" width={160} height={160} className="h-40 w-40 opacity-20" />
|
||||
@@ -695,7 +700,7 @@ export default function ShowCaseInfo() {
|
||||
</div>
|
||||
|
||||
{btn.isLink && idx < item.length - 1 && (
|
||||
<div className="w-3 h-[3px] bg-white opacity-80 mx-1" />
|
||||
<div className="w-3 h-0.75 bg-white opacity-80 mx-1" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -855,8 +860,8 @@ export default function ShowCaseInfo() {
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex h-[650px] w-1/3 flex-col justify-between py-3 z-10">
|
||||
<div className="flex w-full flex-col justify-between gap-y-0.5 text-base h-[500px]">
|
||||
<div className="flex h-162.5 w-1/3 flex-col justify-between py-3 z-10">
|
||||
<div className="flex w-full flex-col justify-between gap-y-0.5 text-base h-125">
|
||||
{Object.entries(characterStats || {})?.map(([key, stat], index) => {
|
||||
if (!stat || (key.includes("Add") && stat.value === 0)) return null
|
||||
return (
|
||||
@@ -865,7 +870,7 @@ export default function ShowCaseInfo() {
|
||||
<NextImage src={stat?.icon || ""} alt="Stat Icon" width={40} height={40} className="h-auto w-10 p-2" />
|
||||
<span className="font-bold">{stat.name}</span>
|
||||
</div>
|
||||
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
|
||||
<div className="ml-3 mr-3 grow border rounded opacity-50" />
|
||||
<div className="flex cursor-default flex-col text-right font-bold">{
|
||||
stat.value ? stat.unit === "%" ? (stat.value * 100).toFixed(stat.round) : stat.value.toFixed(stat.round) : 0
|
||||
}{stat.unit}</div>
|
||||
@@ -903,7 +908,7 @@ export default function ShowCaseInfo() {
|
||||
</div>
|
||||
|
||||
<div className="w-1/3 z-10">
|
||||
<div className="flex h-[650px] flex-col justify-between py-3 mr-1 text-lg w-full" >
|
||||
<div className="flex h-162.5 flex-col justify-between py-3 mr-1 text-lg w-full" >
|
||||
|
||||
{relicStats?.map((relic, index) => {
|
||||
if (!relic || !avatarInfo) return null
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function RelicShowcase({
|
||||
width={78}
|
||||
height={78}
|
||||
alt="Relic Icon"
|
||||
className="h-auto w-[78px] rounded-lg"
|
||||
className="h-auto w-19.5 rounded-lg"
|
||||
/>
|
||||
|
||||
<div
|
||||
@@ -50,7 +50,7 @@ export default function RelicShowcase({
|
||||
width={35}
|
||||
height={35}
|
||||
alt="Main Affix Icon"
|
||||
className="h-auto w-[35px]"
|
||||
className="h-auto w-8.75"
|
||||
/>
|
||||
</div>
|
||||
<span className="text-base text-yellow-400 font-semibold drop-shadow-[0_0_4px_rgba(251,191,36,0.5)]">
|
||||
@@ -75,10 +75,10 @@ export default function RelicShowcase({
|
||||
width={32}
|
||||
height={32}
|
||||
alt="Sub Affix Icon"
|
||||
className="h-auto w-6 flex-shrink-0"
|
||||
className="h-auto w-6 shrink-0"
|
||||
/>
|
||||
) : (
|
||||
<div className="h-6 w-6 bg-black/60 rounded flex items-center justify-center border border-white/10 flex-shrink-0">
|
||||
<div className="h-6 w-6 bg-black/60 rounded flex items-center justify-center border border-white/10 shrink-0">
|
||||
<span className="text-xs text-white/50">?</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -87,7 +87,7 @@ export default function RelicShowcase({
|
||||
</span>
|
||||
{
|
||||
(avatarInfo?.Relics?.SubAffixPropertyList.findIndex((item) => item === subAffix?.property) !== -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">
|
||||
<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 shrink-0 leading-none">
|
||||
{subAffix?.count}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -163,6 +163,9 @@ export default function SkillsInfo() {
|
||||
filter: (theme === "winter" || theme === "cupcake") ? "invert(1)" : "none"
|
||||
}}
|
||||
className={`w-full h-full object-cover rounded-xl`}
|
||||
onError={(e) => {
|
||||
e.currentTarget.style.display = "none"
|
||||
}}
|
||||
/>
|
||||
{traceButtons.map((btn, index) => {
|
||||
if (!avatarInfo?.SkillTrees?.[btn.id]) {
|
||||
@@ -266,6 +269,12 @@ export default function SkillsInfo() {
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!traceButtons && avatarInfo && (
|
||||
<div className="flex flex-col relative w-full aspect-square">
|
||||
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -147,6 +147,12 @@ export const mappingStats = <Record<string, {name: string, icon: string, unit: s
|
||||
unit: "%",
|
||||
baseStat: "ImaginaryAdd"
|
||||
},
|
||||
"ElationDamageAddedRatioBase": {
|
||||
name:"Elation DMG Boost",
|
||||
icon:"/icon/IconJoy.webp",
|
||||
unit: "%",
|
||||
baseStat: "ElationAdd"
|
||||
},
|
||||
"SPRatioBase": {
|
||||
name:"Energy Regeneration Rate",
|
||||
icon:"/icon/energy-rate.webp",
|
||||
|
||||
@@ -163,7 +163,27 @@ export const traceButtonsInfo: Record<string, { id: string, size: string, left:
|
||||
{ id: 'Point10', size: 'small', left: '13%', top: '40%' },
|
||||
{ id: 'Point11', size: 'small', left: '13%', top: '63%' },
|
||||
{ id: 'Point21', size: 'special', left: '70%', top: '34%'}
|
||||
]
|
||||
],
|
||||
Elation: [
|
||||
{ id: 'Point03', size: 'big', left: '50%', top: '52%' },
|
||||
{ id: 'Point04', size: 'big', left: '50%', top: '35%' },
|
||||
{ id: 'Point02', size: 'big', left: '69%', top: '48%' },
|
||||
{ id: 'Point05', size: 'big', left: '50%', top: '69%' },
|
||||
{ id: 'Point01', size: 'big', left: '33%', top: '48%' },
|
||||
{ id: 'Point08', size: 'medium', left: '50%', top: '22%' },
|
||||
{ id: 'Point07', size: 'medium', left: '67%', top: '83%' },
|
||||
{ id: 'Point06', size: 'medium', left: '33%', top: '83%' },
|
||||
{ id: 'Point16', size: 'small', left: '50%', top: '9%' },
|
||||
{ id: 'Point18', size: 'small', left: '66%', top: '14%' },
|
||||
{ id: 'Point17', size: 'small', left: '34%', top: '14%' },
|
||||
{ id: 'Point09', size: 'small', left: '50%', top: '87%' },
|
||||
{ id: 'Point15', size: 'small', left: '81%', top: '43.5%' },
|
||||
{ id: 'Point12', size: 'small', left: '19%', top: '43.5%' },
|
||||
{ id: 'Point13', size: 'small', left: '81%', top: '70%' },
|
||||
{ id: 'Point10', size: 'small', left: '19%', top: '70%' },
|
||||
{ id: 'Point14', size: 'small', left: '93%', top: '56.5%' },
|
||||
{ id: 'Point11', size: 'small', left: '7%', top: '56.5%' }
|
||||
],
|
||||
}
|
||||
|
||||
export const traceLink : Record<string, Record<string, string[]>> = {
|
||||
@@ -236,7 +256,17 @@ export const traceLink : Record<string, Record<string, string[]>> = {
|
||||
Point09: ["Point10", "Point11"],
|
||||
Point07: ["Point14", "Point15"],
|
||||
Point06: ["Point12", "Point13"],
|
||||
}
|
||||
},
|
||||
Elation: {
|
||||
Point08: ["Point16", "Point17", "Point18"],
|
||||
Point16: ["Point17", "Point18"],
|
||||
Point07: ["Point13", "Point14", "Point15"],
|
||||
Point13: ["Point14", "Point15"],
|
||||
Point14: ["Point15"],
|
||||
Point06: ["Point10", "Point11", "Point12"],
|
||||
Point10: ["Point11", "Point12"],
|
||||
Point11: ["Point12"],
|
||||
},
|
||||
}
|
||||
|
||||
export const traceShowCaseMap : Record<string, Record<string, { id: string, size: string, isLink: boolean }[]>> = {
|
||||
@@ -1016,5 +1046,100 @@ export const traceShowCaseMap : Record<string, Record<string, { id: string, size
|
||||
isLink: false
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
Elation: {
|
||||
"1": [
|
||||
{
|
||||
id: "Point01",
|
||||
size: "big",
|
||||
isLink: false
|
||||
},
|
||||
{
|
||||
id: "Point09",
|
||||
size: "small",
|
||||
isLink: true
|
||||
}
|
||||
],
|
||||
"2": [
|
||||
{
|
||||
id: "Point02",
|
||||
size: "big",
|
||||
isLink: false
|
||||
},
|
||||
{
|
||||
id: "Point06",
|
||||
size: "medium",
|
||||
isLink: true
|
||||
},
|
||||
{
|
||||
id: "Point10",
|
||||
size: "small",
|
||||
isLink: true
|
||||
},
|
||||
{
|
||||
id: "Point11",
|
||||
size: "small",
|
||||
isLink: true
|
||||
},
|
||||
{
|
||||
id: "Point12",
|
||||
size: "small",
|
||||
isLink: true
|
||||
}
|
||||
],
|
||||
"3": [
|
||||
{
|
||||
id: "Point03",
|
||||
size: "big",
|
||||
isLink: false
|
||||
},
|
||||
{
|
||||
id: "Point07",
|
||||
size: "medium",
|
||||
isLink: true
|
||||
},
|
||||
{
|
||||
id: "Point13",
|
||||
size: "small",
|
||||
isLink: true
|
||||
},
|
||||
{
|
||||
id: "Point14",
|
||||
size: "small",
|
||||
isLink: true
|
||||
},
|
||||
{
|
||||
id: "Point15",
|
||||
size: "small",
|
||||
isLink: true
|
||||
}
|
||||
],
|
||||
"4": [
|
||||
{
|
||||
id: "Point04",
|
||||
size: "big",
|
||||
isLink: false
|
||||
},
|
||||
{
|
||||
id: "Point08",
|
||||
size: "medium",
|
||||
isLink: true
|
||||
},
|
||||
{
|
||||
id: "Point16",
|
||||
size: "small",
|
||||
isLink: true
|
||||
},
|
||||
{
|
||||
id: "Point18",
|
||||
size: "small",
|
||||
isLink: true
|
||||
},
|
||||
{
|
||||
id: "Point17",
|
||||
size: "small",
|
||||
isLink: true
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
@@ -35,7 +35,7 @@ const useAvatarStore = create<AvatarState>((set, get) => ({
|
||||
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 },
|
||||
listPath: { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false, elation: false },
|
||||
setListElement: (newListElement: Record<string, boolean>) => set({ listElement: newListElement }),
|
||||
setListPath: (newListPath: Record<string, boolean>) => set({ listPath: newListPath }),
|
||||
setSkillSelected: (newSkillSelected: string | null) => set({ skillSelected: newSkillSelected }),
|
||||
|
||||
@@ -30,7 +30,7 @@ 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 },
|
||||
listPath: { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false, elation: false },
|
||||
listRank: { "3": false, "4": false, "5": false },
|
||||
setListPath: (newListPath: Record<string, boolean>) => set({ listPath: newListPath }),
|
||||
setListRank: (newListRank: Record<string, boolean>) => set({ listRank: newListRank }),
|
||||
|
||||
Reference in New Issue
Block a user