This commit is contained in:
ilya 2024-06-22 20:48:39 +03:00
commit a756a40572
27 changed files with 1152 additions and 0 deletions

18
.eslintrc.cjs Normal file
View file

@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

24
.gitignore vendored Normal file
View 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?

2
.prettierrc.yml Normal file
View file

@ -0,0 +1,2 @@
semi: false
singleQuote: true

30
README.md Normal file
View file

@ -0,0 +1,30 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
export default {
// other rules...
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
}
```
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list

BIN
bun.lockb Normal file

Binary file not shown.

17
components.json Normal file
View file

@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/index.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

13
index.html Normal file
View file

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>1000 руб за мои страдания</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

40
package.json Normal file
View file

@ -0,0 +1,40 @@
{
"name": "darts",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "bunx --bun vite",
"build": "tsc -b && bunx --bun vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "bunx --bun vite preview"
},
"dependencies": {
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-slot": "^1.1.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"immer": "^10.1.1",
"lucide-react": "^0.396.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"zustand": "^4.5.2"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.13.1",
"@typescript-eslint/parser": "^7.13.1",
"@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.4",
"typescript": "^5.2.2",
"vite": "^5.3.1"
}
}

6
postcss.config.js Normal file
View file

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

1
public/vite.svg Normal file
View 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="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

3
src/App.css Normal file
View file

@ -0,0 +1,3 @@
html, body, #root {
@apply h-full w-full;
}

87
src/App.tsx Normal file
View file

@ -0,0 +1,87 @@
import { create } from 'zustand'
import './App.css'
import { produce } from 'immer'
import doska from './assets/doska.svg'
import { useState } from 'react'
import { Button } from './components/ui/button'
import { Input } from './components/ui/input'
import { Label } from './components/ui/label'
interface Player {
points: number
throws: number
}
interface Game {
players: Player[]
currentPlayerIdx: number
round: number
started: boolean
startedAt: number
start(nplayers: number): void
}
const useGame = create<Game>((set) => ({
players: [],
currentPlayerIdx: -1,
round: 0,
started: false,
startedAt: -1,
start(nplayers) {
set(state => produce(state, state => {
state.players = Array.from({ length: nplayers }, () => ({
points: 0,
throws: 0
}))
state.currentPlayerIdx = 0
}))
}
}))
export default function App() {
const [startedAt, started, round] = useGame(game => [game.startedAt, game.started, game.round])
return (
<div className='flex flex-col h-full w-full'>
{/* Верхняя часть: доска */}
<div className='flex flex-col h-full justify-start items-center'>
<div className="flex flex-row justify-between items-center w-full">
<div className="flex flex-row justify-start">
<Label htmlFor="round">Раунд</Label>
<p id="round">{started ? `#${round}` : "Игра не начата"}</p></div>
<p>{started ? new Date(Date.now() - startedAt).toLocaleTimeString() : ""}</p>
</div>
<Board />
</div>
{/* Нижняя часть: управление игрой */}
<Control />
</div>
)
}
function Board() {
const [points, setPoints] = useState(0)
return (
<img src={doska} onMouseMove={e => {}} />
)
}
function Control() {
const [started, round, start, players] = useGame(game => [game.started, game.round, game.start, game.players])
const [nplayers, setNplayers] = useState(2)
return (
<div className='flex flex-col h-full justify-start gap-2'>
<Label htmlFor="nplayers">Количество игроков</Label>
<Input id="nplayers" onChange={ev => setNplayers(parseInt(ev.target.value))} type="number" value={nplayers} disabled={started} />
<Button onClick={() => start(nplayers)} disabled={started}>
{"Начать игру"}
</Button>
</div>
)
}

444
src/assets/doska.svg Normal file
View file

