UPDATE: Responsive for mobile
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m53s

This commit is contained in:
2025-09-30 13:17:45 +07:00
parent 3d59dddbb4
commit d00f9e3d5e
5 changed files with 223 additions and 221 deletions

View File

@@ -376,8 +376,8 @@ export default function ActionBar() {
</button> </button>
</div> </div>
<dialog id="update_profile_modal" className="modal "> <dialog id="update_profile_modal" className="modal">
<div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20"> <div className="modal-box w-11/12 max-w-7xl max-h-[85vh] bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10"> <div className="sticky top-0 z-10">
<motion.button <motion.button
whileHover={{ scale: 1.1, rotate: 90 }} whileHover={{ scale: 1.1, rotate: 90 }}
@@ -419,7 +419,7 @@ export default function ActionBar() {
</dialog> </dialog>
<dialog id="copy_profile_modal" className="modal"> <dialog id="copy_profile_modal" className="modal">
<div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20"> <div className="modal-box w-11/12 max-w-7xl max-h-[85vh] bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10"> <div className="sticky top-0 z-10">
<motion.button <motion.button
whileHover={{ scale: 1.1, rotate: 90 }} whileHover={{ scale: 1.1, rotate: 90 }}
@@ -444,7 +444,7 @@ export default function ActionBar() {
<dialog id="avatars_modal" className="modal"> <dialog id="avatars_modal" className="modal">
<div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20"> <div className="modal-box w-11/12 max-w-7xl max-h-[85vh] bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10"> <div className="sticky top-0 z-10">
<motion.button <motion.button
whileHover={{ scale: 1.1, rotate: 90 }} whileHover={{ scale: 1.1, rotate: 90 }}

View File

@@ -510,7 +510,7 @@ export default function AvatarInfo() {
)} )}
<dialog id="action_detail_modal" className="modal backdrop-blur-sm"> <dialog id="action_detail_modal" className="modal backdrop-blur-sm">
<div className="modal-box w-11/12 max-w-5xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20"> <div className="modal-box w-11/12 max-w-5xl max-h-[85vh] bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10"> <div className="sticky top-0 z-10">
<motion.button <motion.button
whileHover={{ scale: 1.1, rotate: 90 }} whileHover={{ scale: 1.1, rotate: 90 }}

View File

