Tantawi65 commited on
Commit
a7b6b46
·
0 Parent(s):

Initial commit

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +2 -0
  2. .gitignore +40 -0
  3. Assests/card_all_or_nothing.png +3 -0
  4. Assests/card_back.png +3 -0
  5. Assests/card_criminal_mummy.png +3 -0
  6. Assests/card_flip_the_table.png +3 -0
  7. Assests/card_give_and_take.png +3 -0
  8. Assests/card_king_ra_says_no.png +3 -0
  9. Assests/card_me_or_you.png.png +3 -0
  10. Assests/card_mummy.png +3 -0
  11. Assests/card_safe_travels.png +3 -0
  12. Assests/card_sharp_eye.png +3 -0
  13. Assests/card_shuffle_it.png +3 -0
  14. Assests/card_spellbound.png +3 -0
  15. Assests/card_take_a_lap.png +3 -0
  16. Assests/card_this_is_on_you.png +3 -0
  17. Assests/card_wait_a_sec.png +3 -0
  18. Assests/menu_background.png +3 -0
  19. Assests/menu_background_old.png +3 -0
  20. Assests/ui_button.png +3 -0
  21. Assests/ui_logo.png +3 -0
  22. client/index.html +34 -0
  23. client/package-lock.json +2426 -0
  24. client/package.json +28 -0
  25. client/postcss.config.js +6 -0
  26. client/src/App.tsx +51 -0
  27. client/src/components/game/Card.tsx +111 -0
  28. client/src/components/modals/ArrangeHandModal.tsx +124 -0
  29. client/src/components/modals/BlindStealModal.tsx +62 -0
  30. client/src/components/modals/CardTypeSelectModal.tsx +72 -0
  31. client/src/components/modals/DuelResultModal.tsx +109 -0
  32. client/src/components/modals/FlipTableModal.tsx +114 -0
  33. client/src/components/modals/GameOverModal.tsx +124 -0
  34. client/src/components/modals/KingRaPromptModal.tsx +138 -0
  35. client/src/components/modals/ModalRenderer.tsx +91 -0
  36. client/src/components/modals/PeekCardsModal.tsx +57 -0
  37. client/src/components/modals/PlaceMummyModal.tsx +137 -0
  38. client/src/components/modals/SwapResultModal.tsx +62 -0
  39. client/src/components/modals/TargetSelectModal.tsx +68 -0
  40. client/src/components/modals/ViewHandModal.tsx +81 -0
  41. client/src/components/screens/GameBoard.tsx +283 -0
  42. client/src/components/screens/Lobby.tsx +185 -0
  43. client/src/components/screens/MainMenu.tsx +90 -0
  44. client/src/components/screens/Room.tsx +161 -0
  45. client/src/components/ui/LandscapeOverlay.tsx +32 -0
  46. client/src/components/ui/MummyEventOverlay.tsx +100 -0
  47. client/src/components/ui/ToastContainer.tsx +26 -0
  48. client/src/index.css +281 -0
  49. client/src/main.tsx +10 -0
  50. client/src/socket/socket.ts +330 -0
