Backend Complete: Interface Refactoring & Service Container Enhancements
Repository Interface Optimization: - Created IBaseRepository.ts and IPaginatedRepository.ts - Refactored all 7 repository interfaces to extend base interfaces - Eliminated ~200 lines of redundant code (70% reduction) - Improved type safety and maintainability Dependency Injection Improvements: - Added EmailService and GameTokenService to DIContainer - Updated CreateUserCommandHandler constructor for DI - Updated RequestPasswordResetCommandHandler constructor for DI - Enhanced testability and service consistency Environment Configuration: - Created comprehensive .env.example with 40+ variables - Organized into 12 logical sections (Database, Security, Email, etc.) - Added security guidelines and best practices - Documented all backend environment requirements Documentation: - Added comprehensive codebase review - Created refactoring summary report - Added frontend implementation guide Impact: Improved code quality, reduced maintenance overhead, enhanced developer experience
@@ -0,0 +1,65 @@
|
||||
import { useState, useEffect } from "react"
|
||||
import { BrowserRouter as Router, Route, Routes } from "react-router-dom"
|
||||
import AuthRegister from "./pages/Auth/AuthRegister"
|
||||
import AuthLogin from "./pages/Auth/AuthLogin"
|
||||
import EmailVerification from "./pages/Auth/EmailVerification"
|
||||
import Test from "./pages/Testing/Test"
|
||||
import ForgotPassword from "./pages/Auth/ForgotPassword"
|
||||
import ResetPassword from "./pages/Auth/ResetPassword"
|
||||
import Landingpage from "./pages/Landing/Landingpage"
|
||||
import Home from "./pages/Landing/Home"
|
||||
import DeckManagerPage from "./pages/Decks/DeckManagerPage"
|
||||
import CompanyHub from "./pages/Companies/Companies"
|
||||
import About from "./pages/About/About"
|
||||
import ScrollToTop from "./components/ScrollToTop"
|
||||
import GameScreen from "./pages/Game/GameScreen"
|
||||
|
||||
function App() {
|
||||
const [isMobile, setIsMobile] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setIsMobile(window.innerWidth <= 1280)
|
||||
}
|
||||
|
||||
handleResize()
|
||||
window.addEventListener("resize", handleResize)
|
||||
|
||||
return () => window.removeEventListener("resize", handleResize)
|
||||
}, [])
|
||||
|
||||
// if (isMobile) {
|
||||
// return (
|
||||
// <Router>
|
||||
// <Routes>
|
||||
// <Route path="/register" element={<AuthRegister />} />
|
||||
// <Route path="/login" element={<AuthLogin />} />
|
||||
// <Route path="/verify-email" element={<EmailVerification />} />
|
||||
// </Routes>
|
||||
// </Router>
|
||||
// );
|
||||
// }
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/register" element={<AuthRegister />} />
|
||||
<Route path="/login" element={<AuthLogin />} />
|
||||
<Route path="/verify-email" element={<EmailVerification />} />
|
||||
<Route path="/forgot-password" element={<ForgotPassword />} />
|
||||
<Route path="/reset-password" element={<ResetPassword />} />
|
||||
<Route path="/test" element={<Test />} />
|
||||
<Route path="/" element={<Landingpage />} />
|
||||
<Route path="/home" element={<Home />} />
|
||||
<Route path="/decks" element={<DeckManagerPage />} />
|
||||
<Route path="/game" element={<GameScreen />} />
|
||||
<Route path="/companies" element={<CompanyHub />} />
|
||||
|
||||
{/* Add more routes as needed */}
|
||||
</Routes>
|
||||
</Router>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -0,0 +1,96 @@
|
||||
.animation {
|
||||
animation: fill 0.5s ease forwards 2.9s;
|
||||
}
|
||||
|
||||
.path0 {
|
||||
stroke-dasharray: 603.0596923828125;
|
||||
stroke-dashoffset: 603.0596923828125;
|
||||
animation: draw 3s ease-in-out forwards;
|
||||
animation-delay: 0.45s;
|
||||
}
|
||||
|
||||
.path1 {
|
||||
stroke-dasharray: 503.0904846191406;
|
||||
stroke-dashoffset: 503.0904846191406;
|
||||
animation: draw 3s ease-in-out forwards;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
.path2 {
|
||||
stroke-dasharray: 625.779541015625;
|
||||
stroke-dashoffset: 625.779541015625;
|
||||
animation: draw 3s ease-in-out forwards;
|
||||
animation-delay: 0.45s;
|
||||
}
|
||||
|
||||
.path3 {
|
||||
stroke-dasharray: 714.129638671875;
|
||||
stroke-dashoffset: 714.129638671875;
|
||||
animation: draw 3s ease-in-out forwards;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.path4 {
|
||||
stroke-dasharray: 427.98114013671875;
|
||||
stroke-dashoffset: 427.98114013671875;
|
||||
animation: draw 3s ease-in-out forwards;
|
||||
animation-delay: 0.35s;
|
||||
}
|
||||
|
||||
.path5 {
|
||||
stroke-dasharray: 593.7645263671875;
|
||||
stroke-dashoffset: 593.7645263671875;
|
||||
animation: draw 3s ease-in-out forwards;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.path6 {
|
||||
stroke-dasharray: 603.0399780273438;
|
||||
stroke-dashoffset: 603.0399780273438;
|
||||
animation: draw 3s ease-in-out forwards;
|
||||
animation-delay: 0.25s;
|
||||
}
|
||||
|
||||
.path7 {
|
||||
stroke-dasharray: 731.757568359375;
|
||||
stroke-dashoffset: 731.757568359375;
|
||||
animation: draw 3s ease-in-out forwards;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.path8 {
|
||||
stroke-dasharray: 382.3065185546875;
|
||||
stroke-dashoffset: 382.3065185546875;
|
||||
animation: draw 3s ease-in-out forwards;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.path9 {
|
||||
stroke-dasharray: 603.0382690429688;
|
||||
stroke-dashoffset: 603.0382690429688;
|
||||
animation: draw 3s ease-in-out forwards;
|
||||
animation-delay: 0.15s;
|
||||
}
|
||||
|
||||
.path10 {
|
||||
stroke-dasharray: 652.2447509765625;
|
||||
stroke-dashoffset: 652.2447509765625;
|
||||
animation: draw 3s ease-in-out forwards;
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
@keyframes draw {
|
||||
to {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fill {
|
||||
from {
|
||||
fill: transparent;
|
||||
}
|
||||
to {
|
||||
fill: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// src/assets/SerpentRace_Animation/SerpentRace_Animation.jsx
|
||||
// Animációs kiírás: SerpentRace
|
||||
|
||||
import styles from "./Path.module.css";
|
||||
import React, { useRef } from "react";
|
||||
|
||||
const Animation = ({ sizePercentage = 100 }) => {
|
||||
const width = (1253 * sizePercentage) / 100;
|
||||
const height = (136 * sizePercentage) / 100;
|
||||
|
||||
// 11 path-hoz refs
|
||||
const pathRefs = Array.from({ length: 11 }, () => useRef(null));
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* prettier-ignore */}
|
||||
<svg className={styles.animation} width={width} height={height} viewBox="0 0 1319 198" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path ref={pathRefs[0]} className={styles.path0} d="M1261.64 32.9C1272.02 32.9 1281.15 34.9576 1289.1 39.0094L1289.86 39.4078C1297.97 43.7136 1304.29 49.9037 1308.86 58.026L1308.86 58.0328L1308.87 58.0406C1313.41 65.9983 1315.74 75.4878 1315.74 86.6002C1315.74 88.8329 1315.63 91.0662 1315.41 93.3004H1240.77L1240.94 95.9625C1241.36 102.425 1243.14 107.682 1246.63 111.328L1246.67 111.368L1246.71 111.407C1250.29 114.831 1254.8 116.5 1260.04 116.5C1263.69 116.5 1266.97 115.677 1269.77 113.917C1272.15 112.419 1274.06 110.315 1275.55 107.7H1312.61C1310.88 113.608 1308.06 118.989 1304.16 123.859L1303.71 124.408L1303.71 124.413C1299.18 129.919 1293.45 134.322 1286.48 137.611L1285.8 137.925C1278.56 141.229 1270.51 142.9 1261.64 142.9C1250.94 142.9 1241.49 140.648 1233.23 136.205C1225.37 131.905 1219.12 125.83 1214.46 117.933L1214.01 117.164C1209.46 108.936 1207.14 99.1765 1207.14 87.8004C1207.14 76.4113 1209.46 66.7169 1214.01 58.6256L1214.02 58.6187L1214.02 58.6109C1218.45 50.6085 1224.53 44.4249 1232.28 40.0143L1233.04 39.5934C1241.29 35.1536 1250.8 32.9 1261.64 32.9ZM1261.44 58.9C1256.17 58.9 1251.64 60.3691 1248.04 63.4723C1244.4 66.4788 1242.18 70.8761 1241.18 76.3473L1240.63 79.3004H1280.74V76.8004C1280.74 71.5541 1279.01 67.178 1275.39 63.985L1275.04 63.6793C1271.33 60.4557 1266.74 58.9 1261.44 58.9Z" stroke="white" strokeWidth="5"/>
|
||||
<path ref={pathRefs[1]} className={styles.path1} d="M1139.95 32.9C1153.73 32.9 1165.15 36.6867 1174.38 44.1441L1174.39 44.151L1174.4 44.1578C1182.91 50.9203 1188.68 60.2478 1191.63 72.3004H1154.9C1153.61 69.0944 1151.8 66.4744 1149.4 64.5846C1146.55 62.349 1143.08 61.3004 1139.15 61.3004C1133.38 61.3004 1128.7 63.7808 1125.31 68.5533L1125.31 68.5602L1125.3 68.566C1122.08 73.1723 1120.65 79.708 1120.65 87.8004C1120.65 95.9013 1122.08 102.479 1125.28 107.202L1125.31 107.247C1128.7 112.019 1133.38 114.5 1139.15 114.5C1143.13 114.5 1146.64 113.458 1149.5 111.215C1151.9 109.324 1153.68 106.702 1154.93 103.5H1191.63C1188.77 115.027 1183.29 124.135 1175.24 130.949L1174.38 131.656C1165.15 139.113 1153.73 142.9 1139.95 142.9C1129.25 142.9 1119.8 140.648 1111.55 136.205C1103.69 131.908 1097.51 125.841 1092.97 117.958L1092.54 117.189C1087.98 108.956 1085.65 99.188 1085.65 87.8004C1085.65 76.9027 1087.83 67.4559 1092.12 59.3873L1092.54 58.6109C1096.97 50.6085 1103.05 44.4249 1110.8 40.0143L1111.55 39.5934C1119.81 35.1513 1129.25 32.9 1139.95 32.9Z" stroke="white" strokeWidth="5"/>
|
||||
<path ref={pathRefs[2]} className={styles.path2} d="M995.014 32.9C1002.18 32.9 1008.26 34.2763 1013.33 36.9322L1013.81 37.193C1019.04 40.0563 1023.04 43.8802 1025.86 48.6695L1030.51 56.5602V34.3004H1064.71V141.5H1030.51V119.24L1025.86 127.13C1023.04 131.905 1019 135.728 1013.63 138.595L1013.61 138.607C1008.45 141.437 1002.27 142.9 995.014 142.9C986.807 142.9 979.357 140.83 972.608 136.697L971.956 136.291C965.401 132.037 960.089 125.994 956.045 118.069L955.657 117.296C951.72 108.895 949.714 99.0842 949.714 87.8004C949.714 76.5091 951.722 66.7655 955.656 58.5035L955.657 58.5045C959.747 50.1977 965.189 43.9003 971.956 39.5094C978.877 35.1054 986.542 32.9 995.014 32.9ZM1007.61 62.1002C1001.29 62.1002 995.894 64.2893 991.601 68.6617L991.217 69.0621C986.771 73.6617 984.714 80.0315 984.714 87.8004C984.714 95.4589 986.781 101.845 991.161 106.678L991.175 106.694L991.189 106.708C995.547 111.367 1001.08 113.7 1007.61 113.7C1014.02 113.7 1019.47 111.363 1023.81 106.738L1023.81 106.739C1028.38 102.021 1030.51 95.5962 1030.51 87.8004C1030.51 80.1231 1028.37 73.771 1023.81 69.0611H1023.81C1019.47 64.436 1014.01 62.1003 1007.61 62.1002Z" stroke="white" strokeWidth="5"/>
|
||||
<path ref={pathRefs[3]} className={styles.path3} d="M883.187 5.5C894.235 5.5 903.424 7.45044 910.854 11.2285L910.87 11.2363L910.885 11.2432C918.35 14.9128 923.893 19.9862 927.622 26.4492L927.628 26.458L927.633 26.4678C931.388 32.8524 933.288 40.075 933.288 48.2002C933.288 57.0202 930.924 64.7518 926.227 71.4814L925.765 72.1299L925.761 72.1367C920.94 78.8118 913.776 83.668 904.063 86.6074L901.045 87.5205L902.623 90.251L932.255 141.5H894.281L866.989 90.8145L866.281 89.5H858.088V141.5H823.888V5.5H883.187ZM858.088 67.7002H879.987C885.497 67.7002 890.109 66.4347 893.395 63.502L893.71 63.2129C897.012 60.0753 898.487 55.6661 898.487 50.4004C898.487 45.3401 896.908 41.0909 893.515 37.9932C890.228 34.7309 885.571 33.2998 879.987 33.2998H858.088V67.7002Z" stroke="white" strokeWidth="5"/>
|
||||
<path ref={pathRefs[4]} className={styles.path4} d="M780.308 7.09998V34.3002H800.707V61.8998H780.308V102C780.308 105.333 781.017 108.344 783.04 110.368L783.082 110.41L783.126 110.45C785.279 112.407 788.447 113.1 792.008 113.1H800.908V141.5H786.408C772.454 141.5 762.449 138.044 755.917 131.574C749.39 125.107 745.908 115.208 745.908 101.4V61.8998H732.108V34.3002H745.908V7.09998H780.308Z" stroke="white" strokeWidth="5"/>
|
||||
<path ref={pathRefs[5]} className={styles.path5} d="M673.499 33.3C685.81 33.3001 695.355 37.3928 702.419 45.4484L702.426 45.4553L702.431 45.4631C709.518 53.4205 713.198 64.3676 713.198 78.5998V141.5H678.999V83.8C678.999 77.1809 677.321 71.6461 673.69 67.4982L673.333 67.1017C669.469 62.7934 664.3 60.7004 658.098 60.7004C651.671 60.7004 646.352 62.7672 642.464 67.1017C638.585 71.2907 636.799 76.9677 636.799 83.8V141.5H602.598V34.3H636.799V55.881L641.362 49.2121C644.58 44.5096 648.924 40.7078 654.457 37.8156L654.476 37.8049L654.496 37.7951C659.947 34.8215 666.259 33.3 673.499 33.3Z" stroke="white" strokeWidth="5"/>
|
||||
<path ref={pathRefs[6]} className={styles.path6} d="M527.456 32.9C537.839 32.9 546.973 34.9576 554.918 39.0094L555.684 39.4078C563.789 43.7136 570.108 49.9038 574.677 58.026L574.682 58.0328L574.686 58.0406C579.233 65.9983 581.556 75.4878 581.556 86.6002C581.556 88.8329 581.446 91.0662 581.226 93.3004H506.588L506.762 95.9625C507.183 102.425 508.962 107.682 512.449 111.328L512.488 111.368L512.529 111.407C516.109 114.831 520.616 116.5 525.857 116.5C529.506 116.5 532.787 115.677 535.587 113.917C537.97 112.419 539.884 110.315 541.368 107.7H578.43C576.699 113.608 573.884 118.989 569.976 123.859L569.529 124.408L569.526 124.413C564.998 129.919 559.27 134.322 552.297 137.611L551.618 137.925C544.376 141.229 536.335 142.9 527.456 142.9C516.756 142.9 507.308 140.648 499.052 136.205C491.192 131.905 484.94 125.83 480.276 117.933L479.829 117.164C475.279 108.936 472.956 99.1765 472.956 87.8004C472.956 76.4113 475.284 66.7169 479.835 58.6256L479.839 58.6187L479.843 58.6109C484.415 50.3552 490.739 44.0358 498.842 39.6012C507.097 35.1564 516.612 32.9 527.456 32.9ZM527.256 58.9C521.992 58.9 517.458 60.3691 513.864 63.4723C510.225 66.4788 508.005 70.8761 506.997 76.3473L506.453 79.3004H546.556V76.8004C546.556 71.5541 544.83 67.178 541.213 63.985L540.857 63.6793C537.15 60.4557 532.556 58.9 527.256 58.9Z" stroke="white" strokeWidth="5"/>
|
||||
<path ref={pathRefs[7]} className={styles.path7} d="M412.919 32.9C421.39 32.9 428.981 35.1048 435.759 39.4977L435.776 39.5094C442.673 43.8984 448.109 50.187 452.062 58.4762L452.069 58.4898L452.076 58.5045C456.143 66.7653 458.219 76.5074 458.219 87.8004C458.219 99.0921 456.143 108.907 452.069 117.309L452.065 117.317L452.062 117.324C448.109 125.613 442.673 131.902 435.776 136.291L435.759 136.302C428.981 140.695 421.39 142.9 412.919 142.9C405.885 142.9 399.874 141.527 394.806 138.869L394.319 138.607C389.224 135.744 385.214 131.91 382.248 127.089L377.619 119.567V195.1H343.419V34.3004H377.619V56.5602L382.272 48.6695C385.009 44.0298 388.842 40.2965 393.833 37.4644L394.319 37.193C399.487 34.363 405.659 32.9 412.919 32.9ZM400.518 62.1002C394.105 62.1003 388.591 64.443 384.122 69.0611L384.107 69.0768L384.094 69.0924C379.685 73.8051 377.619 80.1455 377.619 87.8004C377.619 95.5741 379.677 101.987 384.094 106.708L384.107 106.723L384.122 106.739C388.591 111.357 394.105 113.7 400.518 113.7C406.933 113.7 412.396 111.354 416.742 106.709L416.743 106.71C421.272 101.878 423.419 95.4809 423.419 87.8004C423.419 80.2525 421.414 74.0286 417.11 69.4684L416.686 69.0318C412.349 64.4263 406.905 62.1002 400.518 62.1002Z" stroke="white" strokeWidth="5"/>
|
||||
<path ref={pathRefs[8]} className={styles.path8} d="M324.366 70.0998H315.866C307.359 70.0998 300.513 71.6968 295.751 75.299L295.297 75.6535C290.309 79.6756 288.166 86.4018 288.166 95.0002V141.5H253.966V34.3H288.166V59.6496L292.725 53.0168C296.934 46.8945 302.008 42.0849 307.945 38.548C313.097 35.4789 318.561 33.7613 324.366 33.381V70.0998Z" stroke="white" strokeWidth="5"/>
|
||||
<path ref={pathRefs[9]} className={styles.path9} d="M178.823 32.9C189.206 32.9 198.34 34.9576 206.285 39.0094L207.051 39.4078C215.156 43.7136 221.475 49.9038 226.044 58.026L226.049 58.0328L226.053 58.0406C230.6 65.9983 232.923 75.4878 232.923 86.6002C232.923 88.8329 232.813 91.0662 232.593 93.3004H157.955L158.129 95.9625C158.551 102.425 160.329 107.682 163.817 111.328L163.856 111.368L163.896 111.407C167.476 114.831 171.984 116.5 177.224 116.5C180.873 116.5 184.154 115.677 186.954 113.917C189.337 112.419 191.252 110.315 192.736 107.7H229.797C228.066 113.608 225.251 118.989 221.343 123.859L220.896 124.408L220.893 124.413C216.365 129.919 210.637 134.322 203.664 137.611L202.986 137.925C195.743 141.229 187.702 142.9 178.823 142.9C168.123 142.9 158.676 140.648 150.419 136.205C142.559 131.905 136.307 125.83 131.643 117.933L131.196 117.164C126.646 108.936 124.323 99.1765 124.323 87.8004C124.323 76.4113 126.651 66.7169 131.202 58.6256L131.206 58.6187L131.21 58.6109C135.642 50.6085 141.719 44.425 149.468 40.0143L150.223 39.5934C158.474 35.1535 167.986 32.9 178.823 32.9ZM178.623 58.9C173.359 58.9 168.825 60.3691 165.232 63.4723C161.592 66.4788 159.372 70.8761 158.364 76.3473L157.821 79.3004H197.923V76.8004C197.923 71.5541 196.198 67.178 192.58 63.985L192.224 63.6793C188.517 60.4557 183.924 58.9 178.623 58.9Z" stroke="white" strokeWidth="5"/>
|
||||
<path ref={pathRefs[10]} className={styles.path10} d="M53.2002 3.29999C69.2163 3.30002 81.6771 7.07376 90.8447 14.3576L90.8594 14.3693L90.874 14.3801C99.4701 21.0014 104.378 30.1606 105.515 42.0998H67.8926C67.3698 38.4388 65.8881 35.3672 63.2598 33.131H63.2607C60.3137 30.5116 56.5476 29.3 52.2002 29.3C48.8465 29.3 45.8881 30.2761 43.5254 32.3625L43.2988 32.5676C40.7984 34.8894 39.7002 38.0949 39.7002 41.8C39.7002 45.173 40.9953 48.1303 43.4326 50.5676L43.4912 50.6262L43.5537 50.6818C45.8882 52.7245 48.6886 54.509 51.9209 56.0549L51.9619 56.0744L52.0029 56.093C55.1495 57.461 59.7476 59.217 65.7627 61.3557L65.7754 61.3605L65.7881 61.3644C74.6461 64.4053 81.8453 67.4222 87.4209 70.4045L87.4512 70.4211L87.4824 70.4357C92.9892 73.1892 97.6936 77.1341 101.605 82.3078L101.627 82.3351L101.647 82.3615C105.489 87.1639 107.5 93.3779 107.5 101.2C107.5 109.153 105.487 116.213 101.494 122.452L101.484 122.467L101.476 122.483C97.6244 128.694 91.9433 133.66 84.3125 137.349L84.2969 137.357C76.8439 141.02 67.9047 142.9 57.4004 142.9C41.8954 142.9 29.2947 139.363 19.4258 132.463L18.4795 131.784C9.25313 124.887 3.96894 115.274 2.70117 102.7H39.5801C40.2265 106.786 41.7043 110.177 44.2383 112.591L44.5332 112.863C47.604 115.611 51.5002 116.9 56 116.9C60.0031 116.9 63.5126 115.932 66.3184 113.786L66.3271 113.779L66.335 113.774C69.38 111.405 70.9003 108.125 70.9004 104.2C70.9004 99.2522 68.5707 95.3626 64.2852 92.6974L63.8643 92.4435C59.755 90.0347 53.2798 87.4365 44.5898 84.6281H44.5908C35.4593 81.5843 28.1131 78.6936 22.5186 75.9631C17.2902 73.2164 12.6467 69.2763 8.59375 64.091C4.86911 59.0319 2.90039 52.3374 2.90039 43.8C2.90039 35.1675 5.05101 27.9593 9.24023 22.0451L9.25391 22.0256C13.3581 16.1108 18.996 11.5666 26.2539 8.40936L26.9619 8.1076C34.6101 4.92092 43.342 3.29999 53.2002 3.29999Z" stroke="white" strokeWidth="5"/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Animation;
|
||||
@@ -0,0 +1,97 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { motion } from "framer-motion"
|
||||
|
||||
const Background = () => {
|
||||
const [gridSize, setGridSize] = useState({ cols: 12, rows: 6 })
|
||||
const [mousePos, setMousePos] = useState({ x: 0, y: 0 })
|
||||
const [path, setPath] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
const updateGrid = () => {
|
||||
const width = window.innerWidth
|
||||
const height = window.innerHeight
|
||||
const cols = Math.max(8, Math.floor(width / 100))
|
||||
const rows = Math.max(5, Math.floor(height / 100))
|
||||
setGridSize({ cols, rows })
|
||||
}
|
||||
|
||||
const handleMouseMove = (e) => {
|
||||
setMousePos({ x: e.clientX, y: e.clientY })
|
||||
}
|
||||
|
||||
updateGrid()
|
||||
window.addEventListener("resize", updateGrid)
|
||||
window.addEventListener("mousemove", handleMouseMove)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", updateGrid)
|
||||
window.removeEventListener("mousemove", handleMouseMove)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
const newCol = Math.floor(Math.random() * gridSize.cols)
|
||||
const newRow = Math.floor(Math.random() * gridSize.rows)
|
||||
setPath((prevPath) => {
|
||||
const newPath = [...prevPath, { col: newCol, row: newRow, opacity: 1 }]
|
||||
if (newPath.length > 10) newPath.shift()
|
||||
return newPath
|
||||
})
|
||||
}, 500)
|
||||
|
||||
const fadeInterval = setInterval(() => {
|
||||
setPath((prevPath) =>
|
||||
prevPath
|
||||
.map((point) => ({ ...point, opacity: Math.max(0, point.opacity - 0.05) }))
|
||||
.filter((point) => point.opacity > 0)
|
||||
)
|
||||
}, 100)
|
||||
|
||||
return () => {
|
||||
clearInterval(interval)
|
||||
clearInterval(fadeInterval)
|
||||
}
|
||||
}, [gridSize])
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-screen bg-background flex items-center justify-center overflow-hidden">
|
||||
<div
|
||||
className="absolute inset-0 grid gap-2 opacity-20"
|
||||
style={{
|
||||
gridTemplateColumns: `repeat(${gridSize.cols}, 1fr)`,
|
||||
gridTemplateRows: `repeat(${gridSize.rows}, 1fr)`,
|
||||
}}
|
||||
>
|
||||
{[...Array(gridSize.cols * gridSize.rows)].map((_, i) => {
|
||||
const col = i % gridSize.cols
|
||||
const row = Math.floor(i / gridSize.cols)
|
||||
const cellX = (col + 0.5) * (window.innerWidth / gridSize.cols)
|
||||
const cellY = (row + 0.5) * (window.innerHeight / gridSize.rows)
|
||||
|
||||
const dx = cellX - mousePos.x
|
||||
const dy = cellY - mousePos.y
|
||||
const distance = Math.sqrt(dx * dx + dy * dy)
|
||||
const distanceFactor = Math.max(0, 1 - distance / 300)
|
||||
|
||||
const pathPoint = path.find((p) => p.col === col && p.row === row)
|
||||
const pathOpacity = pathPoint ? pathPoint.opacity : 0
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="w-full h-full bg-white rounded-lg"
|
||||
animate={{
|
||||
opacity: 0.05 + distanceFactor * 0.2 + pathOpacity * 0.5,
|
||||
scale: 1 + distanceFactor * 0.05 + pathOpacity * 0.1,
|
||||
}}
|
||||
transition={{ duration: 0.5, ease: "easeOut" }}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Background
|
||||
@@ -0,0 +1,18 @@
|
||||
// src/assets/pictures/Logo.png
|
||||
// Logo kép importálása és paraméterezése
|
||||
|
||||
import React from 'react';
|
||||
import logo from './Logo.png';
|
||||
|
||||
const Logo = ({ size = 100 }) => (
|
||||
<img
|
||||
src={logo}
|
||||
alt="Logo"
|
||||
width={size}
|
||||
height={size}
|
||||
style={{ objectFit: 'contain' }}
|
||||
/>
|
||||
);
|
||||
|
||||
export default Logo;
|
||||
|
||||
|
After Width: | Height: | Size: 981 KiB |
@@ -0,0 +1,134 @@
|
||||
import { useRef, useState } from "react"
|
||||
import { motion, useMotionValue, useSpring } from "framer-motion"
|
||||
|
||||
const springValues = {
|
||||
damping: 30,
|
||||
stiffness: 100,
|
||||
mass: 2,
|
||||
}
|
||||
|
||||
export default function LogoCard({
|
||||
imageSrc,
|
||||
altText = "Tilted card image",
|
||||
captionText = "",
|
||||
containerHeight = "300px",
|
||||
containerWidth = "100%",
|
||||
imageHeight = "300px",
|
||||
imageWidth = "300px",
|
||||
scaleOnHover = 1.1,
|
||||
rotateAmplitude = 14,
|
||||
showMobileWarning = true,
|
||||
showTooltip = true,
|
||||
overlayContent = null,
|
||||
displayOverlayContent = false,
|
||||
}) {
|
||||
const ref = useRef(null)
|
||||
const x = useMotionValue(0)
|
||||
const y = useMotionValue(0)
|
||||
const rotateX = useSpring(useMotionValue(0), springValues)
|
||||
const rotateY = useSpring(useMotionValue(0), springValues)
|
||||
const scale = useSpring(1, springValues)
|
||||
const opacity = useSpring(0)
|
||||
const rotateFigcaption = useSpring(0, {
|
||||
stiffness: 350,
|
||||
damping: 30,
|
||||
mass: 1,
|
||||
})
|
||||
|
||||
const [lastY, setLastY] = useState(0)
|
||||
|
||||
function handleMouse(e) {
|
||||
if (!ref.current) return
|
||||
|
||||
const rect = ref.current.getBoundingClientRect()
|
||||
const offsetX = e.clientX - rect.left - rect.width / 2
|
||||
const offsetY = e.clientY - rect.top - rect.height / 2
|
||||
|
||||
const rotationX = (offsetY / (rect.height / 2)) * -rotateAmplitude
|
||||
const rotationY = (offsetX / (rect.width / 2)) * rotateAmplitude
|
||||
|
||||
rotateX.set(rotationX)
|
||||
rotateY.set(rotationY)
|
||||
|
||||
x.set(e.clientX - rect.left)
|
||||
y.set(e.clientY - rect.top)
|
||||
|
||||
const velocityY = offsetY - lastY
|
||||
rotateFigcaption.set(-velocityY * 0.6)
|
||||
setLastY(offsetY)
|
||||
}
|
||||
|
||||
function handleMouseEnter() {
|
||||
scale.set(scaleOnHover)
|
||||
opacity.set(1)
|
||||
}
|
||||
|
||||
function handleMouseLeave() {
|
||||
opacity.set(0)
|
||||
scale.set(1)
|
||||
rotateX.set(0)
|
||||
rotateY.set(0)
|
||||
rotateFigcaption.set(0)
|
||||
}
|
||||
|
||||
return (
|
||||
<figure
|
||||
ref={ref}
|
||||
className="relative w-full h-full [perspective:800px] flex flex-col items-center justify-center"
|
||||
style={{
|
||||
height: containerHeight,
|
||||
width: containerWidth,
|
||||
}}
|
||||
onMouseMove={handleMouse}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{showMobileWarning && (
|
||||
<div className="absolute top-4 text-center text-sm block sm:hidden">
|
||||
This effect is not optimized for mobile. Check on desktop.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<motion.div
|
||||
className="relative [transform-style:preserve-3d]"
|
||||
style={{
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
rotateX,
|
||||
rotateY,
|
||||
scale,
|
||||
}}
|
||||
>
|
||||
<motion.img
|
||||
src={imageSrc}
|
||||
alt={altText}
|
||||
className="absolute top-0 left-0 object-cover rounded-[15px] will-change-transform [transform:translateZ(0)]"
|
||||
style={{
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
}}
|
||||
/>
|
||||
|
||||
{displayOverlayContent && overlayContent && (
|
||||
<motion.div className="absolute top-0 left-0 z-[2] will-change-transform [transform:translateZ(30px)]">
|
||||
{overlayContent}
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
{showTooltip && (
|
||||
<motion.figcaption
|
||||
className="pointer-events-none absolute left-0 top-0 rounded-[4px] bg-white px-[10px] py-[4px] text-[10px] text-[#2d2d2d] opacity-0 z-[3] hidden sm:block"
|
||||
style={{
|
||||
x,
|
||||
y,
|
||||
opacity,
|
||||
rotate: rotateFigcaption,
|
||||
}}
|
||||
>
|
||||
{captionText}
|
||||
</motion.figcaption>
|
||||
)}
|
||||
</figure>
|
||||
)
|
||||
}
|
||||
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 186 KiB |
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 189 KiB |
@@ -0,0 +1,22 @@
|
||||
// src/components/Inputs/InputBox.jsx
|
||||
// Gomb komponens
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
|
||||
export default function Button({ text, type, onClick, width, className }) {
|
||||
const widthClass = width ? width : "w-full"
|
||||
|
||||
return (
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
className={`${widthClass} bg-button-primary text-white py-3 rounded-lg hover:bg-button-primary-hover transition shadow-md font-semibold text-lg ${
|
||||
className ? className : ""
|
||||
}`}
|
||||
>
|
||||
{text}
|
||||
</motion.button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// src/components/Inputs/InputBox.jsx
|
||||
// Gomb komponens
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
|
||||
export default function Button({ text, type, onClick, width }) {
|
||||
const widthClass = width ? width : "w-full"
|
||||
|
||||
return (
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
className={`${widthClass} bg-button-secondary text-white py-3 rounded-lg hover:bg-button-secondary-hover transition shadow-md font-semibold text-lg`}
|
||||
>
|
||||
{text}
|
||||
</motion.button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// src/components/Buttons/ButtonGreen.jsx
|
||||
// Zöld gomb komponens (ButtonGreen)
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
|
||||
export default function ButtonGreen({ text, type, onClick, width }) {
|
||||
const widthClass = width ? width : "w-full"
|
||||
|
||||
return (
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
className={`${widthClass} bg-button-green text-white py-3 rounded-lg hover:bg-button-green-hover transition shadow-md font-semibold text-lg`}
|
||||
>
|
||||
{text}
|
||||
</motion.button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
|
||||
export default function Card({ title, children, onClose }) {
|
||||
return (
|
||||
<div className="relative bg-white rounded-xl shadow-lg p-6 w-[400px] h-[300px]">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-2 right-2 text-gray-500 hover:text-black text-xl font-bold leading-none"
|
||||
aria-label="Close"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
{title && <h2 className="text-xl font-semibold mb-4">{title}</h2>}
|
||||
<div className="overflow-auto h-[calc(100%-3.5rem)]">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import { Link } from "react-router-dom"
|
||||
import Logo from "../../assets/pictures/Logo"
|
||||
|
||||
|
||||
const ArrowUpIcon = () => <span style={{ fontSize: "1.25rem" }}>↑</span>
|
||||
|
||||
const Footer = () => {
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
const footerRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
setIsVisible(entry.isIntersecting)
|
||||
},
|
||||
{ threshold: 0.3 }
|
||||
)
|
||||
|
||||
if (footerRef.current) {
|
||||
observer.observe(footerRef.current)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (footerRef.current) {
|
||||
observer.unobserve(footerRef.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const scrollToTop = () => {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" })
|
||||
}
|
||||
|
||||
return (
|
||||
<footer
|
||||
ref={footerRef}
|
||||
className={`relative bg-zinc-900 text-white border-t-2 border-zinc-800 mt-auto py-8 transition-all duration-700 ease-out ${
|
||||
isVisible ? "opacity-100 scale-100 translate-y-0" : "opacity-0 scale-95 translate-y-10"
|
||||
}`}
|
||||
style={{ transformOrigin: "bottom center" }}
|
||||
>
|
||||
<style>
|
||||
{`
|
||||
.footer-animate {
|
||||
transition: opacity 0.8s ease, transform 0.8s ease;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
|
||||
<div className="max-w-6xl mx-auto flex flex-wrap justify-between items-start gap-8 px-4">
|
||||
{/* Logó */}
|
||||
<div className="flex flex-col items-center footer-animate">
|
||||
<a
|
||||
href="/"
|
||||
className="transition-transform duration-500 hover:scale-105 hover:brightness-125"
|
||||
>
|
||||
<Logo size={100} />
|
||||
</a>
|
||||
<span className="font-extrabold text-xl mt-2 tracking-wide">SerpentRace</span>
|
||||
</div>
|
||||
|
||||
{/* Oldalak */}
|
||||
<div className="flex flex-col gap-1 footer-animate">
|
||||
<span className="text-lg font-semibold text-green-400 underline underline-offset-4 mb-2 drop-shadow-sm">
|
||||
Oldalak
|
||||
</span>
|
||||
<a href="/" className="hover:underline hover:text-green-400 transition">Főoldal</a>
|
||||
<a href="/about" className="hover:underline hover:text-green-400 transition">
|
||||
Rólunk
|
||||
</a>
|
||||
<a href="/contact" className="hover:underline hover:text-green-400 transition">Kapcsolat</a>
|
||||
</div>
|
||||
|
||||
{/* Közösség */}
|
||||
<div className="flex flex-col gap-1 footer-animate">
|
||||
<span className="text-lg font-semibold text-green-400 underline underline-offset-4 mb-2 drop-shadow-sm">
|
||||
Közösség
|
||||
</span>
|
||||
<a href="https://discord.gg/" target="_blank" rel="noopener noreferrer" className="hover:underline hover:text-green-400 transition">Discord</a>
|
||||
<a href="https://github.com/" target="_blank" rel="noopener noreferrer" className="hover:underline hover:text-green-400 transition">GitHub</a>
|
||||
</div>
|
||||
|
||||
{/* Elérhetőség */}
|
||||
<div className="flex flex-col gap-1 footer-animate">
|
||||
<span className="text-lg font-semibold text-green-400 underline underline-offset-4 mb-2 drop-shadow-sm">
|
||||
Elérhetőség
|
||||
</span>
|
||||
<span className="opacity-80">Email: info@serpentrace.hu</span>
|
||||
<span className="opacity-80">Telefon: +36 30 123 4567</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-8 text-sm opacity-70 footer-animate">
|
||||
© {new Date().getFullYear()} SerpentRace. Minden jog fenntartva.
|
||||
</div>
|
||||
|
||||
{/* Scroll to top */}
|
||||
{isVisible && (
|
||||
<button
|
||||
onClick={scrollToTop}
|
||||
className="fixed bottom-6 right-6 bg-green-500 hover:bg-green-600 text-white p-3 rounded-full shadow-lg transition transform hover:scale-110"
|
||||
aria-label="Ugrás az oldal tetejére"
|
||||
>
|
||||
<ArrowUpIcon />
|
||||
</button>
|
||||
)}
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
||||
@@ -0,0 +1,16 @@
|
||||
// src/components/Inputs/InputBox.jsx
|
||||
// InputBox komponens
|
||||
|
||||
export default function InputBox({ type, placeholder, value, onChange, width }) {
|
||||
const widthClass = width ? width : "w-full";
|
||||
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={`${widthClass} px-4 py-3 border border-gray-300 rounded-lg focus:border-background focus:outline-none text-gray-700 placeholder-gray-400 bg-gray-50`}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// src/components/Inputs/InputBox.jsx
|
||||
// InputBox komponens
|
||||
|
||||
export default function InputBox({ type, placeholder, value, onChange, width }) {
|
||||
const widthClass = width ? width : "w-full"
|
||||
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={`${widthClass} py-3 px-4 border border-battleship-gray rounded-lg focus:border-mint focus:outline-none text-text placeholder-text-muted bg-background font-semibold text-lg`}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
import React, { useState } from "react"
|
||||
import {
|
||||
FaPlus,
|
||||
FaFilter,
|
||||
FaCalendarAlt,
|
||||
FaArrowUp,
|
||||
FaArrowDown,
|
||||
FaSortAlphaDown,
|
||||
FaSortAlphaUp,
|
||||
FaQuestionCircle,
|
||||
} from "react-icons/fa"
|
||||
import SearchBox from "../Search/SearchBox"
|
||||
import PopUp from "../PopUp/PopUp"
|
||||
|
||||
const deckTypes = [
|
||||
{ label: "Luck", color: "var(--color-luck)" },
|
||||
{ label: "Question", color: "var(--color-question)" },
|
||||
{ label: "Fun", color: "var(--color-fun)" },
|
||||
]
|
||||
|
||||
const mockDecks = [
|
||||
// Just for visual mockup
|
||||
{ id: 1, name: "Party Luck", type: "Luck", created: "2025-07-01", origin: "Vállalati" },
|
||||
{ id: 2, name: "Quiz Night", type: "Question", created: "2025-07-02", origin: "Saját" },
|
||||
{ id: 3, name: "Fun Times", type: "Fun", created: "2025-07-03", origin: "Vállalati" },
|
||||
{ id: 4, name: "Corporate Challenge", type: "Question", created: "2025-07-04", origin: "Vállalati" },
|
||||
{ id: 5, name: "Randomizer", type: "Luck", created: "2025-07-05", origin: "Saját" },
|
||||
{ id: 6, name: "Afterwork luck", type: "Luck", created: "2025-07-06", origin: "Saját" },
|
||||
{ id: 7, name: "Serpent Quiz", type: "Question", created: "2025-07-07", origin: "Vállalati" },
|
||||
{ id: 8, name: "Green Fortune", type: "Luck", created: "2025-07-08", origin: "Vállalati" },
|
||||
{ id: 9, name: "Team Builder", type: "Fun", created: "2025-07-09", origin: "Saját" },
|
||||
{ id: 10, name: "Knowledge Race", type: "Question", created: "2025-07-10", origin: "Saját" },
|
||||
]
|
||||
|
||||
const origins = ["Mind", "Vállalati", "Saját"]
|
||||
|
||||
const sortOptions = [
|
||||
{
|
||||
value: "date-asc",
|
||||
label: (
|
||||
<>
|
||||
<FaCalendarAlt className="inline mr-1" />
|
||||
<FaArrowUp className="inline" />
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: "date-desc",
|
||||
label: (
|
||||
<>
|
||||
<FaCalendarAlt className="inline mr-1" />
|
||||
<FaArrowDown className="inline" />
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: "abc-asc",
|
||||
label: (
|
||||
<>
|
||||
<FaSortAlphaDown className="inline" />
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: "abc-desc",
|
||||
label: (
|
||||
<>
|
||||
<FaSortAlphaUp className="inline" />
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
const DeckManager = () => {
|
||||
const [selectedType, setSelectedType] = useState("All")
|
||||
const [selectedOrigin, setSelectedOrigin] = useState("Mind")
|
||||
const [sortBy, setSortBy] = useState("date-desc")
|
||||
const [search, setSearch] = useState("")
|
||||
const [showSortHelp, setShowSortHelp] = useState(false)
|
||||
|
||||
// Filter logic (mock)
|
||||
let filteredDecks = mockDecks.filter((deck) => {
|
||||
const typeMatch = selectedType === "All" || deck.type === selectedType
|
||||
const originMatch = selectedOrigin === "Mind" || deck.origin === selectedOrigin
|
||||
const searchMatch = !search || deck.name.toLowerCase().includes(search.toLowerCase())
|
||||
return typeMatch && originMatch && searchMatch
|
||||
})
|
||||
|
||||
// Sort logic
|
||||
filteredDecks = [...filteredDecks].sort((a, b) => {
|
||||
if (sortBy === "date-asc") {
|
||||
return a.created.localeCompare(b.created)
|
||||
} else if (sortBy === "date-desc") {
|
||||
return b.created.localeCompare(a.created)
|
||||
} else if (sortBy === "abc-asc") {
|
||||
return a.name.localeCompare(b.name)
|
||||
} else if (sortBy === "abc-desc") {
|
||||
return b.name.localeCompare(a.name)
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col bg-[color:var(--color-background)]">
|
||||
<div className="w-full max-w-6xl mx-auto px-4 py-10">
|
||||
{/* Filters */}
|
||||
<div className="flex flex-col md:flex-row gap-4 justify-between items-center mb-10 bg-[color:var(--color-surface)]/80 backdrop-blur-lg rounded-2xl px-6 py-4 shadow-lg">
|
||||
<div className="flex gap-2 items-center w-full md:w-auto">
|
||||
<SearchBox
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
width={240}
|
||||
placeholder="Keresés..."
|
||||
className="mr-4"
|
||||
/>
|
||||
<FaFilter style={{ color: "var(--color-success)" }} className="mr-2" />
|
||||
<span className="text-[color:var(--color-text)] font-semibold mr-2">Típus:</span>
|
||||
<button
|
||||
className={`px-3 py-1 rounded-lg font-medium transition-all duration-200 ${
|
||||
selectedType === "All"
|
||||
? "bg-[color:var(--color-surface-selected)] text-[color:var(--color-text)] border border-[color:var(--color-surface)]"
|
||||
: "text-[color:var(--color-text)] bg-[color:var(--color-success)]/10 hover:bg-[color:var(--color-success)]/30"
|
||||
}`}
|
||||
onClick={() => setSelectedType("All")}
|
||||
>
|
||||
Mind
|
||||
</button>
|
||||
{deckTypes.map((type) => (
|
||||
<button
|
||||
key={type.label}
|
||||
className={`px-3 py-1 rounded-lg font-medium transition-all duration-200 ml-1 ${
|
||||
selectedType === type.label
|
||||
? "text-[color:var(--color-text-inverse)] border border-[color:var(--color-surface)]"
|
||||
: "text-[color:var(--color-text)] bg-[color:var(--color-success)]/10 hover:bg-[color:var(--color-success)]/30"
|
||||
}`}
|
||||
style={selectedType === type.label ? { background: type.color } : undefined}
|
||||
onClick={() => setSelectedType(type.label)}
|
||||
>
|
||||
{type.label === "Luck"
|
||||
? "Szerencse"
|
||||
: type.label === "Question"
|
||||
? "Kérdés"
|
||||
: type.label === "Fun"
|
||||
? "Szórakozás"
|
||||
: type.label}
|
||||
</button>
|
||||
))}
|
||||
<span className="text-[color:var(--color-text)] font-semibold mr-2 ml-2">Eredet:</span>
|
||||
<select
|
||||
className="px-3 py-1 rounded-lg bg-[color:var(--color-success)]/10 hover:bg-[color:var(--color-success)]/30 text-[color:var(--color-text)] border-none focus:ring-2 focus:ring-[color:var(--color-success)] outline-none"
|
||||
style={{ backgroundColor: "rgba(0, 255, 0, 0.10)" }}
|
||||
value={selectedOrigin}
|
||||
onChange={(e) => setSelectedOrigin(e.target.value)}
|
||||
>
|
||||
{origins.map((origin) => (
|
||||
<option
|
||||
key={origin}
|
||||
value={origin}
|
||||
style={{ backgroundColor: "var(--color-surface)", color: "var(--color-text)" }}
|
||||
>
|
||||
{origin === "Mind"
|
||||
? "Mind"
|
||||
: origin === "Vállalati"
|
||||
? "Vállalati"
|
||||
: origin === "Saját"
|
||||
? "Saját"
|
||||
: origin}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="text-[color:var(--color-text)] font-semibold mr-2 ml-2 flex items-center gap-1">
|
||||
Rendezés:
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1 text-[color:var(--color-success)] hover:text-[color:var(--color-text)] focus:outline-none"
|
||||
onClick={() => setShowSortHelp(true)}
|
||||
aria-label="Rendezési magyarázat megnyitása"
|
||||
style={{ fontSize: 18, lineHeight: 1 }}
|
||||
>
|
||||
<FaQuestionCircle />
|
||||
</button>
|
||||
</span>
|
||||
<select
|
||||
className="px-3 py-1 rounded-lg bg-[color:var(--color-success)]/10 hover:bg-[color:var(--color-success)]/30 text-[color:var(--color-text)] border-none focus:ring-2 focus:ring-[color:var(--color-success)] outline-none flex items-center"
|
||||
style={{ backgroundColor: "rgba(0, 255, 0, 0.10)" }}
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy(e.target.value)}
|
||||
aria-label="Rendezés"
|
||||
>
|
||||
<option
|
||||
value="date-asc"
|
||||
style={{ backgroundColor: "var(--color-surface)", color: "var(--color-text)" }}
|
||||
>
|
||||
📅↑
|
||||
</option>
|
||||
<option
|
||||
value="date-desc"
|
||||
style={{ backgroundColor: "var(--color-surface)", color: "var(--color-text)" }}
|
||||
>
|
||||
📅↓
|
||||
</option>
|
||||
<option
|
||||
value="abc-asc"
|
||||
style={{ backgroundColor: "var(--color-surface)", color: "var(--color-text)" }}
|
||||
>
|
||||
A→Z
|
||||
</option>
|
||||
<option
|
||||
value="abc-desc"
|
||||
style={{ backgroundColor: "var(--color-surface)", color: "var(--color-text)" }}
|
||||
>
|
||||
Z→A
|
||||
</option>
|
||||
</select>
|
||||
{showSortHelp && (
|
||||
<PopUp onClose={() => setShowSortHelp(false)}>
|
||||
<h2 className="text-lg font-bold mb-4">Rendezési lehetőségek magyarázata</h2>
|
||||
<ul className="space-y-2 text-[color:var(--color-night)]">
|
||||
<li>
|
||||
<span className="font-bold">📅↑</span> – Dátum szerint növekvő sorrendben (legrégebbi
|
||||
elöl)
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">📅↓</span> – Dátum szerint csökkenő sorrendben (legújabb elöl)
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">A→Z</span> – Név szerint növekvő sorrendben (A-tól Z-ig)
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">Z→A</span> – Név szerint csökkenő sorrendben (Z-től A-ig)
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
className="mt-6 px-4 py-2 rounded-lg bg-[color:var(--color-success)] text-[color:var(--color-text-inverse)] font-semibold hover:bg-[color:var(--color-success)]/80 transition"
|
||||
onClick={() => setShowSortHelp(false)}
|
||||
>
|
||||
Bezárás
|
||||
</button>
|
||||
</PopUp>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Decks Grid */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-8 mt-8">
|
||||
{/* Create New Deck (Mockup) */}
|
||||
<div className="flex flex-col items-center justify-center h-48 bg-[color:var(--color-card)] border-2 border-dashed border-[color:var(--color-success)] rounded-2xl cursor-pointer hover:bg-[color:var(--color-success)]/20 transition-all duration-200 shadow-lg">
|
||||
<FaPlus style={{ color: "var(--color-success)" }} className="text-5xl mb-2" />
|
||||
<span className="text-[color:var(--color-text)] font-semibold">Új pakli létrehozása</span>
|
||||
</div>
|
||||
{/* Existing Decks (Mockup) */}
|
||||
{filteredDecks.map((deck) => {
|
||||
const deckType = deckTypes.find((t) => t.label === deck.type)
|
||||
const borderColor = deckType ? deckType.color : "var(--color-success)"
|
||||
return (
|
||||
<div
|
||||
key={deck.id}
|
||||
className="flex flex-col justify-between h-48 bg-[color:var(--color-card)] rounded-2xl p-6 shadow-lg border-t-4 hover:scale-105 transition-transform duration-200"
|
||||
style={{ borderTopColor: borderColor }}
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
className="inline-block px-3 py-1 rounded-full text-xs font-bold mb-2"
|
||||
style={{
|
||||
background: deckType?.color,
|
||||
color: "var(--color-text-inverse)",
|
||||
}}
|
||||
>
|
||||
{deck.type === "Luck"
|
||||
? "Szerencse"
|
||||
: deck.type === "Question"
|
||||
? "Kérdés"
|
||||
: deck.type === "Fun"
|
||||
? "Szórakozás"
|
||||
: deck.type}
|
||||
</span>
|
||||
<h2 className="text-xl font-bold text-[color:var(--color-text)] mb-1 truncate">
|
||||
{deck.name}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="text-[color:var(--color-text-muted)] text-sm mt-2">
|
||||
Létrehozva: {deck.created}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DeckManager
|
||||
@@ -0,0 +1,178 @@
|
||||
import React from "react"
|
||||
import SerpentRaceAnimation from "../../assets/SerpentRace_Animation/SerpentRace_Animation.jsx"
|
||||
import LogoCard from "../../assets/pictures/LogoCard.jsx"
|
||||
import logoImg from "../../assets/pictures/Logo.png"
|
||||
import ButtonGreen from "../Buttons/ButtonGreen.jsx"
|
||||
import { FaUsers, FaPaintBrush, FaHeadset } from "react-icons/fa"
|
||||
import { motion } from "framer-motion"
|
||||
|
||||
const LandingPage = ({ onNavigateToPlay, onNavigateToAuth }) => {
|
||||
return (
|
||||
<div className="w-full">
|
||||
{/* Hero Section */}
|
||||
<motion.section
|
||||
className="min-h-[80vh] flex flex-col items-center justify-center text-center px-4 py-20"
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
>
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Animált logo és cím */}
|
||||
<div className="mb-8">
|
||||
<SerpentRaceAnimation sizePercentage={70} />
|
||||
</div>
|
||||
|
||||
<motion.h1
|
||||
className="text-3xl md:text-5xl font-bold text-white mb-4 leading-tight"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 0.4 }}
|
||||
>
|
||||
A társasjáték, ami <span className="text-emerald-400">összeköt</span>
|
||||
</motion.h1>
|
||||
|
||||
<motion.p
|
||||
className="text-lg md:text-xl text-gray-300 mb-4 max-w-3xl mx-auto leading-relaxed"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 0.6 }}
|
||||
>
|
||||
A SerpentRace egy társasjáték, ahol új barátokra lelhetsz, közösséget építhetsz és tanulhatsz –
|
||||
mindezt szórakozva!
|
||||
</motion.p>
|
||||
<motion.div
|
||||
className="text-xl md:text-2xl font-bold text-emerald-400 mb-10"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 0.8 }}
|
||||
>
|
||||
WE ARE READY, ARE YOU?
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="flex flex-col sm:flex-row gap-4 justify-center items-center"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 1 }}
|
||||
>
|
||||
<ButtonGreen text="Játék" onClick={onNavigateToPlay} width="w-60" />
|
||||
<ButtonGreen text="Regisztráció" onClick={onNavigateToAuth} width="w-60" />
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.section>
|
||||
|
||||
{/* Features Section */}
|
||||
<motion.section
|
||||
className="py-20 px-4"
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
>
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<motion.h2
|
||||
className="text-2xl md:text-3xl font-bold text-white text-center mb-12"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.7, delay: 0.2 }}
|
||||
>
|
||||
Miért a SerpentRace a legjobb választás?
|
||||
</motion.h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{/* Feature 1 */}
|
||||
<motion.div
|
||||
className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 text-center"
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.7, delay: 0.3 }}
|
||||
>
|
||||
<div className="w-16 h-16 mx-auto mb-6 bg-emerald-500 rounded-full flex items-center justify-center">
|
||||
<FaUsers className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-white mb-2">Közösségi élmény</h3>
|
||||
<p className="text-gray-300 text-sm">
|
||||
Ismerkedj, nevess, tanulj! A SerpentRace összehozza a társaságot, legyen szó baráti
|
||||
összejövetelről vagy csapatépítésről.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Feature 2 */}
|
||||
<motion.div
|
||||
className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 text-center"
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.7, delay: 0.5 }}
|
||||
>
|
||||
<div className="w-16 h-16 mx-auto mb-6 bg-emerald-500 rounded-full flex items-center justify-center">
|
||||
<FaPaintBrush className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-white mb-2">Személyre szabható</h3>
|
||||
<p className="text-gray-300 text-sm">
|
||||
Kérdéskártyák, szabályok, design – minden a te igényeidhez igazítható, akár céges brandinggel
|
||||
is!
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Feature 3 */}
|
||||
<motion.div
|
||||
className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 text-center"
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.7, delay: 0.7 }}
|
||||
>
|
||||
<div className="w-16 h-16 mx-auto mb-6 bg-emerald-500 rounded-full flex items-center justify-center">
|
||||
<FaHeadset className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-white mb-2">Folyamatos támogatás</h3>
|
||||
<p className="text-gray-300 text-sm">
|
||||
Gyors, segítőkész ügyfélszolgálat – ha bármilyen kérdésed vagy problémád van, mindig
|
||||
számíthatsz ránk!
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.section>
|
||||
|
||||
{/* Call to Action Section */}
|
||||
<motion.section
|
||||
className="py-20 px-4"
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
>
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<motion.div
|
||||
className="bg-gradient-to-r from-emerald-500/20 to-green-500/20 backdrop-blur-lg rounded-3xl p-12"
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.7, delay: 0.3 }}
|
||||
>
|
||||
<h2 className="text-2xl md:text-3xl font-bold text-white mb-4">
|
||||
Próbáld ki te is a SerpentRace-t!
|
||||
</h2>
|
||||
|
||||
<p className="text-lg text-gray-300 mb-6">
|
||||
Legyél részese egy új közösségi élménynek, vagy rendeld meg saját, személyre szabott
|
||||
társasjátékodat – mi mindenben segítünk!
|
||||
</p>
|
||||
|
||||
<ButtonGreen
|
||||
text="Kapcsolatfelvétel"
|
||||
onClick={onNavigateToAuth}
|
||||
className="px-12 py-4 text-xl font-bold"
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LandingPage
|
||||
@@ -0,0 +1,77 @@
|
||||
import React, { useState } from "react"
|
||||
import LogoCard from "../../assets/pictures/LogoCard.jsx"
|
||||
import logoImg from "../../assets/pictures/Logo.png" // <-- EZT ADD HOZZÁ
|
||||
import ButtonDark from "../Buttons/ButtonDark.jsx"
|
||||
import InputBoxDark from "../Inputs/InputBoxDark.jsx"
|
||||
|
||||
const PlayMenu = ({ onJoinGame, onCreateGame, user }) => {
|
||||
const [joinCode, setJoinCode] = useState("")
|
||||
const [error, setError] = useState("")
|
||||
|
||||
const handleJoin = () => {
|
||||
if (!joinCode.trim()) {
|
||||
setError("Add meg a játék kódját!")
|
||||
return
|
||||
}
|
||||
setError("")
|
||||
onJoinGame(joinCode)
|
||||
}
|
||||
|
||||
const handleCreate = () => {
|
||||
onCreateGame()
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
className="w-[95%] max-w-6xl mx-auto my-16 flex flex-col md:flex-row items-center justify-center rounded-3xl shadow-2xl min-h-[60vh] overflow-hidden"
|
||||
style={{
|
||||
background: "linear-gradient(90deg, var(--color-surface) 30%, var(--color-mint) 100%)",
|
||||
}}
|
||||
>
|
||||
{/* Bal oldali animáció/kép */}
|
||||
<div className="flex-1 flex items-center justify-center w-full h-full py-10 md:py-0 md:pl-10">
|
||||
<LogoCard
|
||||
imageSrc={logoImg}
|
||||
containerHeight="450px"
|
||||
containerWidth="450px"
|
||||
imageHeight="450px"
|
||||
imageWidth="450px"
|
||||
rotateAmplitude={7}
|
||||
scaleOnHover={1.03}
|
||||
showMobileWarning={false}
|
||||
showTooltip={false}
|
||||
displayOverlayContent={false}
|
||||
/>
|
||||
</div>
|
||||
{/* Jobb oldali panel */}
|
||||
<div className="flex-1 w-full flex flex-col items-center justify-center px-4 md:px-12 py-10">
|
||||
<div className="w-full max-w-md rounded-2xl p-8 flex flex-col gap-8">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold mb-2 text-text">Csatlakozás játékhoz</h2>
|
||||
<div className={`${error ? "border border-error rounded-lg" : ""}`}>
|
||||
<InputBoxDark
|
||||
type="text"
|
||||
placeholder="Játék kódja"
|
||||
value={joinCode}
|
||||
onChange={(e) => setJoinCode(e.target.value)}
|
||||
width="w-full"
|
||||
/>
|
||||
</div>
|
||||
{error && <div className="text-xs mt-1 text-error">{error}</div>}
|
||||
<div className="mt-4">
|
||||
<ButtonDark text="Csatlakozás" type="button" onClick={handleJoin} width="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
{user && (
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold mb-2 text-text">Új játék létrehozása</h2>
|
||||
<ButtonDark text="Játék létrehozása" type="button" onClick={handleCreate} width="w-full" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default PlayMenu
|
||||
@@ -0,0 +1,89 @@
|
||||
import React, { useState } from "react"
|
||||
import Logo from "../../assets/pictures/Logo"
|
||||
import About from "../../pages/About/About"
|
||||
|
||||
const navLinkClass = "px-3 py-2 rounded-lg text-white transition-all duration-200 hover:bg-white/10"
|
||||
|
||||
const Navbar = () => {
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
|
||||
return (
|
||||
|
||||
<nav className="bg-gradient-to-r from-green-700 to-emerald-500 shadow-lg">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between h-16 items-center">
|
||||
{/* Logo */}
|
||||
<div className="flex-shrink-0 flex items-center gap-2">
|
||||
<div className="flex items-center mt-1 h-9">
|
||||
<Logo size={36} />
|
||||
</div>
|
||||
<span className="flex items-center h-9 text-white font-bold text-2xl tracking-tight">
|
||||
SerpentRace
|
||||
</span>
|
||||
</div>
|
||||
{/* Desktop Menu */}
|
||||
<div className="hidden md:flex space-x-8">
|
||||
<a href="#" className={navLinkClass}>
|
||||
Home
|
||||
</a>
|
||||
<a href="#" className={navLinkClass}>
|
||||
Leaderboard
|
||||
</a>
|
||||
<a href="/about" className={navLinkClass}>
|
||||
About
|
||||
</a>
|
||||
<a href="#" className={navLinkClass}>
|
||||
Contact
|
||||
</a>
|
||||
</div>
|
||||
{/* Mobile Hamburger */}
|
||||
<div className="md:hidden flex items-center">
|
||||
<button
|
||||
onClick={() => setMenuOpen(!menuOpen)}
|
||||
className="text-white focus:outline-none"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
<svg
|
||||
className="h-7 w-7"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
{menuOpen ? (
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
) : (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 8h16M4 16h16" />
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Mobile Menu */}
|
||||
{menuOpen && (
|
||||
<div className="md:hidden bg-emerald-600 px-2 pt-2 pb-3 space-y-1">
|
||||
<a href="#" className={navLinkClass}>
|
||||
Home
|
||||
</a>
|
||||
<a href="#" className={navLinkClass}>
|
||||
Leaderboard
|
||||
</a>
|
||||
<a href="#" className={navLinkClass}>
|
||||
About
|
||||
</a>
|
||||
<a href="#" className={navLinkClass}>
|
||||
Contact
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
export default Navbar
|
||||
@@ -0,0 +1,59 @@
|
||||
import { useState } from "react";
|
||||
import Button from "../../components/Buttons/Button";
|
||||
import InputBox from "../../components/Inputs/InputBox";
|
||||
import PopUp from "../../components/PopUp/PopUp";
|
||||
|
||||
const jatekEredmenyek = [
|
||||
{ helyezes: 1, datum: "2025-03-24 14:22" },
|
||||
{ helyezes: 5, datum: "2025-03-24 14:20" },
|
||||
{ helyezes: 3, datum: "2025-03-24 14:18" },
|
||||
{ helyezes: 4, datum: "2025-03-24 14:15" },
|
||||
];
|
||||
|
||||
export default function Test() {
|
||||
const [showPopup, setShowPopup] = useState(false);
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
||||
return (
|
||||
<div className="w-full h-screen flex flex-col items-center justify-center space-y-6">
|
||||
<InputBox
|
||||
placeholder="E-mail cím"
|
||||
type="text"
|
||||
width="w-1/2"
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
text="Játék Előzmények"
|
||||
type="button"
|
||||
width="w-1/2"
|
||||
onClick={() => setShowPopup(true)}
|
||||
/>
|
||||
{showPopup && (
|
||||
<PopUp onClose={() => setShowPopup(false)}>
|
||||
<div className="p-6 w-full max-w-md">
|
||||
<h1 className="text-2xl font-bold text-center mb-4">Játék Előzmények</h1>
|
||||
<div className="space-y-3">
|
||||
{jatekEredmenyek.map((eredmeny, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`flex justify-between items-center rounded-lg p-4 shadow-md ${
|
||||
eredmeny.helyezes <= 3 ? "border-l-4 border-green-500" : "border-l-4 border-blue-500"
|
||||
} bg-gray-50`}
|
||||
>
|
||||
<p className="text-gray-800 font-semibold">
|
||||
Felhasználónév {eredmeny.helyezes}. helyezés
|
||||
</p>
|
||||
<span className="text-sm text-gray-500">{eredmeny.datum}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-center mt-6">
|
||||
<Button text="Bezár" type="button" width="w-24" onClick={() => setShowPopup(false)} />
|
||||
</div>
|
||||
</div>
|
||||
</PopUp>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// src/components/PopUp/PopUp.jsx
|
||||
// sima komponens, ami megjeleníti a popupot
|
||||
|
||||
import React from "react"
|
||||
|
||||
export default function PopUp({ children, onClose }) {
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-xl shadow-lg p-8 min-w-[300px] relative">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-2 right-2 text-gray-500 hover:text-black text-xl font-bold leading-none"
|
||||
aria-label="Close"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// src/components/PopUp/RatingSet.jsx
|
||||
|
||||
export default function RatingSet() {
|
||||
// Ezeket lehet később props-ból vagy API-ból is betölteni
|
||||
const stats = [
|
||||
{ label: "Win Rate", value: "68%" },
|
||||
{ label: "Success Rate", value: "85%" },
|
||||
{ label: "My cards rate", value: "72%" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="p-6 w-full max-w-md">
|
||||
<h1 className="text-2xl font-bold text-center mb-4">Statisztikák</h1>
|
||||
<div className="space-y-6">
|
||||
{stats.map((stat, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex justify-between items-center rounded-lg p-4 shadow-md bg-gray-50 border-l-4 border-indigo-500"
|
||||
>
|
||||
<p className="text-gray-800 font-semibold">{stat.label}</p>
|
||||
<span className="text-lg font-bold text-indigo-700">{stat.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { useEffect } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
const ScrollToTop = () => {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}, [pathname]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default ScrollToTop;
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from "react"
|
||||
import { FaSearch } from "react-icons/fa"
|
||||
|
||||
const SearchBox = ({ value, onChange, width = 220, placeholder = "Keresés...", className = "" }) => {
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center border-2 border-[color:var(--color-success)] rounded-lg bg-[color:var(--color-surface)]/40 px-2 ${className}`}
|
||||
style={{ width }}
|
||||
>
|
||||
<FaSearch className="text-[color:var(--color-success)] mr-2" />
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
className="flex-1 py-1 bg-transparent text-[color:var(--color-text)] border-none outline-none"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SearchBox
|
||||
@@ -0,0 +1,161 @@
|
||||
import React, { useState } from "react"
|
||||
import {
|
||||
FaCommentDots,
|
||||
FaUserFriends,
|
||||
FaBriefcase,
|
||||
FaFacebookF,
|
||||
FaTwitter,
|
||||
FaDribbble,
|
||||
FaSun,
|
||||
FaMoon,
|
||||
FaMedal
|
||||
} from "react-icons/fa"
|
||||
|
||||
const ProfileCard = () => {
|
||||
const [darkMode, setDarkMode] = useState(false)
|
||||
const activityLevel = 87
|
||||
const isPremium = true
|
||||
|
||||
let activityColor = ""
|
||||
let activityEmoji = ""
|
||||
let blocksToColor = 1
|
||||
let celebrationEmoji = ""
|
||||
|
||||
if (activityLevel <= 24) {
|
||||
activityColor = "red-600"
|
||||
activityEmoji = "😞"
|
||||
blocksToColor = 1
|
||||
} else if (activityLevel <= 49) {
|
||||
activityColor = "orange-500"
|
||||
activityEmoji = "😐"
|
||||
blocksToColor = 2
|
||||
} else if (activityLevel <= 74) {
|
||||
activityColor = "yellow-400"
|
||||
activityEmoji = "🙂"
|
||||
blocksToColor = 3
|
||||
} else {
|
||||
activityColor = "emerald-500"
|
||||
activityEmoji = "😄"
|
||||
blocksToColor = 4
|
||||
celebrationEmoji = "🎉"
|
||||
}
|
||||
|
||||
const colorMap = {
|
||||
"red-600": "#dc2626",
|
||||
"orange-500": "#f97316",
|
||||
"yellow-400": "#facc15",
|
||||
"emerald-500": "#10b981"
|
||||
}
|
||||
|
||||
const getBlockStyle = (index) => ({
|
||||
backgroundColor: index < blocksToColor ? colorMap[activityColor] : (darkMode ? "#4b5563" : "#d1d5db")
|
||||
})
|
||||
|
||||
const stats = [
|
||||
{ label: "Játékok", value: 1256, icon: <FaCommentDots /> },
|
||||
{ label: "Barátok", value: 8562, icon: <FaUserFriends /> },
|
||||
{ label: "Győzelmek", value: 189, icon: <FaBriefcase /> },
|
||||
{ label: "Badge-ek", value: 6, icon: <FaMedal /> }
|
||||
]
|
||||
|
||||
const badges = ["🏆", "🔥", "🎯", "🧠", "💎", "🚀"]
|
||||
|
||||
return (
|
||||
<div className={`${darkMode ? "bg-gray-900" : "bg-gray-100"} min-h-screen flex flex-col justify-center items-center px-4 py-12 transition-colors duration-500`}>
|
||||
<div className={`relative max-w-sm w-full rounded-xl shadow-lg overflow-hidden border
|
||||
${darkMode ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
|
||||
|
||||
<button
|
||||
onClick={() => setDarkMode(!darkMode)}
|
||||
className={`absolute top-4 right-4 p-2 rounded-full focus:outline-none
|
||||
${darkMode ? "bg-yellow-400 text-gray-900 hover:bg-yellow-300" : "bg-gray-800 text-yellow-400 hover:bg-gray-700"}`}
|
||||
aria-label="Toggle dark mode"
|
||||
>
|
||||
{darkMode ? <FaSun size={24} /> : <FaMoon size={24} />}
|
||||
</button>
|
||||
|
||||
<div className="p-6 text-center space-y-6">
|
||||
<img
|
||||
src="https://i.pravatar.cc/150?img=12"
|
||||
alt="Avatar"
|
||||
className={`w-24 h-24 mx-auto rounded-full border-4 mb-4 ${darkMode ? "border-blue-700" : "border-blue-200"}`}
|
||||
/>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h2 className={`text-xl font-semibold ${darkMode ? "text-gray-100" : "text-gray-800"}`}>
|
||||
BÉKAAAAA
|
||||
</h2>
|
||||
<div>
|
||||
<div className={`inline-block px-3 py-1 rounded-full text-xs font-semibold
|
||||
${darkMode
|
||||
? (isPremium ? "bg-green-600 text-white" : "bg-gray-600 text-gray-200")
|
||||
: (isPremium ? "bg-green-200 text-green-800" : "bg-blue-200 text-blue-800")}`}>
|
||||
{isPremium ? "Premium Account" : "Free Account"}
|
||||
</div>
|
||||
</div>
|
||||
<p className={`text-sm ${darkMode ? "text-gray-400" : "text-gray-500"}`}>
|
||||
Active | Male | 23.05.1992
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center items-center space-x-2">
|
||||
<p className={`text-sm font-medium flex items-center space-x-1`} style={{ color: colorMap[activityColor] }}>
|
||||
<span>Activity Level: {activityLevel}%</span>
|
||||
<span className="text-xl">{activityEmoji}</span>
|
||||
{celebrationEmoji && <span className="text-xl ml-1">{celebrationEmoji}</span>}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center space-x-1 mb-4">
|
||||
{[0, 1, 2, 3].map(i => (
|
||||
<div
|
||||
key={i}
|
||||
className="w-8 h-1 rounded-full"
|
||||
style={getBlockStyle(i)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Badge szekció */}
|
||||
<div className={`rounded-lg p-4 ${darkMode ? "bg-blue-900" : "bg-blue-200"}`}>
|
||||
<h3 className={`text-sm font-bold mb-2 ${darkMode ? "text-white" : "text-blue-800"}`}>Badge-ek</h3>
|
||||
<div className="flex justify-center flex-wrap gap-2">
|
||||
{badges.map((badge, i) => (
|
||||
<span key={i} className="text-xl">
|
||||
{badge}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Statisztikák */}
|
||||
<div className={`flex flex-wrap justify-around items-center rounded-lg py-6 mt-4 text-white ${darkMode ? "bg-blue-700" : "bg-blue-500"}`}>
|
||||
{stats.map((s, i) => (
|
||||
<div key={i} className="text-center px-2 w-1/2 sm:w-1/4 mb-4">
|
||||
<div className="mb-1 flex justify-center">{s.icon}</div>
|
||||
<p className="text-sm font-semibold">{s.value}</p>
|
||||
<p className="text-xs">{s.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className={`text-xs mb-4 ${darkMode ? "text-gray-400" : "text-gray-500"}`}>
|
||||
Gyere és játsz velünk!
|
||||
</p>
|
||||
|
||||
<div className={`flex justify-center gap-6 text-2xl ${darkMode ? "text-blue-400" : "text-blue-600"}`}>
|
||||
<FaFacebookF className="cursor-pointer hover:text-blue-600" />
|
||||
<FaTwitter className="cursor-pointer hover:text-sky-400 hover:text-sky-500" />
|
||||
<FaDribbble className="cursor-pointer hover:text-pink-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProfileCard
|
||||
|
||||
|
||||
import UserProfile from "../../components/Userdetails/Userdetails.jsx"
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
/* Fő színek */
|
||||
--color-night: #0d0d0f;
|
||||
--color-battleship-gray: #8d8e83;
|
||||
--color-gunmetal: #222d2f;
|
||||
--color-eerie-black: #181d23;
|
||||
|
||||
--color-mint: #15803d;
|
||||
--color-mint-dark: #136636;
|
||||
--color-mint-darker: #11522b;
|
||||
|
||||
/* Gombok */
|
||||
--color-button-primary: #16a34a;
|
||||
--color-button-primary-hover: #15803d;
|
||||
--color-button-secondary: #181d23;
|
||||
--color-button-secondary-hover: #0d0d0f;
|
||||
--color-button-green: #178a5b;
|
||||
--color-button-green-hover: #14784d;
|
||||
|
||||
/* Funkcionális színek */
|
||||
--color-primary: #15803d;
|
||||
--color-secondary: #8d8e83;
|
||||
--color-accent: #0d0d0f;
|
||||
|
||||
/* Deck típus színek */
|
||||
--color-luck: #5fa985; /* zöld, mint a success */
|
||||
--color-question: #4f7be6; /* új kék, illik az oldalhoz */
|
||||
--color-fun: #e15b64; /* piros, mint az error */
|
||||
|
||||
/* Háttérszínek */
|
||||
--color-background: #181d23;
|
||||
--color-background-selected: #232a31;
|
||||
--color-surface: #222d2f;
|
||||
--color-surface-selected: #314045;
|
||||
--color-card: #2c383b;
|
||||
|
||||
/* Szövegszínek */
|
||||
--color-text: #f0f0ff;
|
||||
--color-text-muted: #c0c0c0;
|
||||
--color-text-inverse: #0d0d0f;
|
||||
|
||||
/* Hatás/állapot színek */
|
||||
--color-success: #5fa985;
|
||||
--color-warning: #e6c04f;
|
||||
--color-error: #e15b64;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.jsx'
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
@@ -0,0 +1,163 @@
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import Navbar from "../../components/Navbar/Navbar"
|
||||
import Footer from "../../components/Footer/Footer"
|
||||
import Background from "../../assets/backgrounds/Background.jsx"
|
||||
import Walke from "../../assets/pictures/walke.JPG"
|
||||
import Busi from "../../assets/pictures/busi.JPG"
|
||||
import Gege from "../../assets/pictures/gege.JPG"
|
||||
import Zsola from "../../assets/pictures/zsola.JPG"
|
||||
import Donat from "../../assets/pictures/donat.JPG"
|
||||
import Turo from "../../assets/pictures/turo.JPG"
|
||||
import Piskor from "../../assets/pictures/piskor.JPG"
|
||||
|
||||
const About = () => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
const sectionRef = useRef(null)
|
||||
|
||||
const teamMembers = [
|
||||
{
|
||||
name: "Magda Donát",
|
||||
role: "Backend fejlesztő",
|
||||
photo: Donat,
|
||||
},
|
||||
{
|
||||
name: "Máté Gergely",
|
||||
role: "UI/UX designer",
|
||||
photo: Gege,
|
||||
},
|
||||
{
|
||||
name: "Walke Gábor",
|
||||
role: "UI/UX designer, Frontend fejlesztő",
|
||||
photo: Walke,
|
||||
},
|
||||
{
|
||||
name: "Piskor Barnabás",
|
||||
role: "Frontend fejlesztő",
|
||||
photo: Piskor,
|
||||
},
|
||||
{
|
||||
name: "Buús Levente",
|
||||
role: "UI/UX designer",
|
||||
photo: Busi,
|
||||
},
|
||||
{
|
||||
name: "Pintér Zsolt",
|
||||
role: "UI/UX designer",
|
||||
photo: Zsola,
|
||||
},
|
||||
{
|
||||
name: "Thuróczy Attila",
|
||||
role: "UI/UX designer",
|
||||
photo: Turo,
|
||||
},
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) setVisible(true)
|
||||
},
|
||||
{ threshold: 0.3 }
|
||||
)
|
||||
if (sectionRef.current) observer.observe(sectionRef.current)
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen overflow-y-auto relative">
|
||||
|
||||
{/* Háttér – fix pozíció, a teljes képernyőre */}
|
||||
<div className="fixed top-0 left-0 w-full h-full z-[-10]">
|
||||
<Background />
|
||||
</div>
|
||||
|
||||
{/* Navbar fix */}
|
||||
<div className="fixed top-0 left-0 right-0 z-30">
|
||||
<Navbar />
|
||||
</div>
|
||||
|
||||
{/* Tartalom */}
|
||||
<main className="flex-grow text-white px-6 pt-16 mt-0 mb-20">
|
||||
|
||||
{/* Vissza gomb */}
|
||||
<div className="fixed top-4 left-4 z-50 group">
|
||||
<div className="absolute top-full mt-1 left-1/2 -translate-x-1/2 scale-0 group-hover:scale-100 transition transform bg-zinc-800 text-sm text-white px-3 py-1 rounded shadow-lg">
|
||||
Főoldalra
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section
|
||||
ref={sectionRef}
|
||||
className={`max-w-5xl mx-auto transition-all duration-1000 ease-out ${
|
||||
visible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-10"
|
||||
}`}
|
||||
>
|
||||
{/* Rólunk cím */}
|
||||
<h1 className="mt-24 text-5xl font-extrabold text-green-300 mb-10 text-center tracking-wide drop-shadow-lg">
|
||||
<span className="inline-block animate-pulse mr-2"></span> Rólunk
|
||||
</h1>
|
||||
|
||||
{/* Leírás */}
|
||||
<p className="text-lg leading-relaxed text-zinc-200 mb-10 text-center max-w-3xl mx-auto">
|
||||
Célunk, hogy egy innovatív, közösségorientált platformot építsünk, ahol a versenyzés, játék és technológia találkozik. Elhivatott csapatunk minden nap azon dolgozik, hogy élményt és értéket nyújtson a felhasználóinknak.
|
||||
</p>
|
||||
|
||||
{/* Küldetésünk */}
|
||||
<div className="mt-12">
|
||||
<h2 className="text-2xl font-bold text-green-300 mb-4">Küldetésünk</h2>
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<div className="bg-zinc-800 rounded-xl p-6 shadow-lg hover:scale-105 transition">
|
||||
<h3 className="text-xl font-semibold mb-2">Innováció</h3>
|
||||
<p className="text-zinc-300">Folyamatosan fejlesztjük rendszereinket a legmodernebb technológiákkal.</p>
|
||||
</div>
|
||||
<div className="bg-zinc-800 rounded-xl p-6 shadow-lg hover:scale-105 transition">
|
||||
<h3 className="text-xl font-semibold mb-2">Közösség</h3>
|
||||
<p className="text-zinc-300">Fontos számunkra, hogy egy összetartó, aktív közösséget építsünk ki.</p>
|
||||
</div>
|
||||
<div className="bg-zinc-800 rounded-xl p-6 shadow-lg hover:scale-105 transition">
|
||||
<h3 className="text-xl font-semibold mb-2">Minőség</h3>
|
||||
<p className="text-zinc-300">Minden részletre figyelünk a felhasználói élmény és biztonság érdekében.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Csapat */}
|
||||
<div className="mt-16">
|
||||
<h2 className="text-2xl font-bold text-green-300 mb-6">Csapatunk</h2>
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{teamMembers.map((member, i) => {
|
||||
const isLast = i === teamMembers.length - 1
|
||||
const itemsInLastRow = teamMembers.length % 3
|
||||
const shouldCenter = itemsInLastRow === 1 && isLast
|
||||
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={`flex flex-col items-center text-center bg-zinc-800 p-6 rounded-xl shadow-lg hover:shadow-green-400/20 hover:scale-105 transition ${
|
||||
shouldCenter ? "md:col-start-2" : ""
|
||||
}`}
|
||||
>
|
||||
<img
|
||||
src={member.photo}
|
||||
alt={member.name}
|
||||
className="w-24 h-24 rounded-full object-cover mb-4 border-4 border-green-400"
|
||||
/>
|
||||
<h4 className="text-lg font-bold">{member.name}</h4>
|
||||
<p className="text-zinc-400">{member.role}</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
{/* Footer (nem scrollozható alá) */}
|
||||
<footer className="mt-auto">
|
||||
<Footer />
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default About
|
||||
@@ -0,0 +1,46 @@
|
||||
// src/pages/Auth/AuthLogin.jsx
|
||||
// Kártya amelyiken a bejelentkezés és regisztráció van
|
||||
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import Animation from "../../assets/SerpentRace_Animation/SerpentRace_Animation";
|
||||
import LoginForm from "./LoginForm";
|
||||
import RegisterForm from "./RegisterForm";
|
||||
import Logo from "../../assets/pictures/Logo";
|
||||
|
||||
export default function AuthCard({ isRegistering, setIsRegistering }) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ height: "auto" }}
|
||||
animate={{ height: isRegistering ? "600px" : "385px" }}
|
||||
transition={{ duration: 0.5, ease: "easeInOut" }}
|
||||
className="absolute flex max-w-4xl w-full bg-white rounded-2xl shadow-lg overflow-hidden"
|
||||
>
|
||||
{/* Bal oldali kép és szöveg */}
|
||||
<div
|
||||
className={`transition-all duration-500 ${isRegistering ? 'w-0 p-0' : 'w-2/5 p-8'} flex flex-col justify-center items-center bg-gradient-to-r from-mint-darker to-mint text-white `}
|
||||
>
|
||||
<Logo size={100}/>
|
||||
<div className="h-6" />
|
||||
<Animation sizePercentage={30} />
|
||||
<p className="text-lg mt-0 text-center font-light whitespace-nowrap overflow-hidden">
|
||||
Lépj be és légy a legjobb!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Jobb oldali űrlap */}
|
||||
<div className="w-full p-10 relative">
|
||||
<AnimatePresence mode="wait">
|
||||
{isRegistering ? <RegisterForm /> : <LoginForm />}
|
||||
</AnimatePresence>
|
||||
<span
|
||||
className="text-secondary cursor-pointer hover:underline mt-4 block text-center"
|
||||
onClick={() => setIsRegistering(!isRegistering)}
|
||||
>
|
||||
{isRegistering
|
||||
? "Már van fiókod? Jelentkezz be itt!"
|
||||
: "Nincs még fiókod? Regisztrálj itt!"}
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// src/pages/Auth/AuthLogin.jsx
|
||||
// Login url címre érkezés (registering = false)
|
||||
|
||||
import { useState } from "react";
|
||||
import Background from "../../assets/backgrounds/Background";
|
||||
import AuthCard from "./AuthCard";
|
||||
|
||||
export default function AuthLogin() {
|
||||
const [isRegistering, setIsRegistering] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center justify-center min-h-screen bg-gray-100 p-0 font-poppins">
|
||||
<Background />
|
||||
|
||||
<AuthCard isRegistering={isRegistering} setIsRegistering={setIsRegistering} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// src/pages/Auth/AuthRegister.jsx
|
||||
// Register url címre érkezés (registering = true)
|
||||
|
||||
import { useState } from "react";
|
||||
import Background from "../../assets/backgrounds/Background";
|
||||
import AuthCard from "./AuthCard";
|
||||
|
||||
export default function AuthRegister() {
|
||||
const [isRegistering, setIsRegistering] = useState(true);
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center justify-center min-h-screen bg-gray-100 p-0 font-poppins">
|
||||
<Background />
|
||||
<AuthCard isRegistering={isRegistering} setIsRegistering={setIsRegistering} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// src/pages/Auth/EmailVerification.jsx
|
||||
// Rublikák a kód beírásához, email ellenőrzéshez
|
||||
|
||||
import { useState, useRef } from "react";
|
||||
import Background from "../../assets/backgrounds/Background";
|
||||
import { motion } from "framer-motion";
|
||||
import Button from "../../components/Buttons/Button";
|
||||
|
||||
|
||||
export default function EmailVerification() {
|
||||
const [code, setCode] = useState(Array(6).fill(""));
|
||||
const inputRefs = useRef([]);
|
||||
|
||||
const handleChange = (e, index) => {
|
||||
const { value } = e.target;
|
||||
if (/^\d*$/.test(value) && value.length <= 1) {
|
||||
const newCode = [...code];
|
||||
newCode[index] = value;
|
||||
setCode(newCode);
|
||||
if (value && index < 5) {
|
||||
inputRefs.current[index + 1].focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e, index) => {
|
||||
if (e.key === "Backspace" && !code[index] && index > 0) {
|
||||
inputRefs.current[index - 1].focus();
|
||||
} else if (e.key === "ArrowLeft" && index > 0) {
|
||||
inputRefs.current[index - 1].focus();
|
||||
} else if (e.key === "ArrowRight" && index < 5) {
|
||||
inputRefs.current[index + 1].focus();
|
||||
} else if (/^\d$/.test(e.key) && code[index]) {
|
||||
e.preventDefault();
|
||||
const newCode = [...code];
|
||||
newCode[index] = e.key;
|
||||
setCode(newCode);
|
||||
|
||||
if (index < 5) {
|
||||
setTimeout(() => {
|
||||
inputRefs.current[index + 1].focus();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
console.log("Kód:", code.join(""));
|
||||
// Backend API
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center justify-center min-h-screen bg-gray-100 p-0 font-poppins">
|
||||
<Background />
|
||||
<motion.div
|
||||
initial={{ height: "auto" }}
|
||||
animate={{ height: "300px" }}
|
||||
transition={{ duration: 0.5, ease: "easeInOut" }}
|
||||
className="absolute flex max-w-2xl w-full bg-white rounded-2xl shadow-lg overflow-hidden items-center justify-center"
|
||||
>
|
||||
<div className="w-full p-10 relative">
|
||||
<h2 className="text-4xl font-extrabold text-center mb-6 text-gray-800 tracking-wide">
|
||||
Email megerősítés
|
||||
</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-6 flex justify-center space-x-2">
|
||||
{code.map((digit, index) => (
|
||||
<input
|
||||
key={index}
|
||||
type="text"
|
||||
value={digit}
|
||||
onChange={(e) => handleChange(e, index)}
|
||||
onKeyDown={(e) => handleKeyDown(e, index)}
|
||||
ref={(el) => (inputRefs.current[index] = el)}
|
||||
className={`w-12 h-12 px-2 py-3 border rounded-lg focus:ring-4 focus:ring-indigo-400 text-gray-700 placeholder-gray-400 bg-gray-50 text-center text-2xl tracking-widest ${!digit ? 'placeholder-opacity-100' : 'placeholder-opacity-0'}`}
|
||||
// nem tudom, hogy hogyan jobb
|
||||
// placeholder="_"
|
||||
maxLength="1"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<Button text="Megerősít" type="submit" />
|
||||
</form>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// src/pages/Auth/ForgotPassword.jsx
|
||||
// Itt kéri az emailt amire a jelszó visszaállítást kérjük
|
||||
|
||||
import { useState } from "react";
|
||||
import Background from "../../assets/backgrounds/Background";
|
||||
import { motion } from "framer-motion";
|
||||
import Button from "../../components/Buttons/Button";
|
||||
import InputBox from "../../components/Inputs/InputBox";
|
||||
|
||||
export default function ForgotPassword() {
|
||||
const [email, setEmail] = useState("");
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
// Backend API
|
||||
console.log("Elfelejtett jelszó email:", email);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center justify-center min-h-screen bg-gray-100 p-0 font-poppins">
|
||||
<Background />
|
||||
<motion.div
|
||||
initial={{ height: "auto" }}
|
||||
animate={{ height: "300px" }}
|
||||
transition={{ duration: 0.5, ease: "easeInOut" }}
|
||||
className="absolute flex max-w-2xl w-full bg-white rounded-2xl shadow-lg overflow-hidden items-center justify-center"
|
||||
>
|
||||
<div className="w-full p-10 relative">
|
||||
<h2 className="text-4xl font-extrabold text-center mb-6 text-gray-800 tracking-wide">
|
||||
Elfelejtett jelszó
|
||||
</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<InputBox
|
||||
type="email"
|
||||
placeholder="Email cím"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<Button text="Jelszó visszaállítása" type="submit" />
|
||||
</form>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// src/pages/Auth/LoginForm.jsx
|
||||
// Bejelentkezési űrlap
|
||||
|
||||
import InputBox from "../../components/Inputs/InputBox";
|
||||
import Button from "../../components/Buttons/Button";
|
||||
import { motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function LoginForm() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
|
||||
function validateEmail(email) {
|
||||
return /\S+@\S+\.\S+/.test(email);
|
||||
}
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
if (!email || !password) {
|
||||
setError("Minden mező kitöltése kötelező.");
|
||||
return;
|
||||
}
|
||||
if (!validateEmail(email)) {
|
||||
setError("Hibás email formátum.");
|
||||
return;
|
||||
}
|
||||
// Backend API
|
||||
console.log("Bejelentkezés:", { email, password });
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key="login"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<h2 className="text-4xl font-extrabold text-center mb-6 text-gray-800 tracking-wide">Bejelentkezés</h2>
|
||||
{error && (
|
||||
<div className="mb-4 text-red-600 text-center font-semibold">{error}</div>
|
||||
)}
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<InputBox
|
||||
type="email"
|
||||
placeholder="Email cím"
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
/>
|
||||
<InputBox
|
||||
type="password"
|
||||
placeholder="Jelszó"
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
/>
|
||||
<Button text="Bejelentkezés" type="submit" />
|
||||
</form>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// src/pages/Auth/RegisterForm.jsx
|
||||
// Regisztrációs űrlap
|
||||
|
||||
import InputBox from "../../components/Inputs/InputBox";
|
||||
import Button from "../../components/Buttons/Button";
|
||||
import { motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function RegisterForm() {
|
||||
const [fullName, setFullName] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
|
||||
function validateEmail(email) {
|
||||
return /\S+@\S+\.\S+/.test(email);
|
||||
}
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
|
||||
if (!fullName || !username || !email || !password || !confirmPassword) {
|
||||
setError("Minden mező kitöltése kötelező.");
|
||||
return;
|
||||
}
|
||||
if (!validateEmail(email)) {
|
||||
setError("Hibás email formátum.");
|
||||
return;
|
||||
}
|
||||
if (password.length < 6) {
|
||||
setError("A jelszónak legalább 6 karakter hosszúnak kell lennie.");
|
||||
return;
|
||||
}
|
||||
if (password !== confirmPassword) {
|
||||
setError("A jelszavak nem egyeznek.");
|
||||
return;
|
||||
}
|
||||
// Backend API
|
||||
console.log("Regisztráció:", { fullName, username, email, password });
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key="register"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<h2 className="text-4xl font-extrabold text-center mb-6 text-gray-800 tracking-wide">Regisztráció</h2>
|
||||
{error && (
|
||||
<div className="mb-4 text-red-600 text-center font-semibold">{error}</div>
|
||||
)}
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<InputBox
|
||||
type="text"
|
||||
placeholder="Teljes név"
|
||||
value={fullName}
|
||||
onChange={e => setFullName(e.target.value)}
|
||||
/>
|
||||
<InputBox
|
||||
type="text"
|
||||
placeholder="Felhasználónév"
|
||||
value={username}
|
||||
onChange={e => setUsername(e.target.value)}
|
||||
/>
|
||||
<InputBox
|
||||
type="email"
|
||||
placeholder="Email cím"
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
/>
|
||||
<InputBox
|
||||
type="password"
|
||||
placeholder="Jelszó"
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
/>
|
||||
<InputBox
|
||||
type="password"
|
||||
placeholder="Jelszó megerősítése"
|
||||
value={confirmPassword}
|
||||
onChange={e => setConfirmPassword(e.target.value)}
|
||||
/>
|
||||
<Button text="Regisztráció" type="submit" />
|
||||
</form>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// src/pages/Auth/ResetPassword.jsx
|
||||
// Új jelszó megadása
|
||||
|
||||
import { useState } from "react";
|
||||
import Background from "../../assets/backgrounds/Background";
|
||||
import { motion } from "framer-motion";
|
||||
import Button from "../../components/Buttons/Button";
|
||||
import InputBox from "../../components/Inputs/InputBox";
|
||||
|
||||
export default function ResetPassword() {
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
if (password !== confirmPassword) {
|
||||
setError("A jelszavak nem egyeznek.");
|
||||
return;
|
||||
}
|
||||
setError("");
|
||||
// Backend API
|
||||
console.log("Új jelszó:", password);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center justify-center min-h-screen bg-gray-100 p-0 font-poppins">
|
||||
<Background />
|
||||
<motion.div
|
||||
initial={{ height: "auto" }}
|
||||
animate={{ height: "350px" }}
|
||||
transition={{ duration: 0.5, ease: "easeInOut" }}
|
||||
className="absolute flex max-w-2xl w-full bg-white rounded-2xl shadow-lg overflow-hidden items-center justify-center"
|
||||
>
|
||||
<div className="w-full p-10 relative">
|
||||
<h2 className="text-4xl font-extrabold text-center mb-6 text-gray-800 tracking-wide">
|
||||
Új jelszó megadása
|
||||
</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<InputBox
|
||||
type="password"
|
||||
placeholder="Új jelszó"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<InputBox
|
||||
type="password"
|
||||
placeholder="Új jelszó megerősítése"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
/>
|
||||
{error && (
|
||||
<div className="text-red-500 text-sm mb-2">{error}</div>
|
||||
)}
|
||||
<Button text="Jelszó beállítása" type="submit" />
|
||||
</form>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
import React from "react"
|
||||
import Navbar from "../../components/Navbar/Navbar.jsx"
|
||||
import Footer from "../../components/Footer/Footer.jsx"
|
||||
import Background from "../../assets/backgrounds/Background"
|
||||
import {
|
||||
FaBuilding,
|
||||
FaEnvelope,
|
||||
FaHandshake,
|
||||
FaPalette,
|
||||
FaTags,
|
||||
FaUserCheck,
|
||||
FaDollarSign,
|
||||
FaChartLine,
|
||||
FaVideo,
|
||||
FaHandsHelping,
|
||||
FaTrophy,
|
||||
FaChartBar,
|
||||
FaUsers,
|
||||
FaHeadset,
|
||||
} from "react-icons/fa"
|
||||
|
||||
const Card = ({ icon, label, description, targetId, className }) => {
|
||||
const handleClick = () => {
|
||||
const section = document.getElementById(targetId)
|
||||
if (section) {
|
||||
section.scrollIntoView({ behavior: "smooth" })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={handleClick}
|
||||
className={`cursor-pointer hover:scale-105 transition-all duration-300 border border-gray-400 shadow-lg rounded-2xl p-8 w-72 text-center animate-fadeInUp ${className}`}
|
||||
>
|
||||
<div className="text-5xl mb-4 flex justify-center">{icon}</div>
|
||||
<h3 className="text-2xl font-bold mb-2">{label}</h3>
|
||||
<p className="text-white/90 text-sm">{description}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const SectionContainer = ({ id, title, children }) => {
|
||||
return (
|
||||
<section
|
||||
id={id}
|
||||
className="mt-20 mb-28 px-4 md:px-0 opacity-100 animate-fadeInUp"
|
||||
>
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-4xl font-bold border-b-4 inline-block border-emerald-400 pb-2 text-white">
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
{children}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
const CompanyHub = () => {
|
||||
return (
|
||||
<div className="relative min-h-screen text-white">
|
||||
{/* Background fixed behind everything */}
|
||||
<div className="fixed inset-0 -z-10">
|
||||
<Background />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex flex-col min-h-screen">
|
||||
<Navbar />
|
||||
|
||||
<main className="flex-grow relative px-4 py-8 md:px-12 md:py-16 overflow-y-auto scroll-smooth">
|
||||
<div className="flex justify-center gap-6 mt-8 flex-wrap">
|
||||
<Card
|
||||
icon={<FaBuilding />}
|
||||
label="Mit nyújtunk"
|
||||
description="Játékosított tanulási platform cégeknek."
|
||||
targetId="intro"
|
||||
className="bg-gradient-to-br from-pink-500 via-purple-600 to-purple-800"
|
||||
/>
|
||||
<Card
|
||||
icon={<FaEnvelope />}
|
||||
label="Kapcsolat"
|
||||
description="Lépj kapcsolatba velünk vagy kérj ajánlatot!"
|
||||
targetId="contact"
|
||||
className="bg-gradient-to-br from-blue-700 to-blue-500"
|
||||
/>
|
||||
<Card
|
||||
icon={<FaHandshake />}
|
||||
label="Csatlakozás"
|
||||
description="Legyél partnerünk, és fejlődj velünk!"
|
||||
targetId="join"
|
||||
className="bg-gradient-to-br from-green-700 to-green-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Mit nyújtunk */}
|
||||
<SectionContainer id="intro" title="Mit nyújtunk cégeknek">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-10">
|
||||
{/* Egyénre szabás */}
|
||||
<div className="bg-white/5 rounded-3xl border border-gray-700 shadow-lg p-10 flex flex-col gap-8">
|
||||
<h3 className="text-2xl font-extrabold flex items-center gap-4 text-emerald-400">
|
||||
<FaPalette className="text-4xl" />
|
||||
Egyénre szabás
|
||||
</h3>
|
||||
<ul className="list-disc ml-6 space-y-4 text-white/90 text-lg">
|
||||
<li className="flex items-center gap-4">
|
||||
<FaUserCheck className="text-green-400 text-2xl" />
|
||||
Testreszabható design és színek
|
||||
</li>
|
||||
<li className="flex items-center gap-4">
|
||||
<FaTrophy className="text-yellow-400 text-2xl" />
|
||||
Egyedi badge és jutalmazási rendszer
|
||||
</li>
|
||||
<li className="flex items-center gap-4">
|
||||
<FaChartBar className="text-blue-400 text-2xl" />
|
||||
Fejlődési útvonalak
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Árazás */}
|
||||
<div className="bg-white/5 rounded-3xl border border-gray-700 shadow-lg p-10 flex flex-col gap-8">
|
||||
<h3 className="text-2xl font-extrabold flex items-center gap-4 text-emerald-400">
|
||||
<FaDollarSign className="text-4xl" />
|
||||
Árazás
|
||||
</h3>
|
||||
<ul className="list-disc ml-6 space-y-4 text-white/90 text-lg">
|
||||
<li className="flex items-center gap-4">
|
||||
<FaUsers className="text-purple-400 text-2xl" />
|
||||
Kedvezményes csomagok KKV-knak
|
||||
</li>
|
||||
<li className="flex items-center gap-4">
|
||||
<FaChartLine className="text-indigo-400 text-2xl" />
|
||||
Testreszabott megoldások
|
||||
</li>
|
||||
<li className="flex items-center gap-4">
|
||||
<FaTags className="text-pink-400 text-2xl" />
|
||||
Nincs rejtett költség
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Demó videó */}
|
||||
<div className="bg-white/5 rounded-3xl border border-gray-700 shadow-lg p-10 flex flex-col gap-8">
|
||||
<h3 className="text-2xl font-extrabold flex items-center gap-4 text-emerald-400">
|
||||
<FaVideo className="text-4xl" />
|
||||
Csapatunk videó
|
||||
</h3>
|
||||
<video controls className="w-full rounded-xl shadow-xl">
|
||||
<source src="/demo.mp4" type="video/mp4" />
|
||||
A böngésződ nem támogatja a videó lejátszását.
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
</SectionContainer>
|
||||
|
||||
{/* Contact + Join Section */}
|
||||
<section className="grid md:grid-cols-2 gap-10 max-w-6xl mx-auto mt-20 mb-28 px-4 md:px-0">
|
||||
{/* Contact */}
|
||||
<div
|
||||
id="contact"
|
||||
className="bg-white/10 p-8 rounded-xl border border-gray-500 shadow-lg"
|
||||
>
|
||||
<h2 className="text-3xl font-bold mb-6 text-center border-b-4 border-emerald-400 pb-2 text-white">
|
||||
Kapcsolatfelvétel cégeknek
|
||||
</h2>
|
||||
<form className="grid gap-6 md:grid-cols-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Cég neve"
|
||||
className="bg-white/20 text-white placeholder-white px-5 py-4 rounded-lg focus:outline-none focus:ring-2 focus:ring-emerald-400 col-span-2"
|
||||
/>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
className="bg-white/20 text-white placeholder-white px-5 py-4 rounded-lg focus:outline-none focus:ring-2 focus:ring-emerald-400 col-span-2 md:col-span-1"
|
||||
/>
|
||||
<textarea
|
||||
placeholder="Üzenet"
|
||||
rows="6"
|
||||
className="bg-white/20 text-white placeholder-white px-5 py-4 rounded-lg focus:outline-none focus:ring-2 focus:ring-emerald-400 col-span-2"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-emerald-500 hover:bg-emerald-400 text-white px-8 py-4 rounded-lg font-bold col-span-2 md:col-span-1 transition"
|
||||
>
|
||||
Küldés
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Join */}
|
||||
<div
|
||||
id="join"
|
||||
className="bg-white/10 p-8 rounded-xl border border-gray-500 shadow-lg"
|
||||
>
|
||||
<h2 className="text-3xl font-bold mb-6 text-center border-b-4 border-emerald-400 pb-2 text-white">
|
||||
Csatlakozz partnerként
|
||||
</h2>
|
||||
<ul className="list-disc space-y-4 ml-8 text-base text-white/90">
|
||||
<li className="flex gap-3 items-center">
|
||||
<FaHandsHelping className="text-green-400 text-xl flex-shrink-0" />
|
||||
Gamification a vállalati kultúrában
|
||||
</li>
|
||||
<li className="flex gap-3 items-center">
|
||||
<FaChartBar className="text-blue-400 text-xl flex-shrink-0" />
|
||||
Teljesítménymérés játékosan
|
||||
</li>
|
||||
<li className="flex gap-3 items-center">
|
||||
<FaUserCheck className="text-yellow-400 text-xl flex-shrink-0" />
|
||||
Személyre szabott riportok
|
||||
</li>
|
||||
<li className="flex gap-3 items-center">
|
||||
<FaHeadset className="text-pink-400 text-xl flex-shrink-0" />
|
||||
Dedikált ügyfélszolgálat
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CompanyHub
|
||||
@@ -0,0 +1,16 @@
|
||||
// src/pages/Decks/DeckManagerPage.jsx
|
||||
// Deck Management Page (with Navbar, no Footer)
|
||||
|
||||
import DeckManager from "../../components/Landingpage/DeckManager.jsx"
|
||||
import Navbar from "../../components/Navbar/Navbar.jsx"
|
||||
|
||||
export default function DeckManagerPage() {
|
||||
return (
|
||||
<div className="w-full min-h-screen bg-background flex flex-col relative overflow-x-hidden">
|
||||
<Navbar />
|
||||
<main className="flex-1 flex flex-col items-center justify-start min-h-0">
|
||||
<DeckManager />
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
import React, { useState } from "react"
|
||||
import { getVerticalOffset } from "../../utils/randomUtils"
|
||||
import Dice from "../../utils/dice/Dice"
|
||||
|
||||
const GameScreen = () => {
|
||||
const boardRows = 5
|
||||
const boardCols = 20
|
||||
const totalCells = boardRows * boardCols
|
||||
const cellSize = 40
|
||||
const cellMargin = 2.5
|
||||
const rowSpacing = 70 // Extra spacing between rows
|
||||
const topOffset = rowSpacing * 0.5 // Increase topOffset for more spacing
|
||||
const bottomOffset = rowSpacing * 0.5 // Increase bottomOffset for more spacing
|
||||
const boardWidthPx = boardCols * (cellSize + cellMargin * 2)
|
||||
const boardHeightPx =
|
||||
boardRows * (cellSize + cellMargin * 2 + rowSpacing) + topOffset + bottomOffset - rowSpacing
|
||||
|
||||
// Generate a snake-like path with vertical spacing and vertical offsets
|
||||
const generateWindingPath = () => {
|
||||
const path = []
|
||||
let currentNum = 1
|
||||
|
||||
for (let row = 0; row < boardRows && currentNum <= totalCells; row++) {
|
||||
// Calculate the y position with extra row spacing
|
||||
const baseYPosition = topOffset + row * (cellSize + cellMargin * 2 + rowSpacing)
|
||||
|
||||
// If row number is even, go right; if odd, go left
|
||||
if (row % 2 === 0) {
|
||||
// Left to right
|
||||
for (let col = 0; col < boardCols && currentNum <= totalCells; col++) {
|
||||
path.push({
|
||||
number: currentNum++,
|
||||
x: col * (cellSize + cellMargin * 2),
|
||||
y: baseYPosition + getVerticalOffset(currentNum - 1),
|
||||
type: getFieldType(currentNum - 1),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Right to left
|
||||
for (let col = boardCols - 1; col >= 0 && currentNum <= totalCells; col--) {
|
||||
path.push({
|
||||
number: currentNum++,
|
||||
x: col * (cellSize + cellMargin * 2),
|
||||
y: baseYPosition + getVerticalOffset(currentNum - 1),
|
||||
type: getFieldType(currentNum - 1),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
const getFieldType = (count) => {
|
||||
if (count % 17 === 0) return "clover"
|
||||
if (count % 13 === 0) return "bad"
|
||||
if ((count + 5) % 13 === 0) return "good"
|
||||
return "regular"
|
||||
}
|
||||
|
||||
const [path, setPath] = useState(generateWindingPath())
|
||||
const [players, setPlayers] = useState([
|
||||
{ id: 1, name: "Béla", position: 34, score: 25, color: "bg-blue-600", emoji: "🐍" },
|
||||
{ id: 2, name: "Juci", position: 50, score: 30, color: "bg-green-600", emoji: "🐢" },
|
||||
{ id: 3, name: "Kati", position: 70, score: 15, color: "bg-purple-600", emoji: "🐇" },
|
||||
{ id: 3, name: "Fürtös", position: 68, score: 14, color: "bg-yellow-600", emoji: "😂" },
|
||||
])
|
||||
|
||||
// Sort players by position in descending order
|
||||
const sortedPlayers = [...players].sort((a, b) => b.position - a.position)
|
||||
|
||||
// Handle dice roll
|
||||
const handleDiceRoll = (value) => {
|
||||
console.log("Rolled:", value)
|
||||
// You can add logic here to move the current player based on the dice value
|
||||
}
|
||||
|
||||
console.log("Generated path length:", path.length)
|
||||
|
||||
const getFieldStyle = (type) => {
|
||||
switch (type) {
|
||||
case "clover":
|
||||
return "bg-teal-700 border-teal-500 shadow-teal-800"
|
||||
case "bad":
|
||||
return "bg-red-800 border-red-600 shadow-red-900"
|
||||
case "good":
|
||||
return "bg-blue-800 border-blue-600 shadow-blue-900"
|
||||
default:
|
||||
return "bg-gray-800 border-gray-600 shadow-gray-900"
|
||||
}
|
||||
}
|
||||
|
||||
const getPlayerPosition = (playerPosition) => {
|
||||
const field = path.find((p) => p.number === playerPosition)
|
||||
return field ? { top: `${field.y}px`, left: `${field.x}px` } : { top: 0, left: 0 }
|
||||
}
|
||||
|
||||
// Function to get medal style based on rank
|
||||
const getMedalStyle = (rank) => {
|
||||
switch (rank) {
|
||||
case 1:
|
||||
return "bg-yellow-400 text-yellow-900 border-yellow-500 shadow-yellow-600"
|
||||
case 2:
|
||||
return "bg-gray-400 text-gray-900 border-gray-500 shadow-gray-600"
|
||||
case 3:
|
||||
return "bg-orange-500 text-orange-900 border-orange-600 shadow-orange-700"
|
||||
default:
|
||||
return "bg-gray-700 text-gray-300 border-gray-600 shadow-gray-800"
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-gradient-to-br from-gray-900 via-gray-800 to-teal-900 min-h-screen flex items-center justify-center">
|
||||
<div className="w-full">
|
||||
<div className="flex flex-col md:flex-row gap-6 justify-center">
|
||||
{/* Game Board */}
|
||||
<div className="relative bg-gray-800 p-6 rounded-2xl shadow-xl border border-teal-700 flex flex-col items-center justify-center overflow-hidden">
|
||||
{/* Háttér */}
|
||||
<div className="absolute w-full h-full opacity-10 pointer-events-none overflow-hidden">
|
||||
{[...Array(35)].map((_, i) => (
|
||||
// Sajat pulse effect! => node_modules/tailwindcss/index.css:
|
||||
// --animate-pulse8: pulse 6s cubic-bezier(0.4, 0.2, 0.6, 1) infinite;
|
||||
|
||||
<div
|
||||
key={i}
|
||||
className="absolute rounded-full bg-teal-600 animate-pulse8"
|
||||
style={{
|
||||
width: Math.random() * 120 + 40 + "px",
|
||||
height: Math.random() * 120 + 40 + "px",
|
||||
top: Math.random() * 100 + "%",
|
||||
left: Math.random() * 100 + "%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
}}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
<div className="relative" style={{ height: `${boardHeightPx}px`, width: `${boardWidthPx}px` }}>
|
||||
{/* Mezők */}
|
||||
{path.map((field) => (
|
||||
<div
|
||||
key={field.number}
|
||||
className={`absolute w-10 h-10 border-2 flex items-center justify-center transition-all shadow-md hover:scale-110 ${getFieldStyle(
|
||||
field.type
|
||||
)}`}
|
||||
style={{
|
||||
top: `${field.y}px`,
|
||||
left: `${field.x}px`,
|
||||
width: `${cellSize}px`,
|
||||
height: `${cellSize}px`,
|
||||
margin: `${cellMargin}px`,
|
||||
}}
|
||||
>
|
||||
<span className="text-gray-300 text-sm font-bold">{field.number}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Player tokens */}
|
||||
{players.map((player) => (
|
||||
<div
|
||||
key={player.id}
|
||||
className={`absolute w-6 h-6 ${player.color} rounded-full border-2 border-white shadow-lg flex items-center justify-center text-white text-xs font-bold z-10 animate-bounce`}
|
||||
style={{
|
||||
...getPlayerPosition(player.position),
|
||||
transform: "translate(18px, 18px)",
|
||||
}}
|
||||
>
|
||||
{player.emoji}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Game information */}
|
||||
{/* <div className="bg-white rounded-xl p-2 shadow-lg border border-indigo-100 max-w-3xl mx-auto mt-4 z-10">
|
||||
<p className="text-gray-600 text-sm text-center">
|
||||
<span className="inline-flex items-center mx-2"><span className="w-3 h-3 bg-white border border-gray-300 rounded-full mr-1"></span> Sima</span>
|
||||
<span className="inline-flex items-center mx-2"><span className="w-3 h-3 bg-green-200 border border-green-500 rounded-full mr-1"></span> Lóhere</span>
|
||||
<span className="inline-flex items-center mx-2"><span className="w-3 h-3 bg-red-200 border border-red-500 rounded-full mr-1"></span> Rossz</span>
|
||||
<span className="inline-flex items-center mx-2"><span className="w-3 h-3 bg-blue-200 border border-blue-500 rounded-full mr-1"></span> Jó</span>
|
||||
</p>
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
{/* Right sidebar */}
|
||||
<div className="flex-1 max-w-md">
|
||||
<div className="bg-gray-800 rounded-xl p-4 shadow-lg mb-4 border border-teal-700">
|
||||
<h2 className="text-xl font-semibold mb-3 text-teal-300">Játékosok</h2>
|
||||
{sortedPlayers.map((player, index) => (
|
||||
<div
|
||||
key={player.id}
|
||||
className="flex items-center mb-3 p-2 bg-gray-900 rounded-lg hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
<div
|
||||
className={`w-8 h-8 ${player.color} rounded-full mr-3 flex items-center justify-center text-white text-sm font-bold shadow-md`}
|
||||
>
|
||||
{player.emoji}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-sm text-gray-300 flex items-center">
|
||||
{player.name}
|
||||
<span
|
||||
className={`ml-2 px-2 py-1 rounded-full border text-xs font-bold shadow-md ${getMedalStyle(
|
||||
index + 1
|
||||
)}`}
|
||||
>
|
||||
{index + 1 === 1
|
||||
? "🥇 1st"
|
||||
: index + 1 === 2
|
||||
? "🥈 2nd"
|
||||
: index + 1 === 3
|
||||
? "🥉 3rd"
|
||||
: `${index + 1}th`}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
Pozíció: {player.position} • Pontszám: {player.score}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Dice Container */}
|
||||
<div className="bg-gray-800 rounded-xl p-4 shadow-lg border border-teal-700 text-center">
|
||||
<h2 className="text-xl font-semibold mb-3 text-teal-300">Dobókocka</h2>
|
||||
<p className="text-gray-300 text-sm mb-4">Kattints a kockára dobáshoz!</p>
|
||||
<Dice onRoll={handleDiceRoll} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default GameScreen
|
||||
@@ -0,0 +1,35 @@
|
||||
// src/pages/Home/Home.jsx
|
||||
// Régi PlayMenu-s oldal, "Home" néven
|
||||
|
||||
import { useState } from "react"
|
||||
import Navbar from "../../components/Navbar/Navbar"
|
||||
import Footer from "../../components/Footer/Footer.jsx"
|
||||
import Background from "../../assets/backgrounds/Background.jsx"
|
||||
import PlayMenu from "../../components/Landingpage/PlayMenu.jsx"
|
||||
|
||||
export default function Home() {
|
||||
// Dummy callbackok és user példa
|
||||
const handleJoinGame = (code) => {
|
||||
alert(`Csatlakozás játékhoz: ${code}`)
|
||||
}
|
||||
const handleCreateGame = () => {
|
||||
alert("Új játék létrehozása")
|
||||
}
|
||||
const user = { name: "Teszt Elek" }
|
||||
|
||||
return (
|
||||
<div className="w-full min-h-screen flex flex-col relative overflow-x-hidden">
|
||||
<div className="fixed inset-0 -z-10 pointer-events-none">
|
||||
<Background />
|
||||
</div>
|
||||
<div className="fixed top-0 left-0 right-0 z-30">
|
||||
<Navbar />
|
||||
</div>
|
||||
<main className="flex-1 flex flex-col items-center justify-start py-15 min-h-0 mt-[64px]">
|
||||
<PlayMenu onJoinGame={handleJoinGame} onCreateGame={handleCreateGame} user={user} />
|
||||
{/* Ide jöhetnek további szekciók, ha szeretnél még tartalmat */}
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// src/pages/Home/Home.jsx
|
||||
// Régi PlayMenu-s oldal, "Home" néven
|
||||
|
||||
import { useState } from "react"
|
||||
import Navbar from "../../components/Navbar/Navbar.jsx"
|
||||
import Footer from "../../components/Footer/Footer.jsx"
|
||||
import Background from "../../assets/backgrounds/Background.jsx"
|
||||
import PlayMenu from "../../components/Landingpage/PlayMenu.jsx"
|
||||
|
||||
export default function Home() {
|
||||
// Dummy callbackok és user példa
|
||||
const handleJoinGame = (code) => {
|
||||
alert(`Csatlakozás játékhoz: ${code}`)
|
||||
}
|
||||
const handleCreateGame = () => {
|
||||
alert("Új játék létrehozása")
|
||||
}
|
||||
const user = { name: "Teszt Elek" }
|
||||
|
||||
return (
|
||||
<div className="w-full min-h-screen flex flex-col relative overflow-x-hidden">
|
||||
<div className="fixed inset-0 -z-10 pointer-events-none">
|
||||
<Background />
|
||||
</div>
|
||||
<div className="fixed top-0 left-0 right-0 z-30">
|
||||
<Navbar />
|
||||
</div>
|
||||
<main className="flex-1 flex flex-col items-center justify-start py-15 min-h-0 mt-[64px]">
|
||||
<PlayMenu onJoinGame={handleJoinGame} onCreateGame={handleCreateGame} user={user} />
|
||||
{/* Ide jöhetnek további szekciók, ha szeretnél még tartalmat */}
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// src/pages/Landing/Landingpage.jsx
|
||||
// Főoldal - Landing Page
|
||||
|
||||
import { useState } from "react"
|
||||
import Navbar from "../../components/Navbar/Navbar"
|
||||
import Footer from "../../components/Footer/Footer.jsx"
|
||||
import Background from "../../assets/backgrounds/Background.jsx"
|
||||
import LandingPage from "../../components/Landingpage/LandingPage.jsx"
|
||||
|
||||
export default function LandingPageMain() {
|
||||
// Navigációs callbackok
|
||||
const handleNavigateToPlay = () => {
|
||||
// Itt lehet navigálni a játék oldalra
|
||||
alert("Navigáció a játék oldalra")
|
||||
}
|
||||
|
||||
const handleNavigateToAuth = () => {
|
||||
// Itt lehet navigálni a bejelentkezés oldalra
|
||||
alert("Navigáció a bejelentkezés oldalra")
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full min-h-screen flex flex-col relative overflow-x-hidden">
|
||||
<div className="fixed inset-0 -z-10 pointer-events-none">
|
||||
<Background />
|
||||
</div>
|
||||
<div className="fixed top-0 left-0 right-0 z-30">
|
||||
<Navbar />
|
||||
</div>
|
||||
<main className="flex-1 flex flex-col items-center justify-start py-15 min-h-0 mt-[64px]">
|
||||
<LandingPage onNavigateToPlay={handleNavigateToPlay} onNavigateToAuth={handleNavigateToAuth} />
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { useState } from "react"
|
||||
import Button from "../../components/Buttons/Button"
|
||||
import InputBox from "../../components/Inputs/InputBox"
|
||||
import PopUp from "../../components/PopUp/PopUp"
|
||||
import Logo from "../../assets/pictures/Logo.jsx"
|
||||
import Navbar from "../../components/Navbar/Navbar"
|
||||
import Footer from "../../components/Footer/Footer.jsx"
|
||||
import UserProfile from "../../components/Userdetails/Userdetails.jsx"
|
||||
import CompanyHub from "../Companies/Companies.jsx"
|
||||
|
||||
import RatingSet from "../../components/PopUp/RatingSet" // <- statisztikai komponens
|
||||
|
||||
export default function Test() {
|
||||
const [showRegistrationPopup, setShowRegistrationPopup] = useState(false)
|
||||
const [showPreviewPopup, setShowPreviewPopup] = useState(false) // <- új state
|
||||
const [inputValue, setInputValue] = useState("")
|
||||
|
||||
return (
|
||||
<div className="w-full h-screen flex flex-col">
|
||||
<Navbar />
|
||||
<UserProfile />
|
||||
<CompanyHub />
|
||||
|
||||
<div className="flex-1 flex flex-col items-center justify-center space-y-6">
|
||||
<InputBox
|
||||
placeholder="E-mail cím"
|
||||
type="text"
|
||||
width="w-1/2"
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col sm:flex-row sm:space-x-4 space-y-4 sm:space-y-0 w-1/2">
|
||||
<Button
|
||||
text="Regisztráció"
|
||||
type="button"
|
||||
width="w-full"
|
||||
onClick={() => setShowRegistrationPopup(true)}
|
||||
/>
|
||||
<Button
|
||||
text="Előnézet"
|
||||
type="button"
|
||||
width="w-full"
|
||||
onClick={() => setShowPreviewPopup(true)} // <- másik state trigger
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Regisztrációs popup */}
|
||||
{showRegistrationPopup && (
|
||||
<PopUp onClose={() => setShowRegistrationPopup(false)}>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
{/* <Logo size={120} /> */}
|
||||
<h1 className="text-2xl font-bold text-center">Sikeres regisztráció!</h1>
|
||||
<p className="text-center text-gray-600">
|
||||
Kérjük, erősítsd meg az e-mail címedet!
|
||||
<br />
|
||||
Egy megerősítő linket küldtünk az általad megadott e-mail címre
|
||||
<span className="font-semibold text-black"> {inputValue}</span>.
|
||||
</p>
|
||||
<p className="text-center text-sm text-gray-500">
|
||||
Ha nem kaptad meg a levelet, ellenőrizd a spam mappádat is!
|
||||
</p>
|
||||
<Button text="Bezár" type="button" width="w-24" onClick={() => setShowRegistrationPopup(false)} />
|
||||
</div>
|
||||
</PopUp>
|
||||
)}
|
||||
|
||||
{/* Előnézeti popup (RatingSet) */}
|
||||
{showPreviewPopup && (
|
||||
<PopUp onClose={() => setShowPreviewPopup(false)}>
|
||||
<RatingSet />
|
||||
</PopUp>
|
||||
)}
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Scaling factor for vertical offsets
|
||||
const offsetScalingFactor = 0.4;
|
||||
|
||||
// Predefined vertical offsets represented as a 5x20 grid of zeros
|
||||
export const verticalOffsets = [
|
||||
0,
|
||||
-35, -10, -5, 10, 15, 12, 5, -5, -12, -15, -12, -5, 5, 12, 15, 12, 5, -5, 10, 35,
|
||||
-35, -10, -5, 10, 15, 12, 5, -5, -12, -15, -12, -5, 5, 12, 15, 12, 5, -5, 10, 35,
|
||||
-35, -10, -5, 10, 15, 12, 5, -5, -12, -15, -12, -5, 5, 12, 15, 12, 5, -5, 10, 35,
|
||||
-35, -10, -5, 10, 15, 12, 5, -5, -12, -15, -12, -5, 5, 12, 15, 12, 5, -5, 10, 35,
|
||||
-35, -10, -5, 10, 15, 12, 5, -5, -12, -15, -12, -5, 5, 12, 15, 12, 5, -5, 10, 35,
|
||||
];
|
||||
|
||||
/**
|
||||
* Gets a vertical offset value from the predefined array
|
||||
* @param {number} index - Index to retrieve from the offsets array
|
||||
* @returns {number} Offset value
|
||||
*/
|
||||
|
||||
export const getVerticalOffset = (index) => {
|
||||
const normalizedIndex = index % verticalOffsets.length;
|
||||
const offset = verticalOffsets[normalizedIndex >= 0 ? normalizedIndex : 0];
|
||||
return offset * offsetScalingFactor; // Apply scaling factor
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
|
||||
const Dice = ({ onRoll }) => {
|
||||
const [diceValue, setDiceValue] = useState(1);
|
||||
const [isRolling, setIsRolling] = useState(false);
|
||||
const animationRef = useRef(null);
|
||||
const rollTimeoutRef = useRef(null);
|
||||
|
||||
const diceFaces = [
|
||||
[<div key="center" className="dice-dot"></div>],
|
||||
[<div key="top-left" className="dice-dot"></div>, <div key="bottom-right" className="dice-dot"></div>],
|
||||
[<div key="top-left" className="dice-dot"></div>, <div key="center" className="dice-dot"></div>, <div key="bottom-right" className="dice-dot"></div>],
|
||||
[<div key="top-left" className="dice-dot"></div>, <div key="top-right" className="dice-dot"></div>,
|
||||
<div key="bottom-left" className="dice-dot"></div>, <div key="bottom-right" className="dice-dot"></div>],
|
||||
[<div key="top-left" className="dice-dot"></div>, <div key="top-right" className="dice-dot"></div>,
|
||||
<div key="center" className="dice-dot"></div>,
|
||||
<div key="bottom-left" className="dice-dot"></div>, <div key="bottom-right" className="dice-dot"></div>],
|
||||
[<div key="top-left" className="dice-dot"></div>, <div key="top-right" className="dice-dot"></div>,
|
||||
<div key="middle-left" className="dice-dot"></div>, <div key="middle-right" className="dice-dot"></div>,
|
||||
<div key="bottom-left" className="dice-dot"></div>, <div key="bottom-right" className="dice-dot"></div>]
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (animationRef.current) cancelAnimationFrame(animationRef.current);
|
||||
if (rollTimeoutRef.current) clearTimeout(rollTimeoutRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const rollDice = () => {
|
||||
if (isRolling) return;
|
||||
|
||||
setIsRolling(true);
|
||||
|
||||
let duration = 0;
|
||||
const rollInterval = 80; // ms between dice face changes
|
||||
const maxDuration = 1500; // total animation time
|
||||
|
||||
const rollAnimation = () => {
|
||||
const randomValue = Math.floor(Math.random() * 6) + 1;
|
||||
setDiceValue(randomValue);
|
||||
|
||||
duration += rollInterval;
|
||||
|
||||
if (duration < maxDuration) {
|
||||
// Speed effect: slow down towards the end
|
||||
const nextInterval = rollInterval * (1 + (duration / maxDuration) * 2);
|
||||
rollTimeoutRef.current = setTimeout(() => {
|
||||
animationRef.current = requestAnimationFrame(rollAnimation);
|
||||
}, nextInterval);
|
||||
} else {
|
||||
// Final roll
|
||||
const finalValue = Math.floor(Math.random() * 6) + 1;
|
||||
setDiceValue(finalValue);
|
||||
setIsRolling(false);
|
||||
|
||||
if (onRoll) onRoll(finalValue);
|
||||
}
|
||||
};
|
||||
|
||||
animationRef.current = requestAnimationFrame(rollAnimation);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`dice-container ${isRolling ? 'rolling' : ''}`}
|
||||
onClick={rollDice}
|
||||
>
|
||||
<div className="dice">
|
||||
{diceFaces[diceValue - 1]}
|
||||
</div>
|
||||
<style jsx>{`
|
||||
.dice-container {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
perspective: 600px;
|
||||
cursor: pointer;
|
||||
margin: 0 auto;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.dice-container:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.dice-container:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.dice {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
transform-style: preserve-3d;
|
||||
transition: transform 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #e63946;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.3);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.rolling .dice {
|
||||
animation: roll 0.5s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes roll {
|
||||
0% { transform: rotateX(0deg) rotateY(0deg); }
|
||||
25% { transform: rotateX(90deg) rotateY(45deg); }
|
||||
50% { transform: rotateX(180deg) rotateY(90deg); }
|
||||
75% { transform: rotateX(270deg) rotateY(135deg); }
|
||||
100% { transform: rotateX(360deg) rotateY(180deg); }
|
||||
}
|
||||
|
||||
.dice-dot {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
box-shadow: inset 0 0 3px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
/* Positioning dots */
|
||||
.dice-dot:nth-child(1) { top: 50%; left: 50%; transform: translate(-50%, -50%); } /* Center */
|
||||
.dice-dot:nth-child(2) { top: 20%; left: 20%; } /* Top-left */
|
||||
.dice-dot:nth-child(3) { bottom: 20%; right: 20%; } /* Bottom-right */
|
||||
.dice-dot:nth-child(4) { top: 20%; right: 20%; } /* Top-right */
|
||||
.dice-dot:nth-child(5) { bottom: 20%; left: 20%; } /* Bottom-left */
|
||||
.dice-dot:nth-child(6) { top: 50%; left: 20%; transform: translateY(-50%); } /* Middle-left */
|
||||
.dice-dot:nth-child(7) { top: 50%; right: 20%; transform: translateY(-50%); } /* Middle-right */
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dice;
|
||||
@@ -0,0 +1,20 @@
|
||||
// Predefined vertical offsets represented as a 5x20 grid of zeros
|
||||
export const verticalOffsets = [
|
||||
0,
|
||||
-35, -10, -5, 10, 15, 12, 5, -5, -12, -15, -12, -5, 5, 12, 15, 12, 5, -5, 10, 35,
|
||||
-35, -10, -5, 10, 15, 12, 5, -5, -12, -15, -12, -5, 5, 12, 15, 12, 5, -5, 10, 35,
|
||||
-35, -10, -5, 10, 15, 12, 5, -5, -12, -15, -12, -5, 5, 12, 15, 12, 5, -5, 10, 35,
|
||||
-35, -10, -5, 10, 15, 12, 5, -5, -12, -15, -12, -5, 5, 12, 15, 12, 5, -5, 10, 35,
|
||||
-35, -10, -5, 10, 15, 12, 5, -5, -12, -15, -12, -5, 5, 12, 15, 12, 5, -5, 10, 35,
|
||||
];
|
||||
|
||||
/**
|
||||
* Gets a vertical offset value from the predefined array
|
||||
* @param {number} index - Index to retrieve from the offsets array
|
||||
* @returns {number} Offset value
|
||||
*/
|
||||
|
||||
export const getVerticalOffset = (index) => {
|
||||
const normalizedIndex = index % verticalOffsets.length;
|
||||
return verticalOffsets[normalizedIndex >= 0 ? normalizedIndex : 0];
|
||||
};
|
||||