init
30
.eslintrc.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es2017": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:import/recommended",
|
||||
"plugin:import/typescript",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"next"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"ignorePatterns": [".out/*"],
|
||||
"plugins": ["@typescript-eslint", "import", "prettier"],
|
||||
"rules": {
|
||||
"quotes": ["error", "double", { "avoidEscape": true }],
|
||||
"prettier/prettier": ["error", {"endOfLine": "auto"}],
|
||||
"require-jsdoc": "off",
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"react/no-unescaped-entities": 0
|
||||
}
|
||||
}
|
||||
20
.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
|
||||
# next.js
|
||||
.next/
|
||||
out/
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# env files
|
||||
.env*.local
|
||||
.env
|
||||
|
||||
# firebase
|
||||
.firebase
|
||||
|
||||
# ts build
|
||||
tsconfig.tsbuildinfo
|
||||
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 GuillaumeSD
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
41
README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# FreeChess
|
||||
|
||||
This project is solely a frontend with tools for the chess community.
|
||||
|
||||
It is built with [Next.js](https://nextjs.org/docs), [React](https://react.dev/learn/describing-the-ui) and [TypeScript](https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html).
|
||||
|
||||
It is deployed on [Firebase](https://firebase.google.com/docs/hosting), you can see it live [here](https://freechess.web.app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, you need at least node18 & npm installed on your machine. Then, you can install the dependencies :
|
||||
|
||||
```bash
|
||||
npm i
|
||||
```
|
||||
|
||||
Then, you can run the development server :
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the app running.
|
||||
|
||||
The development server will automatically reload if you change any of the source files.
|
||||
|
||||
## Lint
|
||||
|
||||
ESLint is configured with this project. You can run it with :
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
|
||||
## Deploy
|
||||
|
||||
To deploy the app, you first need to install the firebase CLI, then run :
|
||||
|
||||
```bash
|
||||
npm run deploy
|
||||
```
|
||||
10
firebase.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"hosting": {
|
||||
"site": "freechess",
|
||||
"public": "out",
|
||||
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
|
||||
"cleanUrls": true,
|
||||
"trailingSlash": false,
|
||||
"predeploy": ["npm run lint", "npm run build"]
|
||||
}
|
||||
}
|
||||
5
next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
11
next.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "export",
|
||||
trailingSlash: false,
|
||||
reactStrictMode: true,
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
4141
package-lock.json
generated
Normal file
30
package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "freechess",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint && tsc --noEmit",
|
||||
"deploy": "firebase deploy --project=freechess --only hosting"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "13.4.5",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.3.0",
|
||||
"@types/react": "18.2.11",
|
||||
"@types/react-dom": "18.2.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.11",
|
||||
"@typescript-eslint/parser": "^5.59.11",
|
||||
"eslint": "8.42.0",
|
||||
"eslint-config-next": "13.4.5",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"typescript": "5.1.3"
|
||||
}
|
||||
}
|
||||
BIN
public/analysis_icon.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/back.png
Normal file
|
After Width: | Height: | Size: 795 B |
BIN
public/back_to_start.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/best.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
12
public/black_bishop.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="45" height="45">
|
||||
<g style="opacity:1; fill:none; fill-rule:evenodd; fill-opacity:1; stroke:#000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(0,0.6)">
|
||||
<g style="fill:#000000; stroke:#000000; stroke-linecap:butt;">
|
||||
<path d="M 9,36 C 12.39,35.03 19.11,36.43 22.5,34 C 25.89,36.43 32.61,35.03 36,36 C 36,36 37.65,36.54 39,38 C 38.32,38.97 37.35,38.99 36,38.5 C 32.61,37.53 25.89,38.96 22.5,37.5 C 19.11,38.96 12.39,37.53 9,38.5 C 7.65,38.99 6.68,38.97 6,38 C 7.35,36.54 9,36 9,36 z"/>
|
||||
<path d="M 15,32 C 17.5,34.5 27.5,34.5 30,32 C 30.5,30.5 30,30 30,30 C 30,27.5 27.5,26 27.5,26 C 33,24.5 33.5,14.5 22.5,10.5 C 11.5,14.5 12,24.5 17.5,26 C 17.5,26 15,27.5 15,30 C 15,30 14.5,30.5 15,32 z"/>
|
||||
<path d="M 25 8 A 2.5 2.5 0 1 1 20,8 A 2.5 2.5 0 1 1 25 8 z"/>
|
||||
</g>
|
||||
<path d="M 17.5,26 L 27.5,26 M 15,30 L 30,30 M 22.5,15.5 L 22.5,20.5 M 20,18 L 25,18" style="fill:none; stroke:#ffffff; stroke-linejoin:miter;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
12
public/black_king.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="45" height="45">
|
||||
<g style="fill:none; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;">
|
||||
<path d="M 22.5,11.63 L 22.5,6" style="fill:none; stroke:#000000; stroke-linejoin:miter;" id="path6570"/>
|
||||
<path d="M 22.5,25 C 22.5,25 27,17.5 25.5,14.5 C 25.5,14.5 24.5,12 22.5,12 C 20.5,12 19.5,14.5 19.5,14.5 C 18,17.5 22.5,25 22.5,25" style="fill:#000000;fill-opacity:1; stroke-linecap:butt; stroke-linejoin:miter;"/>
|
||||
<path d="M 12.5,37 C 18,40.5 27,40.5 32.5,37 L 32.5,30 C 32.5,30 41.5,25.5 38.5,19.5 C 34.5,13 25,16 22.5,23.5 L 22.5,27 L 22.5,23.5 C 20,16 10.5,13 6.5,19.5 C 3.5,25.5 12.5,30 12.5,30 L 12.5,37" style="fill:#000000; stroke:#000000;"/>
|
||||
<path d="M 20,8 L 25,8" style="fill:none; stroke:#000000; stroke-linejoin:miter;"/>
|
||||
<path d="M 32,29.5 C 32,29.5 40.5,25.5 38.03,19.85 C 34.15,14 25,18 22.5,24.5 L 22.5,26.6 L 22.5,24.5 C 20,18 10.85,14 6.97,19.85 C 4.5,25.5 13,29.5 13,29.5" style="fill:none; stroke:#ffffff;"/>
|
||||
<path d="M 12.5,30 C 18,27 27,27 32.5,30 M 12.5,33.5 C 18,30.5 27,30.5 32.5,33.5 M 12.5,37 C 18,34 27,34 32.5,37" style="fill:none; stroke:#ffffff;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
22
public/black_knight.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="45" height="45">
|
||||
<g style="opacity:1; fill:none; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(0,0.3)">
|
||||
<path
|
||||
d="M 22,10 C 32.5,11 38.5,18 38,39 L 15,39 C 15,30 25,32.5 23,18"
|
||||
style="fill:#000000; stroke:#000000;" />
|
||||
<path
|
||||
d="M 24,18 C 24.38,20.91 18.45,25.37 16,27 C 13,29 13.18,31.34 11,31 C 9.958,30.06 12.41,27.96 11,28 C 10,28 11.19,29.23 10,30 C 9,30 5.997,31 6,26 C 6,24 12,14 12,14 C 12,14 13.89,12.1 14,10.5 C 13.27,9.506 13.5,8.5 13.5,7.5 C 14.5,6.5 16.5,10 16.5,10 L 18.5,10 C 18.5,10 19.28,8.008 21,7 C 22,7 22,10 22,10"
|
||||
style="fill:#000000; stroke:#000000;" />
|
||||
<path
|
||||
d="M 9.5 25.5 A 0.5 0.5 0 1 1 8.5,25.5 A 0.5 0.5 0 1 1 9.5 25.5 z"
|
||||
style="fill:#ffffff; stroke:#ffffff;" />
|
||||
<path
|
||||
d="M 15 15.5 A 0.5 1.5 0 1 1 14,15.5 A 0.5 1.5 0 1 1 15 15.5 z"
|
||||
transform="matrix(0.866,0.5,-0.5,0.866,9.693,-5.173)"
|
||||
style="fill:#ffffff; stroke:#ffffff;" />
|
||||
<path
|
||||
d="M 24.55,10.4 L 24.1,11.85 L 24.6,12 C 27.75,13 30.25,14.49 32.5,18.75 C 34.75,23.01 35.75,29.06 35.25,39 L 35.2,39.5 L 37.45,39.5 L 37.5,39 C 38,28.94 36.62,22.15 34.25,17.66 C 31.88,13.17 28.46,11.02 25.06,10.5 L 24.55,10.4 z "
|
||||
style="fill:#ffffff; stroke:none;" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
5
public/black_pawn.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="45" height="45">
|
||||
<path d="m 22.5,9 c -2.21,0 -4,1.79 -4,4 0,0.89 0.29,1.71 0.78,2.38 C 17.33,16.5 16,18.59 16,21 c 0,2.03 0.94,3.84 2.41,5.03 C 15.41,27.09 11,31.58 11,39.5 H 34 C 34,31.58 29.59,27.09 26.59,26.03 28.06,24.84 29,23.03 29,21 29,18.59 27.67,16.5 25.72,15.38 26.21,14.71 26.5,13.89 26.5,13 c 0,-2.21 -1.79,-4 -4,-4 z" style="opacity:1; fill:#000000; fill-opacity:1; fill-rule:nonzero; stroke:#000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:miter; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 766 B |
27
public/black_queen.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="45"
|
||||
height="45">
|
||||
<g style="fill:#000000;stroke:#000000;stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round">
|
||||
|
||||
<path d="M 9,26 C 17.5,24.5 30,24.5 36,26 L 38.5,13.5 L 31,25 L 30.7,10.9 L 25.5,24.5 L 22.5,10 L 19.5,24.5 L 14.3,10.9 L 14,25 L 6.5,13.5 L 9,26 z"
|
||||
style="stroke-linecap:butt;fill:#000000" />
|
||||
<path d="m 9,26 c 0,2 1.5,2 2.5,4 1,1.5 1,1 0.5,3.5 -1.5,1 -1,2.5 -1,2.5 -1.5,1.5 0,2.5 0,2.5 6.5,1 16.5,1 23,0 0,0 1.5,-1 0,-2.5 0,0 0.5,-1.5 -1,-2.5 -0.5,-2.5 -0.5,-2 0.5,-3.5 1,-2 2.5,-2 2.5,-4 -8.5,-1.5 -18.5,-1.5 -27,0 z" />
|
||||
<path d="M 11.5,30 C 15,29 30,29 33.5,30" />
|
||||
<path d="m 12,33.5 c 6,-1 15,-1 21,0" />
|
||||
<circle cx="6" cy="12" r="2" />
|
||||
<circle cx="14" cy="9" r="2" />
|
||||
<circle cx="22.5" cy="8" r="2" />
|
||||
<circle cx="31" cy="9" r="2" />
|
||||
<circle cx="39" cy="12" r="2" />
|
||||
<path d="M 11,38.5 A 35,35 1 0 0 34,38.5"
|
||||
style="fill:none; stroke:#000000;stroke-linecap:butt;" />
|
||||
<g style="fill:none; stroke:#ffffff;">
|
||||
<path d="M 11,29 A 35,35 1 0 1 34,29" />
|
||||
<path d="M 12.5,31.5 L 32.5,31.5" />
|
||||
<path d="M 11.5,34.5 A 35,35 1 0 0 33.5,34.5" />
|
||||
<path d="M 10.5,37.5 A 35,35 1 0 0 34.5,37.5" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
39
public/black_rook.svg
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="45" height="45">
|
||||
<g style="opacity:1; fill:#000000; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(0,0.3)">
|
||||
<path
|
||||
d="M 9,39 L 36,39 L 36,36 L 9,36 L 9,39 z "
|
||||
style="stroke-linecap:butt;" />
|
||||
<path
|
||||
d="M 12.5,32 L 14,29.5 L 31,29.5 L 32.5,32 L 12.5,32 z "
|
||||
style="stroke-linecap:butt;" />
|
||||
<path
|
||||
d="M 12,36 L 12,32 L 33,32 L 33,36 L 12,36 z "
|
||||
style="stroke-linecap:butt;" />
|
||||
<path
|
||||
d="M 14,29.5 L 14,16.5 L 31,16.5 L 31,29.5 L 14,29.5 z "
|
||||
style="stroke-linecap:butt;stroke-linejoin:miter;" />
|
||||
<path
|
||||
d="M 14,16.5 L 11,14 L 34,14 L 31,16.5 L 14,16.5 z "
|
||||
style="stroke-linecap:butt;" />
|
||||
<path
|
||||
d="M 11,14 L 11,9 L 15,9 L 15,11 L 20,11 L 20,9 L 25,9 L 25,11 L 30,11 L 30,9 L 34,9 L 34,14 L 11,14 z "
|
||||
style="stroke-linecap:butt;" />
|
||||
<path
|
||||
d="M 12,35.5 L 33,35.5 L 33,35.5"
|
||||
style="fill:none; stroke:#ffffff; stroke-width:1; stroke-linejoin:miter;" />
|
||||
<path
|
||||
d="M 13,31.5 L 32,31.5"
|
||||
style="fill:none; stroke:#ffffff; stroke-width:1; stroke-linejoin:miter;" />
|
||||
<path
|
||||
d="M 14,29.5 L 31,29.5"
|
||||
style="fill:none; stroke:#ffffff; stroke-width:1; stroke-linejoin:miter;" />
|
||||
<path
|
||||
d="M 14,16.5 L 31,16.5"
|
||||
style="fill:none; stroke:#ffffff; stroke-width:1; stroke-linejoin:miter;" />
|
||||
<path
|
||||
d="M 11,14 L 34,14"
|
||||
style="fill:none; stroke:#ffffff; stroke-width:1; stroke-linejoin:miter;" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
BIN
public/blunder.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/book.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/brilliant.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/capture.mp3
Normal file
BIN
public/castle.mp3
Normal file
BIN
public/check.mp3
Normal file
BIN
public/discord.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
public/excellent.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/favicon.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/flip.png
Normal file
|
After Width: | Height: | Size: 520 B |
BIN
public/forced.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/game_end.mp3
Normal file
BIN
public/go_to_end.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/good.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/great.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/inaccuracy.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/mistake.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/move.mp3
Normal file
BIN
public/next.png
Normal file
|
After Width: | Height: | Size: 823 B |
BIN
public/promote.mp3
Normal file
BIN
public/save.png
Normal file
|
After Width: | Height: | Size: 376 B |
12
public/white_bishop.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="45" height="45">
|
||||
<g style="opacity:1; fill:none; fill-rule:evenodd; fill-opacity:1; stroke:#000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(0,0.6)">
|
||||
<g style="fill:#ffffff; stroke:#000000; stroke-linecap:butt;">
|
||||
<path d="M 9,36 C 12.39,35.03 19.11,36.43 22.5,34 C 25.89,36.43 32.61,35.03 36,36 C 36,36 37.65,36.54 39,38 C 38.32,38.97 37.35,38.99 36,38.5 C 32.61,37.53 25.89,38.96 22.5,37.5 C 19.11,38.96 12.39,37.53 9,38.5 C 7.65,38.99 6.68,38.97 6,38 C 7.35,36.54 9,36 9,36 z"/>
|
||||
<path d="M 15,32 C 17.5,34.5 27.5,34.5 30,32 C 30.5,30.5 30,30 30,30 C 30,27.5 27.5,26 27.5,26 C 33,24.5 33.5,14.5 22.5,10.5 C 11.5,14.5 12,24.5 17.5,26 C 17.5,26 15,27.5 15,30 C 15,30 14.5,30.5 15,32 z"/>
|
||||
<path d="M 25 8 A 2.5 2.5 0 1 1 20,8 A 2.5 2.5 0 1 1 25 8 z"/>
|
||||
</g>
|
||||
<path d="M 17.5,26 L 27.5,26 M 15,30 L 30,30 M 22.5,15.5 L 22.5,20.5 M 20,18 L 25,18" style="fill:none; stroke:#000000; stroke-linejoin:miter;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
9
public/white_king.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="45" height="45">
|
||||
<g fill="none" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
|
||||
<path stroke-linejoin="miter" d="M22.5 11.63V6M20 8h5"/>
|
||||
<path fill="#fff" stroke-linecap="butt" stroke-linejoin="miter" d="M22.5 25s4.5-7.5 3-10.5c0 0-1-2.5-3-2.5s-3 2.5-3 2.5c-1.5 3 3 10.5 3 10.5"/>
|
||||
<path fill="#fff" d="M12.5 37c5.5 3.5 14.5 3.5 20 0v-7s9-4.5 6-10.5c-4-6.5-13.5-3.5-16 4V27v-3.5c-2.5-7.5-12-10.5-16-4-3 6 6 10.5 6 10.5v7"/>
|
||||
<path d="M12.5 30c5.5-3 14.5-3 20 0m-20 3.5c5.5-3 14.5-3 20 0m-20 3.5c5.5-3 14.5-3 20 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 700 B |
19
public/white_knight.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="45" height="45">
|
||||
<g style="opacity:1; fill:none; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(0,0.3)">
|
||||
<path
|
||||
d="M 22,10 C 32.5,11 38.5,18 38,39 L 15,39 C 15,30 25,32.5 23,18"
|
||||
style="fill:#ffffff; stroke:#000000;" />
|
||||
<path
|
||||
d="M 24,18 C 24.38,20.91 18.45,25.37 16,27 C 13,29 13.18,31.34 11,31 C 9.958,30.06 12.41,27.96 11,28 C 10,28 11.19,29.23 10,30 C 9,30 5.997,31 6,26 C 6,24 12,14 12,14 C 12,14 13.89,12.1 14,10.5 C 13.27,9.506 13.5,8.5 13.5,7.5 C 14.5,6.5 16.5,10 16.5,10 L 18.5,10 C 18.5,10 19.28,8.008 21,7 C 22,7 22,10 22,10"
|
||||
style="fill:#ffffff; stroke:#000000;" />
|
||||
<path
|
||||
d="M 9.5 25.5 A 0.5 0.5 0 1 1 8.5,25.5 A 0.5 0.5 0 1 1 9.5 25.5 z"
|
||||
style="fill:#000000; stroke:#000000;" />
|
||||
<path
|
||||
d="M 15 15.5 A 0.5 1.5 0 1 1 14,15.5 A 0.5 1.5 0 1 1 15 15.5 z"
|
||||
transform="matrix(0.866,0.5,-0.5,0.866,9.693,-5.173)"
|
||||
style="fill:#000000; stroke:#000000;" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
5
public/white_pawn.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="45" height="45">
|
||||
<path d="m 22.5,9 c -2.21,0 -4,1.79 -4,4 0,0.89 0.29,1.71 0.78,2.38 C 17.33,16.5 16,18.59 16,21 c 0,2.03 0.94,3.84 2.41,5.03 C 15.41,27.09 11,31.58 11,39.5 H 34 C 34,31.58 29.59,27.09 26.59,26.03 28.06,24.84 29,23.03 29,21 29,18.59 27.67,16.5 25.72,15.38 26.21,14.71 26.5,13.89 26.5,13 c 0,-2.21 -1.79,-4 -4,-4 z" style="opacity:1; fill:#ffffff; fill-opacity:1; fill-rule:nonzero; stroke:#000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:miter; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 766 B |
15
public/white_queen.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="45" height="45">
|
||||
<g style="fill:#ffffff;stroke:#000000;stroke-width:1.5;stroke-linejoin:round">
|
||||
<path d="M 9,26 C 17.5,24.5 30,24.5 36,26 L 38.5,13.5 L 31,25 L 30.7,10.9 L 25.5,24.5 L 22.5,10 L 19.5,24.5 L 14.3,10.9 L 14,25 L 6.5,13.5 L 9,26 z"/>
|
||||
<path d="M 9,26 C 9,28 10.5,28 11.5,30 C 12.5,31.5 12.5,31 12,33.5 C 10.5,34.5 11,36 11,36 C 9.5,37.5 11,38.5 11,38.5 C 17.5,39.5 27.5,39.5 34,38.5 C 34,38.5 35.5,37.5 34,36 C 34,36 34.5,34.5 33,33.5 C 32.5,31 32.5,31.5 33.5,30 C 34.5,28 36,28 36,26 C 27.5,24.5 17.5,24.5 9,26 z"/>
|
||||
<path d="M 11.5,30 C 15,29 30,29 33.5,30" style="fill:none"/>
|
||||
<path d="M 12,33.5 C 18,32.5 27,32.5 33,33.5" style="fill:none"/>
|
||||
<circle cx="6" cy="12" r="2" />
|
||||
<circle cx="14" cy="9" r="2" />
|
||||
<circle cx="22.5" cy="8" r="2" />
|
||||
<circle cx="31" cy="9" r="2" />
|
||||
<circle cx="39" cy="12" r="2" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
25
public/white_rook.svg
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="45" height="45">
|
||||
<g style="opacity:1; fill:#ffffff; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(0,0.3)">
|
||||
<path
|
||||
d="M 9,39 L 36,39 L 36,36 L 9,36 L 9,39 z "
|
||||
style="stroke-linecap:butt;" />
|
||||
<path
|
||||
d="M 12,36 L 12,32 L 33,32 L 33,36 L 12,36 z "
|
||||
style="stroke-linecap:butt;" />
|
||||
<path
|
||||
d="M 11,14 L 11,9 L 15,9 L 15,11 L 20,11 L 20,9 L 25,9 L 25,11 L 30,11 L 30,9 L 34,9 L 34,14"
|
||||
style="stroke-linecap:butt;" />
|
||||
<path
|
||||
d="M 34,14 L 31,17 L 14,17 L 11,14" />
|
||||
<path
|
||||
d="M 31,17 L 31,29.5 L 14,29.5 L 14,17"
|
||||
style="stroke-linecap:butt; stroke-linejoin:miter;" />
|
||||
<path
|
||||
d="M 31,29.5 L 32.5,32 L 12.5,32 L 14,29.5" />
|
||||
<path
|
||||
d="M 11,14 L 34,14"
|
||||
style="fill:none; stroke:#000000; stroke-linejoin:miter;" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
268
src/lib/board.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
const boardFlipped = false;
|
||||
const currentMoveIndex = 0;
|
||||
const reportResults: any = undefined;
|
||||
|
||||
export async function drawBoard(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
fen = startingPositionFen
|
||||
) {
|
||||
// Draw surface of board
|
||||
let colours = ["#f6dfc0", "#b88767"];
|
||||
|
||||
for (let y = 0; y < 8; y++) {
|
||||
for (let x = 0; x < 8; x++) {
|
||||
ctx.fillStyle = colours[(x + y) % 2];
|
||||
|
||||
ctx.fillRect(x * 90, y * 90, 90, 90);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw coordinates
|
||||
ctx.font = "20px Arial";
|
||||
|
||||
let files = "abcdefgh".split("");
|
||||
for (let x = 0; x < 8; x++) {
|
||||
ctx.fillStyle = colours[x % 2];
|
||||
ctx.fillText(boardFlipped ? files[7 - x] : files[x], x * 90 + 5, 715);
|
||||
}
|
||||
for (let y = 0; y < 8; y++) {
|
||||
ctx.fillStyle = colours[(y + 1) % 2];
|
||||
ctx.fillText(
|
||||
boardFlipped ? (y + 1).toString() : (8 - y).toString(),
|
||||
5,
|
||||
y * 90 + 22
|
||||
);
|
||||
}
|
||||
|
||||
// Draw last move highlight and top move arrows
|
||||
let lastMove = reportResults?.positions[currentMoveIndex];
|
||||
|
||||
let lastMoveCoordinates = {
|
||||
from: { x: 0, y: 0 },
|
||||
to: { x: 0, y: 0 },
|
||||
};
|
||||
|
||||
if (currentMoveIndex > 0 && lastMove) {
|
||||
let lastMoveUCI = lastMove.move?.uci;
|
||||
if (!lastMoveUCI) return;
|
||||
|
||||
lastMoveCoordinates.from = getBoardCoordinates(lastMoveUCI.slice(0, 2));
|
||||
lastMoveCoordinates.to = getBoardCoordinates(lastMoveUCI.slice(2, 4));
|
||||
|
||||
ctx.globalAlpha = 0.7;
|
||||
ctx.fillStyle =
|
||||
classificationColours[
|
||||
reportResults?.positions[currentMoveIndex].classification ?? "book"
|
||||
];
|
||||
ctx.fillRect(
|
||||
lastMoveCoordinates.from.x * 90,
|
||||
lastMoveCoordinates.from.y * 90,
|
||||
90,
|
||||
90
|
||||
);
|
||||
ctx.fillRect(
|
||||
lastMoveCoordinates.to.x * 90,
|
||||
lastMoveCoordinates.to.y * 90,
|
||||
90,
|
||||
90
|
||||
);
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
// Draw pieces
|
||||
let fenBoard = fen.split(" ")[0];
|
||||
let x = boardFlipped ? 7 : 0,
|
||||
y = x;
|
||||
|
||||
for (let character of fenBoard) {
|
||||
if (character == "/") {
|
||||
x = boardFlipped ? 7 : 0;
|
||||
y += boardFlipped ? -1 : 1;
|
||||
} else if (/\d/g.test(character)) {
|
||||
x += parseInt(character) * (boardFlipped ? -1 : 1);
|
||||
} else {
|
||||
const pieceSrc = pieceImagesSrc[character];
|
||||
if (!pieceSrc) throw new Error(`No image source for piece ${character}`);
|
||||
const pieceImage = await loadImage(pieceSrc);
|
||||
|
||||
ctx.drawImage(pieceImage, x * 90, y * 90, 90, 90);
|
||||
x += boardFlipped ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw last move classification
|
||||
if (currentMoveIndex > 0 && reportResults) {
|
||||
let classification =
|
||||
reportResults?.positions[currentMoveIndex]?.classification;
|
||||
|
||||
if (!classification) return;
|
||||
|
||||
const iconSrc = classificationIconsSrc[classification];
|
||||
if (!iconSrc)
|
||||
throw new Error(`No image source for classification ${classification}`);
|
||||
const classificationIcon = await loadImage(iconSrc);
|
||||
|
||||
ctx.drawImage(
|
||||
classificationIcon,
|
||||
lastMoveCoordinates.to.x * 90 + 68,
|
||||
lastMoveCoordinates.to.y * 90 - 10,
|
||||
32,
|
||||
32
|
||||
);
|
||||
}
|
||||
|
||||
// Draw engine suggestion arrows
|
||||
if (true) {
|
||||
let arrowAttributes = [
|
||||
{
|
||||
width: 20,
|
||||
opacity: 0.8,
|
||||
},
|
||||
{
|
||||
width: 12,
|
||||
opacity: 0.55,
|
||||
},
|
||||
];
|
||||
|
||||
let topLineIndex = -1;
|
||||
for (let topLine of lastMove?.topLines ?? []) {
|
||||
topLineIndex++;
|
||||
|
||||
let from = getBoardCoordinates(topLine.moveUCI.slice(0, 2));
|
||||
let to = getBoardCoordinates(topLine.moveUCI.slice(2, 4));
|
||||
|
||||
let arrow = drawArrow(
|
||||
from.x * 90 + 45,
|
||||
from.y * 90 + 45,
|
||||
to.x * 90 + 45,
|
||||
to.y * 90 + 45,
|
||||
arrowAttributes[topLineIndex].width,
|
||||
ctx
|
||||
);
|
||||
if (!arrow) continue;
|
||||
|
||||
ctx.globalAlpha = arrowAttributes[topLineIndex].opacity;
|
||||
ctx.drawImage(arrow, 0, 0);
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getBoardCoordinates(square: string): { x: number; y: number } {
|
||||
if (boardFlipped) {
|
||||
return {
|
||||
x: 7 - "abcdefgh".split("").indexOf(square.slice(0, 1)),
|
||||
y: parseInt(square.slice(1)) - 1,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
x: "abcdefgh".split("").indexOf(square.slice(0, 1)),
|
||||
y: 8 - parseInt(square.slice(1)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function drawArrow(
|
||||
fromX: number,
|
||||
fromY: number,
|
||||
toX: number,
|
||||
toY: number,
|
||||
width: number,
|
||||
arrowCtx: CanvasRenderingContext2D
|
||||
) {
|
||||
if (!arrowCtx) return;
|
||||
|
||||
arrowCtx.canvas.width = 720;
|
||||
arrowCtx.canvas.height = 720;
|
||||
|
||||
let headlen = 15;
|
||||
let angle = Math.atan2(toY - fromY, toX - fromX);
|
||||
toX -= Math.cos(angle) * (width * 1.15);
|
||||
toY -= Math.sin(angle) * (width * 1.15);
|
||||
|
||||
arrowCtx.beginPath();
|
||||
arrowCtx.moveTo(fromX, fromY);
|
||||
arrowCtx.lineTo(toX, toY);
|
||||
arrowCtx.strokeStyle = classificationColours.best;
|
||||
arrowCtx.lineWidth = width;
|
||||
arrowCtx.stroke();
|
||||
|
||||
arrowCtx.beginPath();
|
||||
arrowCtx.moveTo(toX, toY);
|
||||
arrowCtx.lineTo(
|
||||
toX - headlen * Math.cos(angle - Math.PI / 7),
|
||||
toY - headlen * Math.sin(angle - Math.PI / 7)
|
||||
);
|
||||
|
||||
arrowCtx.lineTo(
|
||||
toX - headlen * Math.cos(angle + Math.PI / 7),
|
||||
toY - headlen * Math.sin(angle + Math.PI / 7)
|
||||
);
|
||||
|
||||
arrowCtx.lineTo(toX, toY);
|
||||
arrowCtx.lineTo(
|
||||
toX - headlen * Math.cos(angle - Math.PI / 7),
|
||||
toY - headlen * Math.sin(angle - Math.PI / 7)
|
||||
);
|
||||
|
||||
arrowCtx.strokeStyle = classificationColours.best;
|
||||
arrowCtx.lineWidth = width;
|
||||
arrowCtx.stroke();
|
||||
arrowCtx.fillStyle = classificationColours.best;
|
||||
arrowCtx.fill();
|
||||
|
||||
return arrowCtx.canvas;
|
||||
}
|
||||
|
||||
const startingPositionFen =
|
||||
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||
|
||||
const classificationColours: { [key: string]: string } = {
|
||||
brilliant: "#1baaa6",
|
||||
great: "#5b8baf",
|
||||
best: "#98bc49",
|
||||
excellent: "#98bc49",
|
||||
good: "#97af8b",
|
||||
inaccuracy: "#f4bf44",
|
||||
mistake: "#e28c28",
|
||||
blunder: "#c93230",
|
||||
forced: "#97af8b",
|
||||
book: "#a88764",
|
||||
};
|
||||
|
||||
const pieceImagesSrc: Record<string, string | undefined> = {
|
||||
P: "white_pawn.svg",
|
||||
N: "white_knight.svg",
|
||||
B: "white_bishop.svg",
|
||||
R: "white_rook.svg",
|
||||
Q: "white_queen.svg",
|
||||
K: "white_king.svg",
|
||||
p: "black_pawn.svg",
|
||||
n: "black_knight.svg",
|
||||
b: "black_bishop.svg",
|
||||
r: "black_rook.svg",
|
||||
q: "black_queen.svg",
|
||||
k: "black_king.svg",
|
||||
};
|
||||
|
||||
const classificationIconsSrc: Record<string, string | undefined> = {
|
||||
brilliant: "brilliant.png",
|
||||
great: "great.png",
|
||||
best: "best.png",
|
||||
excellent: "excellent.png",
|
||||
good: "good.png",
|
||||
inaccuracy: "inaccuracy.png",
|
||||
mistake: "mistake.png",
|
||||
blunder: "blunder.png",
|
||||
forced: "forced.png",
|
||||
book: "book.png",
|
||||
};
|
||||
|
||||
async function loadImage(filename: string): Promise<HTMLImageElement> {
|
||||
return new Promise((res) => {
|
||||
const image = new Image();
|
||||
image.src = filename;
|
||||
|
||||
image.addEventListener("load", () => res(image));
|
||||
});
|
||||
}
|
||||
55
src/lib/evalBar.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
export async function drawEvaluationBar(
|
||||
evaluationBarCtx: CanvasRenderingContext2D,
|
||||
evaluation = initialEvaluation
|
||||
) {
|
||||
evaluationBarCtx.clearRect(0, 0, 30, 720);
|
||||
evaluationBarCtx.font = "16px Arial";
|
||||
evaluationBarCtx.fillStyle = "#1e1e1e";
|
||||
|
||||
if (evaluation.type == "cp") {
|
||||
let height = Math.max(Math.min(360 - evaluation.value / 3, 680), 40);
|
||||
evaluationBarCtx.fillRect(0, 0, 30, height);
|
||||
|
||||
let evaluationText = Math.abs(evaluation.value / 100).toFixed(1);
|
||||
let evaluationTextWidth =
|
||||
evaluationBarCtx.measureText(evaluationText).width;
|
||||
|
||||
let evaluationTextY = evaluation.value >= 0 ? 710 : 20;
|
||||
evaluationBarCtx.fillStyle = evaluation.value >= 0 ? "#1e1e1e" : "#ffffff";
|
||||
evaluationBarCtx.fillText(
|
||||
evaluationText,
|
||||
15 - evaluationTextWidth / 2,
|
||||
evaluationTextY,
|
||||
30
|
||||
);
|
||||
} else {
|
||||
let evaluationText = "M" + Math.abs(evaluation.value).toString();
|
||||
let evaluationTextWidth =
|
||||
evaluationBarCtx.measureText(evaluationText).width;
|
||||
|
||||
if (evaluation.value > 0) {
|
||||
evaluationBarCtx.fillStyle = "#1e1e1e";
|
||||
evaluationBarCtx.fillText(
|
||||
evaluationText,
|
||||
15 - evaluationTextWidth / 2,
|
||||
710,
|
||||
30
|
||||
);
|
||||
} else if (evaluation.value < 0) {
|
||||
evaluationBarCtx.fillRect(0, 0, 30, 720);
|
||||
|
||||
evaluationBarCtx.fillStyle = "#ffffff";
|
||||
evaluationBarCtx.fillText(
|
||||
evaluationText,
|
||||
15 - evaluationTextWidth / 2,
|
||||
20,
|
||||
30
|
||||
);
|
||||
} else if (evaluation.value == 0) {
|
||||
evaluationBarCtx.fillStyle = "#676767";
|
||||
evaluationBarCtx.fillRect(0, 0, 30, 720);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const initialEvaluation = { type: "cp", value: 0 };
|
||||
15
src/pages/_app.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
import "../../styles/global.css";
|
||||
import "../../styles/index.css";
|
||||
|
||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Free Chess</title>
|
||||
</Head>
|
||||
<Component {...pageProps} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
15
src/pages/_document.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Head, Html, Main, NextScript } from "next/document";
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
24
src/pages/index.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import TopBar from "@/sections/index/topBar";
|
||||
import Board from "@/sections/index/board";
|
||||
import ReviewPanelBody from "@/sections/index/reviewPanelBody";
|
||||
import ReviewPanelToolBar from "@/sections/index/reviewPanelToolbar";
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<>
|
||||
<TopBar />
|
||||
|
||||
<div className="center">
|
||||
<div id="review-container">
|
||||
<Board />
|
||||
|
||||
<div id="review-panel">
|
||||
<ReviewPanelBody />
|
||||
|
||||
<ReviewPanelToolBar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
37
src/sections/index/board.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { drawBoard } from "@/lib/board";
|
||||
import { drawEvaluationBar } from "@/lib/evalBar";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export default function Board() {
|
||||
const boardRef = useRef<HTMLCanvasElement>(null);
|
||||
const evalBarRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = boardRef.current?.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
drawBoard(ctx);
|
||||
|
||||
const evalCtx = evalBarRef.current?.getContext("2d");
|
||||
if (!evalCtx) return;
|
||||
drawEvaluationBar(evalCtx);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div id="board-outer-container" className="center">
|
||||
<canvas id="evaluation-bar" width="30" height="720" ref={evalBarRef} />
|
||||
|
||||
<div id="board-inner-container" className="center">
|
||||
<div id="top-player-profile" className="profile">
|
||||
Black Player (?)
|
||||
</div>
|
||||
|
||||
<canvas id="board" width="720" height="720" ref={boardRef} />
|
||||
|
||||
<div id="bottom-player-profile" className="profile">
|
||||
White Player (?)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
30
src/sections/index/reviewPanelBody.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import ReviewResult from "./reviewResult";
|
||||
import SelectDepth from "./selectDepth";
|
||||
import SelectGameOrigin from "./selectGame/selectGameOrigin";
|
||||
|
||||
export default function ReviewPanelBody() {
|
||||
return (
|
||||
<div id="review-panel-main">
|
||||
<h1 id="review-panel-title" className="white">
|
||||
📑 Game Report
|
||||
</h1>
|
||||
|
||||
<SelectGameOrigin />
|
||||
|
||||
<button id="review-button" className="std-btn success-btn white">
|
||||
<img src="analysis_icon.png" height="25" />
|
||||
<b>Analyse</b>
|
||||
</button>
|
||||
|
||||
<SelectDepth />
|
||||
|
||||
{false && <progress id="evaluation-progress-bar" max="100" />}
|
||||
|
||||
<b id="status-message" />
|
||||
|
||||
<b id="secondary-message" className="white" />
|
||||
|
||||
{false && <ReviewResult />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
43
src/sections/index/reviewPanelToolbar.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
export default function ReviewPanelToolBar() {
|
||||
return (
|
||||
<div id="review-panel-toolbar">
|
||||
<div id="review-panel-toolbar-buttons" className="center">
|
||||
<img
|
||||
id="flip-board-button"
|
||||
src="flip.png"
|
||||
alt="Flip Board"
|
||||
title="Flip board"
|
||||
/>
|
||||
<img
|
||||
id="back-start-move-button"
|
||||
src="back_to_start.png"
|
||||
alt="Back to start"
|
||||
title="Back to start"
|
||||
/>
|
||||
<img id="back-move-button" src="back.png" alt="Back" title="Back" />
|
||||
<img id="next-move-button" src="next.png" alt="Next" title="Next" />
|
||||
<img
|
||||
id="go-end-move-button"
|
||||
src="go_to_end.png"
|
||||
alt="Go to end"
|
||||
title="Go to end"
|
||||
/>
|
||||
<img
|
||||
id="save-analysis-button"
|
||||
src="save.png"
|
||||
alt="Save analysis"
|
||||
title="Save analysis"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="white" style={{ marginBottom: "10px" }}>
|
||||
<input
|
||||
id="suggestion-arrows-setting"
|
||||
type="checkbox"
|
||||
style={{ marginRight: "0.4rem" }}
|
||||
/>
|
||||
<span>Suggestion Arrows</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
28
src/sections/index/reviewResult.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
export default function ReviewResult() {
|
||||
return (
|
||||
<div id="report-cards">
|
||||
<h2 id="accuracies-title" className="white">
|
||||
Accuracies
|
||||
</h2>
|
||||
<div id="accuracies">
|
||||
<b id="white-accuracy">0%</b>
|
||||
<b id="black-accuracy">0%</b>
|
||||
</div>
|
||||
|
||||
<div id="classification-message-container">
|
||||
<img id="classification-icon" src="book.png" height="25" />
|
||||
<b id="classification-message" />
|
||||
</div>
|
||||
|
||||
<b id="top-alternative-message">Qxf2+ was best</b>
|
||||
|
||||
<div id="engine-suggestions">
|
||||
<h2 id="engine-suggestions-title" className="white">
|
||||
Engine
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<span id="opening-name" className="white" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
src/sections/index/selectDepth.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
export default function SelectDepth() {
|
||||
return (
|
||||
<>
|
||||
<b className="white">⚙️ Search depth</b>
|
||||
<div id="depth-slider-container">
|
||||
<input
|
||||
id="depth-slider"
|
||||
type="range"
|
||||
min="14"
|
||||
max="20"
|
||||
defaultValue="16"
|
||||
/>
|
||||
<span id="depth-counter" className="white">
|
||||
16 🐇
|
||||
</span>
|
||||
</div>
|
||||
<h6 id="depth-message" className="white">
|
||||
Lower depths recommended for slower devices.
|
||||
</h6>
|
||||
</>
|
||||
);
|
||||
}
|
||||
16
src/sections/index/selectGame/inputGame.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
interface Props {
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export default function InputGame({ placeholder }: Props) {
|
||||
return (
|
||||
<textarea
|
||||
id="pgn"
|
||||
className="white"
|
||||
cols={30}
|
||||
rows={10}
|
||||
spellCheck="false"
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
22
src/sections/index/selectGame/selectGameAccount.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { GameOrigin } from "@/types";
|
||||
|
||||
interface Props {
|
||||
gameOrigin: GameOrigin;
|
||||
}
|
||||
|
||||
export default function SelectGameAccount({}: Props) {
|
||||
return (
|
||||
<div id="chess-site-username-container">
|
||||
<textarea
|
||||
id="chess-site-username"
|
||||
className="white"
|
||||
spellCheck="false"
|
||||
maxLength={48}
|
||||
placeholder="Username..."
|
||||
/>
|
||||
<button id="fetch-account-games-button" className="std-btn success-btn">
|
||||
<img src="next.png" alt=">" height="25" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
46
src/sections/index/selectGame/selectGameOrigin.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { useState } from "react";
|
||||
import InputGame from "./inputGame";
|
||||
import SelectGameAccount from "./selectGameAccount";
|
||||
import { GameOrigin } from "@/types";
|
||||
|
||||
export default function SelectGameOrigin() {
|
||||
const [gameOrigin, setGameOrigin] = useState(GameOrigin.Pgn);
|
||||
return (
|
||||
<>
|
||||
<div id="load-type-dropdown-container" className="white">
|
||||
<span style={{ marginRight: "0.3rem" }}>Load game from</span>
|
||||
<select
|
||||
id="load-type-dropdown"
|
||||
value={gameOrigin}
|
||||
onChange={(e) => setGameOrigin(e.target.value as GameOrigin)}
|
||||
>
|
||||
{Object.values(GameOrigin).map((origin) => (
|
||||
<option key={origin} value={origin}>
|
||||
{gameOriginLabel[origin]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{renderSelectGameInfo(gameOrigin)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const gameOriginLabel: Record<GameOrigin, string> = {
|
||||
[GameOrigin.Pgn]: "PGN",
|
||||
[GameOrigin.ChessCom]: "Chess.com",
|
||||
[GameOrigin.Lichess]: "Lichess",
|
||||
[GameOrigin.Json]: "JSON",
|
||||
};
|
||||
|
||||
const renderSelectGameInfo = (gameOrigin: GameOrigin) => {
|
||||
switch (gameOrigin) {
|
||||
case GameOrigin.Pgn:
|
||||
return <InputGame placeholder="Enter PGN here..." />;
|
||||
case GameOrigin.Json:
|
||||
return <InputGame placeholder="Enter JSON here..." />;
|
||||
default:
|
||||
return <SelectGameAccount gameOrigin={gameOrigin} />;
|
||||
}
|
||||
};
|
||||
7
src/sections/index/topBar.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function TopBar() {
|
||||
return (
|
||||
<div id="announcement">
|
||||
<b>Welcome ❤️</b>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
6
src/types/index.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
export enum GameOrigin {
|
||||
Pgn = "pgn",
|
||||
ChessCom = "chesscom",
|
||||
Lichess = "lichess",
|
||||
Json = "json",
|
||||
}
|
||||
44
styles/global.css
Normal file
@@ -0,0 +1,44 @@
|
||||
:root {
|
||||
--border-color: rgb(83, 83, 83);
|
||||
|
||||
--primary-color: rgb(20, 20, 20);
|
||||
--secondary-color: rgb(26, 26, 26);
|
||||
|
||||
--success-color: rgb(101, 255, 84);
|
||||
--success-color-accent: rgb(91, 229, 75);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: var(--primary-color);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: system-ui;
|
||||
}
|
||||
|
||||
.std-btn {
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
transition: box-shadow 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.success-btn {
|
||||
background-color: var(--success-color);
|
||||
box-shadow: 0px 4px var(--success-color-accent);
|
||||
}
|
||||
|
||||
.success-btn:active {
|
||||
box-shadow: 0px 2px var(--success-color-accent);
|
||||
transform: translateY(2px);
|
||||
}
|
||||
510
styles/index.css
Normal file
@@ -0,0 +1,510 @@
|
||||
.white {
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) and (min-height: 860px) {
|
||||
#review-container {
|
||||
display: grid;
|
||||
|
||||
grid-template-columns: 68vw 28vw;
|
||||
grid-template-rows: 90vh;
|
||||
}
|
||||
|
||||
#review-panel {
|
||||
margin: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 999px), (max-height: 860px) {
|
||||
#review-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
#review-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#secondary-message {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
#review-panel-toolbar {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
REPORT CARDS CONTAINER
|
||||
*/
|
||||
#report-cards {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*
|
||||
ACCURACY PERCENTAGES
|
||||
*/
|
||||
#accuracies-title {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#accuracies {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
#accuracies b {
|
||||
padding: 8px;
|
||||
border: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
#white-accuracy {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#black-accuracy {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/*
|
||||
CLASSIFICATION MESSAGE
|
||||
e.g "Nd4 is a great move"
|
||||
*/
|
||||
#classification-message-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
#classification-message {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#top-alternative-message {
|
||||
color: #98bc49;
|
||||
}
|
||||
|
||||
/*
|
||||
ENGINE SUGGESTIONS
|
||||
*/
|
||||
#engine-suggestions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#engine-suggestions-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.engine-suggestion {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
|
||||
margin: 5px;
|
||||
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.engine-suggestion b {
|
||||
padding: 0 3px;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/*
|
||||
OPENING NAME
|
||||
*/
|
||||
#opening-name {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.profile {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#board-outer-container {
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
#board-inner-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#evaluation-bar {
|
||||
background-color: white;
|
||||
border: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
#board {
|
||||
grid-column: 1 / 2;
|
||||
|
||||
max-width: 100%;
|
||||
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/*
|
||||
DIALOG CONTAINER (SPANS ENTIRE SCREEN)
|
||||
*/
|
||||
#game-select-menu-container {
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
/*
|
||||
MODAL
|
||||
*/
|
||||
#game-select-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
|
||||
min-width: 400px;
|
||||
width: 50vw;
|
||||
|
||||
border-radius: 10px;
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/*
|
||||
MODAL TITLE
|
||||
*/
|
||||
#game-select-menu h1 {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
/*
|
||||
GAMES LIST TIME PERIOD
|
||||
*/
|
||||
#game-select-period {
|
||||
margin-bottom: 10px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
/*
|
||||
GAMES LIST CONTAINER
|
||||
*/
|
||||
#games-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 5px 20px;
|
||||
|
||||
width: 90%;
|
||||
max-height: 420px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
background-color: #232323;
|
||||
}
|
||||
|
||||
#games-list::-webkit-scrollbar {
|
||||
background-color: #0e0e0e;
|
||||
}
|
||||
|
||||
#games-list::-webkit-scrollbar-thumb {
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
/*
|
||||
GAME LISTING
|
||||
*/
|
||||
.game-listing {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
|
||||
width: 100%;
|
||||
|
||||
background-color: var(--secondary-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*
|
||||
PAGINATION BUTTONS
|
||||
*/
|
||||
#game-select-page-buttons button {
|
||||
margin: 10px 5px 0 5px;
|
||||
}
|
||||
|
||||
/*
|
||||
CANCEL SELECTION BUTTON
|
||||
*/
|
||||
#game-select-cancel-button {
|
||||
margin-top: 10px;
|
||||
|
||||
background-color: whitesmoke;
|
||||
box-shadow: 0px 4px 0px #c7c7c7;
|
||||
|
||||
font-size: 18px;
|
||||
|
||||
transition: box-shadow 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
#game-select-cancel-button:active {
|
||||
box-shadow: 0px 2px 0px #c7c7c7;
|
||||
transform: translateY(2px);
|
||||
}
|
||||
|
||||
/*
|
||||
ENTIRE REVIEW PANEL
|
||||
*/
|
||||
#review-panel {
|
||||
display: grid;
|
||||
|
||||
grid-template-columns: 100%;
|
||||
|
||||
background-color: var(--secondary-color);
|
||||
|
||||
border: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
@media (min-width: 1264px) {
|
||||
#review-panel {
|
||||
grid-template-rows: 88% 12%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1264px) and (min-width: 1000px) {
|
||||
#review-panel {
|
||||
grid-template-rows: 82% 18%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 999px) {
|
||||
#review-panel {
|
||||
grid-template-rows: 82% 18%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 860px) {
|
||||
#review-panel {
|
||||
grid-template-rows: 82% 18%;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
REVIEW PANEL NON-TOOLBAR SECTION
|
||||
*/
|
||||
#review-panel-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
height: 100%;
|
||||
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 1265px), (max-height: 860px) {
|
||||
#review-panel-main {
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
REVIEW PANEL HEADING
|
||||
*/
|
||||
#review-panel-title {
|
||||
margin: 0px 0px 10px 0px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 1126px) {
|
||||
#review-panel-title {
|
||||
font-size: 27px;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
GAME LOAD TYPE DROPDOWN
|
||||
*/
|
||||
#load-type-dropdown-container {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#load-type-dropdown {
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
background-color: var(--border-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/*
|
||||
PGN INPUT TEXTAREA
|
||||
*/
|
||||
#pgn {
|
||||
width: 90%;
|
||||
height: 130px;
|
||||
resize: none;
|
||||
|
||||
border-radius: 10px;
|
||||
background-color: var(--border-color);
|
||||
}
|
||||
|
||||
/*
|
||||
CHESS.COM / LICHES USERNAME INPUT TEXTAREA
|
||||
*/
|
||||
#chess-site-username-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
#chess-site-username {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
|
||||
border-radius: 10px;
|
||||
background-color: var(--border-color);
|
||||
|
||||
resize: none;
|
||||
font-size: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#fetch-account-games-button {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
#fetch-account-games-button:active {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
|
||||
/*
|
||||
ANALYSE GREEN BUTTON
|
||||
*/
|
||||
#review-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
margin: 10px;
|
||||
padding: 10px 50px;
|
||||
|
||||
font-size: 20px;
|
||||
|
||||
text-shadow: 0px 1px 2px black;
|
||||
}
|
||||
|
||||
#review-button img {
|
||||
filter: drop-shadow(0px 1px 1px black);
|
||||
}
|
||||
|
||||
/*
|
||||
SEARCH DEPTH SLIDER
|
||||
*/
|
||||
#depth-slider-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
#depth-slider {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
#depth-message {
|
||||
margin: 0px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/*
|
||||
ANALYSIS PROGRESS BAR
|
||||
*/
|
||||
#evaluation-progress-bar {
|
||||
margin-top: 10px;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
/*
|
||||
ANALYSIS STATUS MESSAGE (INFO/ERROR)
|
||||
*/
|
||||
#status-message {
|
||||
text-align: center;
|
||||
color: rgb(255, 53, 53);
|
||||
}
|
||||
|
||||
/*
|
||||
ANALYSIS SECONDARY MESSAGE
|
||||
*/
|
||||
#secondary-message {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/*
|
||||
REVIEW PANEL BOTTOM TOOLBAR
|
||||
*/
|
||||
#review-panel-toolbar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#review-panel-toolbar {
|
||||
grid-row: 2 / 3;
|
||||
|
||||
border-top: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
#review-panel-toolbar-buttons {
|
||||
margin: 10px;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
transition: gap 0.5s ease;
|
||||
}
|
||||
|
||||
#review-panel-toolbar-buttons img {
|
||||
cursor: pointer;
|
||||
transition: transform 0.5s ease;
|
||||
}
|
||||
|
||||
#review-panel-toolbar-buttons img:hover {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
#announcement {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
margin-bottom: 8px;
|
||||
padding: 3px;
|
||||
|
||||
width: 100%;
|
||||
min-height: 2.5rem;
|
||||
background-color: rgb(104, 139, 255);
|
||||
}
|
||||
32
tsconfig.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next.config.js"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||