init
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2
|
||||
}
|
40
index.html
Normal file
40
index.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/webstorm-icon-logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>WS playground</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="logo">
|
||||
<a href="https://www.jetbrains.com/webstorm/">
|
||||
<img src="/webstorm-logo.svg" alt="WebStorm logo" width="344"
|
||||
height="90">
|
||||
</a>
|
||||
</header>
|
||||
<main id="app">
|
||||
<h1 class="title">
|
||||
Thank you for trying it out. <br /> Your first project is up and running
|
||||
now.
|
||||
</h1>
|
||||
<section class="counter">
|
||||
<div class="counter-info">
|
||||
<p class="counter-text">Counter is</p>
|
||||
<p class="counter-value" id="counter-value"></p>
|
||||
</div>
|
||||
<div class="counter-interaction">
|
||||
<button id="increaseByOne" type="button">+1</button>
|
||||
<button id="increaseByTwo" type="button">+2</button>
|
||||
<button id="decreaseByOne" type="button">-1</button>
|
||||
<button id="decreaseByTwo" type="button">-2</button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<footer class="technologies">
|
||||
<img src="/technologies.svg" alt="List of supported technologies">
|
||||
</footer>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
18
package.json
Normal file
18
package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "PointTracker",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"socket.io": "^4.6.1",
|
||||
"cors": "^2.8.5",
|
||||
"bcrypt": "^5.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
}
|
||||
}
|
60
public/background.svg
Normal file
60
public/background.svg
Normal file
@@ -0,0 +1,60 @@
|
||||
<svg width="1920" height="494" viewBox="0 0 1920 494" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_b_201_11645)">
|
||||
<path d="M-555.158 -935.552L206.014 299.32C272.103 406.538 412.596 439.88 519.815 373.791L1754.69 -387.38C1861.9 -453.469 1895.25 -593.963 1829.16 -701.181L1067.99 -1936.05C1001.9 -2043.27 861.404 -2076.61 754.186 -2010.52L-480.686 -1249.35C-587.905 -1183.26 -621.247 -1042.77 -555.158 -935.552Z" fill="url(#paint0_linear_201_11645)"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_b_201_11645)">
|
||||
<path d="M-365.85 -978.718L155.699 157.844C200.984 256.531 317.698 299.822 416.385 254.536L1552.95 -267.013C1651.63 -312.299 1694.93 -429.012 1649.64 -527.699L1128.09 -1664.26C1082.81 -1762.95 966.092 -1806.24 867.404 -1760.95L-269.158 -1239.4C-367.845 -1194.12 -411.136 -1077.41 -365.85 -978.718Z" fill="url(#paint1_linear_201_11645)"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_b_201_11645)">
|
||||
<path d="M-199.592 -995.206L127.265 32.0959C155.644 121.29 250.956 170.59 340.15 142.211L1367.45 -184.646C1456.65 -213.025 1505.95 -308.337 1477.57 -397.531L1150.71 -1424.83C1122.33 -1514.03 1027.02 -1563.33 937.825 -1534.95L-89.4769 -1208.09C-178.671 -1179.71 -227.971 -1084.4 -199.592 -995.206Z" fill="url(#paint2_linear_201_11645)"/>
|
||||
</g>
|
||||
<g filter="url(#filter3_b_201_11645)">
|
||||
<path d="M-55.5683 -991.823L116.176 -78.4863C131.088 0.816985 207.465 53.0162 286.768 38.104L1200.1 -133.64C1279.41 -148.552 1331.61 -224.929 1316.7 -304.232L1144.95 -1217.57C1130.04 -1296.87 1053.66 -1349.07 974.359 -1334.16L61.022 -1162.42C-18.2812 -1147.5 -70.4805 -1071.13 -55.5683 -991.823Z" fill="url(#paint3_linear_201_11645)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_b_201_11645" x="-594.982" y="-2050.35" width="2463.96" height="2463.96" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feGaussianBlur in="BackgroundImageFix" stdDeviation="2.93731"/>
|
||||
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_201_11645"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_201_11645" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_b_201_11645" x="-389.688" y="-1784.79" width="2063.16" height="2063.16" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feGaussianBlur in="BackgroundImageFix" stdDeviation="2.93731"/>
|
||||
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_201_11645"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_201_11645" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter2_b_201_11645" x="-213.49" y="-1548.85" width="1704.95" height="1704.95" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feGaussianBlur in="BackgroundImageFix" stdDeviation="2.93731"/>
|
||||
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_201_11645"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_201_11645" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter3_b_201_11645" x="-63.986" y="-1342.58" width="1389.1" height="1389.1" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feGaussianBlur in="BackgroundImageFix" stdDeviation="2.93731"/>
|
||||
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_201_11645"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_201_11645" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_201_11645" x1="850.924" y1="-972.878" x2="1824.39" y2="-447.912" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#19191C" stop-opacity="0"/>
|
||||
<stop offset="0.33" stop-color="#129CFF" stop-opacity="0.15"/>
|
||||
<stop offset="0.9" stop-color="#00D1FF" stop-opacity="0.55"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_201_11645" x1="841.17" y1="-862.941" x2="1618.95" y2="-311.486" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#19191C" stop-opacity="0"/>
|
||||
<stop offset="0.33" stop-color="#129CFF" stop-opacity="0.15"/>
|
||||
<stop offset="0.9" stop-color="#00D1FF" stop-opacity="0.55"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_201_11645" x1="821.024" y1="-769.324" x2="1428.59" y2="-215.763" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#19191C" stop-opacity="0"/>
|
||||
<stop offset="0.33" stop-color="#129CFF" stop-opacity="0.15"/>
|
||||
<stop offset="0.9" stop-color="#00D1FF" stop-opacity="0.55"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_201_11645" x1="793.987" y1="-691.327" x2="1255.7" y2="-153.842" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#19191C" stop-opacity="0"/>
|
||||
<stop offset="0.33" stop-color="#129CFF" stop-opacity="0.15"/>
|
||||
<stop offset="0.9" stop-color="#00D1FF" stop-opacity="0.55"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/fonts/JetBrainsMono-Regular.woff2
Normal file
BIN
public/fonts/JetBrainsMono-Regular.woff2
Normal file
Binary file not shown.
BIN
public/fonts/JetBrainsSans-Regular.woff2
Normal file
BIN
public/fonts/JetBrainsSans-Regular.woff2
Normal file
Binary file not shown.
1302
public/index.html
Normal file
1302
public/index.html
Normal file
File diff suppressed because it is too large
Load Diff
1
public/javascript.svg
Normal file
1
public/javascript.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path fill="#F7DF1E" d="M0 0h256v256H0V0Z"></path><path d="m67.312 213.932l19.59-11.856c3.78 6.701 7.218 12.371 15.465 12.371c7.905 0 12.89-3.092 12.89-15.12v-81.798h24.057v82.138c0 24.917-14.606 36.259-35.916 36.259c-19.245 0-30.416-9.967-36.087-21.996m85.07-2.576l19.588-11.341c5.157 8.421 11.859 14.607 23.715 14.607c9.969 0 16.325-4.984 16.325-11.858c0-8.248-6.53-11.17-17.528-15.98l-6.013-2.58c-17.357-7.387-28.87-16.667-28.87-36.257c0-18.044 13.747-31.792 35.228-31.792c15.294 0 26.292 5.328 34.196 19.247l-18.732 12.03c-4.125-7.389-8.591-10.31-15.465-10.31c-7.046 0-11.514 4.468-11.514 10.31c0 7.217 4.468 10.14 14.778 14.608l6.014 2.577c20.45 8.765 31.963 17.7 31.963 37.804c0 21.654-17.012 33.51-39.867 33.51c-22.339 0-36.774-10.654-43.819-24.574"></path></svg>
|
After Width: | Height: | Size: 995 B |
187
public/style.css
Normal file
187
public/style.css
Normal file
@@ -0,0 +1,187 @@
|
||||
@font-face {
|
||||
font-family: "JetBrains Sans";
|
||||
src: url("fonts/JetBrainsSans-Regular.woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "JetBrains Mono";
|
||||
src: url("fonts/JetBrainsMono-Regular.woff2");
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family: "JetBrains Sans", Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-weight: 300;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #000000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
align-items: center;
|
||||
|
||||
background-image: url("background.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 29px;
|
||||
line-height: 39px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-top: 45px;
|
||||
margin-left: 20%;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.counter {
|
||||
font-family: "JetBrains Mono", Inter, system-ui, sans-serif;
|
||||
font-weight: 400;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding: 60px;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.counter-interaction {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px 0;
|
||||
width: 470px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.counter-interaction button {
|
||||
width: 48%;
|
||||
height: 47%;
|
||||
align-content: space-between;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 120px;
|
||||
font-size: 68px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.counter-interaction button:nth-child(2n+1) {
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
.counter-interaction button:disabled {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
background-image: none;
|
||||
border-color: inherit;
|
||||
}
|
||||
|
||||
.counter-info {
|
||||
width: 360px;
|
||||
height: 345px;
|
||||
margin-right: 27px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
background-color: rgba(25, 25, 28, 0.5);
|
||||
border-radius: 29px;
|
||||
}
|
||||
|
||||
.counter-text {
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
margin-top: 60px;
|
||||
margin-bottom: 28px;
|
||||
|
||||
}
|
||||
|
||||
.counter-value {
|
||||
font-weight: 200;
|
||||
font-size: 170px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: "JetBrains Mono", Inter, system-ui, sans-serif;
|
||||
background-color: rgba(25, 25, 28, 0.5);
|
||||
cursor: pointer;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-image: radial-gradient(
|
||||
farthest-corner at 77% 83%,
|
||||
rgba(1, 197, 245, 0.5) 2%,
|
||||
rgba(1, 126, 254, 0.5) 28%,
|
||||
rgba(25, 25, 28, 0) 70%
|
||||
);
|
||||
border: 1px solid rgba(76, 166, 255, 0.2);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.technologies {
|
||||
margin-bottom: 36px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.technologies img {
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 875px) {
|
||||
|
||||
.logo {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.counter {
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.counter-info {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
margin-right: 0;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.counter-interaction {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.counter-interaction button {
|
||||
width: 47%;
|
||||
height: 100px;
|
||||
margin-right: 0;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
22
public/technologies.svg
Normal file
22
public/technologies.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<svg width="1235" height="18" viewBox="0 0 1235 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M461.5 1C462.605 1 463.5 1.89543 463.5 3V15C463.5 16.1046 462.605 17 461.5 17H449.5C448.395 17 447.5 16.1046 447.5 15V3C447.5 1.89543 448.395 1 449.5 1H461.5ZM459.076 14.4534C458.51 14.4534 458.008 14.3508 457.571 14.1456C457.134 13.9366 456.792 13.6479 456.545 13.2792C456.298 12.9068 456.173 12.4812 456.169 12.0024H457.52C457.52 12.2532 457.586 12.4774 457.719 12.675C457.852 12.8726 458.037 13.0265 458.272 13.1367C458.512 13.2431 458.785 13.2963 459.093 13.2963C459.393 13.2963 459.657 13.2469 459.885 13.1481C460.113 13.0493 460.288 12.9106 460.41 12.732C460.535 12.5496 460.598 12.3406 460.598 12.105C460.598 11.8086 460.509 11.554 460.33 11.3412C460.151 11.1246 459.914 10.9916 459.617 10.9423L458.267 10.7142C457.879 10.6458 457.543 10.509 457.258 10.3038C456.973 10.0986 456.752 9.84215 456.596 9.53435C456.444 9.22275 456.368 8.88265 456.368 8.51405C456.368 8.07325 456.482 7.67805 456.71 7.32845C456.942 6.97505 457.263 6.69955 457.674 6.50195C458.084 6.30055 458.548 6.19985 459.065 6.19985C459.593 6.19985 460.062 6.29675 460.472 6.49055C460.887 6.68435 461.21 6.95415 461.441 7.29995C461.673 7.64575 461.791 8.04095 461.795 8.48555H460.444C460.444 8.26895 460.387 8.07515 460.273 7.90415C460.159 7.72935 459.999 7.59445 459.794 7.49945C459.589 7.40445 459.351 7.35695 459.082 7.35695C458.812 7.35695 458.574 7.40255 458.369 7.49375C458.164 7.58495 458.004 7.71415 457.89 7.88135C457.776 8.04855 457.719 8.24235 457.719 8.46275C457.719 8.72115 457.803 8.94535 457.97 9.13535C458.137 9.32155 458.356 9.43745 458.626 9.48305L459.954 9.69965C460.345 9.76805 460.693 9.91435 460.997 10.1385C461.305 10.3627 461.542 10.642 461.709 10.9764C461.877 11.3108 461.96 11.6661 461.96 12.0423C461.96 12.4983 461.839 12.9106 461.595 13.2792C461.352 13.644 461.012 13.9309 460.575 14.1399C460.138 14.3489 459.638 14.4534 459.076 14.4534ZM450.449 13H451.794C452 13 452.18 12.9582 452.336 12.8746C452.496 12.7872 452.617 12.6656 452.701 12.5098C452.788 12.3502 452.832 12.1678 452.832 11.9626L452.832 6.33331H454.166L454.166 12.1445C454.166 12.5739 454.071 12.9539 453.881 13.2845C453.691 13.6151 453.427 13.8735 453.088 14.0597C452.75 14.2421 452.363 14.3333 451.926 14.3333H450.449V13Z" fill="white" fill-opacity="0.4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M507.5 1C508.605 1 509.5 1.89543 509.5 3V15C509.5 16.1046 508.605 17 507.5 17H495.5C494.395 17 493.5 16.1046 493.5 15V3C493.5 1.89543 494.395 1 495.5 1H507.5ZM503.573 14.1463C504.01 14.3515 504.511 14.4541 505.077 14.4541C505.64 14.4541 506.14 14.3496 506.577 14.1406C507.014 13.9316 507.354 13.6447 507.597 13.2799C507.84 12.9113 507.962 12.499 507.962 12.043C507.962 11.6668 507.878 11.3115 507.711 10.9771C507.544 10.6427 507.306 10.3634 506.998 10.1392C506.694 9.91502 506.347 9.76872 505.955 9.70032L504.627 9.48372C504.357 9.43812 504.139 9.32222 503.972 9.13602C503.804 8.94602 503.721 8.72182 503.721 8.46342C503.721 8.24302 503.778 8.04922 503.892 7.88202C504.006 7.71482 504.165 7.58562 504.371 7.49442C504.576 7.40322 504.813 7.35762 505.083 7.35762C505.353 7.35762 505.59 7.40512 505.796 7.50012C506.001 7.59512 506.16 7.73002 506.274 7.90482C506.388 8.07582 506.445 8.26962 506.445 8.48622H507.796C507.793 8.04162 507.675 7.64642 507.443 7.30062C507.211 6.95482 506.888 6.68502 506.474 6.49122C506.064 6.29742 505.594 6.20052 505.066 6.20052C504.549 6.20052 504.086 6.30122 503.675 6.50262C503.265 6.70022 502.944 6.97572 502.712 7.32912C502.484 7.67872 502.37 8.07392 502.37 8.51472C502.37 8.88332 502.446 9.22342 502.598 9.53502C502.754 9.84282 502.974 10.0993 503.259 10.3045C503.544 10.5097 503.88 10.6465 504.268 10.7149L505.619 10.9429C505.915 10.9923 506.153 11.1253 506.331 11.3419C506.51 11.5547 506.599 11.8093 506.599 12.1057C506.599 12.3413 506.537 12.5503 506.411 12.7327C506.29 12.9113 506.115 13.05 505.887 13.1488C505.659 13.2476 505.395 13.297 505.095 13.297C504.787 13.297 504.513 13.2438 504.274 13.1374C504.038 13.0272 503.854 12.8733 503.721 12.6757C503.588 12.4781 503.521 12.2539 503.521 12.0031H502.17C502.174 12.4819 502.3 12.9075 502.547 13.2799C502.794 13.6485 503.136 13.9373 503.573 14.1463ZM501.329 6.3338H495.013V7.66714H497.5V14.3338H498.833V7.66714H501.329V6.3338Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M548.5 10.125C549.121 10.125 549.625 9.62133 549.625 9.00001C549.625 8.37869 549.121 7.87501 548.5 7.87501C547.879 7.87501 547.375 8.37869 547.375 9.00001C547.375 9.62133 547.879 10.125 548.5 10.125Z" fill="white" fill-opacity="0.4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M552.437 2.18008C553.399 2.73513 553.626 4.31061 553.194 6.28984C555.124 6.90494 556.375 7.88988 556.375 9.00001C556.375 10.1101 555.124 11.095 553.194 11.7101C553.627 13.6894 553.399 15.2649 552.438 15.8199C551.476 16.375 549.998 15.7844 548.5 14.4202C547.002 15.7844 545.524 16.375 544.562 15.82C543.601 15.2649 543.373 13.6894 543.806 11.7101C541.876 11.095 540.625 10.1101 540.625 9.00001C540.625 7.88988 541.876 6.90494 543.806 6.28984C543.374 4.31059 543.601 2.73509 544.563 2.18004C545.524 1.62499 547.002 2.21554 548.5 3.57965C549.998 2.21556 551.476 1.62503 552.437 2.18008ZM551.875 14.8457C551.801 14.8884 551.631 14.9453 551.274 14.8523C550.911 14.7575 550.44 14.5249 549.891 14.1149C549.696 13.9689 549.497 13.8048 549.296 13.6234C549.668 13.2169 550.036 12.7661 550.392 12.277C550.993 12.2134 551.568 12.1202 552.106 12.0012C552.163 12.2661 552.205 12.5205 552.234 12.7624C552.315 13.4421 552.281 13.9669 552.181 14.3288C552.083 14.6842 551.949 14.8029 551.875 14.8457ZM548.896 12.3708C548.764 12.5293 548.632 12.6814 548.5 12.8268C548.368 12.6814 548.236 12.5293 548.104 12.3708C548.236 12.3736 548.367 12.375 548.5 12.375C548.633 12.375 548.765 12.3736 548.896 12.3708ZM551.814 10.9134C551.622 10.9551 551.424 10.9934 551.221 11.028C551.289 10.9159 551.356 10.8024 551.423 10.6875C551.489 10.5726 551.554 10.4576 551.617 10.3426C551.689 10.5358 551.754 10.7263 551.814 10.9134ZM550.448 10.125C550.667 9.74726 550.865 9.37106 551.044 8.99984C550.865 8.62873 550.667 8.25265 550.449 7.87499C550.231 7.49729 550.004 7.13731 549.772 6.79671C549.361 6.76615 548.936 6.75001 548.5 6.75001C548.064 6.75001 547.639 6.76615 547.228 6.7967C546.996 7.13731 546.769 7.49731 546.551 7.87503C546.333 8.25268 546.135 8.62875 545.956 8.99985C546.135 9.37105 546.333 9.74723 546.552 10.125C546.77 10.5027 546.996 10.8627 547.228 11.2033C547.639 11.2339 548.064 11.25 548.5 11.25C548.936 11.25 549.361 11.2339 549.772 11.2033C550.004 10.8627 550.23 10.5027 550.448 10.125ZM551.423 7.31249C551.489 7.42733 551.554 7.5422 551.617 7.65703C551.688 7.46396 551.754 7.27362 551.814 7.08657C551.622 7.04491 551.424 7.00663 551.222 6.97201C551.289 7.08411 551.357 7.19761 551.423 7.31249ZM550.392 5.72306C550.994 5.78663 551.568 5.87982 552.106 5.99882C552.162 5.7339 552.205 5.47949 552.234 5.23761C552.315 4.5579 552.28 4.0331 552.181 3.67119C552.083 3.31584 551.949 3.19713 551.875 3.15436C551.801 3.11158 551.631 3.05469 551.274 3.14773C550.911 3.24249 550.439 3.4751 549.891 3.88516C549.696 4.03106 549.497 4.19516 549.296 4.37645C549.668 4.78305 550.036 5.23386 550.392 5.72306ZM552.284 8.99983C552.53 8.44714 552.736 7.90322 552.902 7.37767C553.16 7.46128 553.401 7.55159 553.625 7.64756C554.254 7.91721 554.692 8.2094 554.955 8.47655C555.214 8.73886 555.25 8.91445 555.25 9.00001C555.25 9.08556 555.214 9.26116 554.955 9.52347C554.692 9.79062 554.254 10.0828 553.625 10.3525C553.401 10.4484 553.16 10.5387 552.902 10.6223C552.736 10.0967 552.53 9.55262 552.284 8.99983ZM546.608 12.277C546.007 12.2134 545.432 12.1202 544.894 12.0012C544.837 12.2661 544.795 12.5205 544.766 12.7624C544.685 13.4422 544.719 13.967 544.819 14.3289C544.917 14.6842 545.051 14.8029 545.125 14.8457C545.199 14.8885 545.369 14.9454 545.726 14.8523C546.089 14.7576 546.56 14.525 547.109 14.1149C547.304 13.9689 547.503 13.8048 547.704 13.6234C547.332 13.2169 546.964 12.7661 546.608 12.277ZM545.383 10.3426C545.311 10.5358 545.246 10.7263 545.186 10.9134C545.378 10.9551 545.576 10.9934 545.779 11.028C545.711 10.9159 545.644 10.8024 545.577 10.6875C545.511 10.5726 545.446 10.4576 545.383 10.3426ZM544.098 10.6223C544.264 10.0967 544.47 9.55263 544.716 8.99984C544.47 8.44715 544.264 7.90322 544.098 7.37767C543.84 7.46128 543.599 7.55159 543.375 7.64756C542.746 7.91721 542.308 8.2094 542.045 8.47655C541.786 8.73886 541.75 8.91445 541.75 9.00001C541.75 9.08556 541.786 9.26116 542.045 9.52347C542.308 9.79062 542.746 10.0828 543.375 10.3525C543.599 10.4484 543.84 10.5387 544.098 10.6223ZM545.577 7.31253C545.511 7.42735 545.446 7.54222 545.383 7.65704C545.312 7.46397 545.246 7.27362 545.186 7.08657C545.378 7.04491 545.576 7.00663 545.779 6.97201C545.711 7.08411 545.643 7.19764 545.577 7.31253ZM548.5 5.62501C548.367 5.62501 548.235 5.62641 548.104 5.62919C548.236 5.47067 548.368 5.31854 548.5 5.17307C548.632 5.31854 548.764 5.47067 548.896 5.6292C548.765 5.62642 548.633 5.62501 548.5 5.62501ZM546.608 5.72305C546.964 5.23385 547.332 4.78305 547.704 4.37645C547.503 4.19514 547.304 4.03103 547.109 3.88512C546.561 3.47506 546.089 3.24245 545.726 3.14769C545.369 3.05465 545.199 3.11154 545.125 3.15432C545.051 3.1971 544.917 3.3158 544.819 3.67116C544.72 4.03306 544.685 4.55786 544.766 5.23757C544.795 5.47946 544.838 5.73389 544.894 5.99882C545.432 5.87982 546.006 5.78662 546.608 5.72305Z" fill="white" fill-opacity="0.4"/>
|
||||
<g clip-path="url(#clip0_201_11707)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M595.5 5.80001L597.348 2.60001H603.5L595.5 16.4566L587.5 2.60001H593.652L595.5 5.80001ZM595.5 11.4L599.5 4.2H601.5L595.5 14.6L589.5 4.2H591.5L595.5 11.4Z" fill="white" fill-opacity="0.4"/>
|
||||
</g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M635.52 2.02039L636.641 14.6068L641.673 16L646.726 14.6L647.848 2.02039H635.52ZM641.678 4.59271H637.811L638.232 9.25485H641.678V9.25825H643.581L643.401 11.2631L641.681 11.7245L639.957 11.2631L639.848 10.0296H638.299L638.516 12.4524L641.683 13.3359V13.3276L644.838 12.4524L645.263 7.71553H641.684H641.678H639.645L639.503 6.13543H641.678H641.684H645.402L645.545 4.59271H641.684H641.678Z" fill="white" fill-opacity="0.4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M682.44 14.6005L687.492 16L692.558 14.5985L693.684 2H681.314L682.44 14.6005ZM683.615 4.57568L683.756 6.1211H689.675L689.534 7.70361H683.894L684.033 9.249L689.4 9.249L689.22 11.2533L687.493 11.7186L687.492 11.719L685.767 11.2543L685.657 10.0217H684.102L684.319 12.4482L687.491 13.327L687.498 13.3249L690.668 12.4482L691.055 8.1186L691.093 7.70361L691.372 4.57568H683.615Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M727.93 11.5747L727.5 4.77146L732.074 2.655L727.93 11.5747Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M731.853 10.0245H735.147L733.5 6.01644L731.853 10.0245Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M733.5 15.3417L730.363 13.5626L731.002 12.0164H735.998L736.637 13.5626L733.5 15.3417Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M739.07 11.5747L739.5 4.77146L734.926 2.655L739.07 11.5747Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M778.896 16.7304C779.081 16.8371 779.291 16.8925 779.503 16.8925L779.502 16.8895C779.716 16.8895 779.926 16.8345 780.112 16.7273L785.893 13.3877C786.267 13.1705 786.5 12.7666 786.5 12.3338V5.66024C786.5 5.22595 786.267 4.82206 785.893 4.60637L780.112 1.26524C779.748 1.05823 779.262 1.05823 778.896 1.26524L773.107 4.60492C772.732 4.81917 772.5 5.2245 772.5 5.65879V12.3324C772.5 12.7652 772.732 13.1705 773.107 13.3877L774.624 14.262C775.359 14.6239 775.623 14.6239 775.957 14.6239C777.044 14.6239 777.669 13.9653 777.669 12.8188V6.22916C777.669 6.13506 777.594 6.06268 777.503 6.06268H776.77C776.676 6.06268 776.603 6.13506 776.603 6.22916V12.8159C776.603 13.3254 776.077 13.8321 775.217 13.4021L773.633 12.4872C773.578 12.4568 773.544 12.396 773.544 12.3324V5.65879C773.544 5.5951 773.579 5.53285 773.635 5.501L779.415 2.16567C779.469 2.13382 779.54 2.13382 779.593 2.16567L785.374 5.501C785.429 5.5343 785.464 5.59365 785.464 5.66024V12.3338C785.464 12.3975 785.429 12.4597 785.375 12.4901L779.592 15.8284C779.542 15.8573 779.466 15.8573 779.413 15.8284L777.93 14.9482C777.885 14.9222 777.829 14.9135 777.786 14.9381C777.375 15.1711 777.297 15.2015 776.913 15.3362C776.818 15.369 776.677 15.4261 776.965 15.5873L778.896 16.7304Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M778.228 10.1582C778.228 11.1324 778.759 12.294 781.289 12.294L781.281 12.3008C783.113 12.3008 784.164 11.5785 784.164 10.319C784.164 9.06971 783.32 8.73676 781.543 8.5008C779.748 8.26339 779.565 8.14034 779.565 7.71908C779.565 7.37165 779.72 6.90841 781.05 6.90841C782.239 6.90841 782.676 7.16464 782.857 7.96517C782.873 8.04045 782.941 8.09546 783.019 8.09546H783.771C783.817 8.09546 783.862 8.07519 783.894 8.0419C783.925 8.00715 783.943 7.96083 783.938 7.91306C783.823 6.53202 782.905 5.88928 781.053 5.88928C779.404 5.88928 778.42 6.58414 778.42 7.75093C778.42 9.0176 779.399 9.36648 780.982 9.52282C782.876 9.70812 783.024 9.98461 783.024 10.3567C783.024 11.003 782.505 11.2784 781.286 11.2784C779.756 11.2784 779.42 10.8944 779.307 10.1333C779.294 10.0518 779.225 9.99171 779.142 9.99171H778.395C778.303 9.99171 778.228 10.0655 778.228 10.1582Z" fill="white" fill-opacity="0.4"/>
|
||||
<defs>
|
||||
<clipPath id="clip0_201_11707">
|
||||
<rect width="16" height="16" fill="white" transform="translate(587.5 1)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 13 KiB |
19
public/webstorm-icon-logo.svg
Normal file
19
public/webstorm-icon-logo.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg width="76" height="74" viewBox="0 0 76 74" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.85449 54.829L6.85655 68.3792C6.85655 70.9458 9.00468 73.0257 11.6546 73.0257H25.2103C26.6106 73.0257 27.9417 72.4328 28.8534 71.4032L67.3882 27.86C68.1333 27.0175 68.5431 25.9447 68.5431 24.8354V11.2851C68.5431 8.71863 66.395 6.63806 63.7451 6.63806H50.1873C48.787 6.63806 47.4559 7.2309 46.5443 8.26056L8.00944 51.8037C7.26369 52.6462 6.85449 53.719 6.85449 54.829Z" fill="#F0EB18"/>
|
||||
<path d="M6.85449 56.4588V68.3785C6.85449 70.9451 9.00263 73.0256 11.6525 73.0256H27.0767C27.3036 73.0256 27.5305 73.0097 27.7553 72.9792L71.278 66.9579C73.6421 66.6306 75.3974 64.6702 75.3974 62.3572V44.4739C75.3974 41.9067 73.2486 39.8262 70.5981 39.8268L48.7582 39.8322C48.2428 39.8322 47.7308 39.9125 47.2421 40.0705L10.135 52.0507C8.176 52.6833 6.85449 54.4592 6.85449 56.4594V56.4588Z" fill="url(#paint0_linear_259_571)"/>
|
||||
<path d="M0 4.64709V43.4058C0 45.2646 1.14398 46.9449 2.90759 47.6771L47.0746 66.0112C47.6716 66.2588 48.3152 66.3869 48.965 66.3869H70.5992C73.2491 66.3869 75.3972 64.3064 75.3972 61.7398V41.2402C75.3972 40.3221 75.1162 39.4245 74.5898 38.6604L49.3749 2.06928C48.4852 0.777391 46.9882 0.00199161 45.3843 0.00199161L4.79801 0C2.14814 0 0 2.08057 0 4.64709Z" fill="url(#paint1_linear_259_571)"/>
|
||||
<path d="M61.2605 13.6923H14.1372V59.3333H61.2605V13.6923Z" fill="black"/>
|
||||
<path d="M25.0206 31.9042L22.1668 19.4063H18.5947L22.8622 36.5268H26.7765L29.7939 24.0898L32.7735 36.5268H36.7L40.9932 19.4063H37.4955L34.7303 31.9042L31.5485 19.4063H28.0637L25.0206 31.9042Z" fill="white"/>
|
||||
<path d="M45.0299 36.1481C46.0229 36.5962 47.1593 36.8209 48.439 36.8209C49.7188 36.8209 50.8558 36.593 51.8488 36.1357C52.8417 35.6784 53.6122 35.0514 54.1594 34.2522C54.7147 33.4451 54.9927 32.5351 54.9927 31.5249C54.9927 30.7007 54.8033 29.9428 54.4245 29.2503C54.0457 28.5486 53.5154 27.9616 52.8336 27.4893C52.1599 27.0078 51.3902 26.6822 50.523 26.5112L47.5178 25.8751C46.9705 25.7453 46.537 25.5252 46.217 25.2147C45.9052 24.9042 45.7496 24.5131 45.7496 24.0407C45.7496 23.6254 45.8633 23.2631 46.0905 22.9525C46.3178 22.6348 46.6337 22.3898 47.0375 22.2188C47.4494 22.0399 47.9256 21.9495 48.464 21.9495C49.0025 21.9495 49.478 22.0426 49.8906 22.2306C50.3113 22.4094 50.6353 22.6663 50.8626 23.001C51.0898 23.3266 51.2035 23.6981 51.2035 24.1141H54.6504C54.6416 23.136 54.3724 22.2725 53.8421 21.5217C53.3199 20.7631 52.5881 20.1715 51.6452 19.7483C50.7023 19.3244 49.6126 19.1122 48.4255 19.1122C47.2384 19.1122 46.1778 19.3323 45.2437 19.7726C44.3096 20.2049 43.5818 20.8116 43.0596 21.5945C42.5381 22.3695 42.277 23.2454 42.277 24.2235C42.277 25.0306 42.4495 25.7571 42.7944 26.4005C43.1401 27.0445 43.6237 27.5869 44.2467 28.0272C44.8696 28.4595 45.6022 28.7655 46.4436 28.9443L49.5625 29.6172C50.1605 29.7567 50.6272 30.0043 50.964 30.3633C51.309 30.7224 51.4815 31.175 51.4815 31.7208C51.4815 32.161 51.355 32.5521 51.1027 32.8948C50.8585 33.2374 50.5095 33.506 50.0549 33.7019C49.6085 33.8899 49.112 33.9829 48.4891 33.9829C47.8661 33.9829 47.3148 33.8774 46.8353 33.6652C46.3557 33.4529 45.981 33.1594 45.7118 32.7847C45.4425 32.4106 45.3079 31.973 45.3079 31.4764H41.8481C41.865 32.5279 42.1512 33.4575 42.7065 34.2646C43.2706 35.0717 44.0451 35.7 45.0299 36.1481Z" fill="white"/>
|
||||
<path d="M20.0202 50.2051H38.8695V53.6282H20.0202V50.2051Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_259_571" x1="8.97864" y1="73.8442" x2="71.4254" y2="43.9516" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.22" stop-color="#F0EB18"/>
|
||||
<stop offset="0.59" stop-color="#00C4F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_259_571" x1="70.7068" y1="68.2079" x2="3.75661" y2="-0.915939" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.19" stop-color="#00C4F4"/>
|
||||
<stop offset="0.83" stop-color="#007DFE"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 3.9 KiB |
20
public/webstorm-logo.svg
Normal file
20
public/webstorm-logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 16 KiB |
625
server.js
Normal file
625
server.js
Normal file
@@ -0,0 +1,625 @@
|
||||
import express from 'express';
|
||||
import { createServer } from 'http';
|
||||
import { Server } from 'socket.io';
|
||||
import cors from 'cors';
|
||||
import bcrypt from 'bcrypt';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
|
||||
// Get __dirname equivalent in ES modules
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const app = express();
|
||||
const server = createServer(app);
|
||||
const io = new Server(server, {
|
||||
cors: {
|
||||
origin: "*",
|
||||
methods: ["GET", "POST"]
|
||||
}
|
||||
});
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.static('public'));
|
||||
|
||||
// Configuration
|
||||
const CONFIG = {
|
||||
passwords: {
|
||||
addPoints: 'add123',
|
||||
admin: 'admin456'
|
||||
},
|
||||
timers: {
|
||||
solo: 15 * 60, // 15 minutes in seconds
|
||||
together: 60 * 60, // 1 hour in seconds
|
||||
finishUp: 2 * 60 // 2 minutes in seconds
|
||||
},
|
||||
port: 3000
|
||||
};
|
||||
|
||||
// Data file path
|
||||
const DATA_FILE = path.join(__dirname, 'data', 'gameState.json');
|
||||
|
||||
// Initial state structure
|
||||
const initialState = {
|
||||
soloPoints: [],
|
||||
togetherPoints: [],
|
||||
customPoints: [],
|
||||
activeTimers: {},
|
||||
pausedTimers: {},
|
||||
activityLog: [],
|
||||
togetherUsedToday: 0,
|
||||
lastResetDate: new Date().toDateString(),
|
||||
connectedClients: 0
|
||||
};
|
||||
|
||||
// Load or create game state
|
||||
let gameState = loadGameState();
|
||||
|
||||
// Load game state from file
|
||||
function loadGameState() {
|
||||
try {
|
||||
if (fs.existsSync(DATA_FILE)) {
|
||||
const data = fs.readFileSync(DATA_FILE, 'utf8');
|
||||
const savedState = JSON.parse(data);
|
||||
// Convert timer maps back from objects
|
||||
savedState.activeTimers = savedState.activeTimers || {};
|
||||
savedState.pausedTimers = savedState.pausedTimers || {};
|
||||
return { ...initialState, ...savedState };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading game state:', error);
|
||||
}
|
||||
|
||||
// Initialize with default points
|
||||
const state = { ...initialState };
|
||||
state.soloPoints = Array(5).fill(null).map((_, i) => ({
|
||||
id: `solo_${i}`,
|
||||
type: 'solo',
|
||||
status: 'available',
|
||||
duration: CONFIG.timers.solo,
|
||||
label: '15m'
|
||||
}));
|
||||
|
||||
state.togetherPoints = Array(4).fill(null).map((_, i) => ({
|
||||
id: `together_${i}`,
|
||||
type: 'together',
|
||||
status: 'available',
|
||||
duration: CONFIG.timers.together,
|
||||
label: '1h'
|
||||
}));
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
// Save game state to file
|
||||
function saveGameState() {
|
||||
try {
|
||||
const dir = path.dirname(DATA_FILE);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(DATA_FILE, JSON.stringify(gameState, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Error saving game state:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Check and reset daily limit
|
||||
function checkDayReset() {
|
||||
const today = new Date().toDateString();
|
||||
if (gameState.lastResetDate !== today) {
|
||||
gameState.togetherUsedToday = 0;
|
||||
gameState.lastResetDate = today;
|
||||
saveGameState();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add log entry
|
||||
function addLogEntry(message) {
|
||||
const entry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
message: message
|
||||
};
|
||||
|
||||
gameState.activityLog.unshift(entry);
|
||||
|
||||
// Keep only last 5 entries
|
||||
if (gameState.activityLog.length > 5) {
|
||||
gameState.activityLog = gameState.activityLog.slice(0, 5);
|
||||
}
|
||||
|
||||
saveGameState();
|
||||
}
|
||||
|
||||
// Find point by ID
|
||||
function findPoint(pointId) {
|
||||
return [...gameState.soloPoints, ...gameState.togetherPoints, ...gameState.customPoints]
|
||||
.find(p => p.id === pointId);
|
||||
}
|
||||
|
||||
// Timer management
|
||||
const timerIntervals = new Map();
|
||||
|
||||
function startTimer(pointId, duration, type = 'normal') {
|
||||
const startTime = Date.now();
|
||||
const timer = {
|
||||
startTime,
|
||||
duration: duration * 1000,
|
||||
remainingTime: duration * 1000,
|
||||
type,
|
||||
pointId
|
||||
};
|
||||
|
||||
gameState.activeTimers[pointId] = timer;
|
||||
|
||||
// Set up interval to check timer
|
||||
const interval = setInterval(() => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
const remaining = timer.duration - elapsed;
|
||||
|
||||
if (remaining <= 0) {
|
||||
clearInterval(interval);
|
||||
timerIntervals.delete(pointId);
|
||||
handleTimerComplete(pointId);
|
||||
} else {
|
||||
// Emit timer update
|
||||
io.emit('timerUpdate', {
|
||||
pointId,
|
||||
remainingSeconds: Math.floor(remaining / 1000),
|
||||
type: timer.type
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
timerIntervals.set(pointId, interval);
|
||||
saveGameState();
|
||||
}
|
||||
|
||||
function handleTimerComplete(pointId) {
|
||||
const timer = gameState.activeTimers[pointId];
|
||||
if (!timer) return;
|
||||
|
||||
delete gameState.activeTimers[pointId];
|
||||
|
||||
// Handle different completion types
|
||||
if (timer.type === 'finishUp') {
|
||||
const point = findPoint(pointId);
|
||||
if (point) {
|
||||
point.status = 'used';
|
||||
addLogEntry(`${point.customName || point.type} point finish-up time completed`);
|
||||
io.emit('timerComplete', { pointId, type: 'finishUp' });
|
||||
io.emit('stateUpdate', gameState);
|
||||
}
|
||||
} else if (timer.type === 'episode' || timer.type === 'movie') {
|
||||
// Remove temporary point
|
||||
gameState.customPoints = gameState.customPoints.filter(p => p.id !== pointId);
|
||||
addLogEntry(`${timer.type} completed`);
|
||||
io.emit('timerComplete', { pointId, type: timer.type });
|
||||
io.emit('stateUpdate', gameState);
|
||||
} else {
|
||||
// Regular timer - offer finish-up
|
||||
io.emit('timerComplete', { pointId, type: 'normal', offerFinishUp: true });
|
||||
}
|
||||
|
||||
saveGameState();
|
||||
}
|
||||
|
||||
// Socket.IO connection handling
|
||||
io.on('connection', (socket) => {
|
||||
console.log('Client connected:', socket.id);
|
||||
gameState.connectedClients++;
|
||||
|
||||
// Check day reset on new connection
|
||||
if (checkDayReset()) {
|
||||
io.emit('stateUpdate', gameState);
|
||||
}
|
||||
|
||||
// Send initial state
|
||||
socket.emit('initialState', gameState);
|
||||
|
||||
// Handle disconnection
|
||||
socket.on('disconnect', () => {
|
||||
console.log('Client disconnected:', socket.id);
|
||||
gameState.connectedClients--;
|
||||
});
|
||||
|
||||
// Use point
|
||||
socket.on('usePoint', (data) => {
|
||||
const { pointId } = data;
|
||||
const point = findPoint(pointId);
|
||||
|
||||
if (!point || point.status !== 'available') {
|
||||
socket.emit('error', { message: 'Point not available' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check solo limitation
|
||||
if (point.type === 'solo' || point.customType === 'solo') {
|
||||
const activeSolo = [...gameState.soloPoints, ...gameState.customPoints]
|
||||
.find(p => (p.type === 'solo' || p.customType === 'solo') &&
|
||||
(p.status === 'active' || p.status === 'paused'));
|
||||
|
||||
if (activeSolo) {
|
||||
socket.emit('error', { message: 'Only one solo point at a time' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check together daily limit
|
||||
if (point.type === 'together' || point.customType === 'together') {
|
||||
if (gameState.togetherUsedToday >= 1) {
|
||||
socket.emit('error', { message: 'Daily together limit reached' });
|
||||
return;
|
||||
}
|
||||
gameState.togetherUsedToday++;
|
||||
}
|
||||
|
||||
point.status = 'active';
|
||||
startTimer(pointId, point.duration || CONFIG.timers[point.type]);
|
||||
addLogEntry(`Started ${point.customName || point.type} point (${point.label})`);
|
||||
io.emit('stateUpdate', gameState);
|
||||
});
|
||||
|
||||
// Pause timer
|
||||
socket.on('pauseTimer', (data) => {
|
||||
const { pointId } = data;
|
||||
const point = findPoint(pointId);
|
||||
const timer = gameState.activeTimers[pointId];
|
||||
|
||||
if (!point || !timer) return;
|
||||
|
||||
// Clear interval
|
||||
const interval = timerIntervals.get(pointId);
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
timerIntervals.delete(pointId);
|
||||
}
|
||||
|
||||
point.status = 'paused';
|
||||
const elapsed = Date.now() - timer.startTime;
|
||||
timer.remainingTime = Math.max(0, timer.duration - elapsed);
|
||||
|
||||
gameState.pausedTimers[pointId] = timer;
|
||||
delete gameState.activeTimers[pointId];
|
||||
|
||||
addLogEntry(`Paused ${point.customName || point.type} point`);
|
||||
io.emit('stateUpdate', gameState);
|
||||
});
|
||||
|
||||
// Resume timer
|
||||
socket.on('resumeTimer', (data) => {
|
||||
const { pointId } = data;
|
||||
const point = findPoint(pointId);
|
||||
const timer = gameState.pausedTimers[pointId];
|
||||
|
||||
if (!point || !timer) return;
|
||||
|
||||
point.status = 'active';
|
||||
delete gameState.pausedTimers[pointId];
|
||||
|
||||
// Restart with remaining time
|
||||
startTimer(pointId, timer.remainingTime / 1000, timer.type);
|
||||
addLogEntry(`Resumed ${point.customName || point.type} point`);
|
||||
io.emit('stateUpdate', gameState);
|
||||
});
|
||||
|
||||
// Admin authentication
|
||||
socket.on('adminAuth', (data) => {
|
||||
const { password } = data;
|
||||
if (password === CONFIG.passwords.admin) {
|
||||
socket.emit('adminAuthSuccess');
|
||||
addLogEntry('Admin mode activated');
|
||||
} else {
|
||||
socket.emit('adminAuthFailed');
|
||||
}
|
||||
});
|
||||
|
||||
// Add points with password
|
||||
socket.on('addPoints', (data) => {
|
||||
const { password, type, count } = data;
|
||||
|
||||
if (password !== CONFIG.passwords.addPoints) {
|
||||
socket.emit('error', { message: 'Incorrect password' });
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const newPoint = {
|
||||
id: `${type}_${Date.now()}_${i}`,
|
||||
type,
|
||||
status: 'available',
|
||||
duration: CONFIG.timers[type],
|
||||
label: type === 'solo' ? '15m' : '1h'
|
||||
};
|
||||
|
||||
if (type === 'solo') {
|
||||
gameState.soloPoints.push(newPoint);
|
||||
} else {
|
||||
gameState.togetherPoints.push(newPoint);
|
||||
}
|
||||
}
|
||||
|
||||
addLogEntry(`Added ${count} ${type} point(s)`);
|
||||
io.emit('stateUpdate', gameState);
|
||||
});
|
||||
|
||||
// Quick add (admin only)
|
||||
socket.on('quickAddPoint', (data) => {
|
||||
const { type } = data;
|
||||
const newPoint = {
|
||||
id: `${type}_${Date.now()}`,
|
||||
type,
|
||||
status: 'available',
|
||||
duration: CONFIG.timers[type],
|
||||
label: type === 'solo' ? '15m' : '1h'
|
||||
};
|
||||
|
||||
if (type === 'solo') {
|
||||
gameState.soloPoints.push(newPoint);
|
||||
} else {
|
||||
gameState.togetherPoints.push(newPoint);
|
||||
}
|
||||
|
||||
addLogEntry(`Admin added 1 ${type} point`);
|
||||
io.emit('stateUpdate', gameState);
|
||||
});
|
||||
|
||||
// Remove point
|
||||
socket.on('removePoint', (data) => {
|
||||
const { pointId } = data;
|
||||
const point = findPoint(pointId);
|
||||
|
||||
if (!point) {
|
||||
socket.emit('error', { message: 'Point not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle episode/movie removal
|
||||
const timer = gameState.activeTimers[pointId] || gameState.pausedTimers[pointId];
|
||||
if (timer && (timer.type === 'episode' || timer.type === 'movie')) {
|
||||
// Restore consumed solo points
|
||||
if (timer.pointIds) {
|
||||
timer.pointIds.forEach(soloId => {
|
||||
const soloPoint = gameState.soloPoints.find(p => p.id === soloId);
|
||||
if (soloPoint) soloPoint.status = 'available';
|
||||
});
|
||||
}
|
||||
|
||||
// Clean up timers
|
||||
const interval = timerIntervals.get(pointId);
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
timerIntervals.delete(pointId);
|
||||
}
|
||||
|
||||
delete gameState.activeTimers[pointId];
|
||||
delete gameState.pausedTimers[pointId];
|
||||
gameState.customPoints = gameState.customPoints.filter(p => p.id !== pointId);
|
||||
|
||||
addLogEntry(`Admin removed ${timer.type} (restored solo points)`);
|
||||
} else {
|
||||
// Regular point removal
|
||||
if (point.type === 'solo') {
|
||||
gameState.soloPoints = gameState.soloPoints.filter(p => p.id !== pointId);
|
||||
} else if (point.type === 'together') {
|
||||
gameState.togetherPoints = gameState.togetherPoints.filter(p => p.id !== pointId);
|
||||
} else {
|
||||
gameState.customPoints = gameState.customPoints.filter(p => p.id !== pointId);
|
||||
}
|
||||
|
||||
// Clean up any timers
|
||||
const interval = timerIntervals.get(pointId);
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
timerIntervals.delete(pointId);
|
||||
}
|
||||
|
||||
delete gameState.activeTimers[pointId];
|
||||
delete gameState.pausedTimers[pointId];
|
||||
|
||||
addLogEntry(`Admin removed ${point.customName || point.type} point`);
|
||||
}
|
||||
|
||||
io.emit('stateUpdate', gameState);
|
||||
});
|
||||
|
||||
// Expire timer
|
||||
socket.on('expireTimer', (data) => {
|
||||
const { pointId } = data;
|
||||
const timer = gameState.activeTimers[pointId] || gameState.pausedTimers[pointId];
|
||||
|
||||
if (!timer) return;
|
||||
|
||||
// Clear existing interval
|
||||
const interval = timerIntervals.get(pointId);
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
timerIntervals.delete(pointId);
|
||||
}
|
||||
|
||||
// Set to expire in 1 second
|
||||
timer.startTime = Date.now() - timer.duration + 1000;
|
||||
timer.remainingTime = 1000;
|
||||
gameState.activeTimers[pointId] = timer;
|
||||
delete gameState.pausedTimers[pointId];
|
||||
|
||||
const point = findPoint(pointId);
|
||||
if (point) point.status = 'active';
|
||||
|
||||
// Restart timer with 1 second
|
||||
startTimer(pointId, 1, timer.type);
|
||||
|
||||
addLogEntry(`Admin expired ${timer.type || 'timer'}`);
|
||||
io.emit('stateUpdate', gameState);
|
||||
});
|
||||
|
||||
// Reset all points
|
||||
socket.on('resetAllPoints', () => {
|
||||
// Clear all intervals
|
||||
timerIntervals.forEach((interval) => clearInterval(interval));
|
||||
timerIntervals.clear();
|
||||
|
||||
// Reset to initial state with 5 solo and 4 together points
|
||||
gameState.soloPoints = Array(5).fill(null).map((_, i) => ({
|
||||
id: `solo_${Date.now()}_${i}`,
|
||||
type: 'solo',
|
||||
status: 'available',
|
||||
duration: CONFIG.timers.solo,
|
||||
label: '15m'
|
||||
}));
|
||||
|
||||
gameState.togetherPoints = Array(4).fill(null).map((_, i) => ({
|
||||
id: `together_${Date.now()}_${i}`,
|
||||
type: 'together',
|
||||
status: 'available',
|
||||
duration: CONFIG.timers.together,
|
||||
label: '1h'
|
||||
}));
|
||||
|
||||
// Clear custom points
|
||||
gameState.customPoints = [];
|
||||
|
||||
gameState.activeTimers = {};
|
||||
gameState.pausedTimers = {};
|
||||
|
||||
addLogEntry('All points reset by admin');
|
||||
io.emit('stateUpdate', gameState);
|
||||
});
|
||||
|
||||
// Reset daily limit
|
||||
socket.on('resetDailyLimit', () => {
|
||||
gameState.togetherUsedToday = 0;
|
||||
gameState.lastResetDate = new Date().toDateString();
|
||||
|
||||
addLogEntry('Daily together point limit reset by admin');
|
||||
io.emit('stateUpdate', gameState);
|
||||
});
|
||||
|
||||
// Clear log
|
||||
socket.on('clearLog', () => {
|
||||
gameState.activityLog = [];
|
||||
addLogEntry('Activity log cleared by admin');
|
||||
io.emit('stateUpdate', gameState);
|
||||
});
|
||||
|
||||
// Create custom timer
|
||||
socket.on('createCustomTimer', (data) => {
|
||||
const { name, hours, minutes, pointType } = data;
|
||||
|
||||
const duration = (hours * 3600) + (minutes * 60);
|
||||
const label = hours > 0 ? `${hours}h${minutes > 0 ? minutes : ''}` : `${minutes}m`;
|
||||
|
||||
const customPoint = {
|
||||
id: `custom_${Date.now()}`,
|
||||
type: pointType === 'custom' ? 'solo' : pointType,
|
||||
customType: pointType,
|
||||
customName: name,
|
||||
status: 'available',
|
||||
duration: duration,
|
||||
label: label
|
||||
};
|
||||
|
||||
gameState.customPoints.push(customPoint);
|
||||
addLogEntry(`Admin created custom timer: ${name} (${label})`);
|
||||
io.emit('stateUpdate', gameState);
|
||||
});
|
||||
|
||||
// Episode/Movie selection
|
||||
socket.on('confirmSelection', (data) => {
|
||||
const { selectedPoints, type } = data;
|
||||
|
||||
// Mark selected points as used immediately
|
||||
selectedPoints.forEach(pointId => {
|
||||
const point = gameState.soloPoints.find(p => p.id === pointId);
|
||||
if (point) point.status = 'used';
|
||||
});
|
||||
|
||||
const pointCount = selectedPoints.length;
|
||||
const durationText = type === 'episode' ? '30 minutes' : '2 hours';
|
||||
addLogEntry(`Used ${pointCount} solo points for ${type} (${durationText})`);
|
||||
|
||||
io.emit('stateUpdate', gameState);
|
||||
});
|
||||
|
||||
// Handle finish-up response
|
||||
socket.on('finishUpResponse', (data) => {
|
||||
const { pointId, accepted } = data;
|
||||
const point = findPoint(pointId);
|
||||
|
||||
if (!point) return;
|
||||
|
||||
if (accepted) {
|
||||
point.status = 'active';
|
||||
startTimer(pointId, CONFIG.timers.finishUp, 'finishUp');
|
||||
addLogEntry(`${point.customName || point.type} point completed - using 2min finish-up time`);
|
||||
} else {
|
||||
point.status = 'used';
|
||||
addLogEntry(`${point.customName || point.type} point completed`);
|
||||
}
|
||||
|
||||
io.emit('stateUpdate', gameState);
|
||||
});
|
||||
});
|
||||
|
||||
// REST API endpoints
|
||||
app.get('/api/state', (req, res) => {
|
||||
res.json(gameState);
|
||||
});
|
||||
|
||||
app.get('/api/config', (req, res) => {
|
||||
res.json({
|
||||
timers: CONFIG.timers,
|
||||
port: CONFIG.port
|
||||
});
|
||||
});
|
||||
|
||||
// Start server
|
||||
server.listen(CONFIG.port, () => {
|
||||
console.log(`Point Tracker Server running on port ${CONFIG.port}`);
|
||||
console.log(`WebSocket server ready for connections`);
|
||||
|
||||
// Check day reset on startup
|
||||
checkDayReset();
|
||||
|
||||
// Restore any active timers
|
||||
Object.entries(gameState.activeTimers).forEach(([pointId, timer]) => {
|
||||
const elapsed = Date.now() - timer.startTime;
|
||||
const remaining = timer.duration - elapsed;
|
||||
|
||||
if (remaining > 0) {
|
||||
// Resume timer
|
||||
const point = findPoint(pointId);
|
||||
if (point) {
|
||||
startTimer(pointId, remaining / 1000, timer.type);
|
||||
console.log(`Restored active timer for ${pointId}`);
|
||||
}
|
||||
} else {
|
||||
// Timer expired while server was down
|
||||
handleTimerComplete(pointId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('SIGTERM received, shutting down gracefully');
|
||||
|
||||
// Clear all timer intervals
|
||||
timerIntervals.forEach((interval) => clearInterval(interval));
|
||||
|
||||
// Save final state
|
||||
saveGameState();
|
||||
|
||||
server.close(() => {
|
||||
console.log('Server closed');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
export { app, server };
|
32
src/main.js
Normal file
32
src/main.js
Normal file
@@ -0,0 +1,32 @@
|
||||
//TIP With Search Everywhere, you can find any action, file, or symbol in your project. Press <shortcut actionId="Shift"/> <shortcut actionId="Shift"/>, type in <b>terminal</b>, and press <shortcut actionId="EditorEnter"/>. Then run <shortcut raw="npm run dev"/> in the terminal and click the link in its output to open the app in the browser.
|
||||
export function setupCounter(element) {
|
||||
//TIP Try <shortcut actionId="GotoDeclaration"/> on <shortcut raw="counter"/> to see its usages. You can also use this shortcut to jump to a declaration – try it on <shortcut raw="counter"/> on line 13.
|
||||
let counter = 0;
|
||||
|
||||
const adjustCounterValue = value => {
|
||||
if (value >= 100) return value - 100;
|
||||
if (value <= -100) return value + 100;
|
||||
return value;
|
||||
};
|
||||
|
||||
const setCounter = value => {
|
||||
counter = adjustCounterValue(value);
|
||||
//TIP WebStorm has lots of inspections to help you catch issues in your project. It also has quick fixes to help you resolve them. Press <shortcut actionId="ShowIntentionActions"/> on <shortcut raw="text"/> and choose <b>Inline variable</b> to clean up the redundant code.
|
||||
const text = `${counter}`;
|
||||
element.innerHTML = text;
|
||||
};
|
||||
|
||||
document.getElementById('increaseByOne').addEventListener('click', () => setCounter(counter + 1));
|
||||
document.getElementById('decreaseByOne').addEventListener('click', () => setCounter(counter - 1));
|
||||
document.getElementById('increaseByTwo').addEventListener('click', () => setCounter(counter + 2));
|
||||
//TIP In the app running in the browser, you’ll find that clicking <b>-2</b> doesn't work. To fix that, rewrite it using the code from lines 19 - 21 as examples of the logic.
|
||||
document.getElementById('decreaseByTwo')
|
||||
|
||||
//TIP Let’s see how to review and commit your changes. Press <shortcut actionId="GotoAction"/> and look for <b>commit</b>. Try checking the diff for a file – double-click main.js to do that.
|
||||
setCounter(0);
|
||||
}
|
||||
|
||||
//TIP To find text strings in your project, you can use the <shortcut actionId="FindInPath"/> shortcut. Press it and type in <b>counter</b> – you’ll get all matches in one place.
|
||||
setupCounter(document.getElementById('counter-value'));
|
||||
|
||||
//TIP There's much more in WebStorm to help you be more productive. Press <shortcut actionId="Shift"/> <shortcut actionId="Shift"/> and search for <b>Learn WebStorm</b> to open our learning hub with more things for you to try.
|
Reference in New Issue
Block a user