aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--frontend/src/App.css25
-rw-r--r--frontend/src/App.js9
-rw-r--r--frontend/src/components/main.css2
-rw-r--r--frontend/src/components/main.js3
-rw-r--r--frontend/src/components/news.css29
-rw-r--r--frontend/src/components/news.js21
-rw-r--r--frontend/src/components/pages/game.js46
-rw-r--r--frontend/src/components/pages/games.css99
-rw-r--r--frontend/src/components/pages/games.js58
-rw-r--r--frontend/src/components/pages/home.css90
-rw-r--r--frontend/src/components/pages/home.js198
-rw-r--r--frontend/src/components/pages/maplist.css399
-rw-r--r--frontend/src/components/pages/maplist.js844
-rw-r--r--frontend/src/components/pages/summary.css4
-rw-r--r--frontend/src/components/pages/summary.js20
-rw-r--r--frontend/src/components/record.css15
-rw-r--r--frontend/src/components/record.js57
-rw-r--r--frontend/src/components/sidebar.css21
-rw-r--r--frontend/src/components/sidebar.js42
-rw-r--r--frontend/src/fonts/BarlowSemiCondensed-SemiBold.ttfbin0 -> 104016 bytes
-rw-r--r--frontend/src/index.js2
21 files changed, 1949 insertions, 35 deletions
diff --git a/frontend/src/App.css b/frontend/src/App.css
index c96f197..c43738b 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -4,6 +4,26 @@ body {
4 margin: 0; 4 margin: 0;
5} 5}
6 6
7.loader {
8 animation: loader 1.2s ease infinite;
9 background-size: 400% 300%;
10 background-image: linear-gradient(-90deg, #202232 0%, #202232 25%, #2a2c41 50%, #202232 75%, #202232 100%);
11}
12
13@keyframes loader {
14 0% {
15 background-position: 100% 20%;
16 }
17
18 50% {
19 background-position: 0% 50%;
20 }
21
22 100% {
23 background-position: 0% 50%;
24 }
25}
26
7@font-face { 27@font-face {
8 font-family: 'BarlowCondensed-Bold'; 28 font-family: 'BarlowCondensed-Bold';
9 src: local('BarlowCondensed-Bold'), url(./fonts/BarlowCondensed-Bold.ttf) format('truetype'); 29 src: local('BarlowCondensed-Bold'), url(./fonts/BarlowCondensed-Bold.ttf) format('truetype');
@@ -17,4 +37,9 @@ body {
17@font-face { 37@font-face {
18 font-family: 'BarlowSemiCondensed-Regular'; 38 font-family: 'BarlowSemiCondensed-Regular';
19 src: local('BarlowSemiCondensed-Regular'), url(./fonts/BarlowSemiCondensed-Regular.ttf) format('truetype'); 39 src: local('BarlowSemiCondensed-Regular'), url(./fonts/BarlowSemiCondensed-Regular.ttf) format('truetype');
40}
41
42@font-face {
43 font-family: 'BarlowSemiCondensed-SemiBold';
44 src: local('BarlowSemiCondensed-Regular'), url(./fonts/BarlowSemiCondensed-SemiBold.ttf) format('truetype');
20} \ No newline at end of file 45} \ No newline at end of file
diff --git a/frontend/src/App.js b/frontend/src/App.js
index 93652e6..b7bfeb3 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -8,10 +8,13 @@ import "./App.css";
8import Summary from "./components/pages/summary.js" 8import Summary from "./components/pages/summary.js"
9import Profile from "./components/pages/profile.js" 9import Profile from "./components/pages/profile.js"
10import About from './components/pages/about.js'; 10import About from './components/pages/about.js';
11import Games from "./components/pages/games.js";
12import Maplist from './components/pages/maplist.js';
13import Home from "./components/pages/maplist.js";
14import Homepage from './components/pages/home.js';
11 15
12 16
13export default function App() { 17export default function App() {
14
15 const [token, setToken] = React.useState(null); 18 const [token, setToken] = React.useState(null);
16 const [mod,setMod] = React.useState(false) 19 const [mod,setMod] = React.useState(false)
17 React.useEffect(()=>{ 20 React.useEffect(()=>{
@@ -25,7 +28,7 @@ export default function App() {
25 <BrowserRouter> 28 <BrowserRouter>
26 <Sidebar token={token} setToken={setToken}/> 29 <Sidebar token={token} setToken={setToken}/>
27 <Routes> 30 <Routes>
28 <Route index element={<Main text="Homepage"/>}></Route> 31 <Route index element={<Homepage token={token} mod={mod}/>}></Route>
29 <Route path="/news" element={<Main text="News"/>}></Route> 32 <Route path="/news" element={<Main text="News"/>}></Route>
30 <Route path="/records" element={<Main text="Records"/>}></Route> 33 <Route path="/records" element={<Main text="Records"/>}></Route>
31 <Route path="/leaderboards" element={<Main text="Leaderboards"/>}></Route> 34 <Route path="/leaderboards" element={<Main text="Leaderboards"/>}></Route>
@@ -36,6 +39,8 @@ export default function App() {
36 <Route path="/rules" element={<Main text="Rules"/>}></Route> 39 <Route path="/rules" element={<Main text="Rules"/>}></Route>
37 <Route path="/about" element={<About/>}></Route> 40 <Route path="/about" element={<About/>}></Route>
38 <Route path="/maps/*" element={<Summary token={token} mod={mod}/>}></Route> 41 <Route path="/maps/*" element={<Summary token={token} mod={mod}/>}></Route>
42 <Route path="/games" element={<Games/>}></Route>
43 <Route path="/games/game" element={<Maplist token={token} mod={mod} />}></Route>
39 <Route path="*" element={<Main text="404 Page not found"/>}></Route> 44 <Route path="*" element={<Main text="404 Page not found"/>}></Route>
40 </Routes> 45 </Routes>
41 </BrowserRouter> 46 </BrowserRouter>
diff --git a/frontend/src/components/main.css b/frontend/src/components/main.css
index 48e6379..9e9be86 100644
--- a/frontend/src/components/main.css
+++ b/frontend/src/components/main.css
@@ -10,7 +10,7 @@ main {
10 10
11 padding-right: 30px; 11 padding-right: 30px;
12 12
13 font-size: 40px; 13 /* font-size: 40px; */
14 font-family: BarlowSemiCondensed-Regular; 14 font-family: BarlowSemiCondensed-Regular;
15 color: #cdcfdf; 15 color: #cdcfdf;
16 16
diff --git a/frontend/src/components/main.js b/frontend/src/components/main.js
index 18b0f5a..b359105 100644
--- a/frontend/src/components/main.js
+++ b/frontend/src/components/main.js
@@ -2,6 +2,7 @@ import React from 'react';
2 2
3import "../App.css" 3import "../App.css"
4import "./main.css"; 4import "./main.css";
5import { Link } from 'react-router-dom';
5 6
6export default function Main(props) { 7export default function Main(props) {
7 8
@@ -9,7 +10,7 @@ export default function Main(props) {
9return ( 10return (
10 <main> 11 <main>
11 <h1>{props.text}</h1> 12 <h1>{props.text}</h1>
12 </main> 13 </main>
13 ) 14 )
14} 15}
15 16
diff --git a/frontend/src/components/news.css b/frontend/src/components/news.css
new file mode 100644
index 0000000..102e9ba
--- /dev/null
+++ b/frontend/src/components/news.css
@@ -0,0 +1,29 @@
1.news-container {
2 background-color: #2A2D40;
3 border-radius: 24px;
4 font-size: 18px;
5 overflow: hidden;
6 margin-bottom: 10px;
7}
8
9.news-title {
10 padding: 20px;
11 font-family: BarlowSemiCondensed-SemiBold;
12 font-size: 22px;
13}
14
15.news-description-div {
16 margin: 0px 20px;
17 padding: 8px 0px;
18}
19
20.news-title-header {
21 background-color: #2B2E46;
22 padding: 10px 0px;
23}
24
25.news-container>span {
26 font-size: 18px;
27 font-family: BarlowSemiCondensed-Regular;
28 line-height: 0px;
29} \ No newline at end of file
diff --git a/frontend/src/components/news.js b/frontend/src/components/news.js
new file mode 100644
index 0000000..93e6be0
--- /dev/null
+++ b/frontend/src/components/news.js
@@ -0,0 +1,21 @@
1import React, { useEffect, useRef, useState } from 'react';
2import { useLocation, Link } from "react-router-dom";
3
4import "./news.css"
5
6export default function News({newsInfo}) {
7 // const { token } = prop
8 const [news, setNews] = React.useState(null);
9 const location = useLocation();
10
11 return (
12 <div className='news-container'>
13 <div className='news-title-header'>
14 <span className='news-title'>{newsInfo.title}</span>
15 </div>
16 <div className='news-description-div'>
17 <span className='description'>{newsInfo.short_description}</span>
18 </div>
19 </div>
20 )
21} \ No newline at end of file
diff --git a/frontend/src/components/pages/game.js b/frontend/src/components/pages/game.js
new file mode 100644
index 0000000..017760b
--- /dev/null
+++ b/frontend/src/components/pages/game.js
@@ -0,0 +1,46 @@
1import React, { useEffect, useRef, useState } from 'react';
2import { useLocation, Link } from "react-router-dom";
3
4import "./games.css"
5
6export default function GameEntry({ gameInfo }) {
7 const [gameEntry, setGameEntry] = React.useState(null);
8 const location = useLocation();
9
10 const gameInfoCats = gameInfo.category_portals;
11
12 useEffect(() => {
13 gameInfoCats.forEach(catInfo => {
14 const itemBody = document.createElement("div");
15 const itemTitle = document.createElement("span");
16 const spacing = document.createElement("br");
17 const itemNum = document.createElement("span");
18
19 itemTitle.innerText = catInfo.category.name;
20 itemNum.innerText = catInfo.portal_count;
21 itemTitle.classList.add("games-page-item-body-item-title");
22 itemNum.classList.add("games-page-item-body-item-num");
23 itemBody.appendChild(itemTitle);
24 itemBody.appendChild(spacing);
25 itemBody.appendChild(itemNum);
26 itemBody.className = "games-page-item-body-item";
27
28 // itemBody.innerHTML = `
29 // <span className='games-page-item-body-item-title'>${catInfo.category.name}</span><br />
30 // <span className='games-page-item-body-item-num'>${catInfo.portal_count}</span>`
31
32 document.getElementById(`${gameInfo.id}`).appendChild(itemBody);
33 });
34 })
35
36 return (
37 <Link to={"/games/game?game=" + gameInfo.id}><div className='games-page-item'>
38 <div className='games-page-item-header'>
39 <div style={{backgroundImage: `url(${gameInfo.image})`}} className='games-page-item-header-img'></div>
40 <span><b>{gameInfo.name}</b></span>
41 </div>
42 <div id={gameInfo.id} className='games-page-item-body'>
43 </div>
44 </div></Link>
45 )
46}
diff --git a/frontend/src/components/pages/games.css b/frontend/src/components/pages/games.css
new file mode 100644
index 0000000..ec57a71
--- /dev/null
+++ b/frontend/src/components/pages/games.css
@@ -0,0 +1,99 @@
1.games-page {
2 position: absolute;
3 left: 320px;
4 color: white;
5 width: calc(100% - 320px);
6 height: 100%;
7 font-family: BarlowSemiCondensed-Regular;
8 color: #ffffff;
9 overflow-y: scroll;
10 scrollbar-width: thin;
11}
12
13.games-page-item-content {
14 position: absolute;
15 left: 50px;
16 width: calc(100% - 100px);
17}
18
19.games-page-item-content a {
20 color: inherit;
21}
22
23.games-page-header {
24 margin-top: 50px;
25 margin-left: 50px;
26}
27
28span>b {
29 font-size: 56px;
30 font-family: BarlowCondensed-Bold;
31}
32
33.loader-game {
34 width: 100%;
35 height: 256px;
36 border-radius: 24px;
37 overflow: hidden;
38 margin: 25px 0px;
39}
40
41.games-page-item {
42 width: 100%;
43 height: 256px;
44 background: #202232;
45 border-radius: 24px;
46 overflow: hidden;
47 margin: 25px 0px;
48}
49
50.games-page-item-header {
51 width: 100%;
52 height: 50%;
53 background-size: cover;
54 overflow: hidden;
55}
56
57.games-page-item-header-img {
58 width: 100%;
59 height: 100%;
60 backdrop-filter: blur(4px);
61 filter: blur(4px);
62 background-size: cover;
63}
64
65.games-page-item-header span>b {
66 display: flex;
67 justify-content: center;
68 align-items: center;
69 height: 100%;
70 transform: translateY(-100%);
71}
72
73.games-page-item-body {
74 display: flex;
75 justify-content: center;
76 align-items: center;
77 height: 50%;
78}
79
80.games-page-item-body-item {
81 background: #2B2E46;
82 text-align: center;
83 width: max-content;
84 width: calc(100% - 24px);
85 height: 100px;
86 border-radius: 24px;
87 color: #CDCFDF;
88 margin: 12px;
89}
90
91.games-page-item-body-item-title {
92 margin-top: 0px;
93 font-size: 26px;
94}
95
96.games-page-item-body-item-num {
97 font-size: 50px;
98 font-family: BarlowCondensed-Bold;
99} \ No newline at end of file
diff --git a/frontend/src/components/pages/games.js b/frontend/src/components/pages/games.js
new file mode 100644
index 0000000..8c27151
--- /dev/null
+++ b/frontend/src/components/pages/games.js
@@ -0,0 +1,58 @@
1import React, { useEffect, useState } from 'react';
2import { useLocation, Link } from "react-router-dom";
3
4import "./games.css"
5import GameEntry from './game';
6
7export default function Games(prop) {
8 const { token } = prop;
9 const [games, setGames] = useState([]);
10 const location = useLocation();
11
12 useEffect(() => {
13 const fetchGames = async () => {
14 try {
15 const response = await fetch("https://lp.ardapektezol.com/api/v1/games", {
16 headers: {
17 'Authorization': token
18 }
19 });
20
21 const data = await response.json();
22 setGames(data.data);
23 pageLoad();
24 } catch (err) {
25 console.error("Error fetching games:", err);
26 }
27 };
28
29 fetchGames();
30
31 function pageLoad() {
32 const loaders = document.querySelectorAll(".loader");
33 loaders.forEach((loader) => {
34 loader.style.display = "none";
35 });
36 }
37 }, [token]);
38
39 return (
40 <div className='games-page'>
41 <section className='games-page-header'>
42 <span><b>Games list</b></span>
43 </section>
44
45 <section>
46 <div className='games-page-content'>
47 <div className='games-page-item-content'>
48 <div className='loader loader-game'></div>
49 <div className='loader loader-game'></div>
50 {games.map((game, index) => (
51 <GameEntry gameInfo={game} key={index} />
52 ))}
53 </div>
54 </div>
55 </section>
56 </div>
57 );
58}
diff --git a/frontend/src/components/pages/home.css b/frontend/src/components/pages/home.css
new file mode 100644
index 0000000..072b12b
--- /dev/null
+++ b/frontend/src/components/pages/home.css
@@ -0,0 +1,90 @@
1* {
2 scrollbar-width: thin;
3}
4
5.homepage-panel {
6 background-color: #202232;
7 margin: 10px 10px;
8 padding: 10px;
9 border-radius: 24px;
10 overflow: hidden;
11 flex: 1 1 100%;
12 align-items: stretch;
13 width: 100%;
14}
15
16.homepage-panel-title-div {
17 background-color: #2B2E46;
18 width: fit-content;
19 padding: 5px 18px;
20 border-radius: 200px;
21 font-family: BarlowSemiCondensed-SemiBold;
22 font-size: 32px;
23 margin-bottom: 10px;
24}
25
26.stats-div {
27 background-color: #2B2E46;
28 border-radius: 24px;
29 text-align: center;
30 display: grid;
31 padding: 10px 0px;
32 width: 100%;
33}
34
35.stats-div span {
36 font-family: BarlowSemiCondensed-Regular;
37 font-size: 18px;
38}
39
40.stats-div span>b {
41 font-family: BarlowSemiCondensed-SemiBold;
42 font-size: 42px;
43}
44
45.record-title div {
46 --padding: 20px;
47 width: calc(100% - calc(var(--padding * 2)));
48 height: 32px;
49 border-radius: 200px;
50 font-size: 18px;
51 display: grid;
52 grid-template-columns: calc(20% - 3.6px) calc(25% - 4.5px) calc(15% - 2.7px) calc(15% - 2.7px) calc(25% - 4.5px);
53 text-align: center;
54 padding: 0px var(--padding);
55 vertical-align: middle;
56 align-items: center;
57 font-family: BarlowSemiCondensed-SemiBold;
58}
59
60.record-title::after {
61 content: "";
62 display: flex;
63 width: 100%;
64 height: 3px;
65 background-color: #2B2E46;
66 margin-bottom: 5px;
67}
68
69.recommended-map-img {
70 width: 250px;
71 border-radius: 24px;
72 margin-bottom: 0;
73 border: 7px solid #2B2E46;
74}
75
76.difficulty-bar-home {
77 width: 100%;
78 display: grid;
79 grid-template-columns: 20% 20% 20% 20% 20%;
80 align-items: center;
81 margin: 5px;
82 margin-top: 16px;
83}
84
85.difficulty-point {
86 background: #2B2E46;
87 height: 3px;
88 margin: 5px;
89 border-radius: 10px;
90}
diff --git a/frontend/src/components/pages/home.js b/frontend/src/components/pages/home.js
new file mode 100644
index 0000000..c1477ab
--- /dev/null
+++ b/frontend/src/components/pages/home.js
@@ -0,0 +1,198 @@
1import React, { useEffect, useRef, useState } from 'react';
2import { useLocation, Link } from "react-router-dom";
3
4import "./home.css"
5import News from '../news';
6import Record from '../record';
7
8export default function Homepage(prop) {
9 const {token, setToken} = prop
10 const [home, setHome] = React.useState(null);
11 const location = useLocation();
12
13 useEffect(() => {
14 async function fetchMapImg() {
15 try {
16 const response = await fetch("https://lp.ardapektezol.com/api/v1/games", {
17 headers: {
18 'Authorization': token
19 }
20 });
21
22 const data = await response.json();
23
24 const recommendedMapImg = document.querySelector("#recommendedMapImg");
25
26 recommendedMapImg.src = `${data.data[0].image}`
27
28 const column1 = document.querySelector("#column1");
29 const column2 = document.querySelector("#column2");
30
31 column2.style.height = column1.clientHeight + "px";
32 } catch (error) {
33 console.log(error)
34 }
35 }
36
37 fetchMapImg()
38
39 const panels = document.querySelectorAll(".homepage-panel");
40 panels.forEach(e => {
41 // this is cuz react is silly
42 if (e.innerHTML.includes('<div class="homepage-panel-title-div">')) {
43 return
44 }
45 const title = e.getAttribute("title");
46
47 const titleDiv = document.createElement("div");
48 const titleSpan = document.createElement("span");
49
50 titleDiv.classList.add("homepage-panel-title-div")
51
52 titleSpan.innerText = title
53
54 titleDiv.appendChild(titleSpan)
55 e.insertBefore(titleDiv, e.firstChild)
56 });
57 })
58
59 const newsList = [
60 {
61 "title": "Portal Saved on Container Ride",
62 "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas."
63 },
64 {
65 "title": "Portal Saved on Container Ride",
66 "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas."
67 },
68 {
69 "title": "Portal Saved on Container Ride",
70 "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas."
71 },
72 {
73 "title": "Portal Saved on Container Ride",
74 "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas."
75 },
76 {
77 "title": "Portal Saved on Container Ride",
78 "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas."
79 },
80 {
81 "title": "Portal Saved on Container Ride",
82 "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas."
83 },
84 {
85 "title": "Portal Saved on Container Ride",
86 "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas."
87 },
88 {
89 "title": "Portal Saved on Container Ride",
90 "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas."
91 },
92 {
93 "title": "Portal Saved on Container Ride",
94 "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas."
95 },
96 {
97 "title": "Portal Saved on Container Ride",
98 "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas."
99 },
100 {
101 "title": "Portal Saved on Container Ride",
102 "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas."
103 },
104 {
105 "title": "Portal Saved on Container Ride",
106 "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas."
107 },
108 {
109 "title": "Portal Saved on Container Ride",
110 "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas."
111 },
112 {
113 "title": "Portal Saved on Container Ride",
114 "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas."
115 },
116 ]
117
118 return (
119 <main>
120 <section style={{marginTop: "40px", userSelect: "none"}}>
121 <span style={{fontSize: "40px"}}>Welcome back,</span><br/>
122 <span><b style={{ fontSize: "96px", transform: "translateY(-20px)", display: "block"}}>Krzyhau</b></span>
123 </section>
124
125 <div style={{display: "grid", gridTemplateColumns: "calc(50%) calc(50%)"}}>
126 <div id='column1' style={{ display: "flex", alignItems: "self-start", flexWrap: "wrap", alignContent: "start" }}>
127 {/* Column 1 */}
128 <section title="Your Profile" className='homepage-panel'>
129 <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: "12px"}}>
130 <div className='stats-div'>
131 <span>Overall rank</span><br/>
132 <span><b>#69</b></span>
133 </div>
134 <div className='stats-div'>
135 <span>Singleplayer</span><br/>
136 <span style={{fontSize: "22px"}}><b>#10</b>&nbsp;(60/62)</span>
137 </div>
138 <div className='stats-div'>
139 <span>Overall rank</span><br/>
140 <span style={{fontSize: "22px"}}><b>#69</b>&nbsp;(13/37)</span>
141 </div>
142 </div>
143 </section>
144 <section title="What's Next?" className='homepage-panel'>
145 <div style={{display: "flex"}}>
146 <img className='recommended-map-img' id="recommendedMapImg"></img>
147 <div style={{marginLeft: "12px", display: "block", width: "100%"}}>
148 <span style={{fontFamily: "BarlowSemiCondensed-SemiBold", fontSize: "32px", width: "100%", display: "block"}}>Container Ride</span>
149 <span style={{fontSize: "20px"}}>Your Record: 4 portals</span>
150 <span style={{fontFamily: "BarlowSemiCondensed-SemiBold", fontSize: "36px", width: "100%", display: "block"}}>World Record: 2 portals</span>
151 <div className='difficulty-bar-home'>
152 <div className='difficulty-point' style={{backgroundColor: "#51C355"}}></div>
153 <div className='difficulty-point'></div>
154 <div className='difficulty-point'></div>
155 <div className='difficulty-point'></div>
156 <div className='difficulty-point'></div>
157 </div>
158 </div>
159 </div>
160 </section>
161 <section title="Newest Records" className='homepage-panel' style={{height: "250px"}}>
162 <div className='record-title'>
163 <div>
164 <span>Place</span>
165 <span style={{textAlign: "left"}}>Runner</span>
166 <span>Portals</span>
167 <span>Time</span>
168 <span>Date</span>
169 </div>
170 </div>
171 <div style={{overflowY: "scroll", height: "calc(100% - 90px)", paddingRight: "10px"}}>
172 <Record name={"Krzyhau"} portals={"2"} date={new Date("2024-05-21T08:45:00")} place={"2"} time={"20.20"}></Record>
173 <Record name={"Krzyhau"} portals={"2"} date={new Date("2024-05-21T08:45:00")} place={"2"} time={"20.20"}></Record>
174 <Record name={"Krzyhau"} portals={"2"} date={new Date("2024-05-21T08:45:00")} place={"2"} time={"20.20"}></Record>
175 <Record name={"Krzyhau"} portals={"2"} date={new Date("2024-05-21T08:45:00")} place={"2"} time={"20.20"}></Record>
176 <Record name={"Krzyhau"} portals={"2"} date={new Date("2024-05-21T08:45:00")} place={"2"} time={"20.20"}></Record>
177 <Record name={"Krzyhau"} portals={"2"} date={new Date("2024-05-21T08:45:00")} place={"2"} time={"20.20"}></Record>
178 <Record name={"Krzyhau"} portals={"2"} date={new Date("2024-05-21T08:45:00")} place={"2"} time={"20.20"}></Record>
179 </div>
180 </section>
181 </div>
182 {/* Column 2 */}
183 <div id='column2' style={{display: "flex", alignItems: "stretch", height: "87px"}}>
184 <section title="News" className='homepage-panel'>
185 <div id='newsContent' style={{ display: "block", width: "100%", overflowY: "scroll", height: "calc(100% - 50px)"}}>
186 {newsList.map((newsList, index) => (
187 <News newsInfo={newsList} key={index}></News>
188 ))}
189 </div>
190 </section>
191 </div>
192 </div>
193
194
195
196 </main>
197 )
198} \ No newline at end of file
diff --git a/frontend/src/components/pages/maplist.css b/frontend/src/components/pages/maplist.css
new file mode 100644
index 0000000..a63c4ec
--- /dev/null
+++ b/frontend/src/components/pages/maplist.css
@@ -0,0 +1,399 @@
1.maplist-page {
2 position: relative;
3 left: 350px;
4 height: 100vh;
5 color: #cdcfdf;
6 width: calc(100% - 380px);
7 font-family: BarlowSemiCondensed-Regular;
8 overflow-y: scroll;
9 overflow-x: hidden;
10 padding-right: 30px;
11}
12
13a {
14 color: inherit;
15 width: fit-content;
16}
17
18.maplist-page-content {
19 position: absolute;
20 left: 0px;
21 width: calc(100% - 50px);
22}
23
24.maplist-page-header {
25 margin-top: 20px;
26 display: grid;
27 margin-bottom: 10px;
28}
29
30.nav-btn {
31 height: 40px;
32 background-color: #2b2e46;
33 color: inherit;
34 font-size: 18px;
35 font-family: inherit;
36 border: none;
37 border-radius: 20px;
38 transition: background-color .1s;
39 cursor: default;
40 width: fit-content;
41}
42
43.nav-btn>span {
44 padding: 0 8px 0 8px;
45}
46
47.nav-btn:hover {
48 background-color: #202232;
49 cursor: pointer;
50}
51
52.game {
53 width: 100%;
54 height: 192px;
55 background: #202232;
56 border-radius: 24px;
57 overflow: hidden;
58}
59
60.game-header {
61 width: 100%;
62 height: 144px;
63 display: flex;
64 justify-content: center;
65 align-items: center;
66 overflow: hidden;
67}
68
69.game-header-text {
70 display: flex;
71 justify-content: center;
72 align-items: center;
73 position: absolute;
74}
75
76.game-img {
77 width: 100%;
78 height: 100%;
79 background-size: cover;
80 filter: blur(4px);
81}
82
83.game-header-text>span {
84 font-size: 42px;
85 font-weight: 500;
86 margin: 5px;
87}
88
89.game-header-text span>b {
90 font-size: 96px;
91 font-weight: 600;
92}
93
94.game-nav {
95 display: flex;
96 height: 48px;
97}
98
99.game-nav-btn {
100 width: 100%;
101 height: 100%;
102 border: none;
103 border-radius: 0px;
104 color: inherit;
105 font-family: inherit;
106 font-size: 22px;
107 background: #2B2E46;
108 transition: background-color .1s;
109 margin: 0 1px;
110 display: flex;
111 justify-content: center;
112 align-items: center;
113}
114
115.game-nav-btn:hover, .selected {
116 background-color: #202232;
117 cursor: pointer;
118}
119
120.gameview-nav {
121 margin-top: 20px;
122 display: flex;
123 height: 56px;
124 border-radius: 24px;
125 overflow: hidden;
126 background-color: #202232;
127}
128
129.maplist {
130 width: 100%;
131 margin-top: 20px;
132 margin-bottom: 40px;
133}
134
135.chapter-name {
136 font-size: 30px;
137}
138
139.chapter-page-div {
140 display: flex;
141 justify-content: right;
142 transform: translateY(-30px);
143}
144
145.chapter-page-div button {
146 background-color: #00000000;
147 border: 0;
148 cursor: pointer;
149 height: 30px;
150 padding: 0;
151 width: 30px;
152}
153
154.chapter-page-div span {
155 color: #cdcfdf;
156 font-family: BarlowSemiCondensed-Regular;
157 font-size: 20px;
158}
159
160.maplist-maps {
161 display: grid;
162 grid-template-columns: 25% 25% 25% 25%;
163 margin-top: 10px;
164 transform: translateY(-30px);
165}
166
167.maplist-item {
168 background: #202232;
169 border-radius: 24px;
170 overflow: hidden;
171 margin: 10px 10px;
172 /* padding: 10px 15px; */
173 cursor: pointer;
174 user-select: none;
175}
176
177.loader-map {
178 border-radius: 24px;
179 overflow: hidden;
180 margin: 10px 10px;
181 /* padding: 10px 15px; */
182 user-select: none;
183 width: calc(100% - 20px);
184 height: calc(223px);
185}
186
187.maplist-img-div {
188 height: 150px;
189 overflow: hidden;
190}
191
192.maplist-img {
193 width: 100%;
194 height: 100%;
195 background-size: cover;
196 filter: blur(4px);
197 opacity: 0.7;
198}
199
200.maplist-portalcount-div {
201 display: flex;
202 justify-content: center;
203 align-items: center;
204 text-align: center;
205 height: 100%;
206 transform: translateY(-100%);
207 overflow: hidden;
208}
209
210.maplist-title {
211 font-size: 22px;
212 text-align: center;
213 width: 100%;
214 display: inherit;
215 padding: 5px 0px;
216 color: #CDCFDF;
217}
218
219.maplist-portals {
220 margin-left: 5px;
221 font-size: 32px;
222}
223
224.difficulty-div {
225 display: flex;
226 padding: 7px 10px;
227}
228
229.difficulty-label {
230 font-size: 18px;
231}
232
233.difficulty-bar {
234 width: 100%;
235 display: grid;
236 grid-template-columns: 20% 20% 20% 20% 20%;
237 align-items: center;
238 margin: 5px;
239}
240
241.difficulty-point {
242 background: #2B2E46;
243 height: 3px;
244 margin: 5px;
245 border-radius: 10px;
246}
247
248.stats {
249 margin-top: 30px;
250}
251
252.portalcount-over-time-div {
253 width: 100%;
254 height: 450px;
255 position: relative;
256 background-color: #202232;
257 border-radius: 20px;
258}
259
260.graph-title {
261 width: 100%;
262 display: inherit;
263 font-size: 24px;
264 margin-top: 5px;
265 text-align: center;
266 font-family: BarlowSemiCondensed-SemiBold;
267 padding-top: 7px;
268}
269
270.portalcount-graph {
271 height: calc(100% - 30px);
272 width: calc(100% - 80px);
273}
274
275.chart {
276 height: calc(100% - 80px);
277 width: 100%;
278 position: relative;
279 padding: 0px 0px;
280 scrollbar-width: thin;
281}
282
283.line-chart {
284 list-style: none;
285 margin: 0;
286 padding: 0;
287 height: 100%;
288 border-bottom: 2px solid #2B2E46;
289}
290
291.data-point {
292 background-color: #202232;
293 border: 4px solid #006FDE;
294 border-radius: 50%;
295 height: 6px;
296 position: absolute;
297 width: 6px;
298 bottom: calc(var(--y) - 4.5px);
299 left: calc(var(--x) - 6.5px);
300 transition: all 0.2s cubic-bezier(0.075, 0.82, 0.165, 1);
301 z-index: 1;
302 animation: point_intro 0.2s cubic-bezier(0.075, 0.82, 0.165, 1.8);
303 animation-fill-mode: backwards;
304}
305
306.data-point:hover, .data-point-active {
307 background-color: #006FDE;
308 box-shadow: 0px 0px 10px #006FDE;
309}
310
311.line-segment {
312 background-color: #006FDE;
313 bottom: var(--y);
314 height: 4px;
315 left: var(--x);
316 position: absolute;
317 transform: rotate(calc(var(--angle) * -1deg));
318 width: calc(var(--hypotenuse) * 1px);
319 transform-origin: left bottom;
320 border-radius: 20px;
321 z-index: 1;
322 animation: line_intro 0.05s cubic-bezier(0, 1, 0.31, 0.96);
323 animation-fill-mode: backwards;
324}
325
326#dataPointInfo {
327 position: absolute;
328 width: 400px;
329 height: 85px;
330 background: #202232;
331 box-shadow: 0px 4px 16px 0px #00000080;
332 transition: all 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
333 z-index: 1000;
334 opacity: 0;
335 left: auto;
336 border-radius: 20px;
337 padding: 15px 7px;
338}
339
340.section-header {
341 display: flex;
342 text-align: center;
343 font-family: BarlowSemiCondensed-SemiBold;
344 font-size: 18px;
345 height: 40%;
346 justify-content: space-evenly;
347 align-items: center;
348}
349
350.section-header span, .section-data span {
351 flex: 1;
352}
353
354.divider {
355 width: 100%;
356 height: 2px;
357 background-color: #2B2E46;
358 display: flex;
359 margin: 5px 0px 8px 0px;
360}
361
362.section-data {
363 display: flex;
364 grid-template-columns: 25% 25% 25% 25%;
365 text-align: center;
366 background-color: #2B2E46;
367 height: 52%;
368 border-radius: 200px;
369 align-items: center;
370 justify-content: space-evenly;
371 flex-grow: 1;
372 font-family: BarlowSemiCondensed-Regular;
373 font-size: 18px;
374 padding: 0px 5px;
375}
376
377@keyframes line_intro {
378 0% {
379 width: 0;
380 }
381 100% {
382 width: calc(var(--hypotenuse) * 1px);
383 }
384}
385
386@keyframes point_intro {
387 0% {
388 opacity: 0;
389 width: 0;
390 height: 0;
391 transform: translate(3px, -3px);
392 }
393 100% {
394 width: 6px;
395 height: 6px;
396 transform: translate(0px, 0px);
397 opacity: 1;
398 }
399}
diff --git a/frontend/src/components/pages/maplist.js b/frontend/src/components/pages/maplist.js
new file mode 100644
index 0000000..dca76d6
--- /dev/null
+++ b/frontend/src/components/pages/maplist.js
@@ -0,0 +1,844 @@
1import React, { useEffect, useRef, useState } from 'react';
2import { useLocation, Link } from "react-router-dom";
3import { BrowserRouter as Router, Route, Routes, useNavigate } from 'react-router-dom';
4
5import "./maplist.css"
6import img5 from "../../imgs/5.png"
7import img6 from "../../imgs/6.png"
8
9export default function Maplist(prop) {
10 const {token,setToken} = prop
11 const scrollRef = useRef(null)
12 const [games, setGames] = React.useState(null);
13 const location = useLocation();
14
15 let gameTitle;
16 let minPage;
17 let maxPage;
18 let currentPage;
19 let add = 0;
20 let gameState;
21 let catState = 0;
22 async function detectGame() {
23 const response = await fetch("https://lp.ardapektezol.com/api/v1/games", {
24 headers: {
25 'Authorization': token
26 }
27 });
28
29 const data = await response.json();
30
31 const url = new URL(window.location.href)
32
33 const params = new URLSearchParams(url.search)
34 gameState = parseFloat(params.get("game"))
35
36 document.querySelector("#catPortalCount").innerText = data.data[gameState - 1].category_portals[0].portal_count;
37
38 if (gameState == 1){
39 gameTitle = data.data[0].name;
40
41 maxPage = 9;
42 minPage = 1;
43 createCategories(1);
44 } else if (gameState == 2){
45 gameTitle = data.data[1].name;
46
47 maxPage = 16;
48 minPage = 10;
49 add = 10
50 createCategories(2);
51 }
52
53 let chapterParam = params.get("chapter")
54
55 currentPage = minPage;
56
57 if (chapterParam) {
58 currentPage = +chapterParam + add
59 }
60
61 changePage(currentPage);
62
63 // if (chapterParam) {
64 // document.querySelector("#pageNumbers").innerText = `${chapterParam - minPage + 1}/${maxPage - minPage + 1}`
65 // }
66 }
67
68 function changeMaplistOrStatistics(index, name) {
69 const maplistBtns = document.querySelectorAll("#maplistBtn");
70 maplistBtns.forEach((btn, i) => {
71 if (i == index) {
72 btn.className = "game-nav-btn selected"
73
74 if (name == "maplist") {
75 document.querySelector(".stats").style.display = "none";
76 document.querySelector(".maplist").style.display = "block";
77 document.querySelector(".maplist").setAttribute("currentTab", "maplist");
78 } else {
79 document.querySelector(".stats").style.display = "block";
80 document.querySelector(".maplist").style.display = "none";
81
82 document.querySelector(".maplist-page").scrollTo({ top: 372, behavior: "smooth" })
83 document.querySelector(".maplist").setAttribute("currentTab", "stats");
84 }
85 } else {
86 btn.className = "game-nav-btn";
87 }
88 });
89 }
90
91 async function createCategories(gameID) {
92 const response = await fetch("https://lp.ardapektezol.com/api/v1/games", {
93 headers: {
94 'Authorization': token
95 }
96 });
97
98 const data = await response.json();
99 let categoriesArr = data.data[gameID - 1].category_portals;
100
101 const gameNav = document.querySelector(".game-nav");
102 gameNav.innerHTML = "";
103 categoriesArr.forEach((category) => {
104 createCategory(category);
105 });
106 }
107
108 let categoryNum = 0;
109 function createCategory(category) {
110 const gameNav = document.querySelector(".game-nav");
111
112 categoryNum++;
113 const gameNavBtn = document.createElement("button");
114 if (categoryNum == 1) {
115 gameNavBtn.className = "game-nav-btn selected";
116 } else {
117 gameNavBtn.className = "game-nav-btn";
118 }
119 gameNavBtn.id = "catBtn"
120 gameNavBtn.innerText = category.category.name;
121
122 gameNavBtn.addEventListener("click", (e) => {
123 changeCategory(category, e);
124 changePage(currentPage);
125 })
126
127 gameNav.appendChild(gameNavBtn);
128 }
129
130 async function changeCategory(category, btn) {
131 const response = await fetch("https://lp.ardapektezol.com/api/v1/games", {
132 headers: {
133 'Authorization': token
134 }
135 });
136
137 const data = await response.json();
138 catState = category.category.id - 1;
139 console.log(catState)
140 const navBtns = document.querySelectorAll("#catBtn");
141 navBtns.forEach((btns) => {
142 btns.classList.remove("selected");
143 });
144
145 btn.srcElement.classList.add("selected");
146 document.querySelector("#catPortalCount").innerText = category.portal_count;
147 }
148
149 async function changePage(page) {
150
151 const pageNumbers = document.querySelector("#pageNumbers");
152
153 pageNumbers.innerText = `${currentPage - minPage + 1}/${maxPage - minPage + 1}`;
154
155 const maplistMaps = document.querySelector(".maplist-maps");
156 maplistMaps.innerHTML = "";
157 for (let index = 0; index < 8; index++) {
158 const loadingAnimation = document.createElement("div");
159 loadingAnimation.classList.add("loader");
160 loadingAnimation.classList.add("loader-map")
161 maplistMaps.appendChild(loadingAnimation);
162 }
163 const data = await fetchMaps(page);
164 const maps = data.data.maps;
165 const name = data.data.chapter.name;
166
167 let chapterName = "Chapter";
168 const chapterNumberOld = name.split(" - ")[0];
169 let chapterNumber1 = chapterNumberOld.split("Chapter ")[1];
170 if (chapterNumber1 == undefined) {
171 chapterName = "Course"
172 chapterNumber1 = chapterNumberOld.split("Course ")[1];
173 }
174 const chapterNumber = chapterNumber1.toString().padStart(2, "0");
175 const chapterTitle = name.split(" - ")[1];
176
177 const chapterNumberElement = document.querySelector(".chapter-num")
178 const chapterTitleElement = document.querySelector(".chapter-name")
179 chapterNumberElement.innerText = chapterName + " " + chapterNumber;
180 chapterTitleElement.innerText = chapterTitle;
181
182 maplistMaps.innerHTML = "";
183 maps.forEach(map => {
184 let portalCount;
185 if (map.category_portals[catState] != undefined) {
186 portalCount = map.category_portals[catState].portal_count;
187 } else {
188 portalCount = map.category_portals[0].portal_count;
189 }
190 addMap(map.name, portalCount, map.image, map.difficulty + 1, map.id);
191 });
192
193 const gameTitleElement = document.querySelector("#gameTitle");
194 gameTitleElement.innerText = gameTitle;
195
196 const url = new URL(window.location.href)
197
198 const params = new URLSearchParams(url.search)
199
200 let chapterParam = params.get("chapter")
201
202 try {
203 const response = await fetch("https://lp.ardapektezol.com/api/v1/games", {
204 headers: {
205 'Authorization': token
206 }
207 });
208
209 const data = await response.json();
210
211 const gameImg = document.querySelector(".game-img");
212
213 gameImg.style.backgroundImage = `url(${data.data[0].image})`;
214
215 // const mapImg = document.querySelectorAll(".maplist-img");
216 // mapImg.forEach((map) => {
217 // map.style.backgroundImage = `url(${data.data[0].image})`;
218 // });
219
220 } catch (error) {
221 console.log("error fetching games:", error);
222 }
223
224 asignDifficulties();
225 }
226
227 async function addMap(mapName, mapPortalCount, mapImage, difficulty, mapID) {
228 // jesus christ
229 const maplistItem = document.createElement("div");
230 const maplistTitle = document.createElement("span");
231 const maplistImgDiv = document.createElement("div");
232 const maplistImg = document.createElement("div");
233 const maplistPortalcountDiv = document.createElement("div");
234 const maplistPortalcount = document.createElement("span");
235 const b = document.createElement("b");
236 const maplistPortalcountPortals = document.createElement("span");
237 const difficultyDiv = document.createElement("div");
238 const difficultyLabel = document.createElement("span");
239 const difficultyBar = document.createElement("div");
240 const difficultyPoint1 = document.createElement("div");
241 const difficultyPoint2 = document.createElement("div");
242 const difficultyPoint3 = document.createElement("div");
243 const difficultyPoint4 = document.createElement("div");
244 const difficultyPoint5 = document.createElement("div");
245
246 maplistItem.className = "maplist-item";
247 maplistTitle.className = "maplist-title";
248 maplistImgDiv.className = "maplist-img-div";
249 maplistImg.className = "maplist-img";
250 maplistPortalcountDiv.className = "maplist-portalcount-div";
251 maplistPortalcount.className = "maplist-portalcount";
252 maplistPortalcountPortals.className = "maplist-portals";
253 difficultyDiv.className = "difficulty-div";
254 difficultyLabel.className = "difficulty-label";
255 difficultyBar.className = "difficulty-bar";
256 difficultyPoint1.className = "difficulty-point";
257 difficultyPoint2.className = "difficulty-point";
258 difficultyPoint3.className = "difficulty-point";
259 difficultyPoint4.className = "difficulty-point";
260 difficultyPoint5.className = "difficulty-point";
261
262
263 maplistTitle.innerText = mapName;
264 difficultyLabel.innerText = "Difficulty: "
265 maplistPortalcountPortals.innerText = "portals"
266 b.innerText = mapPortalCount;
267 maplistImg.style.backgroundImage = `url(${mapImage})`;
268 difficultyBar.setAttribute("difficulty", difficulty)
269 maplistItem.setAttribute("id", mapID)
270 maplistItem.addEventListener("click", () => {
271 console.log(mapID)
272 window.location.href = "/maps/" + mapID
273 })
274
275 // appends
276 // maplist item
277 maplistItem.appendChild(maplistTitle);
278 maplistImgDiv.appendChild(maplistImg);
279 maplistImgDiv.appendChild(maplistPortalcountDiv);
280 maplistPortalcountDiv.appendChild(maplistPortalcount);
281 maplistPortalcount.appendChild(b);
282 maplistPortalcountDiv.appendChild(maplistPortalcountPortals);
283 maplistItem.appendChild(maplistImgDiv);
284 maplistItem.appendChild(difficultyDiv);
285 difficultyDiv.appendChild(difficultyLabel);
286 difficultyDiv.appendChild(difficultyBar);
287 difficultyBar.appendChild(difficultyPoint1);
288 difficultyBar.appendChild(difficultyPoint2);
289 difficultyBar.appendChild(difficultyPoint3);
290 difficultyBar.appendChild(difficultyPoint4);
291 difficultyBar.appendChild(difficultyPoint5);
292
293 // display in place
294 const maplistMaps = document.querySelector(".maplist-maps");
295 maplistMaps.appendChild(maplistItem);
296 }
297
298 async function fetchMaps(chapterID) {
299 try{
300 const response = await fetch(`https://lp.ardapektezol.com/api/v1/chapters/${chapterID}`, {
301 headers: {
302 'Authorization': token
303 }
304 });
305
306 const data = await response.json();
307 return data;
308 } catch (err) {
309 console.log(err)
310 }
311 }
312
313 // difficulty stuff
314 function asignDifficulties() {
315 const difficulties = document.querySelectorAll(".difficulty-bar");
316 difficulties.forEach((difficultyElement) => {
317 let difficulty = difficultyElement.getAttribute("difficulty");
318 if (difficulty == "1") {
319 difficultyElement.childNodes[0].style.backgroundColor = "#51C355";
320 } else if (difficulty == "2") {
321 difficultyElement.childNodes[0].style.backgroundColor = "#8AC93A";
322 difficultyElement.childNodes[1].style.backgroundColor = "#8AC93A";
323 } else if (difficulty == "3") {
324 difficultyElement.childNodes[0].style.backgroundColor = "#8AC93A";
325 difficultyElement.childNodes[1].style.backgroundColor = "#8AC93A";
326 difficultyElement.childNodes[2].style.backgroundColor = "#8AC93A";
327 } else if (difficulty == "4") {
328 difficultyElement.childNodes[0].style.backgroundColor = "#C35F51";
329 difficultyElement.childNodes[1].style.backgroundColor = "#C35F51";
330 difficultyElement.childNodes[2].style.backgroundColor = "#C35F51";
331 difficultyElement.childNodes[3].style.backgroundColor = "#C35F51";
332 } else if (difficulty == "5") {
333 difficultyElement.childNodes[0].style.backgroundColor = "#C35F51";
334 difficultyElement.childNodes[1].style.backgroundColor = "#C35F51";
335 difficultyElement.childNodes[2].style.backgroundColor = "#C35F51";
336 difficultyElement.childNodes[3].style.backgroundColor = "#C35F51";
337 difficultyElement.childNodes[4].style.backgroundColor = "#C35F51";
338 }
339 });
340 }
341
342 const divRef = useRef(null);
343
344 React.useEffect(() => {
345
346 const lineChart = document.querySelector(".line-chart")
347 function createGraph() {
348 // max
349 let items = [
350 {
351 record: "100",
352 date: new Date(2011, 4, 4),
353 map: "Container Ride",
354 first: "tiny zach"
355 },
356 {
357 record: "98",
358 date: new Date(2012, 6, 4),
359 map: "Container Ride",
360 first: "tiny zach"
361 },
362 {
363 record: "94",
364 date: new Date(2013, 0, 1),
365 map: "Container Ride",
366 first: "tiny zach"
367 },
368 {
369 record: "90",
370 date: new Date(2014, 0, 1),
371 map: "Container Ride",
372 first: "tiny zach"
373 },
374 {
375 record: "88",
376 date: new Date(2015, 6, 14),
377 map: "Container Ride",
378 first: "tiny zach"
379 },
380 {
381 record: "84",
382 date: new Date(2016, 8, 19),
383 map: "Container Ride",
384 first: "tiny zach"
385 },
386 {
387 record: "82",
388 date: new Date(2017, 3, 20),
389 map: "Container Ride",
390 first: "tiny zach"
391 },
392 {
393 record: "81",
394 date: new Date(2018, 2, 25),
395 map: "Container Ride",
396 first: "tiny zach"
397 },
398 {
399 record: "80",
400 date: new Date(2019, 3, 4),
401 map: "Container Ride",
402 first: "tiny zach"
403 },
404 {
405 record: "78",
406 date: new Date(2020, 11, 21),
407 map: "Container Ride",
408 first: "tiny zach"
409 },
410 {
411 record: "77",
412 date: new Date(2021, 10, 25),
413 map: "Container Ride",
414 first: "tiny zach"
415 },
416 {
417 record: "76",
418 date: new Date(2022, 4, 17),
419 map: "Container Ride",
420 first: "tiny zach"
421 },
422 {
423 record: "75",
424 date: new Date(2023, 9, 31),
425 map: "Container Ride",
426 first: "tiny zach"
427 },
428 {
429 record: "74",
430 date: new Date(2024, 4, 4),
431 map: "Container Ride",
432 first: "tiny zach"
433 },
434 ]
435
436 function calculatePosition(date, startDate, endDate, maxWidth) {
437 const totalMilliseconds = endDate - startDate + 10000000000;
438 const millisecondsFromStart = date - startDate + 5000000000;
439 return (millisecondsFromStart / totalMilliseconds) * maxWidth
440 }
441
442 const minDate = items.reduce((min, dp) => dp.date < min ? dp.date : min, items[0].date)
443 const maxDate = items.reduce((max, dp) => dp.date > max ? dp.date : max, items[0].date)
444
445 const graph_width = document.querySelector(".portalcount-over-time-div").clientWidth
446 // console.log(graph_width)
447
448 const uniqueYears = new Set()
449 items.forEach(dp => uniqueYears.add(dp.date.getFullYear()))
450 let minYear = Infinity;
451 let maxYear = -Infinity;
452
453 items.forEach(dp => {
454 const year = dp.date.getFullYear();
455 minYear = Math.min(minYear, year);
456 maxYear = Math.max(maxYear, year);
457 });
458
459 // Add missing years to the set
460 for (let year = minYear; year <= maxYear; year++) {
461 uniqueYears.add(year);
462 }
463 const uniqueYearsArr = Array.from(uniqueYears)
464
465 items = items.map(dp => ({
466 record: dp.record,
467 date: dp.date,
468 x: calculatePosition(dp.date, minDate, maxDate, lineChart.clientWidth),
469 map: dp.map,
470 first: dp.first
471 }))
472
473 const yearInterval = lineChart.clientWidth / uniqueYears.size
474 for (let index = 1; index < (uniqueYears.size); index++) {
475 const placeholderlmao = document.createElement("div")
476 const yearSpan = document.createElement("span")
477 yearSpan.style.position = "absolute"
478 placeholderlmao.style.height = "100%"
479 placeholderlmao.style.width = "2px"
480 placeholderlmao.style.backgroundColor = "#00000080"
481 placeholderlmao.style.position = `absolute`
482 const thing = calculatePosition(new Date(uniqueYearsArr[index], 0, 0), minDate, maxDate, lineChart.clientWidth)
483 placeholderlmao.style.left = `${thing}px`
484 yearSpan.style.left = `${thing}px`
485 yearSpan.style.bottom = "-34px"
486 yearSpan.innerText = uniqueYearsArr[index]
487 yearSpan.style.fontFamily = "BarlowSemiCondensed-Regular"
488 yearSpan.style.fontSize = "22px"
489 yearSpan.style.opacity = "0.8"
490 lineChart.appendChild(yearSpan)
491
492 }
493
494 let maxPortals;
495 let minPortals;
496 let precision;
497 let multiplier = 1;
498 for (let index = 0; index < items.length; index++) {
499 precision = Math.floor((items[0].record - items[items.length - 1].record))
500 if (precision > 20) {
501 precision = 20
502 }
503 minPortals = Math.floor((items[items.length - 1].record) / 10) * 10
504 if (index == 0) {
505 maxPortals = items[index].record - minPortals
506 }
507 }
508 function calculateMultiplier(value) {
509 while (value > precision) {
510 multiplier += 1;
511 value -= precision;
512 }
513 }
514 calculateMultiplier(items[0].record);
515 // if (items[0].record > 10) {
516 // multiplier = 2;
517 // }
518
519 // Original cubic bezier control points
520 const P0 = { x: 0, y: 0 };
521 const P1 = { x: 0.26, y: 1 };
522 const P2 = { x: 0.74, y: 1 };
523 const P3 = { x: 1, y: 0 };
524
525 function calculateIntermediateControlPoints(t, P0, P1, P2, P3) {
526 const x = (1 - t) ** 3 * P0.x +
527 3 * (1 - t) ** 2 * t * P1.x +
528 3 * (1 - t) * t ** 2 * P2.x +
529 t ** 3 * P3.x;
530
531 const y = (1 - t) ** 3 * P0.y +
532 3 * (1 - t) ** 2 * t * P1.y +
533 3 * (1 - t) * t ** 2 * P2.y +
534 t ** 3 * P3.y;
535
536 return { x, y };
537 }
538
539
540 let delay = 0;
541 for (let index = 0; index < items.length; index++) {
542 let chart_height = 340;
543 const item = items[index];
544 delay += 0.05;
545 // console.log(lineChart.clientWidth)
546
547 // maxPortals++;
548 // maxPortals++;
549
550 let point_height = (chart_height / maxPortals)
551
552 for (let index = 0; index < (maxPortals / multiplier); index++) {
553 // console.log((index + 1) * multiplier)
554 let current_portal_count = (index + 1);
555
556 const placeholderDiv = document.createElement("div")
557 const numPortalsText = document.createElement("span")
558 const numPortalsTextBottom = document.createElement("span")
559 numPortalsText.innerText = (current_portal_count * multiplier) + minPortals
560 numPortalsTextBottom.innerText = minPortals
561 placeholderDiv.style.position = "absolute"
562 numPortalsText.style.position = "absolute"
563 numPortalsTextBottom.style.position = "absolute"
564 numPortalsText.style.left = "-37px"
565 numPortalsText.style.opacity = "0.2"
566 numPortalsTextBottom.style.opacity = "0.2"
567 numPortalsText.style.fontFamily = "BarlowSemiCondensed-Regular"
568 numPortalsTextBottom.style.fontFamily = "BarlowSemiCondensed-Regular"
569 numPortalsText.style.fontSize = "22px"
570 numPortalsTextBottom.style.left = "-37px"
571 numPortalsTextBottom.style.fontSize = "22px"
572 numPortalsTextBottom.style.fontWeight = "400"
573 numPortalsText.style.color = "#CDCFDF"
574 numPortalsTextBottom.style.color = "#CDCFDF"
575 numPortalsText.style.fontFamily = "inherit"
576 numPortalsTextBottom.style.fontFamily = "inherit"
577 numPortalsText.style.textAlign = "right"
578 numPortalsTextBottom.style.textAlign = "right"
579 numPortalsText.style.width = "30px"
580 numPortalsTextBottom.style.width = "30px"
581 placeholderDiv.style.bottom = `${(point_height * current_portal_count * multiplier) - 2}px`
582 numPortalsText.style.bottom = `${(point_height * current_portal_count * multiplier) - 2 - 9}px`
583 numPortalsTextBottom.style.bottom = `${0 - 2 - 8}px`
584 placeholderDiv.id = placeholderDiv.style.bottom
585 placeholderDiv.style.width = "100%"
586 placeholderDiv.style.height = "2px"
587 placeholderDiv.style.backgroundColor = "#2B2E46"
588 placeholderDiv.style.zIndex = "0"
589
590 if (index == 0) {
591 lineChart.appendChild(numPortalsTextBottom)
592 }
593 lineChart.appendChild(numPortalsText)
594 lineChart.appendChild(placeholderDiv)
595 }
596
597 const li = document.createElement("li");
598 const lineSeg = document.createElement("div");
599 const dataPoint = document.createElement("div");
600
601 li.style = `--y: ${point_height * (item.record - minPortals) - 3}px; --x: ${item.x}px`;
602 lineSeg.className = "line-segment";
603 dataPoint.className = "data-point";
604
605 if (items[index + 1] !== undefined) {
606 const hypotenuse = Math.sqrt(
607 Math.pow(items[index + 1].x - items[index].x, 2) +
608 Math.pow((point_height * items[index + 1].record) - point_height * item.record, 2)
609 );
610 const angle = Math.asin(
611 ((point_height * item.record) - (point_height * items[index + 1].record)) / hypotenuse
612 );
613
614 lineSeg.style = `--hypotenuse: ${hypotenuse}; --angle: ${angle * (-180 / Math.PI)}`;
615 const t0 = index / items.length;
616 const t1 = (index + 1) / items.length
617
618 const P0t0 = calculateIntermediateControlPoints(t0, P0, P1, P2, P3);
619 const P1t1 = calculateIntermediateControlPoints(t1, P0, P1, P2, P3);
620 const bezierStyle = `cubic-bezier(${P0t0.x.toFixed(3)}, ${P0t0.y.toFixed(3)}, ${P1t1.x.toFixed(3)}, ${P1t1.y.toFixed(3)})`
621 lineSeg.style.animationTimingFunction = bezierStyle
622 lineSeg.style.animationDelay = delay + "s"
623 }
624 dataPoint.style.animationDelay = delay + "s"
625
626 let isHoveringOverData = false;
627 let isDataActive = false;
628 document.querySelector("#dataPointInfo").style.left = item.x + "px";
629 document.querySelector("#dataPointInfo").style.bottom = (point_height * item.record -3) + "px";
630 dataPoint.addEventListener("mouseenter", (e) => {
631 isDataActive = true;
632 isHoveringOverData = true;
633 const dataPoints = document.querySelectorAll(".data-point")
634 dataPoints.forEach(point => {
635 point.classList.remove("data-point-active")
636 });
637 dataPoint.classList.add("data-point-active")
638 document.querySelector("#dataPointRecord").innerText = item.record;
639 document.querySelector("#dataPointMap").innerText = item.map;
640 document.querySelector("#dataPointDate").innerText = item.date.toLocaleDateString("en-GB");
641 document.querySelector("#dataPointFirst").innerText = item.first;
642 if ((lineChart.clientWidth - 400) < item.x) {
643 document.querySelector("#dataPointInfo").style.left = item.x - 400 + "px";
644 } else {
645 document.querySelector("#dataPointInfo").style.left = item.x + "px";
646 }
647 if ((lineChart.clientHeight - 115) < (point_height * (item.record - minPortals) -3)) {
648 document.querySelector("#dataPointInfo").style.bottom = (point_height * (item.record - minPortals) -3) - 115 + "px";
649 } else {
650 document.querySelector("#dataPointInfo").style.bottom = (point_height * (item.record - minPortals) -3) + "px";
651 }
652 document.querySelector("#dataPointInfo").style.opacity = "1";
653 document.querySelector("#dataPointInfo").style.zIndex = "10";
654 });
655 document.querySelector("#dataPointInfo").addEventListener("mouseenter", (e) => {
656 isHoveringOverData = true;
657 })
658 document.querySelector("#dataPointInfo").addEventListener("mouseleave", (e) => {
659 isHoveringOverData = false;
660 })
661 document.addEventListener("mousedown", () => {
662 if (!isHoveringOverData) {
663 isDataActive = false
664 dataPoint.classList.remove("data-point-active")
665 document.querySelector("#dataPointInfo").style.opacity = "0";
666 document.querySelector("#dataPointInfo").style.zIndex = "0";
667 }
668 })
669 dataPoint.addEventListener("mouseenter", (e) => {
670 isHoveringOverData = false;
671 })
672 document.querySelector(".chart").addEventListener("mouseleave", () => {
673 isDataActive = false
674 dataPoint.classList.remove("data-point-active")
675 document.querySelector("#dataPointInfo").style.opacity = "0";
676 document.querySelector("#dataPointInfo").style.zIndex = "0";
677 })
678
679 li.appendChild(lineSeg);
680 li.appendChild(dataPoint);
681 lineChart.appendChild(li);
682 }
683 }
684
685 createGraph()
686
687 async function fetchGames() {
688 try {
689 const response = await fetch("https://lp.ardapektezol.com/api/v1/games", {
690 headers: {
691 'Authorization': token
692 }
693 });
694
695 const data = await response.json();
696
697 const gameImg = document.querySelector(".game-img");
698
699 gameImg.style.backgroundImage = `url(${data.data[0].image})`;
700
701 // const mapImg = document.querySelectorAll(".maplist-img");
702 // mapImg.forEach((map) => {
703 // map.style.backgroundImage = `url(${data.data[0].image})`;
704 // });
705
706 } catch (error) {
707 console.log("error fetching games:", error);
708 }
709 }
710
711 detectGame();
712
713 const maplistImg = document.querySelector("#maplistImg");
714 maplistImg.src = img5;
715 const statisticsImg = document.querySelector("#statisticsImg");
716 statisticsImg.src = img6;
717
718 fetchGames();
719
720 const handleResize = (entries) => {
721 for (let entry of entries) {
722 lineChart.innerHTML = ""
723 createGraph()
724 if (document.querySelector(".maplist").getAttribute("currentTab") == "stats") {
725 document.querySelector(".stats").style.display = "block"
726 } else {
727 document.querySelector(".stats").style.display = "none"
728 }
729 }
730 };
731
732 const resizeObserver = new ResizeObserver(handleResize);
733
734 // if (scrollRef.current) {
735 // //hi
736 // if (new URLSearchParams(new URL(window.location.href).search).get("chapter")) {
737 // setTimeout(() => {
738 // scrollRef.current.scrollIntoView({ behavior: "smooth", block: "start" })
739 // }, 200);
740 // }
741
742 // }
743
744 if (divRef.current) {
745 resizeObserver.observe(divRef.current);
746 }
747
748 return () => {
749 if (divRef.current) {
750 resizeObserver.unobserve(divRef.current);
751 }
752 resizeObserver.disconnect();
753 };
754
755
756 })
757 return (
758 <div ref={divRef} className='maplist-page'>
759 <div className='maplist-page-content'>
760 <section className='maplist-page-header'>
761 <Link to='/games'><button className='nav-btn'>
762 <i className='triangle'></i>
763 <span>Games list</span>
764 </button></Link>
765 <span><b id='gameTitle'>&nbsp;</b></span>
766 </section>
767
768 <div className='game'>
769 <div className='game-header'>
770 <div className='game-img'></div>
771 <div className='game-header-text'>
772 <span><b id='catPortalCount'>0</b></span>
773 <span>portals</span>
774 </div>
775 </div>
776
777 <div className='game-nav'>
778 </div>
779 </div>
780
781 <div className='gameview-nav'>
782 <button id='maplistBtn' onClick={() => {changeMaplistOrStatistics(0, "maplist")}} className='game-nav-btn selected'>
783 <img id='maplistImg'/>
784 <span>Map List</span>
785 </button>
786 <button id='maplistBtn' onClick={() => changeMaplistOrStatistics(1, "stats")} className='game-nav-btn'>
787 <img id='statisticsImg'/>
788 <span>Statistics</span>
789 </button>
790 </div>
791
792 <div ref={scrollRef} className='maplist'>
793 <div className='chapter'>
794 <span className='chapter-num'>undefined</span><br/>
795 <span className='chapter-name'>undefined</span>
796
797 <div className='chapter-page-div'>
798 <button id='pageChanger' onClick={() => { currentPage--; currentPage < minPage ? currentPage = minPage : changePage(currentPage); }}>
799 <i className='triangle'></i>
800 </button>
801 <span id='pageNumbers'>0/0</span>
802 <button id='pageChanger' onClick={() => { currentPage++; currentPage > maxPage ? currentPage = maxPage : changePage(currentPage); }}>
803 <i style={{ transform: "rotate(180deg)" }} className='triangle'></i>
804 </button>
805 </div>
806
807 <div className='maplist-maps'>
808 </div>
809 </div>
810 </div>
811
812 <div style={{display: "block"}} className='stats'>
813 <div className='portalcount-over-time-div'>
814 <span className='graph-title'>Portal count over time</span><br/>
815
816 <div className='portalcount-graph'>
817 <figure className='chart'>
818 <div style={{display: "block"}}></div>
819 <div id="dataPointInfo">
820 <div className='section-header'>
821 <span className='header-title'>Date</span>
822 <span className='header-title'>Map</span>
823 <span className='header-title'>Record</span>
824 <span className='header-title'>First completion</span>
825 </div>
826 <div className='divider'></div>
827 <div className='section-data'>
828 <span id='dataPointDate'></span>
829 <span id='dataPointMap'></span>
830 <span id='dataPointRecord'></span>
831 <span id='dataPointFirst'>Hello</span>
832 </div>
833 </div>
834 <ul className='line-chart'>
835
836 </ul>
837 </figure>
838 </div>
839 </div>
840 </div>
841 </div>
842 </div>
843 )
844} \ No newline at end of file
diff --git a/frontend/src/components/pages/summary.css b/frontend/src/components/pages/summary.css
index c99a0bf..8c6ec35 100644
--- a/frontend/src/components/pages/summary.css
+++ b/frontend/src/components/pages/summary.css
@@ -35,8 +35,8 @@
35 border: none; 35 border: none;
36 transition: background-color .1s; 36 transition: background-color .1s;
37} 37}
38#section1>div>.nav-button:nth-child(1){border-radius: 20px 0 0 20px;} 38/* #section1>div>.nav-button:nth-child(1){border-radius: 0px;}:nth-child(1){border-radius: 20px 0 0 20px;}
39#section1>div>.nav-button:nth-child(2){border-radius: 0 20px 20px 0;margin-left: 2px;} 39#section1>div>.nav-button:nth-child(2){border-radius: 0 20px 20px 0;margin-left: 2px;} */
40.nav-button>span{padding: 0 8px 0 8px;} 40.nav-button>span{padding: 0 8px 0 8px;}
41.nav-button:hover{background-color: #202232;cursor: pointer;} 41.nav-button:hover{background-color: #202232;cursor: pointer;}
42 42
diff --git a/frontend/src/components/pages/summary.js b/frontend/src/components/pages/summary.js
index 0bd26af..9957c5d 100644
--- a/frontend/src/components/pages/summary.js
+++ b/frontend/src/components/pages/summary.js
@@ -1,5 +1,5 @@
1import React from 'react'; 1import React, { useEffect } from 'react';
2import { useLocation } from "react-router-dom"; 2import { useLocation, Link } from "react-router-dom";
3import ReactMarkdown from 'react-markdown' 3import ReactMarkdown from 'react-markdown'
4 4
5import "./summary.css"; 5import "./summary.css";
@@ -317,8 +317,18 @@ if(window.confirm(`Are you sure you want to remove post: ${post.title}?`)){
317} 317}
318 318
319 319
320
321if(data!==null){ 320if(data!==null){
321
322let current_chapter = data.map.chapter_name
323let isCoop = false;
324if (data.map.game_name == "Portal 2 - Cooperative") {
325 isCoop = true
326}
327
328current_chapter = data.map.chapter_name.split(" ")
329// current_chapter = current_chapter.split("-")
330current_chapter = current_chapter[1]
331
322return ( 332return (
323 <> 333 <>
324 {token!==null?mod===true?<Modview selectedRun={selectedRun} data={data} token={token}/>:"":""} 334 {token!==null?mod===true?<Modview selectedRun={selectedRun} data={data} token={token}/>:"":""}
@@ -329,8 +339,8 @@ return (
329 <main> 339 <main>
330 <section id='section1' className='summary1'> 340 <section id='section1' className='summary1'>
331 <div> 341 <div>
332 <button className='nav-button'><i className='triangle'></i><span>{data.map.game_name}</span></button> 342 <Link to="/games"><button className='nav-button' style={{borderRadius: "20px 0px 0px 20px"}}><i className='triangle'></i><span>Games list</span></button></Link>
333 <button className='nav-button'><i className='triangle'></i><span>{data.map.chapter_name}</span></button> 343 <Link to={`/games/${data.map.game_name == "Portal 2 - Singleplayer" ? "game?game=1" : data.map.game_name == "Portal 2 - Cooperative" ? "game?game=2" : "psm"}&chapter=${current_chapter}`}><button className='nav-button' style={{borderRadius: "0px 20px 20px 0px", marginLeft: "2px"}}><i className='triangle'></i><span>{data.map.chapter_name}</span></button></Link>
334 <br/><span><b>{data.map.map_name}</b></span> 344 <br/><span><b>{data.map.map_name}</b></span>
335 </div> 345 </div>
336 346
diff --git a/frontend/src/components/record.css b/frontend/src/components/record.css
new file mode 100644
index 0000000..60d47ee
--- /dev/null
+++ b/frontend/src/components/record.css
@@ -0,0 +1,15 @@
1.record-container {
2 --padding: 20px;
3 width: calc(100% - calc(var(--padding * 2)));
4 height: 42px;
5 background-color: #2B2E46;
6 border-radius: 200px;
7 font-size: 18px;
8 display: grid;
9 grid-template-columns: 20% 25% 15% 15% 25%;
10 text-align: center;
11 padding: 0px var(--padding);
12 vertical-align: middle;
13 align-items: center;
14 margin-bottom: 6px;
15} \ No newline at end of file
diff --git a/frontend/src/components/record.js b/frontend/src/components/record.js
new file mode 100644
index 0000000..a01109c
--- /dev/null
+++ b/frontend/src/components/record.js
@@ -0,0 +1,57 @@
1import React, { useEffect, useRef, useState } from 'react';
2import { useLocation, Link } from "react-router-dom";
3
4import "./record.css"
5
6export default function Record({ name, place, portals, time, date }) {
7 // const {token} = prop;
8 const [record, setRecord] = useState(null);
9 const location = useLocation();
10
11 useEffect(() => {
12 console.log(name, place, portals, time, date);
13 })
14
15 function timeSince() {
16 const now = new Date();
17 const dateNew = new Date(date);
18
19 const secondsPast = Math.floor((now - dateNew) / 1000);
20 console.log('Seconds past:', secondsPast);
21
22 if (secondsPast < 60) {
23 return `${secondsPast} seconds ago`;
24 }
25 if (secondsPast < 3600) {
26 const minutes = Math.floor(secondsPast / 60);
27 return `${minutes} minutes ago`;
28 }
29 if (secondsPast < 86400) {
30 const hours = Math.floor(secondsPast / 3600);
31 return `${hours} hours ago`;
32 }
33 if (secondsPast < 2592000) {
34 const days = Math.floor(secondsPast / 86400);
35 return `${days} days ago`;
36 }
37 if (secondsPast < 31536000) {
38 const months = Math.floor(secondsPast / 2592000);
39 return `${months} months ago`;
40 }
41 const years = Math.floor(secondsPast / 31536000);
42 return `${years} years ago`;
43 }
44
45 return(
46 <div className='record-container'>
47 <span>{place}</span>
48 <div style={{display: "flex", alignItems: "center"}}>
49 <img style={{height: "40px", borderRadius: "200px"}} src="https://avatars.steamstatic.com/32d110951da2339d8b8d8419bc945d9a2b150b2a_full.jpg"></img>
50 <span style={{paddingLeft: "5px", fontFamily: "BarlowSemiCondensed-SemiBold"}}>{name}</span>
51 </div>
52 <span style={{fontFamily: "BarlowCondensed-Bold", color: "#D980FF"}}>{portals}</span>
53 <span>{time}</span>
54 <span>{timeSince()}</span>
55 </div>
56 )
57}
diff --git a/frontend/src/components/sidebar.css b/frontend/src/components/sidebar.css
index 29978db..34ede80 100644
--- a/frontend/src/components/sidebar.css
+++ b/frontend/src/components/sidebar.css
@@ -15,6 +15,8 @@
15 15
16 height: 80px; 16 height: 80px;
17 padding: 20px 0 20px 30px; 17 padding: 20px 0 20px 30px;
18 cursor: pointer;
19 user-select: none;
18} 20}
19 21
20#logo-text{ 22#logo-text{
@@ -75,11 +77,24 @@ span>b{
75 width: 310px; 77 width: 310px;
76 height: 40px; 78 height: 40px;
77 border-radius: 20px; 79 border-radius: 20px;
78 padding: 5px 0 0 6px; 80 padding: 0.4em 0 0 11px;
79 81
80 transition: 82 transition:
81 width .3s, 83 width .3s,
82 background-color .3s; 84 background-color .15s,
85 padding .3s;
86}
87
88.sidebar-button-selected {
89 background-color: #202232;
90}
91
92.sidebar-button-deselected {
93 background-color: #20223200;
94}
95
96.sidebar-button-deselected:hover {
97 background-color: #202232aa;
83} 98}
84 99
85button>img { 100button>img {
@@ -125,6 +140,7 @@ a{text-decoration: none;height: 40px;}
125 margin: 8px 0 8px 0; 140 margin: 8px 0 8px 0;
126 overflow-y: auto; 141 overflow-y: auto;
127 max-height: calc(100vh - 172px); 142 max-height: calc(100vh - 172px);
143 scrollbar-width: thin;
128} 144}
129#search-data::-webkit-scrollbar{display: none;} 145#search-data::-webkit-scrollbar{display: none;}
130.search-map{ 146.search-map{
@@ -140,6 +156,7 @@ a{text-decoration: none;height: 40px;}
140 transition: background-color .1s; 156 transition: background-color .1s;
141 background-color: #2b2e46; 157 background-color: #2b2e46;
142 grid-template-rows: 20% 20% 60%; 158 grid-template-rows: 20% 20% 60%;
159 width: calc(100% - 15px);
143} 160}
144.search-map>span{ 161.search-map>span{
145 color: #888; 162 color: #888;
diff --git a/frontend/src/components/sidebar.js b/frontend/src/components/sidebar.js
index 29859ab..6777867 100644
--- a/frontend/src/components/sidebar.js
+++ b/frontend/src/components/sidebar.js
@@ -1,4 +1,4 @@
1import React from 'react'; 1import React, { useEffect } from 'react';
2import { Link, useLocation } from "react-router-dom"; 2import { Link, useLocation } from "react-router-dom";
3 3
4import "../App.css" 4import "../App.css"
@@ -18,6 +18,7 @@ import Login from "./login.js"
18export default function Sidebar(prop) { 18export default function Sidebar(prop) {
19const {token,setToken} = prop 19const {token,setToken} = prop
20const [profile, setProfile] = React.useState(null); 20const [profile, setProfile] = React.useState(null);
21
21React.useEffect(() => { 22React.useEffect(() => {
22 fetch(`https://lp.ardapektezol.com/api/v1/profile`,{ 23 fetch(`https://lp.ardapektezol.com/api/v1/profile`,{
23 headers: { 24 headers: {
@@ -28,7 +29,6 @@ React.useEffect(() => {
28 .then(d => setProfile(d.data)) 29 .then(d => setProfile(d.data))
29 }, [token]); 30 }, [token]);
30 31
31
32// Locks search button for 300ms before it can be clicked again, prevents spam 32// Locks search button for 300ms before it can be clicked again, prevents spam
33const [isLocked, setIsLocked] = React.useState(false); 33const [isLocked, setIsLocked] = React.useState(false);
34function HandleLock(arg) { 34function HandleLock(arg) {
@@ -40,34 +40,38 @@ if (!isLocked) {
40} 40}
41 41
42 42
43// The menu button
44const [sidebar, setSidebar] = React.useState();
45
43// Clicked buttons 46// Clicked buttons
44function SidebarClick(x){ 47function SidebarClick(x){
45const btn = document.querySelectorAll("button.sidebar-button"); 48const btn = document.querySelectorAll("button.sidebar-button");
46 49
47if(sidebar===1){setSidebar(0);SidebarHide()} 50if(sidebar===1){setSidebar(0);SidebarHide()}
48 51
52// clusterfuck
49btn.forEach((e,i) =>{ 53btn.forEach((e,i) =>{
50 btn[i].style.backgroundColor="inherit" 54 btn[i].classList.remove("sidebar-button-selected")
55 btn[i].classList.add("sidebar-button-deselected")
51}) 56})
52btn[x].style.backgroundColor="#202232" 57btn[x].classList.add("sidebar-button-selected")
58btn[x].classList.remove("sidebar-button-deselected")
53 59
54} 60}
55 61
56
57// The menu button
58const [sidebar, setSidebar] = React.useState();
59
60function SidebarHide(){ 62function SidebarHide(){
61const btn = document.querySelectorAll("button.sidebar-button") 63const btn = document.querySelectorAll("button.sidebar-button")
62const span = document.querySelectorAll("button.sidebar-button>span"); 64const span = document.querySelectorAll("button.sidebar-button>span");
63const side = document.querySelector("#sidebar-list"); 65const side = document.querySelector("#sidebar-list");
64const login = document.querySelectorAll(".login>button")[1]; 66const login = document.querySelectorAll(".login>button")[1];
67const searchbar = document.querySelector("#searchbar");
65 68
66if(sidebar===1){ 69if(sidebar===1){
67 setSidebar(0) 70 setSidebar(0)
68 side.style.width="320px" 71 side.style.width="320px"
69 btn.forEach((e, i) =>{ 72 btn.forEach((e, i) =>{
70 e.style.width="310px" 73 e.style.width="310px"
74 e.style.padding = "0.4em 0 0 11px"
71 setTimeout(() => { 75 setTimeout(() => {
72 span[i].style.opacity="1" 76 span[i].style.opacity="1"
73 login.style.opacity="1" 77 login.style.opacity="1"
@@ -77,28 +81,29 @@ if(sidebar===1){
77 side.style.zIndex="2" 81 side.style.zIndex="2"
78} else { 82} else {
79 side.style.width="40px"; 83 side.style.width="40px";
84 searchbar.focus();
85 setSearch(searchbar.value)
80 setSidebar(1) 86 setSidebar(1)
81 btn.forEach((e,i) =>{ 87 btn.forEach((e,i) =>{
82 e.style.width="40px" 88 e.style.width="40px"
89 e.style.padding = "0.4em 0 0 5px"
83 span[i].style.opacity="0" 90 span[i].style.opacity="0"
84 }) 91 })
85 login.style.opacity="0" 92 login.style.opacity="0"
86 setTimeout(() => { 93 setTimeout(() => {
87 side.style.zIndex="0" 94 side.style.zIndex="0"
88 }, 300); 95 }, 300);
89 } 96 }
90} 97}
91
92// Links 98// Links
93const location = useLocation() 99const location = useLocation()
94React.useEffect(()=>{ 100React.useEffect(()=>{
95 if(location.pathname==="/"){SidebarClick(1)} 101 if(location.pathname==="/"){SidebarClick(1)}
96 if(location.pathname.includes("news")){SidebarClick(2)} 102 if(location.pathname.includes("news")){SidebarClick(2)}
97 if(location.pathname.includes("records")){SidebarClick(3)} 103 if(location.pathname.includes("games")){SidebarClick(3)}
98 if(location.pathname.includes("leaderboards")){SidebarClick(4)} 104 if(location.pathname.includes("leaderboards")){SidebarClick(4)}
99 if(location.pathname.includes("discussions")){SidebarClick(5)} 105 if(location.pathname.includes("scorelog")){SidebarClick(5)}
100 if(location.pathname.includes("scorelog")){SidebarClick(6)} 106 if(location.pathname.includes("profile")){SidebarClick(6)}
101 if(location.pathname.includes("profile")){SidebarClick(7)}
102 if(location.pathname.includes("rules")){SidebarClick(9)} 107 if(location.pathname.includes("rules")){SidebarClick(9)}
103 if(location.pathname.includes("about")){SidebarClick(10)} 108 if(location.pathname.includes("about")){SidebarClick(10)}
104 109
@@ -107,6 +112,7 @@ React.useEffect(()=>{
107 112
108const [search,setSearch] = React.useState(null) 113const [search,setSearch] = React.useState(null)
109const [searchData,setSearchData] = React.useState(null) 114const [searchData,setSearchData] = React.useState(null)
115
110React.useEffect(()=>{ 116React.useEffect(()=>{
111 fetch(`https://lp.ardapektezol.com/api/v1/search?q=${search}`) 117 fetch(`https://lp.ardapektezol.com/api/v1/search?q=${search}`)
112 .then(r=>r.json()) 118 .then(r=>r.json())
@@ -139,18 +145,14 @@ return (
139 <button className='sidebar-button'><img src={img3} alt="" /><span>News</span></button> 145 <button className='sidebar-button'><img src={img3} alt="" /><span>News</span></button>
140 </Link> 146 </Link>
141 147
142 <Link to="/records" tabIndex={-1}> 148 <Link to="/games" tabIndex={-1}>
143 <button className='sidebar-button'><img src={img4} alt="" /><span>Records</span></button> 149 <button className='sidebar-button'><img src={img4} alt="" /><span>Games</span></button>
144 </Link> 150 </Link>
145 151
146 <Link to="/leaderboards" tabIndex={-1}> 152 <Link to="/leaderboards" tabIndex={-1}>
147 <button className='sidebar-button'><img src={img5} alt="" /><span>Leaderboards</span></button> 153 <button className='sidebar-button'><img src={img5} alt="" /><span>Leaderboards</span></button>
148 </Link> 154 </Link>
149 155
150 <Link to="/discussions" tabIndex={-1}>
151 <button className='sidebar-button'><img src={img6} alt="" /><span>Discussions</span></button>
152 </Link>
153
154 <Link to="/scorelog" tabIndex={-1}> 156 <Link to="/scorelog" tabIndex={-1}>
155 <button className='sidebar-button'><img src={img7} alt="" /><span>Score&nbsp;Logs</span></button> 157 <button className='sidebar-button'><img src={img7} alt="" /><span>Score&nbsp;Logs</span></button>
156 </Link> 158 </Link>
diff --git a/frontend/src/fonts/BarlowSemiCondensed-SemiBold.ttf b/frontend/src/fonts/BarlowSemiCondensed-SemiBold.ttf
new file mode 100644
index 0000000..7d737f2
--- /dev/null
+++ b/frontend/src/fonts/BarlowSemiCondensed-SemiBold.ttf
Binary files differ
diff --git a/frontend/src/index.js b/frontend/src/index.js
index 20f2cdc..f648298 100644
--- a/frontend/src/index.js
+++ b/frontend/src/index.js
@@ -4,7 +4,5 @@ import App from './App';
4 4
5const root = ReactDOM.createRoot(document.getElementById('root')); 5const root = ReactDOM.createRoot(document.getElementById('root'));
6root.render( 6root.render(
7 <React.StrictMode>
8 <App/> 7 <App/>
9 </React.StrictMode>
10); 8);