Tantawi65 commited on
Commit
c0f8abf
·
1 Parent(s): e07d173

Mobile fullscreen support and responsive layout improvements

Browse files
client/index.html CHANGED
@@ -3,13 +3,18 @@
3
  <head>
4
  <meta charset="UTF-8" />
5
  <link rel="icon" type="image/png" href="/Assests/ui_logo.png" />
 
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
 
7
  <meta name="apple-mobile-web-app-capable" content="yes" />
8
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
9
  <meta name="mobile-web-app-capable" content="yes" />
10
  <meta name="screen-orientation" content="landscape" />
11
  <meta name="theme-color" content="#1a1a2e" />
12
- <title>Mummy Card Game - هتتحنط هنا</title>
 
 
 
13
  <style>
14
  /* Prevent flash of unstyled content */
15
  body {
 
3
  <head>
4
  <meta charset="UTF-8" />
5
  <link rel="icon" type="image/png" href="/Assests/ui_logo.png" />
6
+ <link rel="manifest" href="/manifest.json" />
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
8
+ <!-- PWA / Fullscreen support -->
9
  <meta name="apple-mobile-web-app-capable" content="yes" />
10
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
11
  <meta name="mobile-web-app-capable" content="yes" />
12
  <meta name="screen-orientation" content="landscape" />
13
  <meta name="theme-color" content="#1a1a2e" />
14
+ <meta name="msapplication-navbutton-color" content="#1a1a2e" />
15
+ <meta name="apple-mobile-web-app-title" content="Khofo" />
16
+ <link rel="apple-touch-icon" href="/Assests/ui_logo.png" />
17
+ <title>Khofo - هتتحنط هنا</title>
18
  <style>
19
  /* Prevent flash of unstyled content */
20
  body {
client/public/manifest.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "Khofo Card Game",
3
+ "short_name": "Khofo",
4
+ "description": "Egyptian Mummy Card Game",
5
+ "start_url": "/",
6
+ "display": "fullscreen",
7
+ "orientation": "landscape",
8
+ "background_color": "#1a1a2e",
9
+ "theme_color": "#1a1a2e",
10
+ "icons": [
11
+ {
12
+ "src": "/Assests/ui_logo.png",
13
+ "sizes": "192x192",
14
+ "type": "image/png"
15
+ },
16
+ {
17
+ "src": "/Assests/ui_logo.png",
18
+ "sizes": "512x512",
19
+ "type": "image/png"
20
+ }
21
+ ]
22
+ }
client/src/components/screens/GameBoard.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { useState, useEffect } from 'react';
2
  import { motion, AnimatePresence } from 'framer-motion';
3
  import { useGameStore } from '../../store/gameStore';
4
  import { Card, CardBack } from '../game/Card';
@@ -6,6 +6,18 @@ import { emitDrawCard, emitPlayCard } from '../../socket/socket';
6
  import type { CardInstance, Player } from '@shared/types';
7
  import { CARD_DATABASE } from '@shared/types';
8
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  export function GameBoard() {
10
  const gameState = useGameStore((state) => state.gameState);
11
  const myHand = useGameStore((state) => state.myHand);
@@ -19,6 +31,20 @@ export function GameBoard() {
19
  const reactionWindowActive = useGameStore((state) => state.reactionWindowActive);
20
 
21
  const [pendingCard, setPendingCard] = useState<CardInstance | null>(null);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  // Clear pendingCard when relevant modals close (not target-select or card-type-select)
24
  useEffect(() => {
@@ -119,15 +145,25 @@ export function GameBoard() {
119
 
120
  return (
121
  <div
122
- className="game-board flex-1 flex flex-col"
123
  style={{
124
- backgroundImage: 'url(/menu_background.png)',
125
  backgroundSize: 'cover',
126
  backgroundPosition: 'center',
127
  }}
128
  >
 
 
 
 
 
 
 
 
 
 
129
  {/* Top area - Other players */}
130
- <div className="flex justify-center gap-4 p-2">
131
  {otherPlayers.map((player) => (
132
  <PlayerDisplay
133
  key={player.id}
@@ -138,16 +174,16 @@ export function GameBoard() {
138
  </div>
139
 
140
  {/* Middle area - Deck and discard */}
141
- <div className="flex-1 flex items-center justify-center gap-8">
142
  {/* Deck */}
143
  <motion.div
144
- className="deck-pile"
145
  data-count={gameState.deckCount}
146
  whileHover={isMyTurn ? { scale: 1.05 } : {}}
147
  onClick={handleDrawCard}
148
  >
149
  <CardBack size="large" />
150
- <p className="text-center text-papyrus mt-2">Deck</p>
151
  </motion.div>
152
 
153
  {/* Turn info */}
@@ -156,13 +192,13 @@ export function GameBoard() {
156
  key={currentPlayer?.id}
157
  initial={{ opacity: 0, scale: 0.8 }}
158
  animate={{ opacity: 1, scale: 1 }}
159
- className="bg-black/60 rounded-lg px-6 py-3"
160
  >
161
- <p className="text-papyrus mb-1">
162
  {currentPlayer?.id === playerId ? "Your Turn!" : `${currentPlayer?.name}'s Turn`}
163
  </p>
164
  {turnsRemaining > 1 && (
165
- <p className="text-egyptian-gold text-sm">
166
  {turnsRemaining} turns remaining
167
  </p>
168
  )}
@@ -170,7 +206,7 @@ export function GameBoard() {
170
  </div>
171
 
172
  {/* Discard pile */}
173
- <div className="relative">
174
  {gameState.discardPile.length > 0 ? (
175
  <Card
176
  cardId={gameState.discardPile[gameState.discardPile.length - 1]}
@@ -178,25 +214,25 @@ export function GameBoard() {
178
  disabled
179
  />
180
  ) : (
181
- <div className="w-32 h-48 border-2 border-dashed border-papyrus/30 rounded-lg flex items-center justify-center">
182
- <p className="text-papyrus/40 text-center text-sm">Discard</p>
183
  </div>
184
  )}
185
- <p className="text-center text-papyrus mt-2">Discard ({gameState.discardPile.length})</p>
186
  </div>
187
  </div>
188
 
189
  {/* Bottom area - My hand */}
190
- <div className="bg-black/50 backdrop-blur-sm p-4">
191
  {/* My player info */}
192
- <div className="flex items-center justify-between mb-2">
193
- <div className="flex items-center gap-2">
194
- <div className={`player-avatar ${isMyTurn ? 'current-turn' : ''}`}>
195
  {myPlayer?.name.charAt(0).toUpperCase()}
196
  </div>
197
- <span className="text-papyrus">{myPlayer?.name} (You)</span>
198
  </div>
199
- <span className="text-egyptian-gold">{myHand.length} cards</span>
200
  </div>
201
 
202
  {/* Hand */}
@@ -229,11 +265,11 @@ export function GameBoard() {
229
  {/* Draw button for mobile */}
230
  {isMyTurn && (
231
  <motion.button
232
- className="btn btn-primary w-full mt-2"
233
  onClick={handleDrawCard}
234
  whileTap={{ scale: 0.95 }}
235
  >
236
- Draw Card (End Turn)
237
  </motion.button>
238
  )}
239
  </div>
@@ -249,32 +285,32 @@ interface PlayerDisplayProps {
249
  function PlayerDisplay({ player, isCurrentTurn }: PlayerDisplayProps) {
250
  return (
251
  <motion.div
252
- className={`bg-black/50 rounded-lg p-2 ${isCurrentTurn ? 'ring-2 ring-green-500' : ''}`}
253
  animate={isCurrentTurn ? { scale: [1, 1.02, 1] } : {}}
254
  transition={{ repeat: isCurrentTurn ? Infinity : 0, duration: 1.5 }}
255
  >
256
- <div className="flex items-center gap-2 mb-2">
257
- <div className={`player-avatar w-8 h-8 text-sm ${!player.isAlive ? 'eliminated' : ''} ${isCurrentTurn ? 'current-turn' : ''}`}>
258
  {player.name.charAt(0).toUpperCase()}
259
  </div>
260
  <div>
261
- <p className={`text-sm ${player.isAlive ? 'text-papyrus' : 'text-papyrus/50 line-through'}`}>
262
  {player.name}
263
  </p>
264
- {!player.isAlive && <p className="text-mummy-red text-xs">☠️ Mummified</p>}
265
  </div>
266
  </div>
267
 
268
  {player.isAlive && (
269
- <div className="flex gap-1 justify-center">
270
- {Array.from({ length: Math.min(player.cardCount, 8) }).map((_, i) => (
271
  <div
272
  key={i}
273
- className="w-4 h-6 bg-gradient-to-br from-egyptian-gold to-sand rounded shadow-sm"
274
  />
275
  ))}
276
- {player.cardCount > 8 && (
277
- <span className="text-egyptian-gold text-xs">+{player.cardCount - 8}</span>
278
  )}
279
  </div>
280
  )}
 
1
+ import { useState, useEffect, useCallback } from 'react';
2
  import { motion, AnimatePresence } from 'framer-motion';
3
  import { useGameStore } from '../../store/gameStore';
4
  import { Card, CardBack } from '../game/Card';
 
6
  import type { CardInstance, Player } from '@shared/types';
7
  import { CARD_DATABASE } from '@shared/types';
8
 
9
+ // Fullscreen helper
10
+ const requestFullscreen = () => {
11
+ const elem = document.documentElement;
12
+ if (elem.requestFullscreen) {
13
+ elem.requestFullscreen();
14
+ } else if ((elem as any).webkitRequestFullscreen) {
15
+ (elem as any).webkitRequestFullscreen();
16
+ } else if ((elem as any).msRequestFullscreen) {
17
+ (elem as any).msRequestFullscreen();
18
+ }
19
+ };
20
+
21
  export function GameBoard() {
22
  const gameState = useGameStore((state) => state.gameState);
23
  const myHand = useGameStore((state) => state.myHand);
 
31
  const reactionWindowActive = useGameStore((state) => state.reactionWindowActive);
32
 
33
  const [pendingCard, setPendingCard] = useState<CardInstance | null>(null);
34
+ const [isFullscreen, setIsFullscreen] = useState(false);
35
+
36
+ // Track fullscreen state
37
+ useEffect(() => {
38
+ const handleFullscreenChange = () => {
39
+ setIsFullscreen(!!document.fullscreenElement);
40
+ };
41
+ document.addEventListener('fullscreenchange', handleFullscreenChange);
42
+ document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
43
+ return () => {
44
+ document.removeEventListener('fullscreenchange', handleFullscreenChange);
45
+ document.removeEventListener('webkitfullscreenchange', handleFullscreenChange);
46
+ };
47
+ }, []);
48
 
49
  // Clear pendingCard when relevant modals close (not target-select or card-type-select)
50
  useEffect(() => {
 
145
 
146
  return (
147
  <div
148
+ className="game-board flex-1 flex flex-col h-full"
149
  style={{
150
+ backgroundImage: 'url(/Assests/menu_background.png)',
151
  backgroundSize: 'cover',
152
  backgroundPosition: 'center',
153
  }}
154
  >
155
+ {/* Fullscreen button - mobile only */}
156
+ {!isFullscreen && (
157
+ <button
158
+ onClick={requestFullscreen}
159
+ className="fixed top-2 right-2 z-50 bg-black/60 text-papyrus px-2 py-1 rounded text-xs md:hidden"
160
+ >
161
+ ⛶ Fullscreen
162
+ </button>
163
+ )}
164
+
165
  {/* Top area - Other players */}
166
+ <div className="flex justify-center gap-2 md:gap-4 p-1 md:p-2 flex-shrink-0">
167
  {otherPlayers.map((player) => (
168
  <PlayerDisplay
169
  key={player.id}
 
174
  </div>
175
 
176
  {/* Middle area - Deck and discard */}
177
+ <div className="flex-1 flex items-center justify-center gap-4 md:gap-8 min-h-0">
178
  {/* Deck */}
179
  <motion.div
180
+ className="deck-pile scale-75 md:scale-100"
181
  data-count={gameState.deckCount}
182
  whileHover={isMyTurn ? { scale: 1.05 } : {}}
183
  onClick={handleDrawCard}
184
  >
185
  <CardBack size="large" />
186
+ <p className="text-center text-papyrus text-xs md:text-base mt-1">Deck</p>
187
  </motion.div>
188
 
189
  {/* Turn info */}
 
192
  key={currentPlayer?.id}
193
  initial={{ opacity: 0, scale: 0.8 }}
194
  animate={{ opacity: 1, scale: 1 }}
195
+ className="bg-black/60 rounded-lg px-3 py-2 md:px-6 md:py-3"
196
  >
197
+ <p className="text-papyrus text-xs md:text-base mb-1">
198
  {currentPlayer?.id === playerId ? "Your Turn!" : `${currentPlayer?.name}'s Turn`}
199
  </p>
200
  {turnsRemaining > 1 && (
201
+ <p className="text-egyptian-gold text-xs">
202
  {turnsRemaining} turns remaining
203
  </p>
204
  )}
 
206
  </div>
207
 
208
  {/* Discard pile */}
209
+ <div className="relative scale-75 md:scale-100">
210
  {gameState.discardPile.length > 0 ? (
211
  <Card
212
  cardId={gameState.discardPile[gameState.discardPile.length - 1]}
 
214
  disabled
215
  />
216
  ) : (
217
+ <div className="w-24 h-36 md:w-32 md:h-48 border-2 border-dashed border-papyrus/30 rounded-lg flex items-center justify-center">
218
+ <p className="text-papyrus/40 text-center text-xs">Discard</p>
219
  </div>
220
  )}
221
+ <p className="text-center text-papyrus text-xs md:text-base mt-1">Discard ({gameState.discardPile.length})</p>
222
  </div>
223
  </div>
224
 
225
  {/* Bottom area - My hand */}
226
+ <div className="bg-black/50 backdrop-blur-sm p-2 md:p-4 flex-shrink-0">
227
  {/* My player info */}
228
+ <div className="flex items-center justify-between mb-1 md:mb-2">
229
+ <div className="flex items-center gap-1 md:gap-2">
230
+ <div className={`player-avatar w-6 h-6 md:w-12 md:h-12 text-xs md:text-lg ${isMyTurn ? 'current-turn' : ''}`}>
231
  {myPlayer?.name.charAt(0).toUpperCase()}
232
  </div>
233
+ <span className="text-papyrus text-xs md:text-base">{myPlayer?.name}</span>
234
  </div>
235
+ <span className="text-egyptian-gold text-xs md:text-base">{myHand.length} cards</span>
236
  </div>
237
 
238
  {/* Hand */}
 
265
  {/* Draw button for mobile */}
266
  {isMyTurn && (
267
  <motion.button
268
+ className="btn btn-primary w-full mt-1 md:mt-2 py-2 text-sm md:text-base"
269
  onClick={handleDrawCard}
270
  whileTap={{ scale: 0.95 }}
271
  >
272
+ Draw Card
273
  </motion.button>
274
  )}
275
  </div>
 
285
  function PlayerDisplay({ player, isCurrentTurn }: PlayerDisplayProps) {
286
  return (
287
  <motion.div
288
+ className={`bg-black/50 rounded-lg p-1 md:p-2 ${isCurrentTurn ? 'ring-2 ring-green-500' : ''}`}
289
  animate={isCurrentTurn ? { scale: [1, 1.02, 1] } : {}}
290
  transition={{ repeat: isCurrentTurn ? Infinity : 0, duration: 1.5 }}
291
  >
292
+ <div className="flex items-center gap-1 md:gap-2 mb-1">
293
+ <div className={`player-avatar w-5 h-5 md:w-8 md:h-8 text-xs md:text-sm ${!player.isAlive ? 'eliminated' : ''} ${isCurrentTurn ? 'current-turn' : ''}`}>
294
  {player.name.charAt(0).toUpperCase()}
295
  </div>
296
  <div>
297
+ <p className={`text-xs md:text-sm ${player.isAlive ? 'text-papyrus' : 'text-papyrus/50 line-through'}`}>
298
  {player.name}
299
  </p>
300
+ {!player.isAlive && <p className="text-mummy-red text-xs">☠️</p>}
301
  </div>
302
  </div>
303
 
304
  {player.isAlive && (
305
+ <div className="flex gap-0.5 justify-center">
306
+ {Array.from({ length: Math.min(player.cardCount, 6) }).map((_, i) => (
307
  <div
308
  key={i}
309
+ className="w-2 h-3 md:w-4 md:h-6 bg-gradient-to-br from-egyptian-gold to-sand rounded shadow-sm"
310
  />
311
  ))}
312
+ {player.cardCount > 6 && (
313
+ <span className="text-egyptian-gold text-xs">+{player.cardCount - 6}</span>
314
  )}
315
  </div>
316
  )}
client/src/components/screens/Lobby.tsx CHANGED
@@ -46,9 +46,9 @@ export function Lobby() {
46
 
47
  return (
48
  <div
49
- className="flex-1 flex flex-col items-center justify-center p-4"
50
  style={{
51
- backgroundImage: 'url(/menu_background.png)',
52
  backgroundSize: 'cover',
53
  backgroundPosition: 'center',
54
  }}
@@ -56,24 +56,24 @@ export function Lobby() {
56
  <motion.div
57
  initial={{ opacity: 0, scale: 0.9 }}
58
  animate={{ opacity: 1, scale: 1 }}
59
- className="bg-black/70 backdrop-blur-sm rounded-2xl p-6 max-w-lg w-full max-h-[90vh] overflow-y-auto"
60
  >
61
  {/* Header */}
62
- <div className="flex items-center justify-between mb-6">
63
- <button onClick={handleBack} className="text-egyptian-gold hover:text-yellow-400">
64
  ← Back
65
  </button>
66
- <h1 className="text-2xl font-bold text-egyptian-gold">Lobby</h1>
67
- <span className="text-papyrus">{playerName}</span>
68
  </div>
69
 
70
  {/* Tabs */}
71
- <div className="flex border-b border-egyptian-gold/30 mb-6">
72
  {(['create', 'join', 'browse'] as const).map((tab) => (
73
  <button
74
  key={tab}
75
  onClick={() => setActiveTab(tab)}
76
- className={`flex-1 py-2 text-center capitalize transition-colors ${
77
  activeTab === tab
78
  ? 'text-egyptian-gold border-b-2 border-egyptian-gold'
79
  : 'text-papyrus/60 hover:text-papyrus'
@@ -90,10 +90,10 @@ export function Lobby() {
90
  initial={{ opacity: 0, x: -20 }}
91
  animate={{ opacity: 1, x: 0 }}
92
  >
93
- <p className="text-papyrus mb-4">Create a new room for your friends to join.</p>
94
 
95
- <div className="mb-4">
96
- <label className="block text-papyrus mb-2">Room Name</label>
97
  <input
98
  type="text"
99
  value={roomName}
 
46
 
47
  return (
48
  <div
49
+ className="flex-1 flex flex-col items-center justify-center p-2 md:p-4 overflow-hidden"
50
  style={{
51
+ backgroundImage: 'url(/Assests/menu_background.png)',
52
  backgroundSize: 'cover',
53
  backgroundPosition: 'center',
54
  }}
 
56
  <motion.div
57
  initial={{ opacity: 0, scale: 0.9 }}
58
  animate={{ opacity: 1, scale: 1 }}
59
+ className="bg-black/70 backdrop-blur-sm rounded-2xl p-3 md:p-6 max-w-lg w-full max-h-[90vh] overflow-y-auto"
60
  >
61
  {/* Header */}
62
+ <div className="flex items-center justify-between mb-3 md:mb-6">
63
+ <button onClick={handleBack} className="text-egyptian-gold hover:text-yellow-400 text-sm md:text-base">
64
  ← Back
65
  </button>
66
+ <h1 className="text-lg md:text-2xl font-bold text-egyptian-gold">Lobby</h1>
67
+ <span className="text-papyrus text-xs md:text-base">{playerName}</span>
68
  </div>
69
 
70
  {/* Tabs */}
71
+ <div className="flex border-b border-egyptian-gold/30 mb-3 md:mb-6">
72
  {(['create', 'join', 'browse'] as const).map((tab) => (
73
  <button
74
  key={tab}
75
  onClick={() => setActiveTab(tab)}
76
+ className={`flex-1 py-1 md:py-2 text-center capitalize transition-colors text-sm md:text-base ${
77
  activeTab === tab
78
  ? 'text-egyptian-gold border-b-2 border-egyptian-gold'
79
  : 'text-papyrus/60 hover:text-papyrus'
 
90
  initial={{ opacity: 0, x: -20 }}
91
  animate={{ opacity: 1, x: 0 }}
92
  >
93
+ <p className="text-papyrus text-sm md:text-base mb-2 md:mb-4">Create a new room for your friends to join.</p>
94
 
95
+ <div className="mb-3 md:mb-4">
96
+ <label className="block text-papyrus text-sm mb-1 md:mb-2">Room Name</label>
97
  <input
98
  type="text"
99
  value={roomName}
client/src/components/screens/MainMenu.tsx CHANGED
@@ -19,9 +19,9 @@ export function MainMenu() {
19
 
20
  return (
21
  <div
22
- className="flex-1 flex flex-col items-center justify-center p-4"
23
  style={{
24
- backgroundImage: 'url(/menu_background.png)',
25
  backgroundSize: 'cover',
26
  backgroundPosition: 'center',
27
  }}
@@ -30,13 +30,13 @@ export function MainMenu() {
30
  initial={{ opacity: 0, y: -50 }}
31
  animate={{ opacity: 1, y: 0 }}
32
  transition={{ duration: 0.5 }}
33
- className="bg-black/60 backdrop-blur-sm rounded-2xl p-8 max-w-md w-full"
34
  >
35
  {/* Logo */}
36
  <motion.img
37
- src="/ui_logo.png"
38
- alt="Mummy Card Game"
39
- className="w-48 h-48 mx-auto mb-6 object-contain"
40
  initial={{ scale: 0 }}
41
  animate={{ scale: 1, rotate: [0, -5, 5, 0] }}
42
  transition={{ duration: 0.5, delay: 0.2 }}
@@ -45,30 +45,30 @@ export function MainMenu() {
45
  }}
46
  />
47
 
48
- <h1 className="text-3xl font-bold text-center text-egyptian-gold mb-2">
49
- Mummy Card Game
50
  </h1>
51
- <h2 className="text-xl text-center text-papyrus mb-8" dir="rtl">
52
  هتتحنط هنا
53
  </h2>
54
 
55
  {/* Connection status */}
56
- <div className="flex items-center justify-center gap-2 mb-6">
57
- <span className={`w-3 h-3 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'}`} />
58
- <span className="text-sm text-papyrus">
59
- {isConnected ? 'Connected to server' : 'Connecting...'}
60
  </span>
61
  </div>
62
 
63
  {/* Name input */}
64
- <div className="mb-6">
65
- <label className="block text-papyrus mb-2">Your Name</label>
66
  <input
67
  type="text"
68
  value={playerName}
69
  onChange={(e) => setPlayerName(e.target.value)}
70
  placeholder="Enter your name..."
71
- className="w-full"
72
  maxLength={20}
73
  onKeyDown={(e) => e.key === 'Enter' && handlePlay()}
74
  />
@@ -76,7 +76,7 @@ export function MainMenu() {
76
 
77
  {/* Play button */}
78
  <motion.button
79
- className="btn btn-primary w-full text-xl"
80
  onClick={handlePlay}
81
  disabled={!isConnected}
82
  whileHover={{ scale: 1.02 }}
 
19
 
20
  return (
21
  <div
22
+ className="flex-1 flex flex-col items-center justify-center p-2 md:p-4 overflow-hidden"
23
  style={{
24
+ backgroundImage: 'url(/Assests/menu_background.png)',
25
  backgroundSize: 'cover',
26
  backgroundPosition: 'center',
27
  }}
 
30
  initial={{ opacity: 0, y: -50 }}
31
  animate={{ opacity: 1, y: 0 }}
32
  transition={{ duration: 0.5 }}
33
+ className="bg-black/60 backdrop-blur-sm rounded-2xl p-4 md:p-8 max-w-md w-full"
34
  >
35
  {/* Logo */}
36
  <motion.img
37
+ src="/Assests/ui_logo.png"
38
+ alt="Khofo Card Game"
39
+ className="w-24 h-24 md:w-48 md:h-48 mx-auto mb-3 md:mb-6 object-contain"
40
  initial={{ scale: 0 }}
41
  animate={{ scale: 1, rotate: [0, -5, 5, 0] }}
42
  transition={{ duration: 0.5, delay: 0.2 }}
 
45
  }}
46
  />
47
 
48
+ <h1 className="text-xl md:text-3xl font-bold text-center text-egyptian-gold mb-1 md:mb-2">
49
+ Khofo Card Game
50
  </h1>
51
+ <h2 className="text-base md:text-xl text-center text-papyrus mb-4 md:mb-8" dir="rtl">
52
  هتتحنط هنا
53
  </h2>
54
 
55
  {/* Connection status */}
56
+ <div className="flex items-center justify-center gap-2 mb-3 md:mb-6">
57
+ <span className={`w-2 h-2 md:w-3 md:h-3 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'}`} />
58
+ <span className="text-xs md:text-sm text-papyrus">
59
+ {isConnected ? 'Connected' : 'Connecting...'}
60
  </span>
61
  </div>
62
 
63
  {/* Name input */}
64
+ <div className="mb-4 md:mb-6">
65
+ <label className="block text-papyrus text-sm md:text-base mb-1 md:mb-2">Your Name</label>
66
  <input
67
  type="text"
68
  value={playerName}
69
  onChange={(e) => setPlayerName(e.target.value)}
70
  placeholder="Enter your name..."
71
+ className="w-full text-sm md:text-base"
72
  maxLength={20}
73
  onKeyDown={(e) => e.key === 'Enter' && handlePlay()}
74
  />
 
76
 
77
  {/* Play button */}
78
  <motion.button
79
+ className="btn btn-primary w-full text-base md:text-xl py-2 md:py-3"
80
  onClick={handlePlay}
81
  disabled={!isConnected}
82
  whileHover={{ scale: 1.02 }}
client/src/components/screens/Room.tsx CHANGED
@@ -36,9 +36,9 @@ export function Room() {
36
 
37
  return (
38
  <div
39
- className="flex-1 flex flex-col items-center justify-center p-4"
40
  style={{
41
- backgroundImage: 'url(/menu_background.png)',
42
  backgroundSize: 'cover',
43
  backgroundPosition: 'center',
44
  }}
@@ -46,15 +46,15 @@ export function Room() {
46
  <motion.div
47
  initial={{ opacity: 0, scale: 0.9 }}
48
  animate={{ opacity: 1, scale: 1 }}
49
- className="bg-black/70 backdrop-blur-sm rounded-2xl p-6 max-w-lg w-full"
50
  >
51
  {/* Header */}
52
- <div className="flex items-center justify-between mb-4">
53
- <button onClick={handleLeave} className="text-egyptian-gold hover:text-yellow-400">
54
  ← Leave
55
  </button>
56
- <h1 className="text-xl font-bold text-egyptian-gold">{currentRoom.name}</h1>
57
- <span className="text-papyrus/60">
58
  {currentRoom.players.length}/{currentRoom.maxPlayers}
59
  </span>
60
  </div>
 
36
 
37
  return (
38
  <div
39
+ className="flex-1 flex flex-col items-center justify-center p-2 md:p-4 overflow-hidden"
40
  style={{
41
+ backgroundImage: 'url(/Assests/menu_background.png)',
42
  backgroundSize: 'cover',
43
  backgroundPosition: 'center',
44
  }}
 
46
  <motion.div
47
  initial={{ opacity: 0, scale: 0.9 }}
48
  animate={{ opacity: 1, scale: 1 }}
49
+ className="bg-black/70 backdrop-blur-sm rounded-2xl p-3 md:p-6 max-w-lg w-full"
50
  >
51
  {/* Header */}
52
+ <div className="flex items-center justify-between mb-2 md:mb-4">
53
+ <button onClick={handleLeave} className="text-egyptian-gold hover:text-yellow-400 text-sm md:text-base">
54
  ← Leave
55
  </button>
56
+ <h1 className="text-base md:text-xl font-bold text-egyptian-gold truncate max-w-[150px] md:max-w-none">{currentRoom.name}</h1>
57
+ <span className="text-papyrus/60 text-sm md:text-base">
58
  {currentRoom.players.length}/{currentRoom.maxPlayers}
59
  </span>
60
  </div>
client/src/index.css CHANGED
@@ -17,6 +17,8 @@ html, body {
17
  touch-action: none;
18
  user-select: none;
19
  -webkit-user-select: none;
 
 
20
  }
21
 
22
  body {
@@ -25,13 +27,23 @@ body {
25
  color: #f5f5dc;
26
  min-height: 100vh;
27
  min-height: 100dvh;
 
 
 
 
 
 
 
 
28
  }
29
 
30
  #root {
31
  min-height: 100vh;
32
  min-height: 100dvh;
 
33
  display: flex;
34
  flex-direction: column;
 
35
  }
36
 
37
  /* Landscape lock overlay */
@@ -144,16 +156,17 @@ body {
144
  .modal-overlay {
145
  @apply fixed inset-0 bg-black/70 flex items-center justify-center z-50;
146
  @apply backdrop-blur-sm;
 
147
  }
148
 
149
  .modal-content {
150
- @apply bg-gradient-to-br from-nile-blue to-gray-900 rounded-xl p-6;
151
  @apply border-2 border-egyptian-gold shadow-2xl;
152
- @apply max-w-lg w-full mx-4 max-h-[90vh] overflow-y-auto;
153
  }
154
 
155
  .modal-title {
156
- @apply text-xl font-bold text-egyptian-gold mb-4 text-center;
157
  }
158
 
159
  /* Toast/Notification styles */
@@ -181,12 +194,12 @@ body {
181
 
182
  /* Player avatar */
183
  .player-avatar {
184
- @apply w-12 h-12 rounded-full bg-egyptian-gold flex items-center justify-center;
185
- @apply text-nile-blue font-bold text-lg;
186
  }
187
 
188
  .player-avatar.current-turn {
189
- @apply ring-4 ring-green-500 animate-pulse;
190
  }
191
 
192
  .player-avatar.eliminated {
@@ -206,35 +219,56 @@ body {
206
 
207
  /* Game board */
208
  .game-board {
209
- @apply flex-1 flex flex-col justify-between p-4;
210
  background-size: cover;
211
  background-position: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  }
213
 
214
  /* Hand area */
215
  .hand-area {
216
- @apply flex justify-center items-end gap-1 p-2;
217
  max-width: 100%;
218
- flex-wrap: wrap;
219
- overflow: hidden;
 
 
 
 
 
 
 
220
  }
221
 
222
  .hand-area .card {
223
  @apply flex-shrink-0;
224
- width: 60px;
225
- max-width: 80px;
 
226
  }
227
 
228
  @media (min-width: 768px) {
229
  .hand-area {
230
  @apply gap-2 p-4;
231
- flex-wrap: nowrap;
232
  overflow-x: auto;
233
  }
234
 
235
  .hand-area .card {
236
  width: 100px;
237
  max-width: 120px;
 
238
  }
239
  }
240
 
 
17
  touch-action: none;
18
  user-select: none;
19
  -webkit-user-select: none;
20
+ /* Support safe areas for notch/home indicator */
21
+ padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
22
  }
23
 
24
  body {
 
27
  color: #f5f5dc;
28
  min-height: 100vh;
29
  min-height: 100dvh;
30
+ /* Fullscreen mobile */
31
+ position: fixed;
32
+ top: 0;
33
+ left: 0;
34
+ right: 0;
35
+ bottom: 0;
36
+ width: 100%;
37
+ height: 100%;
38
  }
39
 
40
  #root {
41
  min-height: 100vh;
42
  min-height: 100dvh;
43
+ height: 100%;
44
  display: flex;
45
  flex-direction: column;
46
+ overflow: hidden;
47
  }
48
 
49
  /* Landscape lock overlay */
 
156
  .modal-overlay {
157
  @apply fixed inset-0 bg-black/70 flex items-center justify-center z-50;
158
  @apply backdrop-blur-sm;
159
+ padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
160
  }
161
 
162
  .modal-content {
163
+ @apply bg-gradient-to-br from-nile-blue to-gray-900 rounded-xl p-3 md:p-6;
164
  @apply border-2 border-egyptian-gold shadow-2xl;
165
+ @apply max-w-lg w-full mx-2 md:mx-4 max-h-[85vh] overflow-y-auto;
166
  }
167
 
168
  .modal-title {
169
+ @apply text-base md:text-xl font-bold text-egyptian-gold mb-2 md:mb-4 text-center;
170
  }
171
 
172
  /* Toast/Notification styles */
 
194
 
195
  /* Player avatar */
196
  .player-avatar {
197
+ @apply rounded-full bg-egyptian-gold flex items-center justify-center flex-shrink-0;
198
+ @apply text-nile-blue font-bold;
199
  }
200
 
201
  .player-avatar.current-turn {
202
+ @apply ring-2 md:ring-4 ring-green-500 animate-pulse;
203
  }
204
 
205
  .player-avatar.eliminated {
 
219
 
220
  /* Game board */
221
  .game-board {
222
+ @apply flex-1 flex flex-col justify-between;
223
  background-size: cover;
224
  background-position: center;
225
+ height: 100%;
226
+ overflow: hidden;
227
+ padding: 4px;
228
+ padding-top: max(4px, env(safe-area-inset-top));
229
+ padding-bottom: max(4px, env(safe-area-inset-bottom));
230
+ padding-left: max(4px, env(safe-area-inset-left));
231
+ padding-right: max(4px, env(safe-area-inset-right));
232
+ }
233
+
234
+ @media (min-width: 768px) {
235
+ .game-board {
236
+ padding: 16px;
237
+ }
238
  }
239
 
240
  /* Hand area */
241
  .hand-area {
242
+ @apply flex justify-center items-end gap-0 p-1;
243
  max-width: 100%;
244
+ flex-wrap: nowrap;
245
+ overflow-x: auto;
246
+ overflow-y: hidden;
247
+ -webkit-overflow-scrolling: touch;
248
+ scrollbar-width: none;
249
+ }
250
+
251
+ .hand-area::-webkit-scrollbar {
252
+ display: none;
253
  }
254
 
255
  .hand-area .card {
256
  @apply flex-shrink-0;
257
+ width: 50px;
258
+ max-width: 60px;
259
+ margin: 0 -4px;
260
  }
261
 
262
  @media (min-width: 768px) {
263
  .hand-area {
264
  @apply gap-2 p-4;
 
265
  overflow-x: auto;
266
  }
267
 
268
  .hand-area .card {
269
  width: 100px;
270
  max-width: 120px;
271
+ margin: 0;
272
  }
273
  }
274