- Introduced animated Board component with smooth mount transitions.
- Added hover scale, tap animations, and cell pop-in effects for moves. - Implemented animated status transitions and winner pulse effect. - Highlighted winning symbols with glow styling. - Improved game feel with responsive and modern interaction feedback.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
interface BoardProps {
|
||||
board: string[][];
|
||||
@@ -29,7 +30,7 @@ export default function Board({
|
||||
const isMyTurn = gameReady && myIndex !== -1 && turn === myIndex;
|
||||
|
||||
// -------------------------------
|
||||
// 🟦 HEADER STATUS FIXED
|
||||
// STATUS
|
||||
// -------------------------------
|
||||
let status;
|
||||
if (!gameReady) {
|
||||
@@ -43,16 +44,37 @@ export default function Board({
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 style={{ marginBottom: 8 }}>{status}</h2>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.35 }}
|
||||
>
|
||||
<motion.h2
|
||||
key={status}
|
||||
initial={{ opacity: 0, y: -6 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
style={{ marginBottom: 8 }}
|
||||
>
|
||||
{status}
|
||||
</motion.h2>
|
||||
|
||||
{gameReady && mySymbol && (
|
||||
<div style={{ marginBottom: 8, fontSize: 14, color: "#666" }}>
|
||||
You: <strong>{mySymbol}</strong> — Opponent: <strong>{opponentSymbol}</strong>
|
||||
</div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.75 }}
|
||||
style={{ marginBottom: 8, fontSize: 14 }}
|
||||
>
|
||||
You: <strong>{mySymbol}</strong> — Opponent:{" "}
|
||||
<strong>{opponentSymbol}</strong>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<div
|
||||
{/* -------------------------
|
||||
BOARD
|
||||
-------------------------- */}
|
||||
<motion.div
|
||||
layout
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(3, 80px)",
|
||||
@@ -70,24 +92,86 @@ export default function Board({
|
||||
!isMyTurn;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={`${rIdx}-${cIdx}`}
|
||||
<motion.button
|
||||
key={`${rIdx}-${cIdx}-${cell}`} // rerender when cell changes
|
||||
layout
|
||||
whileHover={
|
||||
!disabled
|
||||
? {
|
||||
scale: 1.1,
|
||||
boxShadow: "0px 0px 10px rgba(255,255,255,0.4)",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
whileTap={!disabled ? { scale: 0.85 } : {}}
|
||||
onClick={() => !disabled && onCellClick(rIdx, cIdx)}
|
||||
style={{
|
||||
width: "80px",
|
||||
height: "80px",
|
||||
fontSize: "2rem",
|
||||
borderRadius: "10px",
|
||||
border: "2px solid #333",
|
||||
background: "#111",
|
||||
color: "white",
|
||||
cursor: disabled ? "not-allowed" : "pointer",
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!disabled) onCellClick(rIdx, cIdx);
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{cell}
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
{cell && (
|
||||
<motion.span
|
||||
key="symbol"
|
||||
initial={{ scale: 0.3, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
exit={{ scale: 0.3, opacity: 0 }}
|
||||
transition={{ type: "spring", stiffness: 200, damping: 12 }}
|
||||
style={{
|
||||
color:
|
||||
winner === cell
|
||||
? "#f1c40f" // highlight winning symbol
|
||||
: "white",
|
||||
textShadow:
|
||||
winner === cell
|
||||
? "0 0 12px rgba(241,196,15,0.8)"
|
||||
: "none",
|
||||
}}
|
||||
>
|
||||
{cell}
|
||||
</motion.span>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.button>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Winner pulse animation */}
|
||||
{winner && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
scale: [1, 1.06, 1],
|
||||
}}
|
||||
transition={{
|
||||
repeat: Infinity,
|
||||
duration: 1.4,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
style={{
|
||||
color: "#f1c40f",
|
||||
fontSize: "20px",
|
||||
marginTop: "14px",
|
||||
fontWeight: 700,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
🎉 {winner} Wins! 🎉
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user