- 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",
|
||||
"version": "1.0.0",
|
||||
"version": "v0.2.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "tictactoe-vite",
|
||||
"version": "1.0.0",
|
||||
"version": "v0.2.0",
|
||||
"dependencies": {
|
||||
"@emotion/react": "latest",
|
||||
"@emotion/styled": "latest",
|
||||
@@ -14,6 +14,7 @@
|
||||
"@mui/icons-material": "latest",
|
||||
"@mui/material": "latest",
|
||||
"axios": "latest",
|
||||
"framer-motion": "latest",
|
||||
"markdown-to-jsx": "latest",
|
||||
"marked": "latest",
|
||||
"react": "latest",
|
||||
@@ -2064,6 +2065,33 @@
|
||||
"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": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@@ -3263,6 +3291,21 @@
|
||||
"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": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -3761,6 +3804,12 @@
|
||||
"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": {
|
||||
"version": "11.0.5",
|
||||
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
"remark-gfm": "latest",
|
||||
"marked": "latest",
|
||||
"axios": "latest",
|
||||
"@heroiclabs/nakama-js": "^2.8.0"
|
||||
"@heroiclabs/nakama-js": "^2.8.0",
|
||||
"framer-motion": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "latest",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import Leaderboard from "./Leaderboard";
|
||||
import { useNakama } from "./providers/NakamaProvider";
|
||||
|
||||
@@ -45,38 +46,167 @@ export default function Player({
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: "20px" }}>
|
||||
{session && !matchId && (
|
||||
<>
|
||||
<h2>Hello, {session.username}</h2>
|
||||
<AnimatePresence mode="wait">
|
||||
{/* ---------------- LOGIN SCREEN ---------------- */}
|
||||
{!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 */}
|
||||
<label style={{ display: "block", marginTop: "10px" }}>
|
||||
Select Game Mode:
|
||||
<input
|
||||
placeholder="Enter username"
|
||||
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>
|
||||
|
||||
<select
|
||||
value={selectedMode}
|
||||
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="blitz">Blitz</option>
|
||||
</select>
|
||||
|
||||
{/* Join matchmaking */}
|
||||
<button onClick={() => startQueue(selectedMode)}>Join Matchmaking</button>
|
||||
<button onClick={() => logout()}>Logout</button>
|
||||
<motion.button
|
||||
whileTap={{ scale: 0.95 }}
|
||||
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 *!/*/}
|
||||
{/*<MatchList matches={openMatches} />*/}
|
||||
<Leaderboard/>
|
||||
</>
|
||||
<motion.button
|
||||
whileTap={{ scale: 0.95 }}
|
||||
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 && (
|
||||
<>
|
||||
<h2>Go {session.username}!</h2>
|
||||
</>
|
||||
<motion.div
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user