.gitattributes ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ *.png filter=lfs diff=lfs merge=lfs -text
2
+ client/public/Assests/*.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dependencies
2
+ node_modules/
3
+ .pnp
4
+ .pnp.js
5
+
6
+ # Build outputs
7
+ client/dist/
8
+ server/dist/
9
+
10
+ # Environment files
11
+ .env
12
+ .env.local
13
+ .env.development.local
14
+ .env.test.local
15
+ .env.production.local
16
+
17
+ # Logs
18
+ npm-debug.log*
19
+ yarn-debug.log*
20
+ yarn-error.log*
21
+
22
+ # IDE
23
+ .vscode/
24
+ .idea/
25
+ *.swp
26
+ *.swo
27
+
28
+ # OS
29
+ .DS_Store
30
+ Thumbs.db
31
+
32
+ # TypeScript
33
+ *.tsbuildinfo
34
+
35
+ # Testing
36
+ coverage/
37
+
38
+ # Temp
39
+ *.tmp
40
+ *.temp
Assests/card_all_or_nothing.png ADDED

Git LFS Details

  • SHA256: 0690f18de06f83335dcd3ba07d4a06bb48a4e05e924844f8159b728060ef76b2
  • Pointer size: 131 Bytes
  • Size of remote file: 321 kB
Assests/card_back.png ADDED

Git LFS Details

  • SHA256: d294014849a4e23e2054ef43a4215fe6b0c9496566a8bc1335ae22c8bcde6ef3
  • Pointer size: 130 Bytes
  • Size of remote file: 29 kB
Assests/card_criminal_mummy.png ADDED

Git LFS Details

  • SHA256: 918b9aea9a56aa39a7d3f176d1e612788a449dc05c3dbef8f06383884e646539
  • Pointer size: 131 Bytes
  • Size of remote file: 319 kB
Assests/card_flip_the_table.png ADDED

Git LFS Details

  • SHA256: 7b76b8c56a28a8df57e617c6a78f4645ae56f77a515a12f68b37fe3e5612e392
  • Pointer size: 131 Bytes
  • Size of remote file: 321 kB
Assests/card_give_and_take.png ADDED

Git LFS Details

  • SHA256: 4ef9c38b8e389f579121f3f1b85da6b707b7a0c32e97b3f2b621076175f0b36c
  • Pointer size: 131 Bytes
  • Size of remote file: 321 kB
Assests/card_king_ra_says_no.png ADDED

Git LFS Details

  • SHA256: f07792dd2d7467fa6a16cf8c2e3cef6be999429e3a7d0bf85cf4a0cddf8e5392
  • Pointer size: 131 Bytes
  • Size of remote file: 321 kB
Assests/card_me_or_you.png.png ADDED

Git LFS Details

  • SHA256: 2e17124016c04444f974f5aa4f4fa30e9cc3e179aa707b953af4b783e18520f7
  • Pointer size: 131 Bytes
  • Size of remote file: 323 kB
Assests/card_mummy.png ADDED

Git LFS Details

  • SHA256: dfb39f07dba40122e6dea960307255553bc139821c8f2240cbc38f923dcd09bb
  • Pointer size: 131 Bytes
  • Size of remote file: 321 kB
Assests/card_safe_travels.png ADDED

Git LFS Details

  • SHA256: edacf1cd263c427b268c8df85b7d27d6deeab422bcaf8ae2a43b926cea070e60
  • Pointer size: 131 Bytes
  • Size of remote file: 321 kB
Assests/card_sharp_eye.png ADDED

Git LFS Details

  • SHA256: c84d6fd20e5937515558353a8ed9a39149475f19431c4fd2832f68516a1ff0b3
  • Pointer size: 131 Bytes
  • Size of remote file: 322 kB
Assests/card_shuffle_it.png ADDED

Git LFS Details

  • SHA256: d2a49f03e994ed6cf1dd8e3c55f9c4baca7fcaac97aa19112b92545d6660e45d
  • Pointer size: 131 Bytes
  • Size of remote file: 320 kB
Assests/card_spellbound.png ADDED

Git LFS Details

  • SHA256: ded909785128761f4f5a19fab39e5b05e41db6eefe12cf169bb48b6000321267
  • Pointer size: 131 Bytes
  • Size of remote file: 321 kB
Assests/card_take_a_lap.png ADDED

Git LFS Details

  • SHA256: 8f2d1f5cddd0ad4df7b25ff1cbdb18d5b8d549f6ed465eddb3dff060b01e9e54
  • Pointer size: 131 Bytes
  • Size of remote file: 322 kB
Assests/card_this_is_on_you.png ADDED

Git LFS Details

  • SHA256: acbbfbb51df15671db46588fcc7137facef6c9a853a3bcad8b52f65e065a332f
  • Pointer size: 131 Bytes
  • Size of remote file: 323 kB
Assests/card_wait_a_sec.png ADDED

Git LFS Details

  • SHA256: 35989714a07df7046ae7e70f78f6bbc4a18e5bfe4f375721238fd501fc4c5d3f
  • Pointer size: 131 Bytes
  • Size of remote file: 321 kB
Assests/menu_background.png ADDED

Git LFS Details

  • SHA256: 93878ed4802b269056ab87816a216683ced847697076bd707a66f8ca7ea3446b
  • Pointer size: 132 Bytes
  • Size of remote file: 8.46 MB
Assests/menu_background_old.png ADDED

Git LFS Details

  • SHA256: a29c272f332937823a8a7e44a7d3282e0acd808cf3df65a970cce1b43c945b87
  • Pointer size: 132 Bytes
  • Size of remote file: 8.32 MB
Assests/ui_button.png ADDED

Git LFS Details

  • SHA256: 8788102ef3b46ddb0c467eeeb9a5d4c7d67074063832fa339dbdc8998fca2feb
  • Pointer size: 131 Bytes
  • Size of remote file: 312 kB
Assests/ui_logo.png ADDED

Git LFS Details

  • SHA256: 0672ca6041db16db0f883aab6fc63f9034f8b53d29bb8aa6b320cb73b687fbdc
  • Pointer size: 131 Bytes
  • Size of remote file: 297 kB
client/index.html ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
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 {
16
+ background-color: #1a1a2e;
17
+ margin: 0;
18
+ padding: 0;
19
+ }
20
+ </style>
21
+ </head>
22
+ <body>
23
+ <div id="root"></div>
24
+ <script type="module" src="/src/main.tsx"></script>
25
+ <script>
26
+ // Force landscape on mobile
27
+ if (screen.orientation && screen.orientation.lock) {
28
+ screen.orientation.lock('landscape').catch(() => {
29
+ console.log('Orientation lock not supported');
30
+ });
31
+ }
32
+ </script>
33
+ </body>
34
+ </html>
client/package-lock.json ADDED
@@ -0,0 +1,2426 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "mummy-card-game-client",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "mummy-card-game-client",
9
+ "version": "1.0.0",
10
+ "dependencies": {
11
+ "framer-motion": "^10.16.4",
12
+ "react": "^18.2.0",
13
+ "react-dom": "^18.2.0",
14
+ "socket.io-client": "^4.7.2",
15
+ "zustand": "^4.4.1"
16
+ },
17
+ "devDependencies": {
18
+ "@types/react": "^18.2.28",
19
+ "@types/react-dom": "^18.2.13",
20
+ "@vitejs/plugin-react": "^4.1.0",
21
+ "autoprefixer": "^10.4.16",
22
+ "postcss": "^8.4.31",
23
+ "tailwindcss": "^3.3.3",
24
+ "typescript": "^5.2.2",
25
+ "vite": "^4.4.11"
26
+ }
27
+ },
28
+ "node_modules/@alloc/quick-lru": {
29
+ "version": "5.2.0",
30
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
31
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
32
+ "dev": true,
33
+ "license": "MIT",
34
+ "engines": {
35
+ "node": ">=10"
36
+ },
37
+ "funding": {
38
+ "url": "https://github.com/sponsors/sindresorhus"
39
+ }
40
+ },
41
+ "node_modules/@babel/code-frame": {
42
+ "version": "7.29.0",
43
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
44
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
45
+ "dev": true,
46
+ "license": "MIT",
47
+ "dependencies": {
48
+ "@babel/helper-validator-identifier": "^7.28.5",
49
+ "js-tokens": "^4.0.0",
50
+ "picocolors": "^1.1.1"
51
+ },
52
+ "engines": {
53
+ "node": ">=6.9.0"
54
+ }
55
+ },
56
+ "node_modules/@babel/compat-data": {
57
+ "version": "7.29.0",
58
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
59
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
60
+ "dev": true,
61
+ "license": "MIT",
62
+ "engines": {
63
+ "node": ">=6.9.0"
64
+ }
65
+ },
66
+ "node_modules/@babel/core": {
67
+ "version": "7.29.0",
68
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
69
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
70
+ "dev": true,
71
+ "license": "MIT",
72
+ "dependencies": {
73
+ "@babel/code-frame": "^7.29.0",
74
+ "@babel/generator": "^7.29.0",
75
+ "@babel/helper-compilation-targets": "^7.28.6",
76
+ "@babel/helper-module-transforms": "^7.28.6",
77
+ "@babel/helpers": "^7.28.6",
78
+ "@babel/parser": "^7.29.0",
79
+ "@babel/template": "^7.28.6",
80
+ "@babel/traverse": "^7.29.0",
81
+ "@babel/types": "^7.29.0",
82
+ "@jridgewell/remapping": "^2.3.5",
83
+ "convert-source-map": "^2.0.0",
84
+ "debug": "^4.1.0",
85
+ "gensync": "^1.0.0-beta.2",
86
+ "json5": "^2.2.3",
87
+ "semver": "^6.3.1"
88
+ },
89
+ "engines": {
90
+ "node": ">=6.9.0"
91
+ },
92
+ "funding": {
93
+ "type": "opencollective",
94
+ "url": "https://opencollective.com/babel"
95
+ }
96
+ },
97
+ "node_modules/@babel/generator": {
98
+ "version": "7.29.1",
99
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
100
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
101
+ "dev": true,
102
+ "license": "MIT",
103
+ "dependencies": {
104
+ "@babel/parser": "^7.29.0",
105
+ "@babel/types": "^7.29.0",
106
+ "@jridgewell/gen-mapping": "^0.3.12",
107
+ "@jridgewell/trace-mapping": "^0.3.28",
108
+ "jsesc": "^3.0.2"
109
+ },
110
+ "engines": {
111
+ "node": ">=6.9.0"
112
+ }
113
+ },
114
+ "node_modules/@babel/helper-compilation-targets": {
115
+ "version": "7.28.6",
116
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
117
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
118
+ "dev": true,
119
+ "license": "MIT",
120
+ "dependencies": {
121
+ "@babel/compat-data": "^7.28.6",
122
+ "@babel/helper-validator-option": "^7.27.1",
123
+ "browserslist": "^4.24.0",
124
+ "lru-cache": "^5.1.1",
125
+ "semver": "^6.3.1"
126
+ },
127
+ "engines": {
128
+ "node": ">=6.9.0"
129
+ }
130
+ },
131
+ "node_modules/@babel/helper-globals": {
132
+ "version": "7.28.0",
133
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
134
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
135
+ "dev": true,
136
+ "license": "MIT",
137
+ "engines": {
138
+ "node": ">=6.9.0"
139
+ }
140
+ },
141
+ "node_modules/@babel/helper-module-imports": {
142
+ "version": "7.28.6",
143
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
144
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
145
+ "dev": true,
146
+ "license": "MIT",
147
+ "dependencies": {
148
+ "@babel/traverse": "^7.28.6",
149
+ "@babel/types": "^7.28.6"
150
+ },
151
+ "engines": {
152
+ "node": ">=6.9.0"
153
+ }
154
+ },
155
+ "node_modules/@babel/helper-module-transforms": {
156
+ "version": "7.28.6",
157
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
158
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
159
+ "dev": true,
160
+ "license": "MIT",
161
+ "dependencies": {
162
+ "@babel/helper-module-imports": "^7.28.6",
163
+ "@babel/helper-validator-identifier": "^7.28.5",
164
+ "@babel/traverse": "^7.28.6"
165
+ },
166
+ "engines": {
167
+ "node": ">=6.9.0"
168
+ },
169
+ "peerDependencies": {
170
+ "@babel/core": "^7.0.0"
171
+ }
172
+ },
173
+ "node_modules/@babel/helper-plugin-utils": {
174
+ "version": "7.28.6",
175
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
176
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
177
+ "dev": true,
178
+ "license": "MIT",
179
+ "engines": {
180
+ "node": ">=6.9.0"
181
+ }
182
+ },
183
+ "node_modules/@babel/helper-string-parser": {
184
+ "version": "7.27.1",
185
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
186
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
187
+ "dev": true,
188
+ "license": "MIT",
189
+ "engines": {
190
+ "node": ">=6.9.0"
191
+ }
192
+ },
193
+ "node_modules/@babel/helper-validator-identifier": {
194
+ "version": "7.28.5",
195
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
196
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
197
+ "dev": true,
198
+ "license": "MIT",
199
+ "engines": {
200
+ "node": ">=6.9.0"
201
+ }
202
+ },
203
+ "node_modules/@babel/helper-validator-option": {
204
+ "version": "7.27.1",
205
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
206
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
207
+ "dev": true,
208
+ "license": "MIT",
209
+ "engines": {
210
+ "node": ">=6.9.0"
211
+ }
212
+ },
213
+ "node_modules/@babel/helpers": {
214
+ "version": "7.28.6",
215
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
216
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
217
+ "dev": true,
218
+ "license": "MIT",
219
+ "dependencies": {
220
+ "@babel/template": "^7.28.6",
221
+ "@babel/types": "^7.28.6"
222
+ },
223
+ "engines": {
224
+ "node": ">=6.9.0"
225
+ }
226
+ },
227
+ "node_modules/@babel/parser": {
228
+ "version": "7.29.0",
229
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
230
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
231
+ "dev": true,
232
+ "license": "MIT",
233
+ "dependencies": {
234
+ "@babel/types": "^7.29.0"
235
+ },
236
+ "bin": {
237
+ "parser": "bin/babel-parser.js"
238
+ },
239
+ "engines": {
240
+ "node": ">=6.0.0"
241
+ }
242
+ },
243
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
244
+ "version": "7.27.1",
245
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
246
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
247
+ "dev": true,
248
+ "license": "MIT",
249
+ "dependencies": {
250
+ "@babel/helper-plugin-utils": "^7.27.1"
251
+ },
252
+ "engines": {
253
+ "node": ">=6.9.0"
254
+ },
255
+ "peerDependencies": {
256
+ "@babel/core": "^7.0.0-0"
257
+ }
258
+ },
259
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
260
+ "version": "7.27.1",
261
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
262
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
263
+ "dev": true,
264
+ "license": "MIT",
265
+ "dependencies": {
266
+ "@babel/helper-plugin-utils": "^7.27.1"
267
+ },
268
+ "engines": {
269
+ "node": ">=6.9.0"
270
+ },
271
+ "peerDependencies": {
272
+ "@babel/core": "^7.0.0-0"
273
+ }
274
+ },
275
+ "node_modules/@babel/template": {
276
+ "version": "7.28.6",
277
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
278
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
279
+ "dev": true,
280
+ "license": "MIT",
281
+ "dependencies": {
282
+ "@babel/code-frame": "^7.28.6",
283
+ "@babel/parser": "^7.28.6",
284
+ "@babel/types": "^7.28.6"
285
+ },
286
+ "engines": {
287
+ "node": ">=6.9.0"
288
+ }
289
+ },
290
+ "node_modules/@babel/traverse": {
291
+ "version": "7.29.0",
292
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
293
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
294
+ "dev": true,
295
+ "license": "MIT",
296
+ "dependencies": {
297
+ "@babel/code-frame": "^7.29.0",
298
+ "@babel/generator": "^7.29.0",
299
+ "@babel/helper-globals": "^7.28.0",
300
+ "@babel/parser": "^7.29.0",
301
+ "@babel/template": "^7.28.6",
302
+ "@babel/types": "^7.29.0",
303
+ "debug": "^4.3.1"
304
+ },
305
+ "engines": {
306
+ "node": ">=6.9.0"
307
+ }
308
+ },
309
+ "node_modules/@babel/types": {
310
+ "version": "7.29.0",
311
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
312
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
313
+ "dev": true,
314
+ "license": "MIT",
315
+ "dependencies": {
316
+ "@babel/helper-string-parser": "^7.27.1",
317
+ "@babel/helper-validator-identifier": "^7.28.5"
318
+ },
319
+ "engines": {
320
+ "node": ">=6.9.0"
321
+ }
322
+ },
323
+ "node_modules/@emotion/is-prop-valid": {
324
+ "version": "0.8.8",
325
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
326
+ "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
327
+ "license": "MIT",
328
+ "optional": true,
329
+ "dependencies": {
330
+ "@emotion/memoize": "0.7.4"
331
+ }
332
+ },
333
+ "node_modules/@emotion/memoize": {
334
+ "version": "0.7.4",
335
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
336
+ "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==",
337
+ "license": "MIT",
338
+ "optional": true
339
+ },
340
+ "node_modules/@esbuild/android-arm": {
341
+ "version": "0.18.20",
342
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
343
+ "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
344
+ "cpu": [
345
+ "arm"
346
+ ],
347
+ "dev": true,
348
+ "license": "MIT",
349
+ "optional": true,
350
+ "os": [
351
+ "android"
352
+ ],
353
+ "engines": {
354
+ "node": ">=12"
355
+ }
356
+ },
357
+ "node_modules/@esbuild/android-arm64": {
358
+ "version": "0.18.20",
359
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
360
+ "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
361
+ "cpu": [
362
+ "arm64"
363
+ ],
364
+ "dev": true,
365
+ "license": "MIT",
366
+ "optional": true,
367
+ "os": [
368
+ "android"
369
+ ],
370
+ "engines": {
371
+ "node": ">=12"
372
+ }
373
+ },
374
+ "node_modules/@esbuild/android-x64": {
375
+ "version": "0.18.20",
376
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
377
+ "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
378
+ "cpu": [
379
+ "x64"
380
+ ],
381
+ "dev": true,
382
+ "license": "MIT",
383
+ "optional": true,
384
+ "os": [
385
+ "android"
386
+ ],
387
+ "engines": {
388
+ "node": ">=12"
389
+ }
390
+ },
391
+ "node_modules/@esbuild/darwin-arm64": {
392
+ "version": "0.18.20",
393
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
394
+ "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
395
+ "cpu": [
396
+ "arm64"
397
+ ],
398
+ "dev": true,
399
+ "license": "MIT",
400
+ "optional": true,
401
+ "os": [
402
+ "darwin"
403
+ ],
404
+ "engines": {
405
+ "node": ">=12"
406
+ }
407
+ },
408
+ "node_modules/@esbuild/darwin-x64": {
409
+ "version": "0.18.20",
410
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
411
+ "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
412
+ "cpu": [
413
+ "x64"
414
+ ],
415
+ "dev": true,
416
+ "license": "MIT",
417
+ "optional": true,
418
+ "os": [
419
+ "darwin"
420
+ ],
421
+ "engines": {
422
+ "node": ">=12"
423
+ }
424
+ },
425
+ "node_modules/@esbuild/freebsd-arm64": {
426
+ "version": "0.18.20",
427
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
428
+ "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
429
+ "cpu": [
430
+ "arm64"
431
+ ],
432
+ "dev": true,
433
+ "license": "MIT",
434
+ "optional": true,
435
+ "os": [
436
+ "freebsd"
437
+ ],
438
+ "engines": {
439
+ "node": ">=12"
440
+ }
441
+ },
442
+ "node_modules/@esbuild/freebsd-x64": {
443
+ "version": "0.18.20",
444
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
445
+ "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
446
+ "cpu": [
447
+ "x64"
448
+ ],
449
+ "dev": true,
450
+ "license": "MIT",
451
+ "optional": true,
452
+ "os": [
453
+ "freebsd"
454
+ ],
455
+ "engines": {
456
+ "node": ">=12"
457
+ }
458
+ },
459
+ "node_modules/@esbuild/linux-arm": {
460
+ "version": "0.18.20",
461
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
462
+ "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
463
+ "cpu": [
464
+ "arm"
465
+ ],
466
+ "dev": true,
467
+ "license": "MIT",
468
+ "optional": true,
469
+ "os": [
470
+ "linux"
471
+ ],
472
+ "engines": {
473
+ "node": ">=12"
474
+ }
475
+ },
476
+ "node_modules/@esbuild/linux-arm64": {
477
+ "version": "0.18.20",
478
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
479
+ "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
480
+ "cpu": [
481
+ "arm64"
482
+ ],
483
+ "dev": true,
484
+ "license": "MIT",
485
+ "optional": true,
486
+ "os": [
487
+ "linux"
488
+ ],
489
+ "engines": {
490
+ "node": ">=12"
491
+ }
492
+ },
493
+ "node_modules/@esbuild/linux-ia32": {
494
+ "version": "0.18.20",
495
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
496
+ "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
497
+ "cpu": [
498
+ "ia32"
499
+ ],
500
+ "dev": true,
501
+ "license": "MIT",
502
+ "optional": true,
503
+ "os": [
504
+ "linux"
505
+ ],
506
+ "engines": {
507
+ "node": ">=12"
508
+ }
509
+ },
510
+ "node_modules/@esbuild/linux-loong64": {
511
+ "version": "0.18.20",
512
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
513
+ "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
514
+ "cpu": [
515
+ "loong64"
516
+ ],
517
+ "dev": true,
518
+ "license": "MIT",
519
+ "optional": true,
520
+ "os": [
521
+ "linux"
522
+ ],
523
+ "engines": {
524
+ "node": ">=12"
525
+ }
526
+ },
527
+ "node_modules/@esbuild/linux-mips64el": {
528
+ "version": "0.18.20",
529
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
530
+ "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
531
+ "cpu": [
532
+ "mips64el"
533
+ ],
534
+ "dev": true,
535
+ "license": "MIT",
536
+ "optional": true,
537
+ "os": [
538
+ "linux"
539
+ ],
540
+ "engines": {
541
+ "node": ">=12"
542
+ }
543
+ },
544
+ "node_modules/@esbuild/linux-ppc64": {
545
+ "version": "0.18.20",
546
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
547
+ "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
548
+ "cpu": [
549
+ "ppc64"
550
+ ],
551
+ "dev": true,
552
+ "license": "MIT",
553
+ "optional": true,
554
+ "os": [
555
+ "linux"
556
+ ],
557
+ "engines": {
558
+ "node": ">=12"
559
+ }
560
+ },
561
+ "node_modules/@esbuild/linux-riscv64": {
562
+ "version": "0.18.20",
563
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
564
+ "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
565
+ "cpu": [
566
+ "riscv64"
567
+ ],
568
+ "dev": true,
569
+ "license": "MIT",
570
+ "optional": true,
571
+ "os": [
572
+ "linux"
573
+ ],
574
+ "engines": {
575
+ "node": ">=12"
576
+ }
577
+ },
578
+ "node_modules/@esbuild/linux-s390x": {
579
+ "version": "0.18.20",
580
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
581
+ "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
582
+ "cpu": [
583
+ "s390x"
584
+ ],
585
+ "dev": true,
586
+ "license": "MIT",
587
+ "optional": true,
588
+ "os": [
589
+ "linux"
590
+ ],
591
+ "engines": {
592
+ "node": ">=12"
593
+ }
594
+ },
595
+ "node_modules/@esbuild/linux-x64": {
596
+ "version": "0.18.20",
597
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
598
+ "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
599
+ "cpu": [
600
+ "x64"
601
+ ],
602
+ "dev": true,
603
+ "license": "MIT",
604
+ "optional": true,
605
+ "os": [
606
+ "linux"
607
+ ],
608
+ "engines": {
609
+ "node": ">=12"
610
+ }
611
+ },
612
+ "node_modules/@esbuild/netbsd-x64": {
613
+ "version": "0.18.20",
614
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
615
+ "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
616
+ "cpu": [
617
+ "x64"
618
+ ],
619
+ "dev": true,
620
+ "license": "MIT",
621
+ "optional": true,
622
+ "os": [
623
+ "netbsd"
624
+ ],
625
+ "engines": {
626
+ "node": ">=12"
627
+ }
628
+ },
629
+ "node_modules/@esbuild/openbsd-x64": {
630
+ "version": "0.18.20",
631
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
632
+ "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
633
+ "cpu": [
634
+ "x64"
635
+ ],
636
+ "dev": true,
637
+ "license": "MIT",
638
+ "optional": true,
639
+ "os": [
640
+ "openbsd"
641
+ ],
642
+ "engines": {
643
+ "node": ">=12"
644
+ }
645
+ },
646
+ "node_modules/@esbuild/sunos-x64": {
647
+ "version": "0.18.20",
648
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
649
+ "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
650
+ "cpu": [
651
+ "x64"
652
+ ],
653
+ "dev": true,
654
+ "license": "MIT",
655
+ "optional": true,
656
+ "os": [
657
+ "sunos"
658
+ ],
659
+ "engines": {
660
+ "node": ">=12"
661
+ }
662
+ },
663
+ "node_modules/@esbuild/win32-arm64": {
664
+ "version": "0.18.20",
665
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
666
+ "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
667
+ "cpu": [
668
+ "arm64"
669
+ ],
670
+ "dev": true,
671
+ "license": "MIT",
672
+ "optional": true,
673
+ "os": [
674
+ "win32"
675
+ ],
676
+ "engines": {
677
+ "node": ">=12"
678
+ }
679
+ },
680
+ "node_modules/@esbuild/win32-ia32": {
681
+ "version": "0.18.20",
682
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
683
+ "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
684
+ "cpu": [
685
+ "ia32"
686
+ ],
687
+ "dev": true,
688
+ "license": "MIT",
689
+ "optional": true,
690
+ "os": [
691
+ "win32"
692
+ ],
693
+ "engines": {
694
+ "node": ">=12"
695
+ }
696
+ },
697
+ "node_modules/@esbuild/win32-x64": {
698
+ "version": "0.18.20",
699
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
700
+ "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
701
+ "cpu": [
702
+ "x64"
703
+ ],
704
+ "dev": true,
705
+ "license": "MIT",
706
+ "optional": true,
707
+ "os": [
708
+ "win32"
709
+ ],
710
+ "engines": {
711
+ "node": ">=12"
712
+ }
713
+ },
714
+ "node_modules/@jridgewell/gen-mapping": {
715
+ "version": "0.3.13",
716
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
717
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
718
+ "dev": true,
719
+ "license": "MIT",
720
+ "dependencies": {
721
+ "@jridgewell/sourcemap-codec": "^1.5.0",
722
+ "@jridgewell/trace-mapping": "^0.3.24"
723
+ }
724
+ },
725
+ "node_modules/@jridgewell/remapping": {
726
+ "version": "2.3.5",
727
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
728
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
729
+ "dev": true,
730
+ "license": "MIT",
731
+ "dependencies": {
732
+ "@jridgewell/gen-mapping": "^0.3.5",
733
+ "@jridgewell/trace-mapping": "^0.3.24"
734
+ }
735
+ },
736
+ "node_modules/@jridgewell/resolve-uri": {
737
+ "version": "3.1.2",
738
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
739
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
740
+ "dev": true,
741
+ "license": "MIT",
742
+ "engines": {
743
+ "node": ">=6.0.0"
744
+ }
745
+ },
746
+ "node_modules/@jridgewell/sourcemap-codec": {
747
+ "version": "1.5.5",
748
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
749
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
750
+ "dev": true,
751
+ "license": "MIT"
752
+ },
753
+ "node_modules/@jridgewell/trace-mapping": {
754
+ "version": "0.3.31",
755
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
756
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
757
+ "dev": true,
758
+ "license": "MIT",
759
+ "dependencies": {
760
+ "@jridgewell/resolve-uri": "^3.1.0",
761
+ "@jridgewell/sourcemap-codec": "^1.4.14"
762
+ }
763
+ },
764
+ "node_modules/@nodelib/fs.scandir": {
765
+ "version": "2.1.5",
766
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
767
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
768
+ "dev": true,
769
+ "license": "MIT",
770
+ "dependencies": {
771
+ "@nodelib/fs.stat": "2.0.5",
772
+ "run-parallel": "^1.1.9"
773
+ },
774
+ "engines": {
775
+ "node": ">= 8"
776
+ }
777
+ },
778
+ "node_modules/@nodelib/fs.stat": {
779
+ "version": "2.0.5",
780
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
781
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
782
+ "dev": true,
783
+ "license": "MIT",
784
+ "engines": {
785
+ "node": ">= 8"
786
+ }
787
+ },
788
+ "node_modules/@nodelib/fs.walk": {
789
+ "version": "1.2.8",
790
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
791
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
792
+ "dev": true,
793
+ "license": "MIT",
794
+ "dependencies": {
795
+ "@nodelib/fs.scandir": "2.1.5",
796
+ "fastq": "^1.6.0"
797
+ },
798
+ "engines": {
799
+ "node": ">= 8"
800
+ }
801
+ },
802
+ "node_modules/@rolldown/pluginutils": {
803
+ "version": "1.0.0-beta.27",
804
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
805
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
806
+ "dev": true,
807
+ "license": "MIT"
808
+ },
809
+ "node_modules/@socket.io/component-emitter": {
810
+ "version": "3.1.2",
811
+ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
812
+ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
813
+ "license": "MIT"
814
+ },
815
+ "node_modules/@types/babel__core": {
816
+ "version": "7.20.5",
817
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
818
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
819
+ "dev": true,
820
+ "license": "MIT",
821
+ "dependencies": {
822
+ "@babel/parser": "^7.20.7",
823
+ "@babel/types": "^7.20.7",
824
+ "@types/babel__generator": "*",
825
+ "@types/babel__template": "*",
826
+ "@types/babel__traverse": "*"
827
+ }
828
+ },
829
+ "node_modules/@types/babel__generator": {
830
+ "version": "7.27.0",
831
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
832
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
833
+ "dev": true,
834
+ "license": "MIT",
835
+ "dependencies": {
836
+ "@babel/types": "^7.0.0"
837
+ }
838
+ },
839
+ "node_modules/@types/babel__template": {
840
+ "version": "7.4.4",
841
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
842
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
843
+ "dev": true,
844
+ "license": "MIT",
845
+ "dependencies": {
846
+ "@babel/parser": "^7.1.0",
847
+ "@babel/types": "^7.0.0"
848
+ }
849
+ },
850
+ "node_modules/@types/babel__traverse": {
851
+ "version": "7.28.0",
852
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
853
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
854
+ "dev": true,
855
+ "license": "MIT",
856
+ "dependencies": {
857
+ "@babel/types": "^7.28.2"
858
+ }
859
+ },
860
+ "node_modules/@types/prop-types": {
861
+ "version": "15.7.15",
862
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
863
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
864
+ "devOptional": true,
865
+ "license": "MIT"
866
+ },
867
+ "node_modules/@types/react": {
868
+ "version": "18.3.28",
869
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
870
+ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
871
+ "devOptional": true,
872
+ "license": "MIT",
873
+ "dependencies": {
874
+ "@types/prop-types": "*",
875
+ "csstype": "^3.2.2"
876
+ }
877
+ },
878
+ "node_modules/@types/react-dom": {
879
+ "version": "18.3.7",
880
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
881
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
882
+ "dev": true,
883
+ "license": "MIT",
884
+ "peerDependencies": {
885
+ "@types/react": "^18.0.0"
886
+ }
887
+ },
888
+ "node_modules/@vitejs/plugin-react": {
889
+ "version": "4.7.0",
890
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
891
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
892
+ "dev": true,
893
+ "license": "MIT",
894
+ "dependencies": {
895
+ "@babel/core": "^7.28.0",
896
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
897
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
898
+ "@rolldown/pluginutils": "1.0.0-beta.27",
899
+ "@types/babel__core": "^7.20.5",
900
+ "react-refresh": "^0.17.0"
901
+ },
902
+ "engines": {
903
+ "node": "^14.18.0 || >=16.0.0"
904
+ },
905
+ "peerDependencies": {
906
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
907
+ }
908
+ },
909
+ "node_modules/any-promise": {
910
+ "version": "1.3.0",
911
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
912
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
913
+ "dev": true,
914
+ "license": "MIT"
915
+ },
916
+ "node_modules/anymatch": {
917
+ "version": "3.1.3",
918
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
919
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
920
+ "dev": true,
921
+ "license": "ISC",
922
+ "dependencies": {
923
+ "normalize-path": "^3.0.0",
924
+ "picomatch": "^2.0.4"
925
+ },
926
+ "engines": {
927
+ "node": ">= 8"
928
+ }
929
+ },
930
+ "node_modules/arg": {
931
+ "version": "5.0.2",
932
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
933
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
934
+ "dev": true,
935
+ "license": "MIT"
936
+ },
937
+ "node_modules/autoprefixer": {
938
+ "version": "10.4.24",
939
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz",
940
+ "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==",
941
+ "dev": true,
942
+ "funding": [
943
+ {
944
+ "type": "opencollective",
945
+ "url": "https://opencollective.com/postcss/"
946
+ },
947
+ {
948
+ "type": "tidelift",
949
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
950
+ },
951
+ {
952
+ "type": "github",
953
+ "url": "https://github.com/sponsors/ai"
954
+ }
955
+ ],
956
+ "license": "MIT",
957
+ "dependencies": {
958
+ "browserslist": "^4.28.1",
959
+ "caniuse-lite": "^1.0.30001766",
960
+ "fraction.js": "^5.3.4",
961
+ "picocolors": "^1.1.1",
962
+ "postcss-value-parser": "^4.2.0"
963
+ },
964
+ "bin": {
965
+ "autoprefixer": "bin/autoprefixer"
966
+ },
967
+ "engines": {
968
+ "node": "^10 || ^12 || >=14"
969
+ },
970
+ "peerDependencies": {
971
+ "postcss": "^8.1.0"
972
+ }
973
+ },
974
+ "node_modules/baseline-browser-mapping": {
975
+ "version": "2.9.19",
976
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
977
+ "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
978
+ "dev": true,
979
+ "license": "Apache-2.0",
980
+ "bin": {
981
+ "baseline-browser-mapping": "dist/cli.js"
982
+ }
983
+ },
984
+ "node_modules/binary-extensions": {
985
+ "version": "2.3.0",
986
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
987
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
988
+ "dev": true,
989
+ "license": "MIT",
990
+ "engines": {
991
+ "node": ">=8"
992
+ },
993
+ "funding": {
994
+ "url": "https://github.com/sponsors/sindresorhus"
995
+ }
996
+ },
997
+ "node_modules/braces": {
998
+ "version": "3.0.3",
999
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
1000
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
1001
+ "dev": true,
1002
+ "license": "MIT",
1003
+ "dependencies": {
1004
+ "fill-range": "^7.1.1"
1005
+ },
1006
+ "engines": {
1007
+ "node": ">=8"
1008
+ }
1009
+ },
1010
+ "node_modules/browserslist": {
1011
+ "version": "4.28.1",
1012
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
1013
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
1014
+ "dev": true,
1015
+ "funding": [
1016
+ {
1017
+ "type": "opencollective",
1018
+ "url": "https://opencollective.com/browserslist"
1019
+ },
1020
+ {
1021
+ "type": "tidelift",
1022
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
1023
+ },
1024
+ {
1025
+ "type": "github",
1026
+ "url": "https://github.com/sponsors/ai"
1027
+ }
1028
+ ],
1029
+ "license": "MIT",
1030
+ "dependencies": {
1031
+ "baseline-browser-mapping": "^2.9.0",
1032
+ "caniuse-lite": "^1.0.30001759",
1033
+ "electron-to-chromium": "^1.5.263",
1034
+ "node-releases": "^2.0.27",
1035
+ "update-browserslist-db": "^1.2.0"
1036
+ },
1037
+ "bin": {
1038
+ "browserslist": "cli.js"
1039
+ },
1040
+ "engines": {
1041
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
1042
+ }
1043
+ },
1044
+ "node_modules/camelcase-css": {
1045
+ "version": "2.0.1",
1046
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
1047
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
1048
+ "dev": true,
1049
+ "license": "MIT",
1050
+ "engines": {
1051
+ "node": ">= 6"
1052
+ }
1053
+ },
1054
+ "node_modules/caniuse-lite": {
1055
+ "version": "1.0.30001769",
1056
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz",
1057
+ "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==",
1058
+ "dev": true,
1059
+ "funding": [
1060
+ {
1061
+ "type": "opencollective",
1062
+ "url": "https://opencollective.com/browserslist"
1063
+ },
1064
+ {
1065
+ "type": "tidelift",
1066
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
1067
+ },
1068
+ {
1069
+ "type": "github",
1070
+ "url": "https://github.com/sponsors/ai"
1071
+ }
1072
+ ],
1073
+ "license": "CC-BY-4.0"
1074
+ },
1075
+ "node_modules/chokidar": {
1076
+ "version": "3.6.0",
1077
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
1078
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
1079
+ "dev": true,
1080
+ "license": "MIT",
1081
+ "dependencies": {
1082
+ "anymatch": "~3.1.2",
1083
+ "braces": "~3.0.2",
1084
+ "glob-parent": "~5.1.2",
1085
+ "is-binary-path": "~2.1.0",
1086
+ "is-glob": "~4.0.1",
1087
+ "normalize-path": "~3.0.0",
1088
+ "readdirp": "~3.6.0"
1089
+ },
1090
+ "engines": {
1091
+ "node": ">= 8.10.0"
1092
+ },
1093
+ "funding": {
1094
+ "url": "https://paulmillr.com/funding/"
1095
+ },
1096
+ "optionalDependencies": {
1097
+ "fsevents": "~2.3.2"
1098
+ }
1099
+ },
1100
+ "node_modules/chokidar/node_modules/glob-parent": {
1101
+ "version": "5.1.2",
1102
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
1103
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
1104
+ "dev": true,
1105
+ "license": "ISC",
1106
+ "dependencies": {
1107
+ "is-glob": "^4.0.1"
1108
+ },
1109
+ "engines": {
1110
+ "node": ">= 6"
1111
+ }
1112
+ },
1113
+ "node_modules/commander": {
1114
+ "version": "4.1.1",
1115
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
1116
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
1117
+ "dev": true,
1118
+ "license": "MIT",
1119
+ "engines": {
1120
+ "node": ">= 6"
1121
+ }
1122
+ },
1123
+ "node_modules/convert-source-map": {
1124
+ "version": "2.0.0",
1125
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
1126
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
1127
+ "dev": true,
1128
+ "license": "MIT"
1129
+ },
1130
+ "node_modules/cssesc": {
1131
+ "version": "3.0.0",
1132
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
1133
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
1134
+ "dev": true,
1135
+ "license": "MIT",
1136
+ "bin": {
1137
+ "cssesc": "bin/cssesc"
1138
+ },
1139
+ "engines": {
1140
+ "node": ">=4"
1141
+ }
1142
+ },
1143
+ "node_modules/csstype": {
1144
+ "version": "3.2.3",
1145
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
1146
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
1147
+ "devOptional": true,
1148
+ "license": "MIT"
1149
+ },
1150
+ "node_modules/debug": {
1151
+ "version": "4.4.3",
1152
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
1153
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
1154
+ "license": "MIT",
1155
+ "dependencies": {
1156
+ "ms": "^2.1.3"
1157
+ },
1158
+ "engines": {
1159
+ "node": ">=6.0"
1160
+ },
1161
+ "peerDependenciesMeta": {
1162
+ "supports-color": {
1163
+ "optional": true
1164
+ }
1165
+ }
1166
+ },
1167
+ "node_modules/didyoumean": {
1168
+ "version": "1.2.2",
1169
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
1170
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
1171
+ "dev": true,
1172
+ "license": "Apache-2.0"
1173
+ },
1174
+ "node_modules/dlv": {
1175
+ "version": "1.1.3",
1176
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
1177
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
1178
+ "dev": true,
1179
+ "license": "MIT"
1180
+ },
1181
+ "node_modules/electron-to-chromium": {
1182
+ "version": "1.5.286",
1183
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
1184
+ "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==",
1185
+ "dev": true,
1186
+ "license": "ISC"
1187
+ },
1188
+ "node_modules/engine.io-client": {
1189
+ "version": "6.6.4",
1190
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz",
1191
+ "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==",
1192
+ "license": "MIT",
1193
+ "dependencies": {
1194
+ "@socket.io/component-emitter": "~3.1.0",
1195
+ "debug": "~4.4.1",
1196
+ "engine.io-parser": "~5.2.1",
1197
+ "ws": "~8.18.3",
1198
+ "xmlhttprequest-ssl": "~2.1.1"
1199
+ }
1200
+ },
1201
+ "node_modules/engine.io-parser": {
1202
+ "version": "5.2.3",
1203
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
1204
+ "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
1205
+ "license": "MIT",
1206
+ "engines": {
1207
+ "node": ">=10.0.0"
1208
+ }
1209
+ },
1210
+ "node_modules/esbuild": {
1211
+ "version": "0.18.20",
1212
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
1213
+ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
1214
+ "dev": true,
1215
+ "hasInstallScript": true,
1216
+ "license": "MIT",
1217
+ "bin": {
1218
+ "esbuild": "bin/esbuild"
1219
+ },
1220
+ "engines": {
1221
+ "node": ">=12"
1222
+ },
1223
+ "optionalDependencies": {
1224
+ "@esbuild/android-arm": "0.18.20",
1225
+ "@esbuild/android-arm64": "0.18.20",
1226
+ "@esbuild/android-x64": "0.18.20",
1227
+ "@esbuild/darwin-arm64": "0.18.20",
1228
+ "@esbuild/darwin-x64": "0.18.20",
1229
+ "@esbuild/freebsd-arm64": "0.18.20",
1230
+ "@esbuild/freebsd-x64": "0.18.20",
1231
+ "@esbuild/linux-arm": "0.18.20",
1232
+ "@esbuild/linux-arm64": "0.18.20",
1233
+ "@esbuild/linux-ia32": "0.18.20",
1234
+ "@esbuild/linux-loong64": "0.18.20",
1235
+ "@esbuild/linux-mips64el": "0.18.20",
1236
+ "@esbuild/linux-ppc64": "0.18.20",
1237
+ "@esbuild/linux-riscv64": "0.18.20",
1238
+ "@esbuild/linux-s390x": "0.18.20",
1239
+ "@esbuild/linux-x64": "0.18.20",
1240
+ "@esbuild/netbsd-x64": "0.18.20",
1241
+ "@esbuild/openbsd-x64": "0.18.20",
1242
+ "@esbuild/sunos-x64": "0.18.20",
1243
+ "@esbuild/win32-arm64": "0.18.20",
1244
+ "@esbuild/win32-ia32": "0.18.20",
1245
+ "@esbuild/win32-x64": "0.18.20"
1246
+ }
1247
+ },
1248
+ "node_modules/escalade": {
1249
+ "version": "3.2.0",
1250
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1251
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1252
+ "dev": true,
1253
+ "license": "MIT",
1254
+ "engines": {
1255
+ "node": ">=6"
1256
+ }
1257
+ },
1258
+ "node_modules/fast-glob": {
1259
+ "version": "3.3.3",
1260
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
1261
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
1262
+ "dev": true,
1263
+ "license": "MIT",
1264
+ "dependencies": {
1265
+ "@nodelib/fs.stat": "^2.0.2",
1266
+ "@nodelib/fs.walk": "^1.2.3",
1267
+ "glob-parent": "^5.1.2",
1268
+ "merge2": "^1.3.0",
1269
+ "micromatch": "^4.0.8"
1270
+ },
1271
+ "engines": {
1272
+ "node": ">=8.6.0"
1273
+ }
1274
+ },
1275
+ "node_modules/fast-glob/node_modules/glob-parent": {
1276
+ "version": "5.1.2",
1277
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
1278
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
1279
+ "dev": true,
1280
+ "license": "ISC",
1281
+ "dependencies": {
1282
+ "is-glob": "^4.0.1"
1283
+ },
1284
+ "engines": {
1285
+ "node": ">= 6"
1286
+ }
1287
+ },
1288
+ "node_modules/fastq": {
1289
+ "version": "1.20.1",
1290
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
1291
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
1292
+ "dev": true,
1293
+ "license": "ISC",
1294
+ "dependencies": {
1295
+ "reusify": "^1.0.4"
1296
+ }
1297
+ },
1298
+ "node_modules/fill-range": {
1299
+ "version": "7.1.1",
1300
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
1301
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
1302
+ "dev": true,
1303
+ "license": "MIT",
1304
+ "dependencies": {
1305
+ "to-regex-range": "^5.0.1"
1306
+ },
1307
+ "engines": {
1308
+ "node": ">=8"
1309
+ }
1310
+ },
1311
+ "node_modules/fraction.js": {
1312
+ "version": "5.3.4",
1313
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
1314
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
1315
+ "dev": true,
1316
+ "license": "MIT",
1317
+ "engines": {
1318
+ "node": "*"
1319
+ },
1320
+ "funding": {
1321
+ "type": "github",
1322
+ "url": "https://github.com/sponsors/rawify"
1323
+ }
1324
+ },
1325
+ "node_modules/framer-motion": {
1326
+ "version": "10.18.0",
1327
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.18.0.tgz",
1328
+ "integrity": "sha512-oGlDh1Q1XqYPksuTD/usb0I70hq95OUzmL9+6Zd+Hs4XV0oaISBa/UUMSjYiq6m8EUF32132mOJ8xVZS+I0S6w==",
1329
+ "license": "MIT",
1330
+ "dependencies": {
1331
+ "tslib": "^2.4.0"
1332
+ },
1333
+ "optionalDependencies": {
1334
+ "@emotion/is-prop-valid": "^0.8.2"
1335
+ },
1336
+ "peerDependencies": {
1337
+ "react": "^18.0.0",
1338
+ "react-dom": "^18.0.0"
1339
+ },
1340
+ "peerDependenciesMeta": {
1341
+ "react": {
1342
+ "optional": true
1343
+ },
1344
+ "react-dom": {
1345
+ "optional": true
1346
+ }
1347
+ }
1348
+ },
1349
+ "node_modules/fsevents": {
1350
+ "version": "2.3.3",
1351
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1352
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1353
+ "dev": true,
1354
+ "hasInstallScript": true,
1355
+ "license": "MIT",
1356
+ "optional": true,
1357
+ "os": [
1358
+ "darwin"
1359
+ ],
1360
+ "engines": {
1361
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1362
+ }
1363
+ },
1364
+ "node_modules/function-bind": {
1365
+ "version": "1.1.2",
1366
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
1367
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
1368
+ "dev": true,
1369
+ "license": "MIT",
1370
+ "funding": {
1371
+ "url": "https://github.com/sponsors/ljharb"
1372
+ }
1373
+ },
1374
+ "node_modules/gensync": {
1375
+ "version": "1.0.0-beta.2",
1376
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
1377
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
1378
+ "dev": true,
1379
+ "license": "MIT",
1380
+ "engines": {
1381
+ "node": ">=6.9.0"
1382
+ }
1383
+ },
1384
+ "node_modules/glob-parent": {
1385
+ "version": "6.0.2",
1386
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
1387
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
1388
+ "dev": true,
1389
+ "license": "ISC",
1390
+ "dependencies": {
1391
+ "is-glob": "^4.0.3"
1392
+ },
1393
+ "engines": {
1394
+ "node": ">=10.13.0"
1395
+ }
1396
+ },
1397
+ "node_modules/hasown": {
1398
+ "version": "2.0.2",
1399
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
1400
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
1401
+ "dev": true,
1402
+ "license": "MIT",
1403
+ "dependencies": {
1404
+ "function-bind": "^1.1.2"
1405
+ },
1406
+ "engines": {
1407
+ "node": ">= 0.4"
1408
+ }
1409
+ },
1410
+ "node_modules/is-binary-path": {
1411
+ "version": "2.1.0",
1412
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
1413
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
1414
+ "dev": true,
1415
+ "license": "MIT",
1416
+ "dependencies": {
1417
+ "binary-extensions": "^2.0.0"
1418
+ },
1419
+ "engines": {
1420
+ "node": ">=8"
1421
+ }
1422
+ },
1423
+ "node_modules/is-core-module": {
1424
+ "version": "2.16.1",
1425
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
1426
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
1427
+ "dev": true,
1428
+ "license": "MIT",
1429
+ "dependencies": {
1430
+ "hasown": "^2.0.2"
1431
+ },
1432
+ "engines": {
1433
+ "node": ">= 0.4"
1434
+ },
1435
+ "funding": {
1436
+ "url": "https://github.com/sponsors/ljharb"
1437
+ }
1438
+ },
1439
+ "node_modules/is-extglob": {
1440
+ "version": "2.1.1",
1441
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
1442
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
1443
+ "dev": true,
1444
+ "license": "MIT",
1445
+ "engines": {
1446
+ "node": ">=0.10.0"
1447
+ }
1448
+ },
1449
+ "node_modules/is-glob": {
1450
+ "version": "4.0.3",
1451
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
1452
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
1453
+ "dev": true,
1454
+ "license": "MIT",
1455
+ "dependencies": {
1456
+ "is-extglob": "^2.1.1"
1457
+ },
1458
+ "engines": {
1459
+ "node": ">=0.10.0"
1460
+ }
1461
+ },
1462
+ "node_modules/is-number": {
1463
+ "version": "7.0.0",
1464
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
1465
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
1466
+ "dev": true,
1467
+ "license": "MIT",
1468
+ "engines": {
1469
+ "node": ">=0.12.0"
1470
+ }
1471
+ },
1472
+ "node_modules/jiti": {
1473
+ "version": "1.21.7",
1474
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
1475
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
1476
+ "dev": true,
1477
+ "license": "MIT",
1478
+ "bin": {
1479
+ "jiti": "bin/jiti.js"
1480
+ }
1481
+ },
1482
+ "node_modules/js-tokens": {
1483
+ "version": "4.0.0",
1484
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
1485
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
1486
+ "license": "MIT"
1487
+ },
1488
+ "node_modules/jsesc": {
1489
+ "version": "3.1.0",
1490
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
1491
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
1492
+ "dev": true,
1493
+ "license": "MIT",
1494
+ "bin": {
1495
+ "jsesc": "bin/jsesc"
1496
+ },
1497
+ "engines": {
1498
+ "node": ">=6"
1499
+ }
1500
+ },
1501
+ "node_modules/json5": {
1502
+ "version": "2.2.3",
1503
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
1504
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
1505
+ "dev": true,
1506
+ "license": "MIT",
1507
+ "bin": {
1508
+ "json5": "lib/cli.js"
1509
+ },
1510
+ "engines": {
1511
+ "node": ">=6"
1512
+ }
1513
+ },
1514
+ "node_modules/lilconfig": {
1515
+ "version": "3.1.3",
1516
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
1517
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
1518
+ "dev": true,
1519
+ "license": "MIT",
1520
+ "engines": {
1521
+ "node": ">=14"
1522
+ },
1523
+ "funding": {
1524
+ "url": "https://github.com/sponsors/antonk52"
1525
+ }
1526
+ },
1527
+ "node_modules/lines-and-columns": {
1528
+ "version": "1.2.4",
1529
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
1530
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
1531
+ "dev": true,
1532
+ "license": "MIT"
1533
+ },
1534
+ "node_modules/loose-envify": {
1535
+ "version": "1.4.0",
1536
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
1537
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
1538
+ "license": "MIT",
1539
+ "dependencies": {
1540
+ "js-tokens": "^3.0.0 || ^4.0.0"
1541
+ },
1542
+ "bin": {
1543
+ "loose-envify": "cli.js"
1544
+ }
1545
+ },
1546
+ "node_modules/lru-cache": {
1547
+ "version": "5.1.1",
1548
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
1549
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
1550
+ "dev": true,
1551
+ "license": "ISC",
1552
+ "dependencies": {
1553
+ "yallist": "^3.0.2"
1554
+ }
1555
+ },
1556
+ "node_modules/merge2": {
1557
+ "version": "1.4.1",
1558
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
1559
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
1560
+ "dev": true,
1561
+ "license": "MIT",
1562
+ "engines": {
1563
+ "node": ">= 8"
1564
+ }
1565
+ },
1566
+ "node_modules/micromatch": {
1567
+ "version": "4.0.8",
1568
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
1569
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
1570
+ "dev": true,
1571
+ "license": "MIT",
1572
+ "dependencies": {
1573
+ "braces": "^3.0.3",
1574
+ "picomatch": "^2.3.1"
1575
+ },
1576
+ "engines": {
1577
+ "node": ">=8.6"
1578
+ }
1579
+ },
1580
+ "node_modules/ms": {
1581
+ "version": "2.1.3",
1582
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1583
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1584
+ "license": "MIT"
1585
+ },
1586
+ "node_modules/mz": {
1587
+ "version": "2.7.0",
1588
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
1589
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
1590
+ "dev": true,
1591
+ "license": "MIT",
1592
+ "dependencies": {
1593
+ "any-promise": "^1.0.0",
1594
+ "object-assign": "^4.0.1",
1595
+ "thenify-all": "^1.0.0"
1596
+ }
1597
+ },
1598
+ "node_modules/nanoid": {
1599
+ "version": "3.3.11",
1600
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
1601
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
1602
+ "dev": true,
1603
+ "funding": [
1604
+ {
1605
+ "type": "github",
1606
+ "url": "https://github.com/sponsors/ai"
1607
+ }
1608
+ ],
1609
+ "license": "MIT",
1610
+ "bin": {
1611
+ "nanoid": "bin/nanoid.cjs"
1612
+ },
1613
+ "engines": {
1614
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1615
+ }
1616
+ },
1617
+ "node_modules/node-releases": {
1618
+ "version": "2.0.27",
1619
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
1620
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
1621
+ "dev": true,
1622
+ "license": "MIT"
1623
+ },
1624
+ "node_modules/normalize-path": {
1625
+ "version": "3.0.0",
1626
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
1627
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
1628
+ "dev": true,
1629
+ "license": "MIT",
1630
+ "engines": {
1631
+ "node": ">=0.10.0"
1632
+ }
1633
+ },
1634
+ "node_modules/object-assign": {
1635
+ "version": "4.1.1",
1636
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1637
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
1638
+ "dev": true,
1639
+ "license": "MIT",
1640
+ "engines": {
1641
+ "node": ">=0.10.0"
1642
+ }
1643
+ },
1644
+ "node_modules/object-hash": {
1645
+ "version": "3.0.0",
1646
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
1647
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
1648
+ "dev": true,
1649
+ "license": "MIT",
1650
+ "engines": {
1651
+ "node": ">= 6"
1652
+ }
1653
+ },
1654
+ "node_modules/path-parse": {
1655
+ "version": "1.0.7",
1656
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
1657
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
1658
+ "dev": true,
1659
+ "license": "MIT"
1660
+ },
1661
+ "node_modules/picocolors": {
1662
+ "version": "1.1.1",
1663
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1664
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1665
+ "dev": true,
1666
+ "license": "ISC"
1667
+ },
1668
+ "node_modules/picomatch": {
1669
+ "version": "2.3.1",
1670
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1671
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
1672
+ "dev": true,
1673
+ "license": "MIT",
1674
+ "engines": {
1675
+ "node": ">=8.6"
1676
+ },
1677
+ "funding": {
1678
+ "url": "https://github.com/sponsors/jonschlinkert"
1679
+ }
1680
+ },
1681
+ "node_modules/pify": {
1682
+ "version": "2.3.0",
1683
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
1684
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
1685
+ "dev": true,
1686
+ "license": "MIT",
1687
+ "engines": {
1688
+ "node": ">=0.10.0"
1689
+ }
1690
+ },
1691
+ "node_modules/pirates": {
1692
+ "version": "4.0.7",
1693
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
1694
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
1695
+ "dev": true,
1696
+ "license": "MIT",
1697
+ "engines": {
1698
+ "node": ">= 6"
1699
+ }
1700
+ },
1701
+ "node_modules/postcss": {
1702
+ "version": "8.5.6",
1703
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
1704
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
1705
+ "dev": true,
1706
+ "funding": [
1707
+ {
1708
+ "type": "opencollective",
1709
+ "url": "https://opencollective.com/postcss/"
1710
+ },
1711
+ {
1712
+ "type": "tidelift",
1713
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1714
+ },
1715
+ {
1716
+ "type": "github",
1717
+ "url": "https://github.com/sponsors/ai"
1718
+ }
1719
+ ],
1720
+ "license": "MIT",
1721
+ "dependencies": {
1722
+ "nanoid": "^3.3.11",
1723
+ "picocolors": "^1.1.1",
1724
+ "source-map-js": "^1.2.1"
1725
+ },
1726
+ "engines": {
1727
+ "node": "^10 || ^12 || >=14"
1728
+ }
1729
+ },
1730
+ "node_modules/postcss-import": {
1731
+ "version": "15.1.0",
1732
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
1733
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
1734
+ "dev": true,
1735
+ "license": "MIT",
1736
+ "dependencies": {
1737
+ "postcss-value-parser": "^4.0.0",
1738
+ "read-cache": "^1.0.0",
1739
+ "resolve": "^1.1.7"
1740
+ },
1741
+ "engines": {
1742
+ "node": ">=14.0.0"
1743
+ },
1744
+ "peerDependencies": {
1745
+ "postcss": "^8.0.0"
1746
+ }
1747
+ },
1748
+ "node_modules/postcss-js": {
1749
+ "version": "4.1.0",
1750
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
1751
+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
1752
+ "dev": true,
1753
+ "funding": [
1754
+ {
1755
+ "type": "opencollective",
1756
+ "url": "https://opencollective.com/postcss/"
1757
+ },
1758
+ {
1759
+ "type": "github",
1760
+ "url": "https://github.com/sponsors/ai"
1761
+ }
1762
+ ],
1763
+ "license": "MIT",
1764
+ "dependencies": {
1765
+ "camelcase-css": "^2.0.1"
1766
+ },
1767
+ "engines": {
1768
+ "node": "^12 || ^14 || >= 16"
1769
+ },
1770
+ "peerDependencies": {
1771
+ "postcss": "^8.4.21"
1772
+ }
1773
+ },
1774
+ "node_modules/postcss-load-config": {
1775
+ "version": "6.0.1",
1776
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
1777
+ "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
1778
+ "dev": true,
1779
+ "funding": [
1780
+ {
1781
+ "type": "opencollective",
1782
+ "url": "https://opencollective.com/postcss/"
1783
+ },
1784
+ {
1785
+ "type": "github",
1786
+ "url": "https://github.com/sponsors/ai"
1787
+ }
1788
+ ],
1789
+ "license": "MIT",
1790
+ "dependencies": {
1791
+ "lilconfig": "^3.1.1"
1792
+ },
1793
+ "engines": {
1794
+ "node": ">= 18"
1795
+ },
1796
+ "peerDependencies": {
1797
+ "jiti": ">=1.21.0",
1798
+ "postcss": ">=8.0.9",
1799
+ "tsx": "^4.8.1",
1800
+ "yaml": "^2.4.2"
1801
+ },
1802
+ "peerDependenciesMeta": {
1803
+ "jiti": {
1804
+ "optional": true
1805
+ },
1806
+ "postcss": {
1807
+ "optional": true
1808
+ },
1809
+ "tsx": {
1810
+ "optional": true
1811
+ },
1812
+ "yaml": {
1813
+ "optional": true
1814
+ }
1815
+ }
1816
+ },
1817
+ "node_modules/postcss-nested": {
1818
+ "version": "6.2.0",
1819
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
1820
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
1821
+ "dev": true,
1822
+ "funding": [
1823
+ {
1824
+ "type": "opencollective",
1825
+ "url": "https://opencollective.com/postcss/"
1826
+ },
1827
+ {
1828
+ "type": "github",
1829
+ "url": "https://github.com/sponsors/ai"
1830
+ }
1831
+ ],
1832
+ "license": "MIT",
1833
+ "dependencies": {
1834
+ "postcss-selector-parser": "^6.1.1"
1835
+ },
1836
+ "engines": {
1837
+ "node": ">=12.0"
1838
+ },
1839
+ "peerDependencies": {
1840
+ "postcss": "^8.2.14"
1841
+ }
1842
+ },
1843
+ "node_modules/postcss-selector-parser": {
1844
+ "version": "6.1.2",
1845
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
1846
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
1847
+ "dev": true,
1848
+ "license": "MIT",
1849
+ "dependencies": {
1850
+ "cssesc": "^3.0.0",
1851
+ "util-deprecate": "^1.0.2"
1852
+ },
1853
+ "engines": {
1854
+ "node": ">=4"
1855
+ }
1856
+ },
1857
+ "node_modules/postcss-value-parser": {
1858
+ "version": "4.2.0",
1859
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
1860
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
1861
+ "dev": true,
1862
+ "license": "MIT"
1863
+ },
1864
+ "node_modules/queue-microtask": {
1865
+ "version": "1.2.3",
1866
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
1867
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
1868
+ "dev": true,
1869
+ "funding": [
1870
+ {
1871
+ "type": "github",
1872
+ "url": "https://github.com/sponsors/feross"
1873
+ },
1874
+ {
1875
+ "type": "patreon",
1876
+ "url": "https://www.patreon.com/feross"
1877
+ },
1878
+ {
1879
+ "type": "consulting",
1880
+ "url": "https://feross.org/support"
1881
+ }
1882
+ ],
1883
+ "license": "MIT"
1884
+ },
1885
+ "node_modules/react": {
1886
+ "version": "18.3.1",
1887
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
1888
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
1889
+ "license": "MIT",
1890
+ "dependencies": {
1891
+ "loose-envify": "^1.1.0"
1892
+ },
1893
+ "engines": {
1894
+ "node": ">=0.10.0"
1895
+ }
1896
+ },
1897
+ "node_modules/react-dom": {
1898
+ "version": "18.3.1",
1899
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
1900
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
1901
+ "license": "MIT",
1902
+ "dependencies": {
1903
+ "loose-envify": "^1.1.0",
1904
+ "scheduler": "^0.23.2"
1905
+ },
1906
+ "peerDependencies": {
1907
+ "react": "^18.3.1"
1908
+ }
1909
+ },
1910
+ "node_modules/react-refresh": {
1911
+ "version": "0.17.0",
1912
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
1913
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
1914
+ "dev": true,
1915
+ "license": "MIT",
1916
+ "engines": {
1917
+ "node": ">=0.10.0"
1918
+ }
1919
+ },
1920
+ "node_modules/read-cache": {
1921
+ "version": "1.0.0",
1922
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
1923
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
1924
+ "dev": true,
1925
+ "license": "MIT",
1926
+ "dependencies": {
1927
+ "pify": "^2.3.0"
1928
+ }
1929
+ },
1930
+ "node_modules/readdirp": {
1931
+ "version": "3.6.0",
1932
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
1933
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
1934
+ "dev": true,
1935
+ "license": "MIT",
1936
+ "dependencies": {
1937
+ "picomatch": "^2.2.1"
1938
+ },
1939
+ "engines": {
1940
+ "node": ">=8.10.0"
1941
+ }
1942
+ },
1943
+ "node_modules/resolve": {
1944
+ "version": "1.22.11",
1945
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
1946
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
1947
+ "dev": true,
1948
+ "license": "MIT",
1949
+ "dependencies": {
1950
+ "is-core-module": "^2.16.1",
1951
+ "path-parse": "^1.0.7",
1952
+ "supports-preserve-symlinks-flag": "^1.0.0"
1953
+ },
1954
+ "bin": {
1955
+ "resolve": "bin/resolve"
1956
+ },
1957
+ "engines": {
1958
+ "node": ">= 0.4"
1959
+ },
1960
+ "funding": {
1961
+ "url": "https://github.com/sponsors/ljharb"
1962
+ }
1963
+ },
1964
+ "node_modules/reusify": {
1965
+ "version": "1.1.0",
1966
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
1967
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
1968
+ "dev": true,
1969
+ "license": "MIT",
1970
+ "engines": {
1971
+ "iojs": ">=1.0.0",
1972
+ "node": ">=0.10.0"
1973
+ }
1974
+ },
1975
+ "node_modules/rollup": {
1976
+ "version": "3.29.5",
1977
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz",
1978
+ "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
1979
+ "dev": true,
1980
+ "license": "MIT",
1981
+ "bin": {
1982
+ "rollup": "dist/bin/rollup"
1983
+ },
1984
+ "engines": {
1985
+ "node": ">=14.18.0",
1986
+ "npm": ">=8.0.0"
1987
+ },
1988
+ "optionalDependencies": {
1989
+ "fsevents": "~2.3.2"
1990
+ }
1991
+ },
1992
+ "node_modules/run-parallel": {
1993
+ "version": "1.2.0",
1994
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
1995
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
1996
+ "dev": true,
1997
+ "funding": [
1998
+ {
1999
+ "type": "github",
2000
+ "url": "https://github.com/sponsors/feross"
2001
+ },
2002
+ {
2003
+ "type": "patreon",
2004
+ "url": "https://www.patreon.com/feross"
2005
+ },
2006
+ {
2007
+ "type": "consulting",
2008
+ "url": "https://feross.org/support"
2009
+ }
2010
+ ],
2011
+ "license": "MIT",
2012
+ "dependencies": {
2013
+ "queue-microtask": "^1.2.2"
2014
+ }
2015
+ },
2016
+ "node_modules/scheduler": {
2017
+ "version": "0.23.2",
2018
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
2019
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
2020
+ "license": "MIT",
2021
+ "dependencies": {
2022
+ "loose-envify": "^1.1.0"
2023
+ }
2024
+ },
2025
+ "node_modules/semver": {
2026
+ "version": "6.3.1",
2027
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
2028
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
2029
+ "dev": true,
2030
+ "license": "ISC",
2031
+ "bin": {
2032
+ "semver": "bin/semver.js"
2033
+ }
2034
+ },
2035
+ "node_modules/socket.io-client": {
2036
+ "version": "4.8.3",
2037
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
2038
+ "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
2039
+ "license": "MIT",
2040
+ "dependencies": {
2041
+ "@socket.io/component-emitter": "~3.1.0",
2042
+ "debug": "~4.4.1",
2043
+ "engine.io-client": "~6.6.1",
2044
+ "socket.io-parser": "~4.2.4"
2045
+ },
2046
+ "engines": {
2047
+ "node": ">=10.0.0"
2048
+ }
2049
+ },
2050
+ "node_modules/socket.io-parser": {
2051
+ "version": "4.2.5",
2052
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz",
2053
+ "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==",
2054
+ "license": "MIT",
2055
+ "dependencies": {
2056
+ "@socket.io/component-emitter": "~3.1.0",
2057
+ "debug": "~4.4.1"
2058
+ },
2059
+ "engines": {
2060
+ "node": ">=10.0.0"
2061
+ }
2062
+ },
2063
+ "node_modules/source-map-js": {
2064
+ "version": "1.2.1",
2065
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
2066
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
2067
+ "dev": true,
2068
+ "license": "BSD-3-Clause",
2069
+ "engines": {
2070
+ "node": ">=0.10.0"
2071
+ }
2072
+ },
2073
+ "node_modules/sucrase": {
2074
+ "version": "3.35.1",
2075
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
2076
+ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
2077
+ "dev": true,
2078
+ "license": "MIT",
2079
+ "dependencies": {
2080
+ "@jridgewell/gen-mapping": "^0.3.2",
2081
+ "commander": "^4.0.0",
2082
+ "lines-and-columns": "^1.1.6",
2083
+ "mz": "^2.7.0",
2084
+ "pirates": "^4.0.1",
2085
+ "tinyglobby": "^0.2.11",
2086
+ "ts-interface-checker": "^0.1.9"
2087
+ },
2088
+ "bin": {
2089
+ "sucrase": "bin/sucrase",
2090
+ "sucrase-node": "bin/sucrase-node"
2091
+ },
2092
+ "engines": {
2093
+ "node": ">=16 || 14 >=14.17"
2094
+ }
2095
+ },
2096
+ "node_modules/supports-preserve-symlinks-flag": {
2097
+ "version": "1.0.0",
2098
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
2099
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
2100
+ "dev": true,
2101
+ "license": "MIT",
2102
+ "engines": {
2103
+ "node": ">= 0.4"
2104
+ },
2105
+ "funding": {
2106
+ "url": "https://github.com/sponsors/ljharb"
2107
+ }
2108
+ },
2109
+ "node_modules/tailwindcss": {
2110
+ "version": "3.4.19",
2111
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
2112
+ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
2113
+ "dev": true,
2114
+ "license": "MIT",
2115
+ "dependencies": {
2116
+ "@alloc/quick-lru": "^5.2.0",
2117
+ "arg": "^5.0.2",
2118
+ "chokidar": "^3.6.0",
2119
+ "didyoumean": "^1.2.2",
2120
+ "dlv": "^1.1.3",
2121
+ "fast-glob": "^3.3.2",
2122
+ "glob-parent": "^6.0.2",
2123
+ "is-glob": "^4.0.3",
2124
+ "jiti": "^1.21.7",
2125
+ "lilconfig": "^3.1.3",
2126
+ "micromatch": "^4.0.8",
2127
+ "normalize-path": "^3.0.0",
2128
+ "object-hash": "^3.0.0",
2129
+ "picocolors": "^1.1.1",
2130
+ "postcss": "^8.4.47",
2131
+ "postcss-import": "^15.1.0",
2132
+ "postcss-js": "^4.0.1",
2133
+ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
2134
+ "postcss-nested": "^6.2.0",
2135
+ "postcss-selector-parser": "^6.1.2",
2136
+ "resolve": "^1.22.8",
2137
+ "sucrase": "^3.35.0"
2138
+ },
2139
+ "bin": {
2140
+ "tailwind": "lib/cli.js",
2141
+ "tailwindcss": "lib/cli.js"
2142
+ },
2143
+ "engines": {
2144
+ "node": ">=14.0.0"
2145
+ }
2146
+ },
2147
+ "node_modules/thenify": {
2148
+ "version": "3.3.1",
2149
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
2150
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
2151
+ "dev": true,
2152
+ "license": "MIT",
2153
+ "dependencies": {
2154
+ "any-promise": "^1.0.0"
2155
+ }
2156
+ },
2157
+ "node_modules/thenify-all": {
2158
+ "version": "1.6.0",
2159
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
2160
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
2161
+ "dev": true,
2162
+ "license": "MIT",
2163
+ "dependencies": {
2164
+ "thenify": ">= 3.1.0 < 4"
2165
+ },
2166
+ "engines": {
2167
+ "node": ">=0.8"
2168
+ }
2169
+ },
2170
+ "node_modules/tinyglobby": {
2171
+ "version": "0.2.15",
2172
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
2173
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
2174
+ "dev": true,
2175
+ "license": "MIT",
2176
+ "dependencies": {
2177
+ "fdir": "^6.5.0",
2178
+ "picomatch": "^4.0.3"
2179
+ },
2180
+ "engines": {
2181
+ "node": ">=12.0.0"
2182
+ },
2183
+ "funding": {
2184
+ "url": "https://github.com/sponsors/SuperchupuDev"
2185
+ }
2186
+ },
2187
+ "node_modules/tinyglobby/node_modules/fdir": {
2188
+ "version": "6.5.0",
2189
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
2190
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
2191
+ "dev": true,
2192
+ "license": "MIT",
2193
+ "engines": {
2194
+ "node": ">=12.0.0"
2195
+ },
2196
+ "peerDependencies": {
2197
+ "picomatch": "^3 || ^4"
2198
+ },
2199
+ "peerDependenciesMeta": {
2200
+ "picomatch": {
2201
+ "optional": true
2202
+ }
2203
+ }
2204
+ },
2205
+ "node_modules/tinyglobby/node_modules/picomatch": {
2206
+ "version": "4.0.3",
2207
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
2208
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
2209
+ "dev": true,
2210
+ "license": "MIT",
2211
+ "engines": {
2212
+ "node": ">=12"
2213
+ },
2214
+ "funding": {
2215
+ "url": "https://github.com/sponsors/jonschlinkert"
2216
+ }
2217
+ },
2218
+ "node_modules/to-regex-range": {
2219
+ "version": "5.0.1",
2220
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
2221
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
2222
+ "dev": true,
2223
+ "license": "MIT",
2224
+ "dependencies": {
2225
+ "is-number": "^7.0.0"
2226
+ },
2227
+ "engines": {
2228
+ "node": ">=8.0"
2229
+ }
2230
+ },
2231
+ "node_modules/ts-interface-checker": {
2232
+ "version": "0.1.13",
2233
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
2234
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
2235
+ "dev": true,
2236
+ "license": "Apache-2.0"
2237
+ },
2238
+ "node_modules/tslib": {
2239
+ "version": "2.8.1",
2240
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
2241
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
2242
+ "license": "0BSD"
2243
+ },
2244
+ "node_modules/typescript": {
2245
+ "version": "5.9.3",
2246
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
2247
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
2248
+ "dev": true,
2249
+ "license": "Apache-2.0",
2250
+ "bin": {
2251
+ "tsc": "bin/tsc",
2252
+ "tsserver": "bin/tsserver"
2253
+ },
2254
+ "engines": {
2255
+ "node": ">=14.17"
2256
+ }
2257
+ },
2258
+ "node_modules/update-browserslist-db": {
2259
+ "version": "1.2.3",
2260
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
2261
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
2262
+ "dev": true,
2263
+ "funding": [
2264
+ {
2265
+ "type": "opencollective",
2266
+ "url": "https://opencollective.com/browserslist"
2267
+ },
2268
+ {
2269
+ "type": "tidelift",
2270
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
2271
+ },
2272
+ {
2273
+ "type": "github",
2274
+ "url": "https://github.com/sponsors/ai"
2275
+ }
2276
+ ],
2277
+ "license": "MIT",
2278
+ "dependencies": {
2279
+ "escalade": "^3.2.0",
2280
+ "picocolors": "^1.1.1"
2281
+ },
2282
+ "bin": {
2283
+ "update-browserslist-db": "cli.js"
2284
+ },
2285
+ "peerDependencies": {
2286
+ "browserslist": ">= 4.21.0"
2287
+ }
2288
+ },
2289
+ "node_modules/use-sync-external-store": {
2290
+ "version": "1.6.0",
2291
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
2292
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
2293
+ "license": "MIT",
2294
+ "peerDependencies": {
2295
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
2296
+ }
2297
+ },
2298
+ "node_modules/util-deprecate": {
2299
+ "version": "1.0.2",
2300
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
2301
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
2302
+ "dev": true,
2303
+ "license": "MIT"
2304
+ },
2305
+ "node_modules/vite": {
2306
+ "version": "4.5.14",
2307
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz",
2308
+ "integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==",
2309
+ "dev": true,
2310
+ "license": "MIT",
2311
+ "dependencies": {
2312
+ "esbuild": "^0.18.10",
2313
+ "postcss": "^8.4.27",
2314
+ "rollup": "^3.27.1"
2315
+ },
2316
+ "bin": {
2317
+ "vite": "bin/vite.js"
2318
+ },
2319
+ "engines": {
2320
+ "node": "^14.18.0 || >=16.0.0"
2321
+ },
2322
+ "funding": {
2323
+ "url": "https://github.com/vitejs/vite?sponsor=1"
2324
+ },
2325
+ "optionalDependencies": {
2326
+ "fsevents": "~2.3.2"
2327
+ },
2328
+ "peerDependencies": {
2329
+ "@types/node": ">= 14",
2330
+ "less": "*",
2331
+ "lightningcss": "^1.21.0",
2332
+ "sass": "*",
2333
+ "stylus": "*",
2334
+ "sugarss": "*",
2335
+ "terser": "^5.4.0"
2336
+ },
2337
+ "peerDependenciesMeta": {
2338
+ "@types/node": {
2339
+ "optional": true
2340
+ },
2341
+ "less": {
2342
+ "optional": true
2343
+ },
2344
+ "lightningcss": {
2345
+ "optional": true
2346
+ },
2347
+ "sass": {
2348
+ "optional": true
2349
+ },
2350
+ "stylus": {
2351
+ "optional": true
2352
+ },
2353
+ "sugarss": {
2354
+ "optional": true
2355
+ },
2356
+ "terser": {
2357
+ "optional": true
2358
+ }
2359
+ }
2360
+ },
2361
+ "node_modules/ws": {
2362
+ "version": "8.18.3",
2363
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
2364
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
2365
+ "license": "MIT",
2366
+ "engines": {
2367
+ "node": ">=10.0.0"
2368
+ },
2369
+ "peerDependencies": {
2370
+ "bufferutil": "^4.0.1",
2371
+ "utf-8-validate": ">=5.0.2"
2372
+ },
2373
+ "peerDependenciesMeta": {
2374
+ "bufferutil": {
2375
+ "optional": true
2376
+ },
2377
+ "utf-8-validate": {
2378
+ "optional": true
2379
+ }
2380
+ }
2381
+ },
2382
+ "node_modules/xmlhttprequest-ssl": {
2383
+ "version": "2.1.2",
2384
+ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
2385
+ "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
2386
+ "engines": {
2387
+ "node": ">=0.4.0"
2388
+ }
2389
+ },
2390
+ "node_modules/yallist": {
2391
+ "version": "3.1.1",
2392
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
2393
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
2394
+ "dev": true,
2395
+ "license": "ISC"
2396
+ },
2397
+ "node_modules/zustand": {
2398
+ "version": "4.5.7",
2399
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
2400
+ "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
2401
+ "license": "MIT",
2402
+ "dependencies": {
2403
+ "use-sync-external-store": "^1.2.2"
2404
+ },
2405
+ "engines": {
2406
+ "node": ">=12.7.0"
2407
+ },
2408
+ "peerDependencies": {
2409
+ "@types/react": ">=16.8",
2410
+ "immer": ">=9.0.6",
2411
+ "react": ">=16.8"
2412
+ },
2413
+ "peerDependenciesMeta": {
2414
+ "@types/react": {
2415
+ "optional": true
2416
+ },
2417
+ "immer": {
2418
+ "optional": true
2419
+ },
2420
+ "react": {
2421
+ "optional": true
2422
+ }
2423
+ }
2424
+ }
2425
+ }
2426
+ }
client/package.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "mummy-card-game-client",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "framer-motion": "^10.16.4",
13
+ "react": "^18.2.0",
14
+ "react-dom": "^18.2.0",
15
+ "socket.io-client": "^4.7.2",
16
+ "zustand": "^4.4.1"
17
+ },
18
+ "devDependencies": {
19
+ "@types/react": "^18.2.28",
20
+ "@types/react-dom": "^18.2.13",
21
+ "@vitejs/plugin-react": "^4.1.0",
22
+ "autoprefixer": "^10.4.16",
23
+ "postcss": "^8.4.31",
24
+ "tailwindcss": "^3.3.3",
25
+ "typescript": "^5.2.2",
26
+ "vite": "^4.4.11"
27
+ }
28
+ }
client/postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
client/src/App.tsx ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect } from 'react';
2
+ import { useGameStore } from './store/gameStore';
3
+ import { initializeSocket, disconnectSocket } from './socket/socket';
4
+ import { MainMenu } from './components/screens/MainMenu';
5
+ import { Lobby } from './components/screens/Lobby';
6
+ import { Room } from './components/screens/Room';
7
+ import { GameBoard } from './components/screens/GameBoard';
8
+ import { LandscapeOverlay } from './components/ui/LandscapeOverlay';
9
+ import { ToastContainer } from './components/ui/ToastContainer';
10
+ import { MummyEventOverlay } from './components/ui/MummyEventOverlay';
11
+ import { ModalRenderer } from './components/modals/ModalRenderer';
12
+
13
+ function App() {
14
+ const currentScreen = useGameStore((state) => state.currentScreen);
15
+
16
+ useEffect(() => {
17
+ initializeSocket();
18
+ return () => {
19
+ disconnectSocket();
20
+ };
21
+ }, []);
22
+
23
+ const renderScreen = () => {
24
+ switch (currentScreen) {
25
+ case 'menu':
26
+ return <MainMenu />;
27
+ case 'lobby':
28
+ return <Lobby />;
29
+ case 'room':
30
+ return <Room />;
31
+ case 'game':
32
+ return <GameBoard />;
33
+ default:
34
+ return <MainMenu />;
35
+ }
36
+ };
37
+
38
+ return (
39
+ <>
40
+ <LandscapeOverlay />
41
+ <div className="game-content flex flex-col min-h-screen min-h-[100dvh]">
42
+ {renderScreen()}
43
+ </div>
44
+ <ToastContainer />
45
+ <MummyEventOverlay />
46
+ <ModalRenderer />
47
+ </>
48
+ );
49
+ }
50
+
51
+ export default App;
client/src/components/game/Card.tsx ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { motion } from 'framer-motion';
2
+ import type { CardInstance, CardId } from '@shared/types';
3
+ import { CARD_DATABASE, CARD_ASSETS, CARD_BACK_ASSET } from '@shared/types';
4
+
5
+ interface CardProps {
6
+ card?: CardInstance;
7
+ cardId?: CardId;
8
+ faceDown?: boolean;
9
+ selected?: boolean;
10
+ disabled?: boolean;
11
+ onClick?: () => void;
12
+ size?: 'small' | 'medium' | 'large';
13
+ className?: string;
14
+ showValue?: boolean;
15
+ }
16
+
17
+ const sizeClasses = {
18
+ small: 'w-16',
19
+ medium: 'w-24',
20
+ large: 'w-32',
21
+ };
22
+
23
+ export function Card({
24
+ card,
25
+ cardId,
26
+ faceDown = false,
27
+ selected = false,
28
+ disabled = false,
29
+ onClick,
30
+ size = 'medium',
31
+ className = '',
32
+ showValue = true,
33
+ }: CardProps) {
34
+ const actualCardId = card?.cardId ?? cardId;
35
+ const cardDef = actualCardId ? CARD_DATABASE[actualCardId] : null;
36
+
37
+ const imageSrc = faceDown || !actualCardId
38
+ ? `/${CARD_BACK_ASSET}`
39
+ : `/${CARD_ASSETS[actualCardId]}`;
40
+
41
+ const isMummy = actualCardId === 'mummified';
42
+ const isHalf = cardDef?.isHalf;
43
+
44
+ return (
45
+ <motion.div
46
+ className={`
47
+ card ${sizeClasses[size]} ${className}
48
+ ${selected ? 'selected' : ''}
49
+ ${disabled ? 'disabled' : ''}
50
+ ${isMummy && !faceDown ? 'mummy' : ''}
51
+ `}
52
+ onClick={disabled ? undefined : onClick}
53
+ whileHover={disabled ? {} : { scale: 1.05, y: -10 }}
54
+ whileTap={disabled ? {} : { scale: 0.95 }}
55
+ initial={{ opacity: 0, scale: 0.8 }}
56
+ animate={{ opacity: 1, scale: 1 }}
57
+ transition={{ duration: 0.2 }}
58
+ >
59
+ <img
60
+ src={imageSrc}
61
+ alt={cardDef?.nameEn ?? 'Card'}
62
+ className="card-image"
63
+ onError={(e) => {
64
+ // Fallback if image doesn't exist
65
+ (e.target as HTMLImageElement).src = `/${CARD_BACK_ASSET}`;
66
+ }}
67
+ />
68
+
69
+ {!faceDown && cardDef && (
70
+ <>
71
+ <div className="card-overlay" />
72
+
73
+ {isHalf && (
74
+ <span className="half-indicator">½</span>
75
+ )}
76
+
77
+ {showValue && cardDef.value > 0 && (
78
+ <span className="absolute top-1 left-1 bg-nile-blue/80 text-egyptian-gold text-xs font-bold px-1 rounded">
79
+ {cardDef.value}
80
+ </span>
81
+ )}
82
+
83
+ <div className="card-name">
84
+ <p className="card-name-en">{cardDef.nameEn}</p>
85
+ <p className="card-name-ar">{cardDef.nameAr}</p>
86
+ </div>
87
+ </>
88
+ )}
89
+ </motion.div>
90
+ );
91
+ }
92
+
93
+ interface CardBackProps {
94
+ count?: number;
95
+ onClick?: () => void;
96
+ size?: 'small' | 'medium' | 'large';
97
+ className?: string;
98
+ }
99
+
100
+ export function CardBack({ count, onClick, size = 'medium', className = '' }: CardBackProps) {
101
+ return (
102
+ <div className={`relative ${className}`}>
103
+ <Card faceDown size={size} onClick={onClick} />
104
+ {count !== undefined && (
105
+ <span className="absolute -bottom-2 -right-2 bg-egyptian-gold text-nile-blue text-sm font-bold px-2 py-1 rounded-full">
106
+ {count}
107
+ </span>
108
+ )}
109
+ </div>
110
+ );
111
+ }
client/src/components/modals/ArrangeHandModal.tsx ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { useGameStore } from '../../store/gameStore';
4
+ import { Card } from '../game/Card';
5
+ import { emitArrangeHand } from '../../socket/socket';
6
+
7
+ const AUTO_CONFIRM_TIMEOUT = 15000; // 15 seconds
8
+
9
+ interface ArrangeHandModalProps {
10
+ onClose: () => void;
11
+ }
12
+
13
+ export function ArrangeHandModal({ onClose }: ArrangeHandModalProps) {
14
+ const myHand = useGameStore((state) => state.myHand);
15
+ const [orderedCards, setOrderedCards] = useState(myHand.map(c => c.instanceId));
16
+ const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
17
+ const [timeLeft, setTimeLeft] = useState(AUTO_CONFIRM_TIMEOUT / 1000);
18
+ const hasConfirmed = useRef(false);
19
+
20
+ const handleCardClick = (index: number) => {
21
+ if (selectedIndex === null) {
22
+ // First card selected
23
+ setSelectedIndex(index);
24
+ } else if (selectedIndex === index) {
25
+ // Clicked same card, deselect
26
+ setSelectedIndex(null);
27
+ } else {
28
+ // Second card selected - swap them
29
+ const newOrder = [...orderedCards];
30
+ [newOrder[selectedIndex], newOrder[index]] = [newOrder[index], newOrder[selectedIndex]];
31
+ setOrderedCards(newOrder);
32
+ setSelectedIndex(null);
33
+ }
34
+ };
35
+
36
+ const handleConfirm = () => {
37
+ if (hasConfirmed.current) return;
38
+ hasConfirmed.current = true;
39
+ emitArrangeHand(orderedCards);
40
+ onClose();
41
+ };
42
+
43
+ // Auto-confirm timer
44
+ useEffect(() => {
45
+ const interval = setInterval(() => {
46
+ setTimeLeft((prev) => {
47
+ if (prev <= 1) {
48
+ clearInterval(interval);
49
+ handleConfirm();
50
+ return 0;
51
+ }
52
+ return prev - 1;
53
+ });
54
+ }, 1000);
55
+
56
+ return () => clearInterval(interval);
57
+ }, []);
58
+
59
+ return (
60
+ <motion.div
61
+ className="modal-overlay"
62
+ initial={{ opacity: 0 }}
63
+ animate={{ opacity: 1 }}
64
+ exit={{ opacity: 0 }}
65
+ >
66
+ <motion.div
67
+ className="modal-content max-w-2xl"
68
+ initial={{ scale: 0.8, opacity: 0 }}
69
+ animate={{ scale: 1, opacity: 1 }}
70
+ exit={{ scale: 0.8, opacity: 0 }}
71
+ onClick={(e) => e.stopPropagation()}
72
+ >
73
+ <h2 className="modal-title">🔀 Arrange Your Cards</h2>
74
+ <p className="text-papyrus text-center mb-2">
75
+ Someone is about to steal from you!
76
+ </p>
77
+ <p className="text-yellow-400 text-center text-sm mb-2">
78
+ Tap two cards to swap their positions. Hide your good cards!
79
+ </p>
80
+ <p className={`text-center text-sm mb-4 ${timeLeft <= 3 ? 'text-red-400 font-bold animate-pulse' : 'text-egyptian-gold'}`}>
81
+ ⏱️ Auto-confirm in {timeLeft}s
82
+ </p>
83
+
84
+ <div className="grid grid-cols-4 gap-2 mb-6 justify-items-center">
85
+ {orderedCards.map((instanceId, index) => {
86
+ const card = myHand.find(c => c.instanceId === instanceId);
87
+ if (!card) return null;
88
+
89
+ const isSelected = selectedIndex === index;
90
+
91
+ return (
92
+ <motion.div
93
+ key={instanceId}
94
+ className={`cursor-pointer rounded-lg ${isSelected ? 'ring-4 ring-egyptian-gold' : ''}`}
95
+ onClick={() => handleCardClick(index)}
96
+ whileHover={{ scale: 1.05 }}
97
+ whileTap={{ scale: 0.95 }}
98
+ animate={isSelected ? { y: -10 } : { y: 0 }}
99
+ >
100
+ <Card card={card} size="small" />
101
+ <p className="text-center text-xs text-papyrus mt-1">#{index + 1}</p>
102
+ </motion.div>
103
+ );
104
+ })}
105
+ </div>
106
+
107
+ {selectedIndex !== null && (
108
+ <p className="text-egyptian-gold text-center text-sm mb-4">
109
+ Card #{selectedIndex + 1} selected. Tap another card to swap!
110
+ </p>
111
+ )}
112
+
113
+ <div className="flex gap-3">
114
+ <button onClick={handleConfirm} className="btn btn-secondary flex-1">
115
+ Keep Order
116
+ </button>
117
+ <button onClick={handleConfirm} className="btn btn-primary flex-1">
118
+ Confirm
119
+ </button>
120
+ </div>
121
+ </motion.div>
122
+ </motion.div>
123
+ );
124
+ }
client/src/components/modals/BlindStealModal.tsx ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { motion } from 'framer-motion';
2
+ import { emitBlindStealSelect } from '../../socket/socket';
3
+
4
+ interface BlindStealModalProps {
5
+ cardCount: number;
6
+ onClose: () => void;
7
+ }
8
+
9
+ export function BlindStealModal({ cardCount, onClose }: BlindStealModalProps) {
10
+ const handleSelect = (position: number) => {
11
+ emitBlindStealSelect(position);
12
+ onClose();
13
+ };
14
+
15
+ return (
16
+ <motion.div
17
+ className="modal-overlay"
18
+ initial={{ opacity: 0 }}
19
+ animate={{ opacity: 1 }}
20
+ exit={{ opacity: 0 }}
21
+ >
22
+ <motion.div
23
+ className="modal-content"
24
+ initial={{ scale: 0.8, opacity: 0 }}
25
+ animate={{ scale: 1, opacity: 1 }}
26
+ exit={{ scale: 0.8, opacity: 0 }}
27
+ onClick={(e) => e.stopPropagation()}
28
+ >
29
+ <h2 className="modal-title">🥷 Steal a Card</h2>
30
+ <p className="text-papyrus text-center mb-4">
31
+ Choose a card to steal (you can't see what they are!)
32
+ </p>
33
+
34
+ <div className="flex justify-center gap-2 flex-wrap mb-6">
35
+ {Array.from({ length: cardCount }).map((_, index) => (
36
+ <motion.button
37
+ key={index}
38
+ className="relative"
39
+ onClick={() => handleSelect(index)}
40
+ initial={{ opacity: 0, rotateY: 180 }}
41
+ animate={{ opacity: 1, rotateY: 0 }}
42
+ transition={{ delay: index * 0.1 }}
43
+ whileHover={{ scale: 1.1, y: -10 }}
44
+ whileTap={{ scale: 0.95 }}
45
+ >
46
+ <div className="w-16 h-24 bg-gradient-to-br from-egyptian-gold to-sand rounded-lg shadow-lg flex items-center justify-center">
47
+ <span className="text-nile-blue font-bold text-2xl">?</span>
48
+ </div>
49
+ <span className="absolute -bottom-2 left-1/2 transform -translate-x-1/2 bg-nile-blue text-papyrus text-xs px-2 py-1 rounded">
50
+ {index + 1}
51
+ </span>
52
+ </motion.button>
53
+ ))}
54
+ </div>
55
+
56
+ <p className="text-papyrus/60 text-center text-sm">
57
+ The victim may have rearranged their cards!
58
+ </p>
59
+ </motion.div>
60
+ </motion.div>
61
+ );
62
+ }
client/src/components/modals/CardTypeSelectModal.tsx ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { motion } from 'framer-motion';
2
+ import { CARD_DATABASE, type CardId } from '@shared/types';
3
+ import { emitSpellboundRequest } from '../../socket/socket';
4
+
5
+ interface CardTypeSelectModalProps {
6
+ targetId?: string;
7
+ onClose: () => void;
8
+ }
9
+
10
+ // Cards that can be requested via Spellbound
11
+ const REQUESTABLE_CARDS: CardId[] = [
12
+ 'sharp_eye', 'wait_a_sec', 'me_or_you', 'spellbound', 'criminal_mummy',
13
+ 'give_and_take', 'shuffle_it', 'king_ra_says_no', 'safe_travels',
14
+ 'take_a_lap', 'all_or_nothing', 'flip_the_table', 'this_is_on_you'
15
+ ];
16
+
17
+ export function CardTypeSelectModal({ targetId, onClose }: CardTypeSelectModalProps) {
18
+ const handleSelect = (cardId: CardId) => {
19
+ if (targetId) {
20
+ emitSpellboundRequest(targetId, cardId);
21
+ }
22
+ onClose();
23
+ };
24
+
25
+ return (
26
+ <motion.div
27
+ className="modal-overlay"
28
+ initial={{ opacity: 0 }}
29
+ animate={{ opacity: 1 }}
30
+ exit={{ opacity: 0 }}
31
+ onClick={onClose}
32
+ >
33
+ <motion.div
34
+ className="modal-content max-h-[80vh] overflow-y-auto"
35
+ initial={{ scale: 0.8, opacity: 0 }}
36
+ animate={{ scale: 1, opacity: 1 }}
37
+ exit={{ scale: 0.8, opacity: 0 }}
38
+ onClick={(e) => e.stopPropagation()}
39
+ >
40
+ <h2 className="modal-title">🔮 Spellbound</h2>
41
+ <p className="text-papyrus text-center mb-4">
42
+ Ask for a specific card. If they don't have it, YOU draw!
43
+ </p>
44
+
45
+ <div className="grid grid-cols-2 gap-2 mb-6">
46
+ {REQUESTABLE_CARDS.map((cardId, index) => {
47
+ const card = CARD_DATABASE[cardId];
48
+ return (
49
+ <motion.button
50
+ key={cardId}
51
+ className="p-3 rounded-lg bg-nile-blue/50 border border-egyptian-gold/30 hover:border-egyptian-gold hover:bg-nile-blue transition-all text-left"
52
+ onClick={() => handleSelect(cardId)}
53
+ initial={{ opacity: 0, scale: 0.9 }}
54
+ animate={{ opacity: 1, scale: 1 }}
55
+ transition={{ delay: index * 0.03 }}
56
+ whileHover={{ scale: 1.02 }}
57
+ whileTap={{ scale: 0.98 }}
58
+ >
59
+ <p className="text-papyrus text-sm font-semibold">{card.nameEn}</p>
60
+ <p className="text-egyptian-gold text-xs" dir="rtl">{card.nameAr}</p>
61
+ </motion.button>
62
+ );
63
+ })}
64
+ </div>
65
+
66
+ <button onClick={onClose} className="btn btn-secondary w-full">
67
+ Cancel
68
+ </button>
69
+ </motion.div>
70
+ </motion.div>
71
+ );
72
+ }
client/src/components/modals/DuelResultModal.tsx ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { motion } from 'framer-motion';
2
+ import { Card } from '../game/Card';
3
+ import { useGameStore } from '../../store/gameStore';
4
+ import type { CardInstance } from '@shared/types';
5
+
6
+ interface DuelResultModalProps {
7
+ challenger?: { playerId: string; card: CardInstance };
8
+ opponent?: { playerId: string; card: CardInstance };
9
+ winnerId?: string;
10
+ onClose: () => void;
11
+ }
12
+
13
+ export function DuelResultModal({ challenger, opponent, winnerId, onClose }: DuelResultModalProps) {
14
+ const gameState = useGameStore((state) => state.gameState);
15
+ const playerId = useGameStore((state) => state.playerId);
16
+
17
+ const challengerName = gameState?.players.find(p => p.id === challenger?.playerId)?.name ?? 'Challenger';
18
+ const opponentName = gameState?.players.find(p => p.id === opponent?.playerId)?.name ?? 'Opponent';
19
+ const winnerName = gameState?.players.find(p => p.id === winnerId)?.name ?? 'Winner';
20
+ const isWinner = winnerId === playerId;
21
+
22
+ return (
23
+ <motion.div
24
+ className="modal-overlay"
25
+ initial={{ opacity: 0 }}
26
+ animate={{ opacity: 1 }}
27
+ exit={{ opacity: 0 }}
28
+ onClick={onClose}
29
+ >
30
+ <motion.div
31
+ className="modal-content"
32
+ initial={{ scale: 0.8, opacity: 0 }}
33
+ animate={{ scale: 1, opacity: 1 }}
34
+ exit={{ scale: 0.8, opacity: 0 }}
35
+ onClick={(e) => e.stopPropagation()}
36
+ >
37
+ <h2 className="modal-title">⚔️ Duel Result</h2>
38
+
39
+ <div className="flex items-center justify-center gap-4 mb-6">
40
+ {/* Challenger */}
41
+ <motion.div
42
+ className="text-center"
43
+ initial={{ x: -50, opacity: 0 }}
44
+ animate={{ x: 0, opacity: 1 }}
45
+ transition={{ delay: 0.2 }}
46
+ >
47
+ <p className="text-papyrus mb-2">{challengerName}</p>
48
+ {challenger?.card && (
49
+ <Card card={challenger.card} size="large" />
50
+ )}
51
+ {winnerId === challenger?.playerId && (
52
+ <motion.p
53
+ className="text-green-400 font-bold mt-2"
54
+ animate={{ scale: [1, 1.2, 1] }}
55
+ transition={{ repeat: Infinity, duration: 0.5 }}
56
+ >
57
+ 🏆 WINNER!
58
+ </motion.p>
59
+ )}
60
+ </motion.div>
61
+
62
+ {/* VS */}
63
+ <motion.div
64
+ className="text-4xl font-bold text-egyptian-gold"
65
+ animate={{ rotate: [0, 10, -10, 0] }}
66
+ transition={{ repeat: 2, duration: 0.3 }}
67
+ >
68
+ VS
69
+ </motion.div>
70
+
71
+ {/* Opponent */}
72
+ <motion.div
73
+ className="text-center"
74
+ initial={{ x: 50, opacity: 0 }}
75
+ animate={{ x: 0, opacity: 1 }}
76
+ transition={{ delay: 0.4 }}
77
+ >
78
+ <p className="text-papyrus mb-2">{opponentName}</p>
79
+ {opponent?.card && (
80
+ <Card card={opponent.card} size="large" />
81
+ )}
82
+ {winnerId === opponent?.playerId && (
83
+ <motion.p
84
+ className="text-green-400 font-bold mt-2"
85
+ animate={{ scale: [1, 1.2, 1] }}
86
+ transition={{ repeat: Infinity, duration: 0.5 }}
87
+ >
88
+ 🏆 WINNER!
89
+ </motion.p>
90
+ )}
91
+ </motion.div>
92
+ </div>
93
+
94
+ <motion.p
95
+ className={`text-center text-xl font-bold mb-4 ${isWinner ? 'text-green-400' : 'text-mummy-red'}`}
96
+ initial={{ scale: 0 }}
97
+ animate={{ scale: 1 }}
98
+ transition={{ delay: 0.6 }}
99
+ >
100
+ {isWinner ? "🎉 You won the duel!" : `${winnerName} won the duel!`}
101
+ </motion.p>
102
+
103
+ <button onClick={onClose} className="btn btn-primary w-full">
104
+ Continue
105
+ </button>
106
+ </motion.div>
107
+ </motion.div>
108
+ );
109
+ }
client/src/components/modals/FlipTableModal.tsx ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useRef } from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { Card } from '../game/Card';
4
+ import { emitRearrangeTopCards } from '../../socket/socket';
5
+ import type { CardInstance } from '@shared/types';
6
+
7
+ interface FlipTableModalProps {
8
+ cards: CardInstance[];
9
+ onClose: () => void;
10
+ }
11
+
12
+ export function FlipTableModal({ cards, onClose }: FlipTableModalProps) {
13
+ const [orderedCards, setOrderedCards] = useState(cards.map(c => c.instanceId));
14
+ const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
15
+ const hasConfirmed = useRef(false);
16
+
17
+ const handleCardClick = (index: number) => {
18
+ if (selectedIndex === null) {
19
+ setSelectedIndex(index);
20
+ } else if (selectedIndex === index) {
21
+ setSelectedIndex(null);
22
+ } else {
23
+ // Swap cards
24
+ const newOrder = [...orderedCards];
25
+ [newOrder[selectedIndex], newOrder[index]] = [newOrder[index], newOrder[selectedIndex]];
26
+ setOrderedCards(newOrder);
27
+ setSelectedIndex(null);
28
+ }
29
+ };
30
+
31
+ const handleConfirm = () => {
32
+ if (hasConfirmed.current) return;
33
+ hasConfirmed.current = true;
34
+ emitRearrangeTopCards(orderedCards);
35
+ onClose();
36
+ };
37
+
38
+ const getCardByInstanceId = (instanceId: string) => {
39
+ return cards.find(c => c.instanceId === instanceId);
40
+ };
41
+
42
+ return (
43
+ <motion.div
44
+ className="modal-overlay"
45
+ initial={{ opacity: 0 }}
46
+ animate={{ opacity: 1 }}
47
+ exit={{ opacity: 0 }}
48
+ >
49
+ <motion.div
50
+ className="modal-content max-w-3xl"
51
+ initial={{ scale: 0.8, opacity: 0 }}
52
+ animate={{ scale: 1, opacity: 1 }}
53
+ exit={{ scale: 0.8, opacity: 0 }}
54
+ onClick={(e) => e.stopPropagation()}
55
+ >
56
+ <h2 className="modal-title">🔄 Flip the Table</h2>
57
+ <p className="text-papyrus text-center mb-2">
58
+ Rearrange the top {cards.length} cards of the deck
59
+ </p>
60
+ <p className="text-yellow-400 text-center text-sm mb-4">
61
+ Tap two cards to swap positions. Position 1 will be drawn first.
62
+ </p>
63
+
64
+ <div className="grid grid-cols-5 gap-2 mb-6 justify-items-center">
65
+ {orderedCards.map((instanceId, index) => {
66
+ const card = getCardByInstanceId(instanceId);
67
+ if (!card) return null;
68
+
69
+ const isMummy = card.cardId === 'mummified';
70
+ const isSelected = selectedIndex === index;
71
+
72
+ return (
73
+ <motion.div
74
+ key={instanceId}
75
+ className={`relative cursor-pointer rounded-lg ${isSelected ? 'ring-4 ring-egyptian-gold' : ''}`}
76
+ onClick={() => handleCardClick(index)}
77
+ whileHover={{ scale: 1.05 }}
78
+ whileTap={{ scale: 0.95 }}
79
+ animate={isSelected ? { y: -10 } : { y: 0 }}
80
+ >
81
+ <Card card={card} size="small" />
82
+ <span className="absolute -top-2 -left-2 bg-egyptian-gold text-nile-blue font-bold w-6 h-6 rounded-full flex items-center justify-center text-sm">
83
+ {index + 1}
84
+ </span>
85
+ {isMummy && (
86
+ <span className="absolute -top-2 -right-2 text-xl">💀</span>
87
+ )}
88
+ </motion.div>
89
+ );
90
+ })}
91
+ </div>
92
+
93
+ {selectedIndex !== null && (
94
+ <p className="text-egyptian-gold text-center text-sm mb-4">
95
+ Position #{selectedIndex + 1} selected. Tap another card to swap!
96
+ </p>
97
+ )}
98
+
99
+ <p className="text-papyrus/60 text-center text-sm mb-4">
100
+ 💡 Tip: Push mummies to higher positions (drawn later)
101
+ </p>
102
+
103
+ <div className="flex gap-3">
104
+ <button onClick={handleConfirm} className="btn btn-secondary flex-1">
105
+ Keep Order
106
+ </button>
107
+ <button onClick={handleConfirm} className="btn btn-primary flex-1">
108
+ Confirm
109
+ </button>
110
+ </div>
111
+ </motion.div>
112
+ </motion.div>
113
+ );
114
+ }
client/src/components/modals/GameOverModal.tsx ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { motion } from 'framer-motion';
2
+ import { useGameStore } from '../../store/gameStore';
3
+ import { emitLeaveRoom } from '../../socket/socket';
4
+
5
+ interface GameOverModalProps {
6
+ winnerId?: string;
7
+ winnerName?: string;
8
+ onClose: () => void;
9
+ }
10
+
11
+ export function GameOverModal({ winnerId, winnerName, onClose }: GameOverModalProps) {
12
+ const playerId = useGameStore((state) => state.playerId);
13
+ const reset = useGameStore((state) => state.reset);
14
+
15
+ const isWinner = winnerId === playerId;
16
+
17
+ const handleLeave = () => {
18
+ emitLeaveRoom();
19
+ reset();
20
+ onClose();
21
+ };
22
+
23
+ return (
24
+ <motion.div
25
+ className="modal-overlay"
26
+ initial={{ opacity: 0 }}
27
+ animate={{ opacity: 1 }}
28
+ exit={{ opacity: 0 }}
29
+ >
30
+ <motion.div
31
+ className="modal-content text-center"
32
+ initial={{ scale: 0.5, opacity: 0, rotateY: 180 }}
33
+ animate={{ scale: 1, opacity: 1, rotateY: 0 }}
34
+ transition={{ type: 'spring', duration: 0.8 }}
35
+ onClick={(e) => e.stopPropagation()}
36
+ >
37
+ {isWinner ? (
38
+ <>
39
+ <motion.div
40
+ className="text-8xl mb-4"
41
+ animate={{
42
+ rotate: [0, -10, 10, -10, 10, 0],
43
+ scale: [1, 1.2, 1],
44
+ }}
45
+ transition={{ duration: 0.5, repeat: Infinity, repeatDelay: 1 }}
46
+ >
47
+ 🏆
48
+ </motion.div>
49
+ <h2 className="text-4xl font-bold text-egyptian-gold mb-4">
50
+ YOU WIN!
51
+ </h2>
52
+ <p className="text-2xl text-papyrus mb-2" dir="rtl">
53
+ مبروك! انت الفائز
54
+ </p>
55
+ <motion.p
56
+ className="text-green-400 text-xl"
57
+ animate={{ opacity: [0.5, 1, 0.5] }}
58
+ transition={{ repeat: Infinity, duration: 1 }}
59
+ >
60
+ You survived the mummies!
61
+ </motion.p>
62
+ </>
63
+ ) : (
64
+ <>
65
+ <motion.div
66
+ className="text-8xl mb-4"
67
+ animate={{ opacity: [0.5, 1, 0.5] }}
68
+ transition={{ repeat: Infinity, duration: 1.5 }}
69
+ >
70
+ 👑
71
+ </motion.div>
72
+ <h2 className="text-4xl font-bold text-egyptian-gold mb-4">
73
+ GAME OVER
74
+ </h2>
75
+ <p className="text-papyrus text-xl mb-2">
76
+ <span className="text-egyptian-gold font-bold">{winnerName}</span> wins!
77
+ </p>
78
+ <p className="text-mummy-red text-lg">
79
+ Better luck next time!
80
+ </p>
81
+ </>
82
+ )}
83
+
84
+ <motion.div
85
+ className="mt-8"
86
+ initial={{ y: 20, opacity: 0 }}
87
+ animate={{ y: 0, opacity: 1 }}
88
+ transition={{ delay: 1 }}
89
+ >
90
+ <button onClick={handleLeave} className="btn btn-primary text-xl px-10">
91
+ Back to Menu
92
+ </button>
93
+ </motion.div>
94
+
95
+ {/* Confetti effect for winner */}
96
+ {isWinner && (
97
+ <div className="absolute inset-0 pointer-events-none overflow-hidden">
98
+ {Array.from({ length: 50 }).map((_, i) => (
99
+ <motion.div
100
+ key={i}
101
+ className="absolute w-3 h-3 rounded-full"
102
+ style={{
103
+ backgroundColor: ['#d4a843', '#8b4513', '#f5f5dc', '#1a3a5c'][i % 4],
104
+ left: `${Math.random() * 100}%`,
105
+ }}
106
+ initial={{ y: -20, opacity: 1 }}
107
+ animate={{
108
+ y: '100vh',
109
+ rotate: Math.random() * 360,
110
+ opacity: 0,
111
+ }}
112
+ transition={{
113
+ duration: 2 + Math.random() * 2,
114
+ delay: Math.random() * 0.5,
115
+ repeat: Infinity,
116
+ }}
117
+ />
118
+ ))}
119
+ </div>
120
+ )}
121
+ </motion.div>
122
+ </motion.div>
123
+ );
124
+ }
client/src/components/modals/KingRaPromptModal.tsx ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { CARD_DATABASE, type CardId } from '@shared/types';
4
+ import { useGameStore } from '../../store/gameStore';
5
+ import { emitKingRaResponse } from '../../socket/socket';
6
+
7
+ interface KingRaPromptModalProps {
8
+ playerId?: string;
9
+ cardPlayed?: CardId;
10
+ timeout: number;
11
+ onClose: () => void;
12
+ }
13
+
14
+ export function KingRaPromptModal({ playerId, cardPlayed, timeout, onClose }: KingRaPromptModalProps) {
15
+ const [timeLeft, setTimeLeft] = useState(timeout);
16
+ const gameState = useGameStore((state) => state.gameState);
17
+ const myHand = useGameStore((state) => state.myHand);
18
+ const hasResponded = useRef(false);
19
+ const isMounted = useRef(true);
20
+
21
+ const hasKingRa = myHand.some(c => c.cardId === 'king_ra_says_no');
22
+ console.log('KingRaPromptModal render - myHand:', myHand.map(c => c.cardId), 'hasKingRa:', hasKingRa);
23
+ const playerName = gameState?.players.find(p => p.id === playerId)?.name ?? 'A player';
24
+ const cardDef = cardPlayed ? CARD_DATABASE[cardPlayed] : null;
25
+
26
+ // Track mount state
27
+ useEffect(() => {
28
+ isMounted.current = true;
29
+ hasResponded.current = false; // Reset on mount
30
+ return () => {
31
+ isMounted.current = false;
32
+ };
33
+ }, []);
34
+
35
+ useEffect(() => {
36
+ const interval = setInterval(() => {
37
+ if (!isMounted.current) {
38
+ clearInterval(interval);
39
+ return;
40
+ }
41
+
42
+ setTimeLeft((prev) => {
43
+ if (prev <= 100) {
44
+ clearInterval(interval);
45
+ // Auto-decline on timeout
46
+ if (!hasResponded.current && isMounted.current) {
47
+ hasResponded.current = true;
48
+ emitKingRaResponse(false);
49
+ onClose();
50
+ }
51
+ return 0;
52
+ }
53
+ return prev - 100;
54
+ });
55
+ }, 100);
56
+
57
+ return () => clearInterval(interval);
58
+ }, [onClose]);
59
+
60
+ const handleResponse = (useKingRa: boolean) => {
61
+ console.log('King Ra handleResponse called:', { useKingRa, hasResponded: hasResponded.current, hasKingRa });
62
+ if (hasResponded.current) {
63
+ console.log('Already responded, ignoring');
64
+ return;
65
+ }
66
+ hasResponded.current = true;
67
+ console.log('Emitting kingRaResponse:', useKingRa);
68
+ emitKingRaResponse(useKingRa);
69
+ onClose();
70
+ };
71
+
72
+ const progressPercent = (timeLeft / timeout) * 100;
73
+
74
+ return (
75
+ <motion.div
76
+ className="modal-overlay"
77
+ initial={{ opacity: 0 }}
78
+ animate={{ opacity: 1 }}
79
+ exit={{ opacity: 0 }}
80
+ >
81
+ <motion.div
82
+ className="modal-content"
83
+ initial={{ scale: 0.8, opacity: 0 }}
84
+ animate={{ scale: 1, opacity: 1 }}
85
+ exit={{ scale: 0.8, opacity: 0 }}
86
+ onClick={(e) => e.stopPropagation()}
87
+ >
88
+ <h2 className="modal-title">👑 King Ra Says NO?</h2>
89
+
90
+ <motion.div
91
+ className="text-center mb-4"
92
+ animate={{ scale: [1, 1.05, 1] }}
93
+ transition={{ repeat: Infinity, duration: 0.5 }}
94
+ >
95
+ <p className="text-papyrus">
96
+ <span className="text-egyptian-gold font-bold">{playerName}</span> played:
97
+ </p>
98
+ <p className="text-2xl font-bold text-mummy-red mt-2">
99
+ {cardDef?.nameEn}
100
+ </p>
101
+ <p className="text-xl text-egyptian-gold" dir="rtl">
102
+ {cardDef?.nameAr}
103
+ </p>
104
+ </motion.div>
105
+
106
+ {/* Timer */}
107
+ <div className="king-ra-timer mb-6">
108
+ <motion.div
109
+ className="king-ra-timer-bar"
110
+ style={{ width: `${progressPercent}%` }}
111
+ />
112
+ </div>
113
+
114
+ <p className="text-center text-papyrus/80 mb-4">
115
+ {hasKingRa
116
+ ? 'Do you want to cancel this action?'
117
+ : "You don't have King Ra Says NO!"}
118
+ </p>
119
+
120
+ <div className="flex gap-3">
121
+ <button
122
+ onClick={() => handleResponse(false)}
123
+ className="btn btn-secondary flex-1"
124
+ >
125
+ Let it happen
126
+ </button>
127
+ <button
128
+ onClick={() => handleResponse(true)}
129
+ disabled={!hasKingRa}
130
+ className="btn btn-danger flex-1"
131
+ >
132
+ 👑 CANCEL IT!
133
+ </button>
134
+ </div>
135
+ </motion.div>
136
+ </motion.div>
137
+ );
138
+ }
client/src/components/modals/ModalRenderer.tsx ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { AnimatePresence } from 'framer-motion';
2
+ import { useGameStore } from '../../store/gameStore';
3
+ import { PeekCardsModal } from './PeekCardsModal';
4
+ import { TargetSelectModal } from './TargetSelectModal';
5
+ import { CardTypeSelectModal } from './CardTypeSelectModal';
6
+ import { BlindStealModal } from './BlindStealModal';
7
+ import { ArrangeHandModal } from './ArrangeHandModal';
8
+ import { ViewHandModal } from './ViewHandModal';
9
+ import { FlipTableModal } from './FlipTableModal';
10
+ import { PlaceMummyModal } from './PlaceMummyModal';
11
+ import { KingRaPromptModal } from './KingRaPromptModal';
12
+ import { DuelResultModal } from './DuelResultModal';
13
+ import { SwapResultModal } from './SwapResultModal';
14
+ import { GameOverModal } from './GameOverModal';
15
+
16
+ export function ModalRenderer() {
17
+ const activeModal = useGameStore((state) => state.activeModal);
18
+ const modalData = useGameStore((state) => state.modalData);
19
+ const closeModal = useGameStore((state) => state.closeModal);
20
+
21
+ const renderModal = () => {
22
+ switch (activeModal) {
23
+ case 'peek-cards':
24
+ return <PeekCardsModal cards={modalData.peekCards ?? []} onClose={closeModal} />;
25
+
26
+ case 'target-select':
27
+ return <TargetSelectModal players={modalData.targetPlayers ?? []} onClose={closeModal} />;
28
+
29
+ case 'card-type-select':
30
+ return <CardTypeSelectModal targetId={modalData.spellboundTargetId} onClose={closeModal} />;
31
+
32
+ case 'blind-steal':
33
+ return <BlindStealModal cardCount={modalData.blindStealCount ?? 0} onClose={closeModal} />;
34
+
35
+ case 'arrange-hand':
36
+ return <ArrangeHandModal onClose={closeModal} />;
37
+
38
+ case 'view-hand':
39
+ return <ViewHandModal cards={modalData.viewHandCards ?? []} targetId={modalData.viewHandTargetId} onClose={closeModal} />;
40
+
41
+ case 'flip-table':
42
+ return <FlipTableModal cards={modalData.flipTableCards ?? []} onClose={closeModal} />;
43
+
44
+ case 'place-mummy':
45
+ return <PlaceMummyModal deckSize={modalData.deckSize ?? 0} onClose={closeModal} />;
46
+
47
+ case 'king-ra-prompt':
48
+ return (
49
+ <KingRaPromptModal
50
+ key={`king-ra-${modalData.kingRaPlayerId}-${modalData.kingRaCardPlayed}`}
51
+ playerId={modalData.kingRaPlayerId}
52
+ cardPlayed={modalData.kingRaCardPlayed}
53
+ timeout={modalData.kingRaTimeout ?? 5000}
54
+ onClose={closeModal}
55
+ />
56
+ );
57
+
58
+ case 'duel-result':
59
+ return (
60
+ <DuelResultModal
61
+ challenger={modalData.duelChallenger}
62
+ opponent={modalData.duelOpponent}
63
+ winnerId={modalData.duelWinnerId}
64
+ onClose={closeModal}
65
+ />
66
+ );
67
+
68
+ case 'swap-result':
69
+ return (
70
+ <SwapResultModal
71
+ gaveCard={modalData.swapGaveCard}
72
+ receivedCard={modalData.swapReceivedCard}
73
+ otherPlayer={modalData.swapOtherPlayer}
74
+ onClose={closeModal}
75
+ />
76
+ );
77
+
78
+ case 'game-over':
79
+ return <GameOverModal winnerId={modalData.winnerId} winnerName={modalData.winnerName} onClose={closeModal} />;
80
+
81
+ default:
82
+ return null;
83
+ }
84
+ };
85
+
86
+ return (
87
+ <AnimatePresence>
88
+ {activeModal && renderModal()}
89
+ </AnimatePresence>
90
+ );
91
+ }
client/src/components/modals/PeekCardsModal.tsx ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { motion } from 'framer-motion';
2
+ import { Card } from '../game/Card';
3
+ import type { CardInstance } from '@shared/types';
4
+
5
+ interface PeekCardsModalProps {
6
+ cards: CardInstance[];
7
+ onClose: () => void;
8
+ }
9
+
10
+ export function PeekCardsModal({ cards, onClose }: PeekCardsModalProps) {
11
+ return (
12
+ <motion.div
13
+ className="modal-overlay"
14
+ initial={{ opacity: 0 }}
15
+ animate={{ opacity: 1 }}
16
+ exit={{ opacity: 0 }}
17
+ onClick={onClose}
18
+ >
19
+ <motion.div
20
+ className="modal-content"
21
+ initial={{ scale: 0.8, opacity: 0 }}
22
+ animate={{ scale: 1, opacity: 1 }}
23
+ exit={{ scale: 0.8, opacity: 0 }}
24
+ onClick={(e) => e.stopPropagation()}
25
+ >
26
+ <h2 className="modal-title">👁️ Peek at the Deck</h2>
27
+ <p className="text-papyrus text-center mb-4">
28
+ Top {cards.length} cards (first card is on top)
29
+ </p>
30
+
31
+ <div className="flex justify-center gap-4 flex-wrap mb-6">
32
+ {cards.map((card, index) => (
33
+ <motion.div
34
+ key={card.instanceId}
35
+ initial={{ opacity: 0, y: 20 }}
36
+ animate={{ opacity: 1, y: 0 }}
37
+ transition={{ delay: index * 0.15 }}
38
+ className="relative"
39
+ >
40
+ <Card card={card} size="large" />
41
+ <span className="absolute -top-2 -left-2 bg-egyptian-gold text-nile-blue font-bold w-6 h-6 rounded-full flex items-center justify-center text-sm">
42
+ {index + 1}
43
+ </span>
44
+ {card.cardId === 'mummified' && (
45
+ <span className="absolute -top-2 -right-2 text-2xl">💀</span>
46
+ )}
47
+ </motion.div>
48
+ ))}
49
+ </div>
50
+
51
+ <button onClick={onClose} className="btn btn-primary w-full">
52
+ Close
53
+ </button>
54
+ </motion.div>
55
+ </motion.div>
56
+ );
57
+ }
client/src/components/modals/PlaceMummyModal.tsx ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { emitPlaceMummy } from '../../socket/socket';
4
+
5
+ interface PlaceMummyModalProps {
6
+ deckSize: number;
7
+ onClose: () => void;
8
+ }
9
+
10
+ const AUTO_CONFIRM_TIMEOUT = 5000; // 5 seconds
11
+
12
+ export function PlaceMummyModal({ deckSize, onClose }: PlaceMummyModalProps) {
13
+ const [position, setPosition] = useState(Math.floor(deckSize / 2));
14
+ const [timeLeft, setTimeLeft] = useState(AUTO_CONFIRM_TIMEOUT / 1000);
15
+ const hasConfirmed = useRef(false);
16
+
17
+ const handleConfirm = () => {
18
+ if (hasConfirmed.current) return;
19
+ hasConfirmed.current = true;
20
+ emitPlaceMummy(position);
21
+ onClose();
22
+ };
23
+
24
+ // Auto-confirm timer
25
+ useEffect(() => {
26
+ const interval = setInterval(() => {
27
+ setTimeLeft((prev) => {
28
+ if (prev <= 1) {
29
+ handleConfirm();
30
+ return 0;
31
+ }
32
+ return prev - 1;
33
+ });
34
+ }, 1000);
35
+
36
+ return () => clearInterval(interval);
37
+ }, []);
38
+
39
+ const positionLabel = () => {
40
+ if (position === 0) return 'Top (next draw!)';
41
+ if (position === deckSize) return 'Bottom (last draw)';
42
+ if (position <= deckSize * 0.25) return 'Near top (risky!)';
43
+ if (position >= deckSize * 0.75) return 'Near bottom (safe)';
44
+ return 'Middle';
45
+ };
46
+
47
+ return (
48
+ <motion.div
49
+ className="modal-overlay"
50
+ initial={{ opacity: 0 }}
51
+ animate={{ opacity: 1 }}
52
+ exit={{ opacity: 0 }}
53
+ >
54
+ <motion.div
55
+ className="modal-content"
56
+ initial={{ scale: 0.8, opacity: 0 }}
57
+ animate={{ scale: 1, opacity: 1 }}
58
+ exit={{ scale: 0.8, opacity: 0 }}
59
+ onClick={(e) => e.stopPropagation()}
60
+ >
61
+ <h2 className="modal-title">💀 Hide the Mummy</h2>
62
+ <p className="text-egyptian-gold text-center text-sm mb-2">
63
+ Auto-confirm in {timeLeft}s
64
+ </p>
65
+ <p className="text-papyrus text-center mb-2">
66
+ You defused the mummy! Now hide it back in the deck.
67
+ </p>
68
+ <p className="text-yellow-400 text-center text-sm mb-6">
69
+ Only you know where it's placed!
70
+ </p>
71
+
72
+ {/* Visual deck representation */}
73
+ <div className="relative mb-6">
74
+ <div className="h-48 bg-nile-blue/30 rounded-lg border border-egyptian-gold/30 relative overflow-hidden">
75
+ {/* Position indicator */}
76
+ <motion.div
77
+ className="absolute left-0 right-0 flex items-center justify-center"
78
+ style={{
79
+ top: `${(position / deckSize) * 100}%`,
80
+ transform: 'translateY(-50%)',
81
+ }}
82
+ animate={{ x: [0, 10, 0] }}
83
+ transition={{ repeat: Infinity, duration: 1 }}
84
+ >
85
+ <div className="bg-mummy-red px-3 py-1 rounded-full flex items-center gap-2">
86
+ <span>💀</span>
87
+ <span className="text-white text-sm">Mummy Here</span>
88
+ </div>
89
+ </motion.div>
90
+
91
+ {/* Top label */}
92
+ <span className="absolute top-2 left-2 text-papyrus/60 text-xs">Top (drawn first)</span>
93
+
94
+ {/* Bottom label */}
95
+ <span className="absolute bottom-2 left-2 text-papyrus/60 text-xs">Bottom (drawn last)</span>
96
+ </div>
97
+ </div>
98
+
99
+ {/* Slider */}
100
+ <div className="mb-4">
101
+ <input
102
+ type="range"
103
+ min={0}
104
+ max={deckSize}
105
+ value={position}
106
+ onChange={(e) => setPosition(parseInt(e.target.value))}
107
+ className="w-full accent-egyptian-gold"
108
+ />
109
+ <div className="flex justify-between text-papyrus/60 text-sm">
110
+ <span>Top (0)</span>
111
+ <span className="text-egyptian-gold font-semibold">{positionLabel()}</span>
112
+ <span>Bottom ({deckSize})</span>
113
+ </div>
114
+ </div>
115
+
116
+ {/* Number input */}
117
+ <div className="mb-6">
118
+ <label className="text-papyrus text-sm">
119
+ Position ({deckSize} cards in deck):
120
+ </label>
121
+ <input
122
+ type="number"
123
+ min={0}
124
+ max={deckSize}
125
+ value={position}
126
+ onChange={(e) => setPosition(Math.min(deckSize, Math.max(0, parseInt(e.target.value) || 0)))}
127
+ className="w-full mt-1 text-center"
128
+ />
129
+ </div>
130
+
131
+ <button onClick={handleConfirm} className="btn btn-primary w-full">
132
+ Hide Mummy
133
+ </button>
134
+ </motion.div>
135
+ </motion.div>
136
+ );
137
+ }
client/src/components/modals/SwapResultModal.tsx ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { motion } from 'framer-motion';
2
+ import type { CardInstance } from '@shared/types';
3
+ import { Card } from '../game/Card';
4
+
5
+ interface SwapResultModalProps {
6
+ gaveCard?: CardInstance;
7
+ receivedCard?: CardInstance;
8
+ otherPlayer?: string;
9
+ onClose: () => void;
10
+ }
11
+
12
+ export function SwapResultModal({ gaveCard, receivedCard, otherPlayer, onClose }: SwapResultModalProps) {
13
+ return (
14
+ <motion.div
15
+ className="modal-overlay"
16
+ initial={{ opacity: 0 }}
17
+ animate={{ opacity: 1 }}
18
+ exit={{ opacity: 0 }}
19
+ onClick={onClose}
20
+ >
21
+ <motion.div
22
+ className="modal-content"
23
+ initial={{ scale: 0.8, opacity: 0 }}
24
+ animate={{ scale: 1, opacity: 1 }}
25
+ exit={{ scale: 0.8, opacity: 0 }}
26
+ onClick={(e) => e.stopPropagation()}
27
+ >
28
+ <h2 className="modal-title">🔄 Card Swap</h2>
29
+ <p className="text-papyrus text-center mb-4">
30
+ Exchange with <span className="text-egyptian-gold font-bold">{otherPlayer}</span>
31
+ </p>
32
+
33
+ <div className="flex items-center justify-center gap-4 mb-6">
34
+ {/* What you gave */}
35
+ <div className="text-center">
36
+ <p className="text-red-400 text-sm mb-2">You Gave</p>
37
+ {gaveCard && <Card card={gaveCard} size="medium" disabled />}
38
+ </div>
39
+
40
+ {/* Arrow */}
41
+ <motion.div
42
+ className="text-4xl text-egyptian-gold"
43
+ animate={{ x: [0, 10, 0] }}
44
+ transition={{ repeat: Infinity, duration: 1 }}
45
+ >
46
+
47
+ </motion.div>
48
+
49
+ {/* What you received */}
50
+ <div className="text-center">
51
+ <p className="text-green-400 text-sm mb-2">You Received</p>
52
+ {receivedCard && <Card card={receivedCard} size="medium" disabled />}
53
+ </div>
54
+ </div>
55
+
56
+ <button onClick={onClose} className="btn btn-primary w-full">
57
+ OK
58
+ </button>
59
+ </motion.div>
60
+ </motion.div>
61
+ );
62
+ }
client/src/components/modals/TargetSelectModal.tsx ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { motion } from 'framer-motion';
2
+ import type { Player } from '@shared/types';
3
+
4
+ interface TargetSelectModalProps {
5
+ players: Player[];
6
+ onClose: () => void;
7
+ }
8
+
9
+ export function TargetSelectModal({ players, onClose }: TargetSelectModalProps) {
10
+ const handleSelect = (playerId: string) => {
11
+ // Call the callback set by GameBoard
12
+ // The callback returns true if we should NOT close (e.g., opening another modal)
13
+ const callback = (window as any).__targetSelectCallback;
14
+ if (callback) {
15
+ const keepOpen = callback(playerId);
16
+ if (keepOpen) return; // Don't close if another modal is being opened
17
+ }
18
+ onClose();
19
+ };
20
+
21
+ return (
22
+ <motion.div
23
+ className="modal-overlay"
24
+ initial={{ opacity: 0 }}
25
+ animate={{ opacity: 1 }}
26
+ exit={{ opacity: 0 }}
27
+ onClick={onClose}
28
+ >
29
+ <motion.div
30
+ className="modal-content"
31
+ initial={{ scale: 0.8, opacity: 0 }}
32
+ animate={{ scale: 1, opacity: 1 }}
33
+ exit={{ scale: 0.8, opacity: 0 }}
34
+ onClick={(e) => e.stopPropagation()}
35
+ >
36
+ <h2 className="modal-title">🎯 Select Target</h2>
37
+ <p className="text-papyrus text-center mb-4">Choose an opponent</p>
38
+
39
+ <div className="space-y-2 mb-6">
40
+ {players.map((player, index) => (
41
+ <motion.button
42
+ key={player.id}
43
+ className="w-full p-3 rounded-lg bg-nile-blue/50 border border-egyptian-gold/30 hover:border-egyptian-gold hover:bg-nile-blue transition-all flex items-center justify-between"
44
+ onClick={() => handleSelect(player.id)}
45
+ initial={{ opacity: 0, x: -20 }}
46
+ animate={{ opacity: 1, x: 0 }}
47
+ transition={{ delay: index * 0.1 }}
48
+ whileHover={{ scale: 1.02 }}
49
+ whileTap={{ scale: 0.98 }}
50
+ >
51
+ <div className="flex items-center gap-3">
52
+ <div className="player-avatar w-10 h-10">
53
+ {player.name.charAt(0).toUpperCase()}
54
+ </div>
55
+ <span className="text-papyrus font-semibold">{player.name}</span>
56
+ </div>
57
+ <span className="text-egyptian-gold">{player.cardCount} cards</span>
58
+ </motion.button>
59
+ ))}
60
+ </div>
61
+
62
+ <button onClick={onClose} className="btn btn-secondary w-full">
63
+ Cancel
64
+ </button>
65
+ </motion.div>
66
+ </motion.div>
67
+ );
68
+ }
client/src/components/modals/ViewHandModal.tsx ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { Card } from '../game/Card';
4
+ import { emitBurnCard } from '../../socket/socket';
5
+ import type { CardInstance } from '@shared/types';
6
+ import { useGameStore } from '../../store/gameStore';
7
+
8
+ interface ViewHandModalProps {
9
+ cards: CardInstance[];
10
+ targetId?: string;
11
+ onClose: () => void;
12
+ }
13
+
14
+ export function ViewHandModal({ cards, targetId, onClose }: ViewHandModalProps) {
15
+ const [selectedCard, setSelectedCard] = useState<string | null>(null);
16
+ const gameState = useGameStore((state) => state.gameState);
17
+ const targetName = gameState?.players.find(p => p.id === targetId)?.name ?? 'Opponent';
18
+
19
+ const handleBurn = () => {
20
+ if (selectedCard) {
21
+ emitBurnCard(selectedCard);
22
+ onClose();
23
+ }
24
+ };
25
+
26
+ return (
27
+ <motion.div
28
+ className="modal-overlay"
29
+ initial={{ opacity: 0 }}
30
+ animate={{ opacity: 1 }}
31
+ exit={{ opacity: 0 }}
32
+ >
33
+ <motion.div
34
+ className="modal-content max-w-2xl"
35
+ initial={{ scale: 0.8, opacity: 0 }}
36
+ animate={{ scale: 1, opacity: 1 }}
37
+ exit={{ scale: 0.8, opacity: 0 }}
38
+ onClick={(e) => e.stopPropagation()}
39
+ >
40
+ <h2 className="modal-title">👁️ {targetName}'s Hand</h2>
41
+ <p className="text-papyrus text-center mb-2">
42
+ Select one card to BURN (discard)
43
+ </p>
44
+ <p className="text-mummy-red text-center text-sm mb-4">
45
+ 🔥 The chosen card will be destroyed!
46
+ </p>
47
+
48
+ <div className="flex justify-center gap-3 flex-wrap mb-6">
49
+ {cards.map((card, index) => (
50
+ <motion.div
51
+ key={card.instanceId}
52
+ initial={{ opacity: 0, y: 20 }}
53
+ animate={{ opacity: 1, y: 0 }}
54
+ transition={{ delay: index * 0.1 }}
55
+ onClick={() => setSelectedCard(card.instanceId)}
56
+ >
57
+ <Card
58
+ card={card}
59
+ size="medium"
60
+ selected={selectedCard === card.instanceId}
61
+ />
62
+ </motion.div>
63
+ ))}
64
+ </div>
65
+
66
+ <div className="flex gap-3">
67
+ <button onClick={onClose} className="btn btn-secondary flex-1">
68
+ Cancel
69
+ </button>
70
+ <button
71
+ onClick={handleBurn}
72
+ disabled={!selectedCard}
73
+ className="btn btn-danger flex-1"
74
+ >
75
+ 🔥 Burn Card
76
+ </button>
77
+ </div>
78
+ </motion.div>
79
+ </motion.div>
80
+ );
81
+ }
client/src/components/screens/GameBoard.tsx ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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';
5
+ 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);
12
+ const playerId = useGameStore((state) => state.playerId);
13
+ const isMyTurn = useGameStore((state) => state.isMyTurn);
14
+ const turnsRemaining = useGameStore((state) => state.turnsRemaining);
15
+ const selectedCards = useGameStore((state) => state.selectedCards);
16
+ const openModal = useGameStore((state) => state.openModal);
17
+ const addToast = useGameStore((state) => state.addToast);
18
+ const activeModal = useGameStore((state) => state.activeModal);
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(() => {
25
+ if (pendingCard && activeModal !== 'target-select' && activeModal !== 'card-type-select') {
26
+ setPendingCard(null);
27
+ }
28
+ }, [activeModal]);
29
+
30
+ // Also clear pendingCard when it's no longer in hand (was played)
31
+ useEffect(() => {
32
+ if (pendingCard && !myHand.some(c => c.instanceId === pendingCard.instanceId)) {
33
+ setPendingCard(null);
34
+ }
35
+ }, [myHand, pendingCard]);
36
+
37
+ if (!gameState) {
38
+ return <div className="flex-1 flex items-center justify-center text-papyrus">Loading game...</div>;
39
+ }
40
+
41
+ const currentPlayer = gameState.players[gameState.currentPlayerIndex];
42
+ const otherPlayers = gameState.players.filter(p => p.id !== playerId);
43
+ const myPlayer = gameState.players.find(p => p.id === playerId);
44
+
45
+ const handleCardClick = (card: CardInstance) => {
46
+ // Block card plays during reaction window (except handled by modal)
47
+ if (reactionWindowActive) {
48
+ addToast('Wait for reaction window to end!', 'warning');
49
+ return;
50
+ }
51
+
52
+ if (!isMyTurn) {
53
+ addToast("It's not your turn!", 'warning');
54
+ return;
55
+ }
56
+
57
+ const cardDef = CARD_DATABASE[card.cardId];
58
+
59
+ // Check if it's a half card
60
+ if (cardDef.isHalf) {
61
+ const count = myHand.filter(c => c.cardId === card.cardId).length;
62
+ if (count < 2) {
63
+ addToast('Half cards require 2 copies to play!', 'warning');
64
+ return;
65
+ }
66
+ }
67
+
68
+ // Check if card needs a target
69
+ const cardsNeedingTarget = [
70
+ 'me_or_you', 'spellbound', 'criminal_mummy', 'give_and_take',
71
+ 'all_or_nothing', 'this_is_on_you'
72
+ ];
73
+
74
+ if (cardsNeedingTarget.includes(card.cardId)) {
75
+ // Open target selection modal
76
+ setPendingCard(card);
77
+ const alivePlayers = otherPlayers.filter(p => p.isAlive);
78
+ openModal('target-select', { targetPlayers: alivePlayers });
79
+ return;
80
+ }
81
+
82
+ // Special handling for spellbound (needs card type selection too)
83
+ // This is handled after target selection in modal
84
+
85
+ // Play the card directly
86
+ emitPlayCard(card.instanceId);
87
+ };
88
+
89
+ const handleDrawCard = () => {
90
+ if (!isMyTurn) {
91
+ addToast("It's not your turn!", 'warning');
92
+ return;
93
+ }
94
+ emitDrawCard();
95
+ };
96
+
97
+ const handleTargetSelect = (targetId: string): boolean => {
98
+ if (!pendingCard) return false;
99
+
100
+ if (pendingCard.cardId === 'spellbound') {
101
+ // Emit the card play first to consume the card
102
+ emitPlayCard(pendingCard.instanceId, targetId);
103
+ // Then open card type selection modal
104
+ openModal('card-type-select', { spellboundTargetId: targetId });
105
+ setPendingCard(null);
106
+ return true; // Keep target modal from closing since we're opening another
107
+ } else {
108
+ emitPlayCard(pendingCard.instanceId, targetId);
109
+ setPendingCard(null);
110
+ return false;
111
+ }
112
+ };
113
+
114
+ // Subscribe to target selection from modal
115
+ const setTargetSelectCallback = (callback: (targetId: string) => void) => {
116
+ (window as any).__targetSelectCallback = callback;
117
+ };
118
+ setTargetSelectCallback(handleTargetSelect);
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}
134
+ player={player}
135
+ isCurrentTurn={currentPlayer?.id === player.id}
136
+ />
137
+ ))}
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 */}
154
+ <div className="text-center">
155
+ <motion.div
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
+ )}
169
+ </motion.div>
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]}
177
+ size="large"
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 */}
203
+ <div className="hand-area">
204
+ <AnimatePresence mode="popLayout">
205
+ {myHand.map((card, index) => (
206
+ <motion.div
207
+ key={card.instanceId}
208
+ layout
209
+ initial={{ opacity: 0, y: 50, rotate: -10 }}
210
+ animate={{
211
+ opacity: 1,
212
+ y: 0,
213
+ rotate: 0,
214
+ transition: { delay: index * 0.05 }
215
+ }}
216
+ exit={{ opacity: 0, y: -50, scale: 0.5 }}
217
+ >
218
+ <Card
219
+ card={card}
220
+ selected={selectedCards.includes(card.instanceId)}
221
+ onClick={() => handleCardClick(card)}
222
+ disabled={!isMyTurn}
223
+ />
224
+ </motion.div>
225
+ ))}
226
+ </AnimatePresence>
227
+ </div>
228
+
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>
240
+ </div>
241
+ );
242
+ }
243
+
244
+ interface PlayerDisplayProps {
245
+ player: Player;
246
+ isCurrentTurn: boolean;
247
+ }
248
+
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
+ )}
281
+ </motion.div>
282
+ );
283
+ }
client/src/components/screens/Lobby.tsx ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { useGameStore } from '../../store/gameStore';
4
+ import { emitCreateRoom, emitJoinRoom, emitGetRooms } from '../../socket/socket';
5
+
6
+ export function Lobby() {
7
+ const [roomName, setRoomName] = useState('');
8
+ const [joinCode, setJoinCode] = useState('');
9
+ const [activeTab, setActiveTab] = useState<'create' | 'join' | 'browse'>('create');
10
+
11
+ const playerName = useGameStore((state) => state.playerName);
12
+ const roomList = useGameStore((state) => state.roomList);
13
+ const setScreen = useGameStore((state) => state.setScreen);
14
+
15
+ useEffect(() => {
16
+ emitGetRooms();
17
+ const interval = setInterval(emitGetRooms, 5000);
18
+ return () => clearInterval(interval);
19
+ }, []);
20
+
21
+ const handleCreateRoom = () => {
22
+ if (!roomName.trim()) {
23
+ useGameStore.getState().addToast('Please enter a room name!', 'warning');
24
+ return;
25
+ }
26
+ emitCreateRoom(playerName, roomName.trim());
27
+ };
28
+
29
+ const handleJoinByCode = () => {
30
+ if (!joinCode.trim()) {
31
+ useGameStore.getState().addToast('Please enter a room code!', 'warning');
32
+ return;
33
+ }
34
+ emitJoinRoom(playerName, joinCode.trim().toUpperCase());
35
+ };
36
+
37
+ const handleJoinRoom = (roomId: string) => {
38
+ emitJoinRoom(playerName, roomId);
39
+ };
40
+
41
+ const handleBack = () => {
42
+ setScreen('menu');
43
+ };
44
+
45
+ const availableRooms = roomList.filter(r => !r.isPlaying && r.playerCount < r.maxPlayers);
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
+ }}
55
+ >
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'
80
+ }`}
81
+ >
82
+ {tab}
83
+ </button>
84
+ ))}
85
+ </div>
86
+
87
+ {/* Create Tab */}
88
+ {activeTab === 'create' && (
89
+ <motion.div
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}
100
+ onChange={(e) => setRoomName(e.target.value)}
101
+ placeholder="My Game Room"
102
+ className="w-full"
103
+ maxLength={30}
104
+ />
105
+ </div>
106
+
107
+ <button onClick={handleCreateRoom} className="btn btn-primary w-full">
108
+ Create Room
109
+ </button>
110
+ </motion.div>
111
+ )}
112
+
113
+ {/* Join Tab */}
114
+ {activeTab === 'join' && (
115
+ <motion.div
116
+ initial={{ opacity: 0, x: -20 }}
117
+ animate={{ opacity: 1, x: 0 }}
118
+ >
119
+ <p className="text-papyrus mb-4">Enter a room code to join.</p>
120
+
121
+ <div className="mb-4">
122
+ <label className="block text-papyrus mb-2">Room Code</label>
123
+ <input
124
+ type="text"
125
+ value={joinCode}
126
+ onChange={(e) => setJoinCode(e.target.value.toUpperCase())}
127
+ placeholder="ABC123"
128
+ className="w-full uppercase tracking-widest text-center text-xl"
129
+ maxLength={6}
130
+ />
131
+ </div>
132
+
133
+ <button onClick={handleJoinByCode} className="btn btn-primary w-full">
134
+ Join Room
135
+ </button>
136
+ </motion.div>
137
+ )}
138
+
139
+ {/* Browse Tab */}
140
+ {activeTab === 'browse' && (
141
+ <motion.div
142
+ initial={{ opacity: 0, x: -20 }}
143
+ animate={{ opacity: 1, x: 0 }}
144
+ >
145
+ <p className="text-papyrus mb-4">Available rooms:</p>
146
+
147
+ {availableRooms.length === 0 ? (
148
+ <p className="text-center text-papyrus/60 py-8">
149
+ No rooms available. Create one!
150
+ </p>
151
+ ) : (
152
+ <div className="space-y-2 max-h-60 overflow-y-auto">
153
+ {availableRooms.map((room) => (
154
+ <motion.div
155
+ key={room.id}
156
+ className="bg-nile-blue/50 rounded-lg p-3 flex items-center justify-between"
157
+ initial={{ opacity: 0, y: 10 }}
158
+ animate={{ opacity: 1, y: 0 }}
159
+ >
160
+ <div>
161
+ <p className="text-papyrus font-semibold">{room.name}</p>
162
+ <p className="text-egyptian-gold/60 text-sm">
163
+ {room.playerCount}/{room.maxPlayers} players
164
+ </p>
165
+ </div>
166
+ <button
167
+ onClick={() => handleJoinRoom(room.id)}
168
+ className="btn btn-secondary text-sm py-1 px-3"
169
+ >
170
+ Join
171
+ </button>
172
+ </motion.div>
173
+ ))}
174
+ </div>
175
+ )}
176
+
177
+ <button onClick={emitGetRooms} className="btn btn-secondary w-full mt-4">
178
+ Refresh
179
+ </button>
180
+ </motion.div>
181
+ )}
182
+ </motion.div>
183
+ </div>
184
+ );
185
+ }
client/src/components/screens/MainMenu.tsx ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { useGameStore } from '../../store/gameStore';
4
+
5
+ export function MainMenu() {
6
+ const [playerName, setPlayerName] = useState('');
7
+ const setScreen = useGameStore((state) => state.setScreen);
8
+ const setPlayerNameInStore = useGameStore((state) => state.setPlayerName);
9
+ const isConnected = useGameStore((state) => state.isConnected);
10
+
11
+ const handlePlay = () => {
12
+ if (!playerName.trim()) {
13
+ useGameStore.getState().addToast('Please enter your name!', 'warning');
14
+ return;
15
+ }
16
+ setPlayerNameInStore(playerName.trim());
17
+ setScreen('lobby');
18
+ };
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
+ }}
28
+ >
29
+ <motion.div
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 }}
43
+ onError={(e) => {
44
+ (e.target as HTMLImageElement).style.display = 'none';
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
+ />
75
+ </div>
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 }}
83
+ whileTap={{ scale: 0.98 }}
84
+ >
85
+ Play Game
86
+ </motion.button>
87
+ </motion.div>
88
+ </div>
89
+ );
90
+ }
client/src/components/screens/Room.tsx ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { motion } from 'framer-motion';
2
+ import { useGameStore } from '../../store/gameStore';
3
+ import { emitSetReady, emitStartGame, emitLeaveRoom } from '../../socket/socket';
4
+
5
+ export function Room() {
6
+ const currentRoom = useGameStore((state) => state.currentRoom);
7
+ const playerId = useGameStore((state) => state.playerId);
8
+
9
+ if (!currentRoom) {
10
+ return <div className="flex-1 flex items-center justify-center text-papyrus">Loading...</div>;
11
+ }
12
+
13
+ const isHost = currentRoom.hostId === playerId;
14
+ const currentPlayer = currentRoom.players.find(p => p.id === playerId);
15
+ const isReady = currentPlayer?.isReady ?? false;
16
+
17
+ const allReady = currentRoom.players.every(p => p.isReady || p.isHost);
18
+ const canStart = currentRoom.players.length >= 2 && (allReady || isHost);
19
+
20
+ const handleLeave = () => {
21
+ emitLeaveRoom();
22
+ };
23
+
24
+ const handleReady = () => {
25
+ emitSetReady(!isReady);
26
+ };
27
+
28
+ const handleStart = () => {
29
+ emitStartGame();
30
+ };
31
+
32
+ const copyRoomCode = () => {
33
+ navigator.clipboard.writeText(currentRoom.id);
34
+ useGameStore.getState().addToast('Room code copied!', 'success');
35
+ };
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
+ }}
45
+ >
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>
61
+
62
+ {/* Room Code */}
63
+ <div className="text-center mb-6">
64
+ <p className="text-papyrus mb-2">Room Code:</p>
65
+ <button
66
+ onClick={copyRoomCode}
67
+ className="room-code cursor-pointer hover:bg-nile-blue/80 transition-colors"
68
+ >
69
+ {currentRoom.id}
70
+ </button>
71
+ <p className="text-papyrus/60 text-sm mt-2">Click to copy</p>
72
+ </div>
73
+
74
+ {/* Players */}
75
+ <div className="mb-6">
76
+ <h2 className="text-papyrus font-semibold mb-3">Players</h2>
77
+ <div className="space-y-2">
78
+ {currentRoom.players.map((player, index) => (
79
+ <motion.div
80
+ key={player.id}
81
+ className={`flex items-center justify-between p-3 rounded-lg ${
82
+ player.id === playerId ? 'bg-egyptian-gold/20' : 'bg-nile-blue/30'
83
+ }`}
84
+ initial={{ opacity: 0, x: -20 }}
85
+ animate={{ opacity: 1, x: 0 }}
86
+ transition={{ delay: index * 0.1 }}
87
+ >
88
+ <div className="flex items-center gap-3">
89
+ <div className="player-avatar">
90
+ {player.name.charAt(0).toUpperCase()}
91
+ </div>
92
+ <div>
93
+ <p className="text-papyrus font-semibold">
94
+ {player.name}
95
+ {player.id === playerId && ' (You)'}
96
+ </p>
97
+ {player.isHost && (
98
+ <span className="text-egyptian-gold text-sm">👑 Host</span>
99
+ )}
100
+ </div>
101
+ </div>
102
+ <div>
103
+ {player.isHost ? (
104
+ <span className="text-green-400">Ready</span>
105
+ ) : player.isReady ? (
106
+ <span className="text-green-400">✓ Ready</span>
107
+ ) : (
108
+ <span className="text-yellow-400">Waiting...</span>
109
+ )}
110
+ </div>
111
+ </motion.div>
112
+ ))}
113
+ </div>
114
+
115
+ {/* Empty slots */}
116
+ {Array.from({ length: currentRoom.maxPlayers - currentRoom.players.length }).map((_, i) => (
117
+ <div
118
+ key={`empty-${i}`}
119
+ className="p-3 rounded-lg bg-nile-blue/10 border border-dashed border-papyrus/20 mt-2"
120
+ >
121
+ <p className="text-papyrus/40 text-center">Waiting for player...</p>
122
+ </div>
123
+ ))}
124
+ </div>
125
+
126
+ {/* Actions */}
127
+ <div className="flex gap-3">
128
+ {!isHost && (
129
+ <button
130
+ onClick={handleReady}
131
+ className={`btn flex-1 ${isReady ? 'btn-danger' : 'btn-secondary'}`}
132
+ >
133
+ {isReady ? 'Not Ready' : 'Ready'}
134
+ </button>
135
+ )}
136
+
137
+ {isHost && (
138
+ <button
139
+ onClick={handleStart}
140
+ disabled={!canStart}
141
+ className="btn btn-primary flex-1"
142
+ >
143
+ {currentRoom.players.length < 2
144
+ ? 'Need 2+ Players'
145
+ : !allReady
146
+ ? 'Waiting for Players'
147
+ : 'Start Game'}
148
+ </button>
149
+ )}
150
+ </div>
151
+
152
+ {/* Min players warning */}
153
+ {currentRoom.players.length < 2 && (
154
+ <p className="text-center text-yellow-400 mt-4 text-sm">
155
+ Need at least 2 players to start
156
+ </p>
157
+ )}
158
+ </motion.div>
159
+ </div>
160
+ );
161
+ }
client/src/components/ui/LandscapeOverlay.tsx ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export function LandscapeOverlay() {
2
+ return (
3
+ <div className="landscape-required">
4
+ <div className="text-egyptian-gold text-6xl mb-6">📱</div>
5
+ <h1 className="text-2xl font-bold text-egyptian-gold mb-4">
6
+ Rotate Your Device
7
+ </h1>
8
+ <p className="text-papyrus text-lg mb-2">
9
+ Please rotate your device to landscape mode
10
+ </p>
11
+ <p className="text-egyptian-gold text-xl" dir="rtl">
12
+ من فضلك قم بتدوير جهازك
13
+ </p>
14
+ <div className="mt-8 animate-bounce">
15
+ <svg
16
+ xmlns="http://www.w3.org/2000/svg"
17
+ className="h-16 w-16 text-egyptian-gold transform rotate-90"
18
+ fill="none"
19
+ viewBox="0 0 24 24"
20
+ stroke="currentColor"
21
+ >
22
+ <path
23
+ strokeLinecap="round"
24
+ strokeLinejoin="round"
25
+ strokeWidth={2}
26
+ d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z"
27
+ />
28
+ </svg>
29
+ </div>
30
+ </div>
31
+ );
32
+ }
client/src/components/ui/MummyEventOverlay.tsx ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { motion, AnimatePresence } from 'framer-motion';
2
+ import { useGameStore } from '../../store/gameStore';
3
+
4
+ export function MummyEventOverlay() {
5
+ const mummyEvent = useGameStore((state) => state.mummyEvent);
6
+
7
+ if (!mummyEvent) return null;
8
+
9
+ const getEventStyle = () => {
10
+ switch (mummyEvent.type) {
11
+ case 'drawn':
12
+ return {
13
+ bg: 'from-amber-900/95 to-amber-950/95',
14
+ icon: '⚠️',
15
+ title: 'MUMMY DRAWN!',
16
+ subtitle: `${mummyEvent.playerName} drew a mummy card!`,
17
+ textColor: 'text-amber-300',
18
+ };
19
+ case 'defused':
20
+ return {
21
+ bg: 'from-green-900/95 to-green-950/95',
22
+ icon: '🛡️',
23
+ title: 'MUMMY DEFUSED!',
24
+ subtitle: `${mummyEvent.playerName} defused the mummy!`,
25
+ textColor: 'text-green-300',
26
+ };
27
+ case 'eliminated':
28
+ return {
29
+ bg: 'from-red-900/95 to-red-950/95',
30
+ icon: '💀',
31
+ title: 'MUMMIFIED!',
32
+ subtitle: `${mummyEvent.playerName} has been eliminated!`,
33
+ textColor: 'text-red-300',
34
+ };
35
+ }
36
+ };
37
+
38
+ const style = getEventStyle();
39
+
40
+ return (
41
+ <AnimatePresence>
42
+ <motion.div
43
+ key={`${mummyEvent.type}-${mummyEvent.playerName}`}
44
+ className="fixed inset-0 z-[100] flex items-center justify-center pointer-events-none"
45
+ initial={{ opacity: 0 }}
46
+ animate={{ opacity: 1 }}
47
+ exit={{ opacity: 0 }}
48
+ >
49
+ <motion.div
50
+ className={`bg-gradient-to-b ${style.bg} px-12 py-8 rounded-2xl shadow-2xl border-2 border-egyptian-gold/50`}
51
+ initial={{ scale: 0.5, opacity: 0, y: 50 }}
52
+ animate={{
53
+ scale: 1,
54
+ opacity: 1,
55
+ y: 0,
56
+ transition: { type: 'spring', damping: 15 }
57
+ }}
58
+ exit={{
59
+ scale: 0.8,
60
+ opacity: 0,
61
+ y: -50,
62
+ transition: { duration: 0.3 }
63
+ }}
64
+ >
65
+ <motion.div
66
+ className="text-6xl text-center mb-4"
67
+ animate={{
68
+ scale: [1, 1.2, 1],
69
+ rotate: mummyEvent.type === 'eliminated' ? [0, -5, 5, 0] : 0,
70
+ }}
71
+ transition={{
72
+ duration: 0.5,
73
+ repeat: mummyEvent.type === 'eliminated' ? 2 : 0,
74
+ }}
75
+ >
76
+ {style.icon}
77
+ </motion.div>
78
+
79
+ <motion.h2
80
+ className={`text-4xl font-bold text-center ${style.textColor} mb-2`}
81
+ initial={{ y: 20, opacity: 0 }}
82
+ animate={{ y: 0, opacity: 1 }}
83
+ transition={{ delay: 0.1 }}
84
+ >
85
+ {style.title}
86
+ </motion.h2>
87
+
88
+ <motion.p
89
+ className="text-xl text-center text-papyrus/90"
90
+ initial={{ y: 20, opacity: 0 }}
91
+ animate={{ y: 0, opacity: 1 }}
92
+ transition={{ delay: 0.2 }}
93
+ >
94
+ {style.subtitle}
95
+ </motion.p>
96
+ </motion.div>
97
+ </motion.div>
98
+ </AnimatePresence>
99
+ );
100
+ }
client/src/components/ui/ToastContainer.tsx ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { motion, AnimatePresence } from 'framer-motion';
2
+ import { useGameStore } from '../../store/gameStore';
3
+
4
+ export function ToastContainer() {
5
+ const toasts = useGameStore((state) => state.toasts);
6
+ const removeToast = useGameStore((state) => state.removeToast);
7
+
8
+ return (
9
+ <div className="fixed top-4 left-1/2 transform -translate-x-1/2 z-50 flex flex-col gap-2">
10
+ <AnimatePresence>
11
+ {toasts.map((toast) => (
12
+ <motion.div
13
+ key={toast.id}
14
+ initial={{ opacity: 0, y: -20, scale: 0.9 }}
15
+ animate={{ opacity: 1, y: 0, scale: 1 }}
16
+ exit={{ opacity: 0, y: -20, scale: 0.9 }}
17
+ className={`toast toast-${toast.type} cursor-pointer`}
18
+ onClick={() => removeToast(toast.id)}
19
+ >
20
+ {toast.message}
21
+ </motion.div>
22
+ ))}
23
+ </AnimatePresence>
24
+ </div>
25
+ );
26
+ }
client/src/index.css ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700&display=swap');
2
+
3
+ @tailwind base;
4
+ @tailwind components;
5
+ @tailwind utilities;
6
+
7
+ /* Base styles */
8
+ * {
9
+ box-sizing: border-box;
10
+ -webkit-tap-highlight-color: transparent;
11
+ }
12
+
13
+ html, body {
14
+ margin: 0;
15
+ padding: 0;
16
+ overflow: hidden;
17
+ touch-action: none;
18
+ user-select: none;
19
+ -webkit-user-select: none;
20
+ }
21
+
22
+ body {
23
+ font-family: 'Cinzel', serif;
24
+ background-color: #1a1a2e;
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 */
38
+ .landscape-required {
39
+ display: none;
40
+ position: fixed;
41
+ top: 0;
42
+ left: 0;
43
+ width: 100%;
44
+ height: 100%;
45
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
46
+ z-index: 9999;
47
+ justify-content: center;
48
+ align-items: center;
49
+ flex-direction: column;
50
+ text-align: center;
51
+ padding: 20px;
52
+ }
53
+
54
+ @media screen and (max-width: 768px) and (orientation: portrait) {
55
+ .landscape-required {
56
+ display: flex !important;
57
+ }
58
+
59
+ .game-content {
60
+ display: none !important;
61
+ }
62
+ }
63
+
64
+ /* Card styles */
65
+ .card {
66
+ @apply relative rounded-lg overflow-hidden cursor-pointer transition-all duration-200;
67
+ aspect-ratio: 2/3;
68
+ transform-style: preserve-3d;
69
+ perspective: 1000px;
70
+ }
71
+
72
+ .card:hover {
73
+ @apply scale-105;
74
+ z-index: 10;
75
+ }
76
+
77
+ .card.selected {
78
+ @apply ring-4 ring-egyptian-gold scale-110;
79
+ z-index: 20;
80
+ }
81
+
82
+ .card.disabled {
83
+ @apply opacity-50 cursor-not-allowed;
84
+ }
85
+
86
+ .card.disabled:hover {
87
+ transform: none;
88
+ }
89
+
90
+ .card-image {
91
+ @apply w-full h-full object-cover;
92
+ }
93
+
94
+ .card-overlay {
95
+ @apply absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent;
96
+ }
97
+
98
+ .card-name {
99
+ @apply absolute bottom-0 left-0 right-0 p-2 text-center;
100
+ }
101
+
102
+ .card-name-en {
103
+ @apply text-xs font-semibold text-papyrus;
104
+ }
105
+
106
+ .card-name-ar {
107
+ @apply text-sm font-bold text-egyptian-gold;
108
+ direction: rtl;
109
+ }
110
+
111
+ /* Half card indicator */
112
+ .half-indicator {
113
+ @apply absolute top-1 right-1 bg-egyptian-gold text-black text-xs font-bold px-1 rounded;
114
+ }
115
+
116
+ /* Mummy card danger */
117
+ .card.mummy {
118
+ @apply animate-pulse-danger;
119
+ }
120
+
121
+ /* Button styles */
122
+ .btn {
123
+ @apply px-6 py-3 rounded-lg font-semibold transition-all duration-200;
124
+ @apply disabled:opacity-50 disabled:cursor-not-allowed;
125
+ }
126
+
127
+ .btn-primary {
128
+ @apply bg-egyptian-gold text-nile-blue hover:bg-yellow-500;
129
+ @apply active:scale-95;
130
+ }
131
+
132
+ .btn-danger {
133
+ @apply bg-mummy-red text-white hover:bg-red-700;
134
+ @apply active:scale-95;
135
+ }
136
+
137
+ .btn-secondary {
138
+ @apply bg-nile-blue text-papyrus border-2 border-egyptian-gold;
139
+ @apply hover:bg-egyptian-gold hover:text-nile-blue;
140
+ @apply active:scale-95;
141
+ }
142
+
143
+ /* Modal styles */
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 */
160
+ .toast {
161
+ @apply fixed top-4 left-1/2 transform -translate-x-1/2 z-50;
162
+ @apply px-6 py-3 rounded-lg font-semibold shadow-lg;
163
+ @apply animate-bounce;
164
+ }
165
+
166
+ .toast-info {
167
+ @apply bg-nile-blue text-papyrus border border-egyptian-gold;
168
+ }
169
+
170
+ .toast-success {
171
+ @apply bg-green-700 text-white;
172
+ }
173
+
174
+ .toast-warning {
175
+ @apply bg-yellow-600 text-black;
176
+ }
177
+
178
+ .toast-danger {
179
+ @apply bg-mummy-red text-white;
180
+ }
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 {
193
+ @apply opacity-50 grayscale;
194
+ }
195
+
196
+ /* Deck pile */
197
+ .deck-pile {
198
+ @apply relative cursor-pointer;
199
+ }
200
+
201
+ .deck-pile::after {
202
+ @apply absolute -bottom-1 -right-1 bg-egyptian-gold text-nile-blue;
203
+ @apply text-xs font-bold px-2 py-1 rounded;
204
+ content: attr(data-count);
205
+ }
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
+
241
+ /* Scrollbar styling */
242
+ ::-webkit-scrollbar {
243
+ width: 8px;
244
+ height: 8px;
245
+ }
246
+
247
+ ::-webkit-scrollbar-track {
248
+ background: rgba(0, 0, 0, 0.3);
249
+ border-radius: 4px;
250
+ }
251
+
252
+ ::-webkit-scrollbar-thumb {
253
+ background: #d4a843;
254
+ border-radius: 4px;
255
+ }
256
+
257
+ ::-webkit-scrollbar-thumb:hover {
258
+ background: #e5b854;
259
+ }
260
+
261
+ /* King Ra countdown */
262
+ .king-ra-timer {
263
+ @apply w-full h-2 bg-gray-700 rounded-full overflow-hidden mt-2;
264
+ }
265
+
266
+ .king-ra-timer-bar {
267
+ @apply h-full bg-gradient-to-r from-egyptian-gold to-mummy-red;
268
+ transition: width 0.1s linear;
269
+ }
270
+
271
+ /* Input styles */
272
+ input[type="text"], input[type="number"] {
273
+ @apply w-full px-4 py-2 rounded-lg bg-nile-blue/50 border border-egyptian-gold;
274
+ @apply text-papyrus placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-egyptian-gold;
275
+ }
276
+
277
+ /* Room code display */
278
+ .room-code {
279
+ @apply bg-nile-blue px-4 py-2 rounded-lg text-2xl font-bold tracking-widest;
280
+ @apply border-2 border-egyptian-gold text-egyptian-gold;
281
+ }
client/src/main.tsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import App from './App';
4
+ import './index.css';
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ );
client/src/socket/socket.ts ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { io, Socket } from 'socket.io-client';
2
+ import type { ServerToClientEvents, ClientToServerEvents, CardId } from '@shared/types';
3
+ import { useGameStore } from '../store/gameStore';
4
+
5
+ const SERVER_URL = import.meta.env.PROD
6
+ ? window.location.origin
7
+ : 'http://localhost:3001';
8
+
9
+ export const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(SERVER_URL, {
10
+ autoConnect: false,
11
+ transports: ['websocket'],
12
+ upgrade: false,
13
+ reconnectionAttempts: 5,
14
+ reconnectionDelay: 1000,
15
+ });
16
+
17
+ let isInitialized = false;
18
+ const processedEvents = new Set<string>();
19
+
20
+ // Prevent duplicate event processing (clears every 5 seconds)
21
+ function shouldProcessEvent(eventKey: string): boolean {
22
+ if (processedEvents.has(eventKey)) return false;
23
+ processedEvents.add(eventKey);
24
+ setTimeout(() => processedEvents.delete(eventKey), 5000);
25
+ return true;
26
+ }
27
+
28
+ export function initializeSocket(): void {
29
+ // Prevent multiple initializations (React StrictMode, hot reload)
30
+ if (isInitialized) {
31
+ if (!socket.connected) {
32
+ socket.connect();
33
+ }
34
+ return;
35
+ }
36
+ isInitialized = true;
37
+
38
+ const store = useGameStore.getState();
39
+
40
+ socket.on('connect', () => {
41
+ console.log('Connected to server:', socket.id);
42
+ store.setPlayerId(socket.id ?? null);
43
+ store.setConnected(true);
44
+ socket.emit('getRooms');
45
+ });
46
+
47
+ socket.on('disconnect', () => {
48
+ console.log('Disconnected from server');
49
+ store.setConnected(false);
50
+ });
51
+
52
+ socket.on('error', (message) => {
53
+ store.addToast(message, 'danger');
54
+ });
55
+
56
+ // Room events
57
+ socket.on('roomList', (rooms) => {
58
+ store.setRoomList(rooms);
59
+ });
60
+
61
+ socket.on('roomJoined', (room) => {
62
+ store.setRoom(room);
63
+ store.setScreen('room');
64
+ });
65
+
66
+ socket.on('roomUpdated', (room) => {
67
+ store.setRoom(room);
68
+ });
69
+
70
+ socket.on('roomLeft', () => {
71
+ store.setRoom(null);
72
+ store.setScreen('lobby');
73
+ socket.emit('getRooms');
74
+ });
75
+
76
+ // Game events
77
+ socket.on('gameStarted', (state, hand) => {
78
+ store.setGameState(state);
79
+ store.setMyHand(hand);
80
+ store.setScreen('game');
81
+ });
82
+
83
+ socket.on('gameStateUpdated', (state) => {
84
+ store.setGameState(state);
85
+ });
86
+
87
+ socket.on('handUpdated', (hand) => {
88
+ store.setMyHand(hand);
89
+ });
90
+
91
+ // Card events
92
+ socket.on('cardPlayed', (_playerId, _cardId, _targetId) => {
93
+ // Card play events are handled visually, no toast needed
94
+ });
95
+
96
+ socket.on('peekCards', (cards) => {
97
+ const eventKey = `peekCards-${cards.map(c => c.instanceId).join(',')}`;
98
+ if (shouldProcessEvent(eventKey)) {
99
+ store.openModal('peek-cards', { peekCards: cards });
100
+ }
101
+ });
102
+
103
+ socket.on('duelResult', (challenger, opponent, winnerId) => {
104
+ const eventKey = `duelResult-${challenger.card.instanceId}-${opponent.card.instanceId}`;
105
+ if (shouldProcessEvent(eventKey)) {
106
+ store.openModal('duel-result', {
107
+ duelChallenger: challenger,
108
+ duelOpponent: opponent,
109
+ duelWinnerId: winnerId,
110
+ });
111
+ }
112
+ });
113
+
114
+ socket.on('swapResult', (youGave, youReceived, otherPlayerName) => {
115
+ const eventKey = `swapResult-${youGave.instanceId}-${youReceived.instanceId}`;
116
+ if (shouldProcessEvent(eventKey)) {
117
+ store.openModal('swap-result', {
118
+ swapGaveCard: youGave,
119
+ swapReceivedCard: youReceived,
120
+ swapOtherPlayer: otherPlayerName,
121
+ });
122
+ }
123
+ });
124
+
125
+ socket.on('spellboundResult', (_success, _cardId) => {
126
+ // Result shown in modal/game state update
127
+ });
128
+
129
+ socket.on('blindStealStart', (_thiefId, _targetId, cardCount) => {
130
+ const state = useGameStore.getState();
131
+ if (socket.id === state.playerId) {
132
+ const eventKey = `blindSteal-${Date.now()}`;
133
+ if (shouldProcessEvent(eventKey)) {
134
+ store.openModal('blind-steal', { blindStealCount: cardCount });
135
+ }
136
+ }
137
+ });
138
+
139
+ socket.on('arrangeHandPrompt', (_thiefId) => {
140
+ const eventKey = `arrangeHand-${Date.now()}`;
141
+ if (shouldProcessEvent(eventKey)) {
142
+ store.openModal('arrange-hand');
143
+ }
144
+ });
145
+
146
+ socket.on('viewHand', (targetId, cards) => {
147
+ const eventKey = `viewHand-${targetId}-${cards.map(c => c.instanceId).join(',')}`;
148
+ if (shouldProcessEvent(eventKey)) {
149
+ store.openModal('view-hand', { viewHandCards: cards, viewHandTargetId: targetId });
150
+ }
151
+ });
152
+
153
+ socket.on('flipTableCards', (cards) => {
154
+ const eventKey = `flipTable-${cards.map(c => c.instanceId).join(',')}`;
155
+ if (shouldProcessEvent(eventKey)) {
156
+ store.openModal('flip-table', { flipTableCards: cards });
157
+ }
158
+ });
159
+
160
+ // King Ra events
161
+ socket.on('kingRaPrompt', (playerId, cardPlayed, timeout) => {
162
+ const state = useGameStore.getState();
163
+
164
+ // Don't show prompt to the player who played the card (they can't cancel their own action)
165
+ if (playerId === state.playerId) {
166
+ return;
167
+ }
168
+
169
+ // Don't show if action was recently resolved (stale event from network delay)
170
+ if (Date.now() - state.lastActionResolvedAt < 2000) {
171
+ return;
172
+ }
173
+
174
+ // Don't show if a non-related modal is open (action already resolved and moved on)
175
+ const currentModal = state.activeModal;
176
+ if (currentModal && currentModal !== 'king-ra-prompt') {
177
+ // Another modal is already showing (like duel result), skip this
178
+ return;
179
+ }
180
+
181
+ // Show the reaction window to ALL players (they can only use it if they have King Ra)
182
+ store.openModal('king-ra-prompt', {
183
+ kingRaPlayerId: playerId,
184
+ kingRaCardPlayed: cardPlayed,
185
+ kingRaTimeout: timeout,
186
+ });
187
+ });
188
+
189
+ socket.on('kingRaResponse', (_responderId, _didCancel) => {
190
+ // King Ra response is shown visually
191
+ });
192
+
193
+ socket.on('actionPending', (_cardId: CardId, _timeout: number) => {
194
+ store.setReactionWindowActive(true);
195
+ });
196
+
197
+ socket.on('actionResolved', (_cardId: CardId) => {
198
+ // Reaction window ended, action proceeds - close King Ra modal if open
199
+ const state = useGameStore.getState();
200
+ if (state.activeModal === 'king-ra-prompt') {
201
+ store.closeModal();
202
+ }
203
+ store.setReactionWindowActive(false);
204
+ store.setLastActionResolvedAt(Date.now());
205
+ });
206
+
207
+ socket.on('actionCancelled', (_cardId: CardId, _cancelCount) => {
208
+ // Close King Ra modal if open
209
+ const state = useGameStore.getState();
210
+ if (state.activeModal === 'king-ra-prompt') {
211
+ store.closeModal();
212
+ }
213
+ store.setReactionWindowActive(false);
214
+ store.setLastActionResolvedAt(Date.now());
215
+ });
216
+
217
+ // Mummy events
218
+ socket.on('mummyDrawn', (playerId) => {
219
+ const state = useGameStore.getState().gameState;
220
+ const player = state?.players.find(p => p.id === playerId);
221
+ store.setMummyEvent({ type: 'drawn', playerName: player?.name ?? 'A player' });
222
+ });
223
+
224
+ socket.on('mummyDefused', (playerId) => {
225
+ const state = useGameStore.getState().gameState;
226
+ const player = state?.players.find(p => p.id === playerId);
227
+ store.setMummyEvent({ type: 'defused', playerName: player?.name ?? 'A player' });
228
+ });
229
+
230
+ socket.on('placeMummyPrompt', (deckSize) => {
231
+ const eventKey = `placeMummy-${deckSize}-${Date.now()}`;
232
+ if (shouldProcessEvent(eventKey)) {
233
+ store.openModal('place-mummy', { deckSize });
234
+ }
235
+ });
236
+
237
+ socket.on('playerEliminated', (playerId) => {
238
+ const state = useGameStore.getState().gameState;
239
+ const player = state?.players.find(p => p.id === playerId);
240
+ store.setMummyEvent({ type: 'eliminated', playerName: player?.name ?? 'A player' });
241
+ });
242
+
243
+ // Turn events
244
+ socket.on('turnStarted', (playerId, turnsRemaining) => {
245
+ store.setTurnInfo(playerId, turnsRemaining);
246
+ });
247
+
248
+ // Ignore server notifications - only show mummy events via overlay
249
+ socket.on('notification', () => {
250
+ // Notifications disabled - mummy events handled separately
251
+ });
252
+
253
+ // Game over
254
+ socket.on('gameOver', (winnerId, winnerName) => {
255
+ const eventKey = `gameOver-${winnerId}`;
256
+ if (shouldProcessEvent(eventKey)) {
257
+ store.openModal('game-over', { winnerId, winnerName });
258
+ }
259
+ });
260
+
261
+ // Connect
262
+ socket.connect();
263
+ }
264
+
265
+ export function disconnectSocket(): void {
266
+ socket.disconnect();
267
+ }
268
+
269
+ // Emit helpers
270
+ export const emitCreateRoom = (playerName: string, roomName: string): void => {
271
+ socket.emit('createRoom', playerName, roomName);
272
+ };
273
+
274
+ export const emitJoinRoom = (playerName: string, roomId: string): void => {
275
+ socket.emit('joinRoom', playerName, roomId);
276
+ };
277
+
278
+ export const emitLeaveRoom = (): void => {
279
+ socket.emit('leaveRoom');
280
+ };
281
+
282
+ export const emitSetReady = (ready: boolean): void => {
283
+ socket.emit('setReady', ready);
284
+ };
285
+
286
+ export const emitStartGame = (): void => {
287
+ socket.emit('startGame');
288
+ };
289
+
290
+ export const emitPlayCard = (cardInstanceId: string, targetPlayerId?: string, additionalData?: unknown): void => {
291
+ socket.emit('playCard', cardInstanceId, targetPlayerId, additionalData);
292
+ };
293
+
294
+ export const emitDrawCard = (): void => {
295
+ socket.emit('drawCard');
296
+ };
297
+
298
+ export const emitSpellboundRequest = (targetId: string, requestedCardId: string): void => {
299
+ socket.emit('spellboundRequest', targetId, requestedCardId as any);
300
+ };
301
+
302
+ export const emitBlindStealSelect = (position: number): void => {
303
+ socket.emit('blindStealSelect', position);
304
+ };
305
+
306
+ export const emitArrangeHand = (newOrder: string[]): void => {
307
+ socket.emit('arrangeHand', newOrder);
308
+ };
309
+
310
+ export const emitBurnCard = (cardInstanceId: string): void => {
311
+ socket.emit('burnCard', cardInstanceId);
312
+ };
313
+
314
+ export const emitRearrangeTopCards = (newOrder: string[]): void => {
315
+ socket.emit('rearrangeTopCards', newOrder);
316
+ };
317
+
318
+ export const emitPlaceMummy = (position: number): void => {
319
+ socket.emit('placeMummy', position);
320
+ };
321
+
322
+ export const emitKingRaResponse = (useKingRa: boolean): void => {
323
+ console.log('socket.ts emitKingRaResponse called with:', useKingRa);
324
+ socket.emit('kingRaResponse', useKingRa);
325
+ console.log('socket.ts kingRaResponse emitted');
326
+ };
327
+
328
+ export const emitGetRooms = (): void => {
329
+ socket.emit('getRooms');
330
+ };