@@ -495,8 +495,8 @@ export default function Header() {
</Link> </Link>
</div> </div>
<dialog id="connect_modal" className="modal sm:modal-middle backdrop-blur-sm"> <dialog id="connect_modal" className="modal">
<div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20"> <div className="modal-box w-11/12 max-w-7xl max-h-[85vh] bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10"> <div className="sticky top-0 z-10">
<motion.button <motion.button
whileHover={{ scale: 1.1, rotate: 90 }} whileHover={{ scale: 1.1, rotate: 90 }}
@@ -597,71 +597,73 @@ export default function Header() {
</div> </div>
)} )}
<div className="flex flex-col sm:flex-row justify-between items-center mt-6 mb-2"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6 mb-2">
<div className="mb-4 sm:mb-0"> {/* Status */}
<div className="flex items-center justify-center md:justify-start">
<span className="text-md mr-2">{transI18n("status")}:</span> <span className="text-md mr-2">{transI18n("status")}:</span>
<span className={`badge ${isConnectPS ? 'badge-success' : 'badge-error'} badge-lg`}> <span
className={`badge ${isConnectPS ? "badge-success" : "badge-error"
} badge-lg`}
>
{isConnectPS ? transI18n("connected") : transI18n("unconnected")} {isConnectPS ? transI18n("connected") : transI18n("unconnected")}
</span> </span>
</div> </div>
<div className="flex space-x-2"> {/* Buttons */}
<div className="flex flex-col sm:flex-row gap-2 w-full justify-center md:justify-end">
<button <button
onClick={ onClick={async () => {
async () => { const response = await connectToPS();
const response = await connectToPS() if (response.success) {
if (response.success) { setIsConnectPS(true);
setIsConnectPS(true) setMessage({
setMessage({ type: "success",
type: "success", text: transI18n("connectedSuccess"),
text: transI18n("connectedSuccess") });
}) } else {
} else { setIsConnectPS(false);
setIsConnectPS(false) setMessage({
setMessage({ type: "error",
type: "error", text: response.message,
text: response.message });
})
}
} }
} }}
className={`btn btn-primary`} className="btn btn-primary w-full sm:w-auto"
> >
{transI18n("connectPs")} {transI18n("connectPs")}
</button> </button>
{isConnectPS && ( {isConnectPS && (
<button <button
onClick={ onClick={async () => {
async () => { const response = await syncDataToPS();
const response = await syncDataToPS() if (response.success) {
if (response.success) { setMessage({
setMessage({ type: "success",
type: "success", text: transI18n("syncSuccess"),
text: transI18n("syncSuccess") });
}) } else {
} else { setMessage({
setMessage({ type: "error",
type: "error", text: `${transI18n("syncFailed")}: ${response.message}`,
text: `${transI18n("syncFailed")}: ${response.message}` });
})
}
} }
} }}
className={`btn btn-success`} className="btn btn-success w-full sm:w-auto"
> >
{transI18n("sync")} {transI18n("sync")}
</button> </button>
)} )}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</dialog> </dialog>
<dialog id="import_modal" className="modal backdrop-blur-sm"> <dialog id="import_modal" className="modal">
<div className="modal-box w-11/12 max-w-max bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20"> <div className="modal-box w-11/12 max-w-7xl max-h-[85vh] bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10"> <div className="sticky top-0 z-10">
<motion.button <motion.button
whileHover={{ scale: 1.1, rotate: 90 }} whileHover={{ scale: 1.1, rotate: 90 }}
@@ -687,8 +689,8 @@ export default function Header() {
</div> </div>
</dialog> </dialog>
<dialog id="monster_modal" className="modal sm:backdrop-blur-sm md:backdrop-blur-none"> <dialog id="monster_modal" className="modal">
<div className="modal-box w-11/12 max-w-max bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20"> <div className="modal-box w-11/12 max-w-7xl max-h-[85vh] bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10"> <div className="sticky top-0 z-10">
<motion.button <motion.button
whileHover={{ scale: 1.1, rotate: 90 }} whileHover={{ scale: 1.1, rotate: 90 }}

View File

@@ -20,7 +20,7 @@ export default function RelicMaker() {
const { avatarSelected } = useAvatarStore() const { avatarSelected } = useAvatarStore()
const { setIsOpenRelic } = useModelStore() const { setIsOpenRelic } = useModelStore()
const { mapRelicInfo } = useRelicStore() const { mapRelicInfo } = useRelicStore()
const { mapMainAffix, mapSubAffix} = useAffixStore() const { mapMainAffix, mapSubAffix } = useAffixStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { const {
selectedRelicSlot, selectedRelicSlot,
@@ -93,7 +93,7 @@ export default function RelicMaker() {
} }
if (updated) setListSelectedSubStats(newSubAffixes); if (updated) setListSelectedSubStats(newSubAffixes);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedMainStat, mapSubAffix, mapMainAffix, selectedRelicSlot]); }, [selectedMainStat, mapSubAffix, mapMainAffix, selectedRelicSlot]);
const exSubAffixOptions = useMemo(() => { const exSubAffixOptions = useMemo(() => {
@@ -119,7 +119,7 @@ export default function RelicMaker() {
const data = affixSet[selectedMainStat]; const data = affixSet[selectedMainStat];
if (!data) return 0; if (!data) return 0;
return calcMainAffixBonus(data, selectedRelicLevel); return calcMainAffixBonus(data, selectedRelicLevel);
}, [mapMainAffix, selectedRelicSlot, selectedMainStat, selectedRelicLevel]); }, [mapMainAffix, selectedRelicSlot, selectedMainStat, selectedRelicLevel]);
const handleSubStatChange = (key: string, index: number, rollCount: number, stepCount: number) => { const handleSubStatChange = (key: string, index: number, rollCount: number, stepCount: number) => {
@@ -155,7 +155,7 @@ export default function RelicMaker() {
newSubAffixes[index].stepCount = preSubAffixes.stepCount; newSubAffixes[index].stepCount = preSubAffixes.stepCount;
setListSelectedSubStats(newSubAffixes); setListSelectedSubStats(newSubAffixes);
popHistory(index); popHistory(index);
}; };
const resetSubStat = (index: number) => { const resetSubStat = (index: number) => {
const newSubAffixes = cloneDeep(listSelectedSubStats); const newSubAffixes = cloneDeep(listSelectedSubStats);
@@ -172,7 +172,7 @@ export default function RelicMaker() {
const exKeys = Object.keys(exSubAffixOptions); const exKeys = Object.keys(exSubAffixOptions);
for (let i = 0; i < newSubAffixes.length; i++) { for (let i = 0; i < newSubAffixes.length; i++) {
const keys = Object.keys(subAffixOptions).filter((key) => !exKeys.includes(key)); const keys = Object.keys(subAffixOptions).filter((key) => !exKeys.includes(key));
const randomKey = keys[Math.floor(Math.random() * keys.length )]; const randomKey = keys[Math.floor(Math.random() * keys.length)];
exKeys.push(randomKey); exKeys.push(randomKey);
const randomValue = subAffixOptions[randomKey]; const randomValue = subAffixOptions[randomKey];
newSubAffixes[i].affixId = randomKey; newSubAffixes[i].affixId = randomKey;
@@ -326,18 +326,18 @@ export default function RelicMaker() {
{/* Right Panel - Sub Stats */} {/* Right Panel - Sub Stats */}
<div className="space-y-4"> <div className="space-y-4">
{/* Total Roll */} {/* Total Roll */}
<div className="bg-base-100 rounded-xl p-6 border border-slate-700 z-[1]"> <div className="bg-base-100 rounded-xl p-4 border border-slate-700 z-[1]">
<h3 className="text-lg font-bold mb-4">{transI18n("totalRoll")} {listSelectedSubStats.reduce((a, b) => a + b.rollCount, 0)}</h3> <h3 className="text-lg font-bold mb-4">{transI18n("totalRoll")} {listSelectedSubStats.reduce((a, b) => a + b.rollCount, 0)}</h3>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-2">
<button <button
className="btn btn-outline btn-success" className="btn btn-outline btn-success sm:btn-sm"
onClick={randomizeStats} onClick={randomizeStats}
> >
{transI18n("randomizeStats")} {transI18n("randomizeStats")}
</button> </button>
<button <button
className="btn btn-outline btn-success" className="btn btn-outline btn-success sm:btn-sm"
onClick={randomizeRolls} onClick={randomizeRolls}
> >
{transI18n("randomizeRolls")} {transI18n("randomizeRolls")}
@@ -345,118 +345,118 @@ export default function RelicMaker() {
</div> </div>
</div> </div>
{listSelectedSubStats.map((v, index) => ( {listSelectedSubStats.map((v, index) => (
<div key={index} className={`bg-base-100 rounded-xl p-4 border border-slate-700`}> <div key={index} className={`bg-base-100 rounded-xl p-4 border border-slate-700`}>
<div className="grid grid-cols-12 gap-2 items-center"> <div className="grid grid-cols-12 gap-2 items-center">
{/* Stat Selection */} {/* Stat Selection */}
<div className="col-span-8"> <div className="col-span-8">
<SelectCustomImage <SelectCustomImage
customSet={Object.entries(subAffixOptions).map(([key, value]) => ({ customSet={Object.entries(subAffixOptions).map(([key, value]) => ({
value: key, value: key,
label: mappingStats[value.property].name + " " + mappingStats[value.property].unit, label: mappingStats[value.property].name + " " + mappingStats[value.property].unit,
imageUrl: mappingStats[value.property].icon imageUrl: mappingStats[value.property].icon
}))} }))}
excludeSet={Object.entries(exSubAffixOptions).map(([key, value]) => ({ excludeSet={Object.entries(exSubAffixOptions).map(([key, value]) => ({
value: key, value: key,
label: mappingStats[value.property].name + " " + mappingStats[value.property].unit, label: mappingStats[value.property].name + " " + mappingStats[value.property].unit,
imageUrl: mappingStats[value.property].icon imageUrl: mappingStats[value.property].icon
}))} }))}
selectedCustomSet={v.affixId} selectedCustomSet={v.affixId}
placeholder={transI18n("selectASubStat")} placeholder={transI18n("selectASubStat")}
setSelectedCustomSet={(key) => handleSubStatChange(key, index, 0, 0)} setSelectedCustomSet={(key) => handleSubStatChange(key, index, 0, 0)}
/> />
</div> </div>
{/* Current Value */} {/* Current Value */}
<div className="col-span-4 text-center flex items-center justify-center gap-2"> <div className="col-span-4 text-center flex items-center justify-center gap-2">
<span className="text-2xl font-mono">+{ }</span> <span className="text-2xl font-mono">+{ }</span>
<div className="text-xl font-bold text-info">{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount, v.rollCount)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}</div> <div className="text-xl font-bold text-info">{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount, v.rollCount)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}</div>
</div> </div>
{/* Up Roll Values */} {/* Up Roll Values */}
<div className="col-span-12"> <div className="col-span-12">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<ChevronUp className="w-4 h-4 text-success" /> <ChevronUp className="w-4 h-4 text-success" />
<span className="text-sm font-semibold text-success">{transI18n("upRoll")}</span> <span className="text-sm font-semibold text-success">{transI18n("upRoll")}</span>
</div> </div>
<div className="grid grid-cols-3 gap-1"> <div className="grid grid-cols-3 gap-1">
<button <button
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 0)} onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 0)}
className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0" className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], 0 , v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], 0, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
</button> </button>
<button <button
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 1)} onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 1)}
className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0" className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 1, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 1, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
</button> </button>
<button <button
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 2)} onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 2)}
className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0" className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 2, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 2, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
</button> </button>
</div> </div>
</div> </div>
{/* Down Roll Values */} {/* Down Roll Values */}
<div className="col-span-12"> <div className="col-span-12">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<ChevronDown className="w-4 h-4 text-error" /> <ChevronDown className="w-4 h-4 text-error" />
<span className="text-sm font-semibold text-error">{transI18n("downRoll")}</span> <span className="text-sm font-semibold text-error">{transI18n("downRoll")}</span>
</div> </div>
<div className="grid grid-cols-3 gap-1"> <div className="grid grid-cols-3 gap-1">
<button <button
onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount, 0))} onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount, 0))}
className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0" className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], 0 , Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], 0, Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
</button> </button>
<button <button
onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount - 1, 0))} onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount - 1, 0))}
className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0" className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 1, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 1, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
</button> </button>
<button <button
onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount - 2, 0))} onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount - 2, 0))}
className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0" className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 2, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 2, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
</button> </button>
</div> </div>
</div> </div>
{/* Reset Button & Roll Info */} {/* Reset Button & Roll Info */}
<div className="col-span-12 text-center"> <div className="col-span-12 text-center w-full">
<div className="grid grid-cols-2 gap-1 items-center justify-items-start"> <div className="grid grid-rows-2 gap-1 items-center justify-items-start w-full">
<div className="flex items-center gap-2"> <div className="grid grid-cols-2 gap-2 items-center w-full">
<button <button
className="btn btn-error btn-sm mb-1" className="btn btn-error btn-sm mb-1"
onClick={() => resetSubStat(index)} onClick={() => resetSubStat(index)}
> >
{transI18n("reset")} {transI18n("reset")}
</button> </button>
<button <button
className="btn btn-warning btn-sm mb-1" className="btn btn-warning btn-sm mb-1"
onClick={() => handlerRollback(index)} onClick={() => handlerRollback(index)}
> >
{transI18n("rollBack")} {transI18n("rollBack")}
</button> </button>
</div> </div>
<div className="text-lg flex items-center gap-2 text-gray-400"> <div className="grid grid-cols-2 gap-2 items-center w-full">
<span className="font-bold">{transI18n("roll")}: <span className="text-info">{v.rollCount}</span></span> <span className="font-bold">{transI18n("roll")}: <span className="text-info">{v.rollCount}</span></span>
<span className="font-bold">{transI18n("step")}: <span className="text-info">{v.stepCount}</span></span> <span className="font-bold">{transI18n("step")}: <span className="text-info">{v.stepCount}</span></span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
))} ))}
</div> </div>

View File

@@ -315,8 +315,8 @@ export default function RelicsInfo() {
</div> </div>
</div> </div>
<dialog id="action_detail_modal" className="modal lg:backdrop-blur-sm z-10"> <dialog id="action_detail_modal" className="modal">
<div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20"> <div className="modal-box w-11/12 max-w-7xl max-h-[85vh] bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10"> <div className="sticky top-0 z-10">
<motion.button <motion.button
whileHover={{ scale: 1.1, rotate: 90 }} whileHover={{ scale: 1.1, rotate: 90 }}
@@ -335,8 +335,8 @@ export default function RelicsInfo() {
</dialog> </dialog>
<dialog id="quick_view_modal" className="modal lg:backdrop-blur-sm z-10"> <dialog id="quick_view_modal" className="modal">
<div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20"> <div className="modal-box w-11/12 max-w-7xl max-h-[85vh] bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10"> <div className="sticky top-0 z-10">
<motion.button <motion.button
whileHover={{ scale: 1.1, rotate: 90 }} whileHover={{ scale: 1.1, rotate: 90 }}