@ -0,0 +1,444 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.0"
width="453"
height="453"
id="svg5532"
sodipodi:docname="dartboard.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1060"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
showgrid="false"
inkscape:zoom="1.8122538"
inkscape:cx="350.39242"
inkscape:cy="147.60626"
inkscape:window-width="3440"
inkscape:window-height="1376"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg5532" />
<defs
id="defs5534">
<marker
refX="0"
refY="0"
orient="auto"
id="Arrow2Send"
style="overflow:visible">
<path
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.97309,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z"
transform="matrix(-0.3,0,0,-0.3,1.5,0)"
id="path9898"
style="font-size:12px;fill:#62adff;fill-opacity:1;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
</marker>
</defs>
<g
transform="translate(-9.31263,-200.1479)"
id="layer1">
<g
transform="translate(235.81263,426.6479)"
id="Board">
<g
id="BaseBoard">
<path
d="M 226.5,0 A 226.5,226.5 0 1 1 -226.5,2.7737334e-14 A 226.5,226.5 0 1 1 226.5,-5.5474668e-14 z"
id="path1307"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.10000002;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
<g
id="Inside Spider"
style="stroke:#d0edfd;stroke-width:1.10000002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
<path
d="M 170.55,0 A 170.55,170.55 0 1 1 -170.55,2.0885662e-14 A 170.55,170.55 0 1 1 170.55,-4.1771323e-14 z"
id="DoubleRed"
style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd" />
<path
d="M 146.91298,0 A 146.91298,146.91298 0 1 1 -146.91298,0 146.91298,146.91298 0 1 1 146.91298,0 Z"
id="path2195"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke-width:1.00096" />
<path
d="M 107.55,0 A 107.55,107.55 0 1 1 -107.55,1.3170642e-14 A 107.55,107.55 0 1 1 107.55,-2.6341283e-14 z"
id="TrebleRed"
style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd" />
<path
d="M 85.734511,0 A 85.734511,85.734511 0 1 1 -85.734511,0 85.734511,85.734511 0 1 1 85.734511,0 Z"
id="path2197"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke-width:0.957928" />
<g
id="GreenDoubleSpiders"
style="opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd">
<path
d="M 170.55,0 A 170.55,170.55 0 0 1 162.20269,52.70285 L 0,0 z"
transform="matrix(-0.45399,-0.891007,0.891007,-0.45399,0,0)"
id="path4826"
style="fill:#00a000;fill-opacity:1" />
<path
d="M 170.55,0 A 170.55,170.55 0 0 1 162.20269,52.70285 L 0,0 z"
transform="matrix(-0.891006,-0.453991,0.453991,-0.891006,0,0)"
id="path4830"
style="fill:#00a000;fill-opacity:1" />
<path
d="M 170.55,0 A 170.55,170.55 0 0 1 162.20269,52.70285 L 0,0 z"
transform="matrix(-0.987688,0.156434,-0.156434,-0.987688,0,0)"
id="path4832"
style="fill:#00a000;fill-opacity:1" />
<path
d="M 170.55,0 A 170.55,170.55 0 0 1 162.20269,52.70285 L 0,0 z"
transform="matrix(-0.707107,0.707106,-0.707106,-0.707107,0,0)"
id="path4834"
style="fill:#00a000;fill-opacity:1" />
<path
d="M 170.55,0 A 170.55,170.55 0 0 1 162.20269,52.70285 L 0,0 z"
transform="matrix(-0.156435,0.987688,-0.987688,-0.156435,0,0)"
id="path4836"
style="fill:#00a000;fill-opacity:1" />
<path
d="M 170.55,0 A 170.55,170.55 0 0 1 162.20269,52.70285 L 0,0 z"
transform="matrix(0.45399,0.891007,-0.891007,0.45399,0,0)"
id="path4838"
style="fill:#00a000;fill-opacity:1" />
<path
d="M 170.55,0 A 170.55,170.55 0 0 1 162.20269,52.70285 L 0,0 z"
transform="matrix(0.891006,0.453991,-0.453991,0.891006,0,0)"
id="path4840"
style="fill:#00a000;fill-opacity:1" />
<path
d="M 170.55,0 A 170.55,170.55 0 0 1 162.20269,52.70285 L 0,0 z"
transform="matrix(0.987688,-0.156434,0.156434,0.987688,0,0)"
id="path4842"
style="fill:#00a000;fill-opacity:1" />
<path
d="M 170.55,0 A 170.55,170.55 0 0 1 162.20269,52.70285 L 0,0 z"
transform="matrix(0.707107,-0.707106,0.707106,0.707107,0,0)"
id="path4844"
style="fill:#00a000;fill-opacity:1" />
<path
d="M 170.55,0 A 170.55,170.55 0 0 1 162.20269,52.70285 L 0,0 z"
transform="matrix(0.156435,-0.987688,0.987688,0.156435,0,0)"
id="path4846"
style="fill:#00a000;fill-opacity:1" />
</g>
<g
id="g4876"
style="opacity:1;fill:#e7e4c7;fill-opacity:1;fill-rule:evenodd">
<path
d="m -66.614831,-130.81306 a 146.84024,146.84024 0 0 1 43.693212,-14.19675 L 0.04915736,0.02259513 Z"
id="path4878"
style="stroke-width:1.00046" />
<path
d="m -130.83239,-66.636197 a 146.87608,146.87608 0 0 1 27.0105,-37.176703 L 0.03510631,0.04423649 Z"
id="path4880"
style="stroke-width:1.0007" />
<path
d="m -145.08371,22.980164 a 146.90004,146.90004 3.5625082 0 1 2e-5,-45.960459 L 0.00775386,-5.8949186e-6 Z"
id="path4882"
style="stroke-width:1.00087" />
<path
d="M -103.71911,103.70974 A 146.731,146.731 84.345034 0 1 -130.70287,66.569709 L 0.03545567,-0.04467679 Z"
id="path4884"
style="stroke-width:0.999716" />
<path
d="M -22.935899,145.09705 A 146.92846,146.92846 3.5625082 0 1 -66.65534,130.89171 L 0.04886028,-0.02245864 Z"
id="path4886"
style="stroke-width:1.00106" />
<path
d="m 66.541322,130.66961 a 146.67951,146.67951 84.345034 0 1 -43.645387,14.1812 L -0.04969816,-0.02284371 Z"
id="path4888"
style="stroke-width:0.999365" />
<path
d="M 130.75061,66.594361 A 146.78454,146.78454 0 0 1 103.75694,103.7479 L -0.03532682,-0.04451434 Z"
id="path4890"
style="stroke-width:1.00008" />
<path
d="m 145.10415,-22.982174 a 146.91288,146.91288 0 0 1 -2e-5,45.964478 L 0,5.8734336e-6 Z"
id="path4892"
style="stroke-width:1.00095" />
<path
d="m 103.76307,-103.75374 a 146.79296,146.79296 0 0 1 26.99515,37.155715 L -0.03530643,0.04448874 Z"
id="path4894"
style="stroke-width:1.00014" />
<path
d="m 22.897236,-144.85771 a 146.68651,146.68651 0 0 1 43.647449,14.18195 L -0.04967436,0.02283283 Z"
id="path4896"
style="stroke-width:0.999413" />
</g>
<g
id="GreenTrebleSpiders"
style="opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd">
<path
d="M 107.55,0 A 107.55,107.55 0 0 1 102.28613,33.234779 L 0,0 z"
transform="matrix(-0.45399,-0.891007,0.891007,-0.45399,0,0)"
id="path4856" />
<path
d="M 107.55,0 A 107.55,107.55 0 0 1 102.28613,33.234779 L 0,0 z"
transform="matrix(-0.891006,-0.453991,0.453991,-0.891006,0,0)"
id="path4858" />
<path
d="M 107.55,0 A 107.55,107.55 0 0 1 102.28613,33.234779 L 0,0 z"
transform="matrix(-0.987688,0.156434,-0.156434,-0.987688,0,0)"
id="path4860" />
<path
d="M 107.55,0 A 107.55,107.55 0 0 1 102.28613,33.234779 L 0,0 z"
transform="matrix(-0.707107,0.707106,-0.707106,-0.707107,0,0)"
id="path4862" />
<path
d="M 107.55,0 A 107.55,107.55 0 0 1 102.28613,33.234779 L 0,0 z"
transform="matrix(-0.156435,0.987688,-0.987688,-0.156435,0,0)"
id="path4864" />
<path
d="M 107.55,0 A 107.55,107.55 0 0 1 102.28613,33.234779 L 0,0 z"
transform="matrix(0.45399,0.891007,-0.891007,0.45399,0,0)"
id="path4866" />
<path
d="M 107.55,0 A 107.55,107.55 0 0 1 102.28613,33.234779 L 0,0 z"
transform="matrix(0.891006,0.453991,-0.453991,0.891006,0,0)"
id="path4868" />
<path
d="M 107.55,0 A 107.55,107.55 0 0 1 102.28613,33.234779 L 0,0 z"
transform="matrix(0.987688,-0.156434,0.156434,0.987688,0,0)"
id="path4870" />
<path
d="M 107.55,0 A 107.55,107.55 0 0 1 102.28613,33.234779 L 0,0 z"
id="path4872"
transform="matrix(0.707107,-0.707106,0.707106,0.707107,0,0)" />
<path
d="M 107.55,0 A 107.55,107.55 0 0 1 102.28613,33.234779 L 0,0 z"
transform="matrix(0.156435,-0.987688,0.987688,0.156435,0,0)"
id="path4874" />
</g>
<g
id="g5771"
style="opacity:1;fill:#e7e4c7;fill-opacity:1;fill-rule:evenodd">
<path
d="m -38.833913,-76.321755 a 85.694189,85.694189 0 0 1 25.498829,-8.285053 L 0.07038427,0.03235206 Z"
id="path5773"
style="fill:#e7e4c7;fill-opacity:1;stroke-width:0.957477" />
<path
d="m -76.229063,-38.802647 a 85.610831,85.610831 82.981878 0 1 15.743825,-21.669485 L 0.05071872,0.06390925 Z"
id="path5775"
style="fill:#e7e4c7;fill-opacity:1;stroke-width:0.956546" />
<path
d="m -84.815746,13.435193 a 85.884098,85.884098 88.977296 0 1 1.1e-5,-26.870466 L 0.01098178,-5.1174552e-6 Z"
id="path5777"
style="fill:#e7e4c7;fill-opacity:1;stroke-width:0.959599" />
<path
d="M -60.542317,60.529123 A 85.690985,85.690985 0 0 1 -76.300853,38.839328 L 0.05040196,-0.06351024 Z"
id="path5779"
style="fill:#e7e4c7;fill-opacity:1;stroke-width:0.957441" />
<path
d="M -13.335505,84.608722 A 85.696138,85.696138 1.7881672 0 1 -38.834904,76.323448 L 0.07037326,-0.03234708 Z"
id="path5781"
style="fill:#e7e4c7;fill-opacity:1;stroke-width:0.957499" />
<path
d="M 38.823169,76.300864 A 85.670808,85.670808 0 0 1 13.331297,84.583656 L -0.07051328,-0.03241135 Z"
id="path5783"
style="fill:#e7e4c7;fill-opacity:1;stroke-width:0.957216" />
<path
d="M 76.275705,38.826567 A 85.662947,85.662947 28.154966 0 1 60.522296,60.509243 L -0.05051285,-0.06364984 Z"
id="path5785"
style="fill:#e7e4c7;fill-opacity:1;stroke-width:0.957128" />
<path
d="m 84.274967,-13.349618 a 85.337062,85.337062 0 0 1 -1.1e-5,26.699316 L -0.01145986,5.3402359e-6 Z"
id="path5787"
style="fill:#e7e4c7;fill-opacity:1;stroke-width:0.953487" />
<path
d="m 60.4633,-60.449992 a 85.579859,85.579859 22.5 0 1 15.738099,21.661667 L -0.05084094,0.06406339 Z"
id="path5789"
style="fill:#e7e4c7;fill-opacity:1;stroke-width:0.9562" />
<path
d="m 13.276586,-84.248473 a 85.332333,85.332333 0 0 1 25.391147,8.2501 L -0.07238066,0.03326978 Z"
id="path5791"
style="fill:#e7e4c7;fill-opacity:1;stroke-width:0.953434" />
</g>
<path
d="M 16.450001,0 A 16.450001,16.450001 0 1 1 -16.450001,2.0144775e-15 A 16.450001,16.450001 0 1 1 16.450001,-4.0289551e-15 z"
id="path5939"
style="opacity:1;fill:#00a000;fill-opacity:1;fill-rule:evenodd" />
<path
d="M 6.9000001,0 A 6.9000001,6.9000001 0 1 1 -6.9000001,8.449784e-16 A 6.9000001,6.9000001 0 1 1 6.9000001,-1.6899568e-15 z"
id="path5944"
style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd" />
</g>
<g
id="BoardNumbers"
style="font-size:28px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;font-family:Sans">
<text
x="-16.95171"
y="-187.82498"
id="text6007"
xml:space="preserve"><tspan
x="-16.95171"
y="-187.82498"
id="tspan6009">20</tspan></text>
<text
x="51.872234"
y="-180.36624"
id="text6895"
xml:space="preserve"><tspan
x="51.872234"
y="-180.36624"
id="tspan6897">1</tspan></text>
<text
x="100.0151"
y="-150.53122"
id="text6903"
xml:space="preserve"><tspan
x="100.0151"
y="-150.53122"
id="tspan6907">18</tspan></text>
<text
x="153.24347"
y="-103.74449"
id="text6911"
xml:space="preserve"><tspan
x="153.24347"
y="-103.74449"
id="tspan6913">4</tspan></text>
<text
x="173.92455"
y="-45.769642"
id="text6915"
xml:space="preserve"><tspan
x="173.92455"
y="-45.769642"
id="tspan6917">13</tspan></text>
<text
x="187.14688"
y="9.1538963"
id="text6966"
xml:space="preserve"><tspan
x="187.14688"
y="9.1538963"
id="tspan6968">6</tspan><tspan
x="187.14688"
y="37.153896"
id="tspan6970" /></text>
<text
x="171.5513"
y="67.467781"
id="text6923"
xml:space="preserve"><tspan
x="171.5513"
y="67.467781"
id="tspan6925">10</tspan></text>
<text
x="142.7334"
y="127.13781"
id="text6927"
xml:space="preserve"><tspan
x="142.7334"
y="127.13781"
id="tspan6929">15</tspan></text>
<text
x="106.11771"
y="169.85611"
id="text6931"
xml:space="preserve"><tspan
x="106.11771"
y="169.85611"
id="tspan6933">2</tspan></text>
<text
x="42.718311"
y="196.63982"
id="text6975"
xml:space="preserve"><tspan
x="42.718311"
y="196.63982"
id="tspan6977">17</tspan></text>
<text
x="-6.1026163"
y="208.16699"
id="text6979"
xml:space="preserve"><tspan
x="-6.1026163"
y="208.16699"
id="tspan6981">3</tspan></text>
<text
x="-74.248497"
y="199.01306"
id="text6983"
xml:space="preserve"><tspan
x="-74.248497"
y="199.01306"
id="tspan6985">19</tspan></text>
<text
x="-123.74748"
y="173.24644"
id="text6987"
xml:space="preserve"><tspan
x="-123.74748"
y="173.24644"
id="tspan6989">7</tspan></text>
<text
x="-174.94165"
y="127.13781"
id="text6991"
xml:space="preserve"><tspan
x="-174.94165"
y="127.13781"
id="tspan6993">16</tspan></text>
<text
x="-193.58855"
y="72.892326"
id="text6995"
xml:space="preserve"><tspan
x="-193.58855"
y="72.892326"
id="tspan6997">8</tspan></text>
<text
x="-214.60867"
y="8.8148642"
id="text7011"
xml:space="preserve"><tspan
x="-214.60867"
y="8.8148642"
id="tspan7013">11</tspan></text>
<text
x="-208.50606"
y="-48.820953"
id="text7015"
xml:space="preserve"><tspan
x="-208.50606"
y="-48.820953"
id="tspan7017">14</tspan></text>
<text
x="-165.10965"
y="-109.50809"
id="text7019"
xml:space="preserve"><tspan
x="-165.10965"
y="-109.50809"
id="tspan7021">9</tspan></text>
<text
x="-134.25755"
y="-152.22639"
id="text7023"
xml:space="preserve"><tspan
x="-134.25755"
y="-152.22639"
id="tspan7025">12</tspan></text>
<text
x="-67.467812"
y="-178.67107"
id="text7027"
xml:space="preserve"><tspan
x="-67.467812"
y="-178.67107"
id="tspan7029">5</tspan></text>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -0,0 +1,36 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

View file

@ -0,0 +1,56 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View file

@ -0,0 +1,79 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View file

@ -0,0 +1,25 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }

View file

@ -0,0 +1,24 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }

76
src/index.css Normal file
View file

@ -0,0 +1,76 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

6
src/lib/utils.ts Normal file
View file

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

10
src/main.tsx Normal file
View file

@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

1
src/vite-env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="vite/client" />

80
tailwind.config.ts Normal file
View file

@ -0,0 +1,80 @@
import type { Config } from "tailwindcss"
const config = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
} satisfies Config
export default config

27
tsconfig.app.json Normal file
View file

@ -0,0 +1,27 @@
{
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}

19
tsconfig.json Normal file
View file

@ -0,0 +1,19 @@
{
"files": [],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
},
},
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.node.json"
}
]
}

13
tsconfig.node.json Normal file
View file

@ -0,0 +1,13 @@
{
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true,
"noEmit": true
},
"include": ["vite.config.ts"]
}

15
vite.config.ts Normal file
View file

@ -0,0 +1,15 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
// @ts-ignore
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
// @ts-ignore
"@": path.resolve(__dirname, "./src"),
},
},
})