- Rebuilt Player.tsx with full UI overhaul using Framer Motion animations.
- Added animated transitions between login, lobby, and match states. - Improved layout, spacing, and visual styling for modern game feel. - Added smooth auto-connect flow and integrated username lock-in behavior. - Updated matchmaking and logout buttons with animated interactions. - Integrated Leaderboard cleanly inside lobby panel.
This commit is contained in:
53
package-lock.json
generated
53
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "tictactoe-vite",
|
"name": "tictactoe-vite",
|
||||||
"version": "1.0.0",
|
"version": "v0.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "tictactoe-vite",
|
"name": "tictactoe-vite",
|
||||||
"version": "1.0.0",
|
"version": "v0.2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "latest",
|
"@emotion/react": "latest",
|
||||||
"@emotion/styled": "latest",
|
"@emotion/styled": "latest",
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"@mui/icons-material": "latest",
|
"@mui/icons-material": "latest",
|
||||||
"@mui/material": "latest",
|
"@mui/material": "latest",
|
||||||
"axios": "latest",
|
"axios": "latest",
|
||||||
|
"framer-motion": "latest",
|
||||||
"markdown-to-jsx": "latest",
|
"markdown-to-jsx": "latest",
|
||||||
"marked": "latest",
|
"marked": "latest",
|
||||||
"react": "latest",
|
"react": "latest",
|
||||||
@@ -2064,6 +2065,33 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "12.23.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
|
||||||
|
"integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-dom": "^12.23.23",
|
||||||
|
"motion-utils": "^12.23.6",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -3263,6 +3291,21 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/motion-dom": {
|
||||||
|
"version": "12.23.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
|
||||||
|
"integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-utils": "^12.23.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-utils": {
|
||||||
|
"version": "12.23.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
|
||||||
|
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -3761,6 +3804,12 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/unified": {
|
"node_modules/unified": {
|
||||||
"version": "11.0.5",
|
"version": "11.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
"remark-gfm": "latest",
|
"remark-gfm": "latest",
|
||||||
"marked": "latest",
|
"marked": "latest",
|
||||||
"axios": "latest",
|
"axios": "latest",
|
||||||
"@heroiclabs/nakama-js": "^2.8.0"
|
"@heroiclabs/nakama-js": "^2.8.0",
|
||||||
|
"framer-motion": "latest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-react": "latest",
|
"@vitejs/plugin-react": "latest",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import Leaderboard from "./Leaderboard";
|
import Leaderboard from "./Leaderboard";
|
||||||
import { useNakama } from "./providers/NakamaProvider";
|
import { useNakama } from "./providers/NakamaProvider";
|
||||||
|
|
||||||
@@ -45,38 +46,167 @@ export default function Player({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginBottom: "20px" }}>
|
<div style={{ marginBottom: "20px" }}>
|
||||||
{session && !matchId && (
|
<AnimatePresence mode="wait">
|
||||||
<>
|
{/* ---------------- LOGIN SCREEN ---------------- */}
|
||||||
<h2>Hello, {session.username}</h2>
|
{!session && (
|
||||||
|
<motion.div
|
||||||
|
key="login"
|
||||||
|
initial={{ opacity: 0, y: 20, scale: 0.97 }}
|
||||||
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, y: -20, scale: 0.97 }}
|
||||||
|
transition={{ duration: 0.4, ease: "easeOut" }}
|
||||||
|
style={{
|
||||||
|
background: "#111",
|
||||||
|
padding: "24px",
|
||||||
|
borderRadius: "16px",
|
||||||
|
width: "280px",
|
||||||
|
margin: "0 auto",
|
||||||
|
color: "white",
|
||||||
|
boxShadow:
|
||||||
|
"0 4px 16px rgba(0,0,0,0.4), inset 0 0 20px rgba(255,255,255,0.03)",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h2 style={{ marginBottom: "16px" }}>Welcome!</h2>
|
||||||
|
|
||||||
{/* Game mode selection */}
|
<input
|
||||||
<label style={{ display: "block", marginTop: "10px" }}>
|
placeholder="Enter username"
|
||||||
Select Game Mode:
|
value={username}
|
||||||
|
disabled={username.length > 0}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
style={{
|
||||||
|
padding: "10px",
|
||||||
|
width: "100%",
|
||||||
|
borderRadius: "12px",
|
||||||
|
background: "#222",
|
||||||
|
color: "white",
|
||||||
|
border: "1px solid #333",
|
||||||
|
marginBottom: "12px",
|
||||||
|
fontSize: "14px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<motion.button
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
onClick={handleConnect}
|
||||||
|
disabled={username.length === 0}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
padding: "10px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
background: username.length ? "#2ecc71" : "#444",
|
||||||
|
border: "none",
|
||||||
|
cursor: username.length ? "pointer" : "not-allowed",
|
||||||
|
color: "white",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "14px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Connect
|
||||||
|
</motion.button>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ---------------- LOBBY SCREEN ---------------- */}
|
||||||
|
{session && !matchId && (
|
||||||
|
<motion.div
|
||||||
|
key="lobby"
|
||||||
|
initial={{ opacity: 0, y: 40 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -40 }}
|
||||||
|
transition={{ duration: 0.45, ease: "easeOut" }}
|
||||||
|
style={{
|
||||||
|
padding: "20px",
|
||||||
|
background: "#0f0f0f",
|
||||||
|
borderRadius: "20px",
|
||||||
|
color: "white",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h2 style={{ marginBottom: "10px" }}>
|
||||||
|
Hello, <span style={{ color: "#2ecc71" }}>{session.username}</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<label style={{ display: "block", marginTop: "10px", opacity: 0.7 }}>
|
||||||
|
Select Game Mode
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
value={selectedMode}
|
value={selectedMode}
|
||||||
onChange={(e) => setSelectedMode(e.target.value)}
|
onChange={(e) => setSelectedMode(e.target.value)}
|
||||||
style={{ padding: "6px", marginBottom: "10px" }}
|
style={{
|
||||||
|
padding: "8px",
|
||||||
|
margin: "10px 0 16px",
|
||||||
|
width: "60%",
|
||||||
|
borderRadius: "10px",
|
||||||
|
background: "#222",
|
||||||
|
color: "white",
|
||||||
|
border: "1px solid #333",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<option value="classic">Classic</option>
|
<option value="classic">Classic</option>
|
||||||
<option value="blitz">Blitz</option>
|
<option value="blitz">Blitz</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
{/* Join matchmaking */}
|
<motion.button
|
||||||
<button onClick={() => startQueue(selectedMode)}>Join Matchmaking</button>
|
whileTap={{ scale: 0.95 }}
|
||||||
<button onClick={() => logout()}>Logout</button>
|
onClick={() => startQueue(selectedMode)}
|
||||||
|
style={{
|
||||||
|
padding: "10px 20px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
background: "#3498db",
|
||||||
|
color: "white",
|
||||||
|
border: "none",
|
||||||
|
marginRight: "10px",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Join Matchmaking
|
||||||
|
</motion.button>
|
||||||
|
|
||||||
{/*/!* List open matches *!/*/}
|
<motion.button
|
||||||
{/*<MatchList matches={openMatches} />*/}
|
whileTap={{ scale: 0.95 }}
|
||||||
<Leaderboard/>
|
onClick={logout}
|
||||||
</>
|
style={{
|
||||||
|
padding: "10px 20px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
background: "#e74c3c",
|
||||||
|
color: "white",
|
||||||
|
border: "none",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</motion.button>
|
||||||
|
|
||||||
|
<div style={{ marginTop: "24px" }}>
|
||||||
|
<Leaderboard />
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* ---------------- MATCH SCREEN ---------------- */}
|
||||||
{session && matchId && (
|
{session && matchId && (
|
||||||
<>
|
<motion.div
|
||||||
<h2>Go {session.username}!</h2>
|
key="match"
|
||||||
</>
|
initial={{ opacity: 0, scale: 0.9, y: 30 }}
|
||||||
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.9, y: -20 }}
|
||||||
|
transition={{ duration: 0.35, ease: "easeOut" }}
|
||||||
|
style={{
|
||||||
|
padding: "20px",
|
||||||
|
color: "white",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h2 style={{ fontSize: "22px", color: "#f1c40f" }}>
|
||||||
|
Go {session.username}!
|
||||||
|
</h2>
|
||||||
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user