diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..b58b603fe --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 000000000..3e22f4b71 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 000000000..79ee123c2 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000..03d9549ea --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..b870cb1a5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/react-memo.iml b/.idea/react-memo.iml new file mode 100644 index 000000000..24643cc37 --- /dev/null +++ b/.idea/react-memo.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..94a25f7f4 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 9b90842c4..67e1636bb 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ В этом репозитории реализован MVP карточкой игры "Мемо" по [тех.заданию](./docs/mvp-spec.md) Проект задеплоен на gh pages: -https://skypro-web-developer.github.io/react-memo/ +https://aleks3y1.github.io/react-memo/ ## Разработка @@ -44,3 +44,19 @@ https://skypro-web-developer.github.io/react-memo/ Запускает eslint проверку кода, эта же команда запускается перед каждым коммитом. Если не получается закоммитить, попробуйте запустить эту команду и исправить все ошибки и предупреждения. + +## Домашняя работа №1 + +- Ожидаемое время исполнение: 3 часа. +- Затраченное время: 4 часа. +- ПРОВЕРКА! + +## Домашняя работа №2 + +- Ожидаемое время исполнение: 3 часа. +- Затраченное время: 5 часа. + +## Домашняя работа №3 + +- Ожидаемое время исполнение: 10 часов. +- Затраченное время: 12 часов. diff --git a/docs/mvp-spec.md b/docs/mvp-spec.md index fab47685e..e083d7788 100644 --- a/docs/mvp-spec.md +++ b/docs/mvp-spec.md @@ -14,9 +14,10 @@ Количество карточек для каждого уровня сложности можете назначать и свои или выбрать готовый пресет. Предлагаем следующее пресеты: - - Легкий уровень - 6 карточек (3 пары) - - Средний уровень - 12 карточек (6 пар) - - Сложный уровень - 18 карточек (9 пар) + +- Легкий уровень - 6 карточек (3 пары) +- Средний уровень - 12 карточек (6 пар) +- Сложный уровень - 18 карточек (9 пар) Как только уровень сложности выбран, игроку показывается на игровой поле. diff --git a/package-lock.json b/package-lock.json index edaf5083f..c315326d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,9 +36,9 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==" + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", + "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==" }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", @@ -64,12 +64,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", - "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dependencies": { - "@babel/highlight": "^7.22.10", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -154,13 +154,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", - "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "dependencies": { - "@babel/types": "^7.22.10", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -282,31 +282,34 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -426,28 +429,28 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "engines": { "node": ">=6.9.0" } @@ -487,22 +490,23 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", - "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", - "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1973,32 +1977,32 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz", - "integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==", - "dependencies": { - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.10", - "@babel/types": "^7.22.10", - "debug": "^4.1.0", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -2006,12 +2010,12 @@ } }, "node_modules/@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -3099,13 +3103,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -3120,9 +3124,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "engines": { "node": ">=6.0.0" } @@ -3142,9 +3146,9 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -5716,12 +5720,12 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -5729,7 +5733,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -5808,11 +5812,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -6287,9 +6291,9 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -7277,9 +7281,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dependencies": { "jake": "^10.8.5" }, @@ -8330,16 +8334,16 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -8552,9 +8556,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -8641,9 +8645,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -12962,9 +12966,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "funding": [ { "type": "github", @@ -13465,9 +13469,9 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -13642,9 +13646,9 @@ } }, "node_modules/postcss": { - "version": "8.4.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", - "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", "funding": [ { "type": "opencollective", @@ -13660,9 +13664,9 @@ } ], "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -15044,9 +15048,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -16196,9 +16200,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } @@ -17532,9 +17536,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dependencies": { "colorette": "^2.0.10", "memfs": "^3.4.3", @@ -17710,9 +17714,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "engines": { "node": ">=10.0.0" }, @@ -18299,9 +18303,9 @@ } }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "engines": { "node": ">=8.3.0" }, @@ -18393,9 +18397,9 @@ "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==" }, "@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==" + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", + "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==" }, "@alloc/quick-lru": { "version": "5.2.0", @@ -18412,12 +18416,12 @@ } }, "@babel/code-frame": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", - "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "requires": { - "@babel/highlight": "^7.22.10", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" } }, "@babel/compat-data": { @@ -18477,13 +18481,13 @@ } }, "@babel/generator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", - "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "requires": { - "@babel/types": "^7.22.10", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" } }, @@ -18575,25 +18579,28 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "requires": { + "@babel/types": "^7.24.7" + } }, "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" } }, "@babel/helper-member-expression-to-functions": { @@ -18674,22 +18681,22 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" } }, "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==" }, "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==" }, "@babel/helper-validator-option": { "version": "7.22.5", @@ -18717,19 +18724,20 @@ } }, "@babel/highlight": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", - "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "requires": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" } }, "@babel/parser": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", - "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.22.5", @@ -19674,39 +19682,39 @@ } }, "@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/traverse": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz", - "integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==", - "requires": { - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.10", - "@babel/types": "^7.22.10", - "debug": "^4.1.0", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "requires": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", + "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" } }, @@ -20426,13 +20434,13 @@ } }, "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "requires": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" } }, "@jridgewell/resolve-uri": { @@ -20441,9 +20449,9 @@ "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==" }, "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" }, "@jridgewell/source-map": { "version": "0.3.5", @@ -20460,9 +20468,9 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "requires": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -22378,12 +22386,12 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "requires": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -22391,7 +22399,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -22459,11 +22467,11 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-process-hrtime": { @@ -22805,9 +22813,9 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" }, "cookie-signature": { "version": "1.0.6", @@ -23489,9 +23497,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "requires": { "jake": "^10.8.5" } @@ -24243,16 +24251,16 @@ } }, "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -24431,9 +24439,9 @@ "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==" }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "requires": { "to-regex-range": "^5.0.1" } @@ -24501,9 +24509,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" }, "for-each": { "version": "0.3.3", @@ -27568,9 +27576,9 @@ } }, "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" }, "natural-compare": { "version": "1.4.0", @@ -27924,9 +27932,9 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, "picomatch": { "version": "2.3.1", @@ -28048,13 +28056,13 @@ } }, "postcss": { - "version": "8.4.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", - "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", "requires": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" } }, "postcss-attribute-case-insensitive": { @@ -28869,9 +28877,9 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "requires": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -29711,9 +29719,9 @@ "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" }, "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==" }, "source-map-loader": { "version": "3.0.2", @@ -30704,9 +30712,9 @@ } }, "webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "requires": { "colorette": "^2.0.10", "memfs": "^3.4.3", @@ -30825,9 +30833,9 @@ } }, "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "requires": {} } } @@ -31297,9 +31305,9 @@ } }, "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "requires": {} }, "xml-name-validator": { diff --git a/package.json b/package.json index e9b7a089e..dd4111729 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "memo-card-game", "version": "0.1.0", "private": true, - "homepage": "/react-memo/", + "homepage": "https://aleks3y1.github.io/react-memo/", "dependencies": { "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", diff --git a/public/11.svg b/public/11.svg new file mode 100644 index 000000000..b3cb1dfa4 --- /dev/null +++ b/public/11.svg @@ -0,0 +1,42 @@ + + + Created with Pixso. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/22.svg b/public/22.svg new file mode 100644 index 000000000..2cc4626dd --- /dev/null +++ b/public/22.svg @@ -0,0 +1,18 @@ + + + Created with Pixso. + + + + + + + + + + + + + + + diff --git a/public/frame_off.svg b/public/frame_off.svg new file mode 100644 index 000000000..b49cb5c68 --- /dev/null +++ b/public/frame_off.svg @@ -0,0 +1,20 @@ + + + Created with Pixso. + + + + + + + + + + + + + + + + + diff --git a/public/frame_on.svg b/public/frame_on.svg new file mode 100644 index 000000000..eb15a920b --- /dev/null +++ b/public/frame_on.svg @@ -0,0 +1,18 @@ + + + Created with Pixso. + + + + + + + + + + + + + + + diff --git a/public/magic_off.svg b/public/magic_off.svg new file mode 100644 index 000000000..c09c69b48 --- /dev/null +++ b/public/magic_off.svg @@ -0,0 +1,33 @@ + + + Created with Pixso. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/magic_on.svg b/public/magic_on.svg new file mode 100644 index 000000000..72e2de0ed --- /dev/null +++ b/public/magic_on.svg @@ -0,0 +1,17 @@ + + + Created with Pixso. + + + + + + + + + + + + + + diff --git a/src/api.js b/src/api.js new file mode 100644 index 000000000..66bf57f71 --- /dev/null +++ b/src/api.js @@ -0,0 +1,25 @@ +export const getLeaderboard = async () => { + const response = await fetch("https://wedev-api.sky.pro/api/v2/leaderboard"); + if (!response.ok) { + throw new Error("Ошибка получения список лидеров"); + } + const result = await response.json(); + return result; +}; + +export async function addLeader(name, time, achievements) { + const response = await fetch("https://wedev-api.sky.pro/api/v2/leaderboard", { + method: "POST", + body: JSON.stringify({ + name, + time, + achievements, + }), + }); + + if (!response.ok) { + throw new Error("Ошибка при добавлении лидера"); + } + + return await response.json(); +} diff --git a/src/components/AchiveDescription/AchiveDescription.jsx b/src/components/AchiveDescription/AchiveDescription.jsx new file mode 100644 index 000000000..8c3ee5569 --- /dev/null +++ b/src/components/AchiveDescription/AchiveDescription.jsx @@ -0,0 +1,31 @@ +import React, { useState } from "react"; +import styles from "./AchiveDescription.module.css"; + +const AchiveDescription = ({ children, description, style }) => { + const [isHovered, setIsHovered] = useState(false); + + const handleMouseEnter = () => { + setIsHovered(true); + }; + + const handleMouseLeave = () => { + setIsHovered(false); + }; + + return ( +
+ {isHovered &&
} +
+ {children} +
+ {description} +
+
+
+ ); +}; + +export default AchiveDescription; diff --git a/src/components/AchiveDescription/AchiveDescription.module.css b/src/components/AchiveDescription/AchiveDescription.module.css new file mode 100644 index 000000000..01a1fee10 --- /dev/null +++ b/src/components/AchiveDescription/AchiveDescription.module.css @@ -0,0 +1,54 @@ +.itemContainer { + position: absolute; + display: inline-block; +} + +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 5; + pointer-events: none; +} + +.content { + position: relative; +} + +.description { + min-width: 174px; + height: auto; + border-radius: 12px; + display: block; + position: absolute; + top: -100px; + left: 100px; + transform: translateX(-50%); + padding: 10px; + border: 1px solid #c2f5ff; + background-color: #c2f5ff; + white-space: normal; + color: #004980; + z-index: 20; + visibility: visible; + opacity: 1; + transition: opacity 0.2s; + font-size: 18px; + font-weight: 400; +} + +.description::before { + content: ""; + width: 0; + height: 0; + border-left: 0 solid transparent; + border-right: 10px solid transparent; + border-top: 10px solid #c2f5ff; + position: absolute; + bottom: -10px; + left: 10%; + transform: translateX(-100%); + z-index: -1; +} diff --git a/src/components/AttemptsCounter/AttemptsCounter.jsx b/src/components/AttemptsCounter/AttemptsCounter.jsx new file mode 100644 index 000000000..56bd82b82 --- /dev/null +++ b/src/components/AttemptsCounter/AttemptsCounter.jsx @@ -0,0 +1,9 @@ +import styles from "./AttemptsCounter.module.css"; + +export function AttemptsCounter({ value }) { + return ( +
+ Осталось {value} попытки +
+ ); +} diff --git a/src/components/AttemptsCounter/AttemptsCounter.module.css b/src/components/AttemptsCounter/AttemptsCounter.module.css new file mode 100644 index 000000000..0298ba2c2 --- /dev/null +++ b/src/components/AttemptsCounter/AttemptsCounter.module.css @@ -0,0 +1,15 @@ +.attemptsBlock { + width: 672px; + margin: 0 auto; + padding: 26px; + padding-top: 22px; + box-sizing: border-box; +} + +.attemptsText { + color: #fff; + font-family: StratosSkyeng, serif; + font-size: 48px; + font-style: normal; + font-weight: 400; +} diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index 7526a56c8..fb28f000a 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -2,65 +2,64 @@ import { shuffle } from "lodash"; import { useEffect, useState } from "react"; import { generateDeck } from "../../utils/cards"; import styles from "./Cards.module.css"; -import { EndGameModal } from "../../components/EndGameModal/EndGameModal"; -import { Button } from "../../components/Button/Button"; -import { Card } from "../../components/Card/Card"; +import { EndGameModal } from "../EndGameModal/EndGameModal"; +import { Card } from "../Card/Card"; +import { AttemptsCounter } from "../AttemptsCounter/AttemptsCounter"; +import { useCustomContext } from "../../hooks/useCustomContext"; +import { Helps } from "../Helps/Helps"; -// Игра закончилась const STATUS_LOST = "STATUS_LOST"; const STATUS_WON = "STATUS_WON"; -// Идет игра: карты закрыты, игрок может их открыть const STATUS_IN_PROGRESS = "STATUS_IN_PROGRESS"; -// Начало игры: игрок видит все карты в течении нескольких секунд const STATUS_PREVIEW = "STATUS_PREVIEW"; function getTimerValue(startDate, endDate) { if (!startDate && !endDate) { - return { - minutes: 0, - seconds: 0, - }; + return { minutes: 0, seconds: 0 }; } if (endDate === null) { endDate = new Date(); } - const diffInSecconds = Math.floor((endDate.getTime() - startDate.getTime()) / 1000); - const minutes = Math.floor(diffInSecconds / 60); - const seconds = diffInSecconds % 60; - return { - minutes, - seconds, - }; + const diffInSeconds = Math.floor((endDate.getTime() - startDate.getTime()) / 1000); + const minutes = Math.floor(diffInSeconds / 60); + const seconds = diffInSeconds % 60; + return { minutes, seconds }; } -/** - * Основной компонент игры, внутри него находится вся игровая механика и логика. - * pairsCount - сколько пар будет в игре - * previewSeconds - сколько секунд пользователь будет видеть все карты открытыми до начала игры - */ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { - // В cards лежит игровое поле - массив карт и их состояние открыта\закрыта + const { + attempts, + startLife, + handleAttemptsChange, + hardGame, + handleIsAlahomora, + isAlahomoraUsed, + isClairvoyanceUsed, + handleIsClairvoyance, + } = useCustomContext(); const [cards, setCards] = useState([]); - // Текущий статус игры const [status, setStatus] = useState(STATUS_PREVIEW); - - // Дата начала игры const [gameStartDate, setGameStartDate] = useState(null); - // Дата конца игры const [gameEndDate, setGameEndDate] = useState(null); + const [playerLost, setPlayerLost] = useState(false); + const [isInLeaderboard] = useState(hardGame); + + const [timer, setTimer] = useState({ seconds: 0, minutes: 0 }); - // Стейт для таймера, высчитывается в setInteval на основе gameStartDate и gameEndDate - const [timer, setTimer] = useState({ - seconds: 0, - minutes: 0, - }); + useEffect(() => { + if (attempts === 0) { + setPlayerLost(true); + finishGame(STATUS_LOST); + } + }, [attempts]); - function finishGame(status = STATUS_LOST) { + function finishGame(status) { setGameEndDate(new Date()); setStatus(status); } + function startGame() { const startDate = new Date(); setGameEndDate(null); @@ -68,108 +67,152 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { setTimer(getTimerValue(startDate, null)); setStatus(STATUS_IN_PROGRESS); } + function resetGame() { setGameStartDate(null); setGameEndDate(null); setTimer(getTimerValue(null, null)); setStatus(STATUS_PREVIEW); + handleAttemptsChange(startLife); + setPlayerLost(false); + handleIsAlahomora(false); + handleIsClairvoyance(false); } - /** - * Обработка основного действия в игре - открытие карты. - * После открытия карты игра может пепереходит в следующие состояния - * - "Игрок выиграл", если на поле открыты все карты - * - "Игрок проиграл", если на поле есть две открытые карты без пары - * - "Игра продолжается", если не случилось первых двух условий - */ const openCard = clickedCard => { - // Если карта уже открыта, то ничего не делаем - if (clickedCard.open) { - return; - } - // Игровое поле после открытия кликнутой карты - const nextCards = cards.map(card => { - if (card.id !== clickedCard.id) { - return card; - } - - return { - ...card, - open: true, - }; - }); + if (clickedCard.open) return; + const nextCards = cards.map(card => (card.id !== clickedCard.id ? card : { ...card, open: true })); setCards(nextCards); const isPlayerWon = nextCards.every(card => card.open); - - // Победа - все карты на поле открыты if (isPlayerWon) { finishGame(STATUS_WON); return; } - // Открытые карты на игровом поле const openCards = nextCards.filter(card => card.open); + const openCardsWithoutPair = openCards.filter( + card => openCards.filter(openCard => card.suit === openCard.suit && card.rank === openCard.rank).length < 2, + ); - // Ищем открытые карты, у которых нет пары среди других открытых - const openCardsWithoutPair = openCards.filter(card => { - const sameCards = openCards.filter(openCard => card.suit === openCard.suit && card.rank === openCard.rank); - - if (sameCards.length < 2) { - return true; - } - - return false; - }); + if (openCardsWithoutPair.length >= 2 && attempts > 0) { + handleAttemptsChange(attempts - 1); - const playerLost = openCardsWithoutPair.length >= 2; + setTimeout(() => { + const closeCards = cards.map(card => + openCardsWithoutPair.some(openCard => openCard.id === card.id) ? { ...card, open: false } : card, + ); + setCards(closeCards); + }, 1000); + } - // "Игрок проиграл", т.к на поле есть две открытые карты без пары if (playerLost) { finishGame(STATUS_LOST); + handleAttemptsChange(startLife); + return; + } + }; + + const alahomora = () => { + if (isAlahomoraUsed) { + return; + } + + const closedCards = cards.filter(card => !card.open); + if (closedCards.length > 0) { + const randomIndex = Math.floor(Math.random() * closedCards.length); + const randomCard = closedCards[randomIndex]; + const pairCard = closedCards.find( + card => card.suit === randomCard.suit && card.rank === randomCard.rank && card.id !== randomCard.id, + ); + + if (pairCard) { + const nextCards = cards.map(card => { + if (card.id === randomCard.id || card.id === pairCard.id) { + return { ...card, open: true }; + } + return card; + }); + + setCards(nextCards); + handleIsAlahomora(true); + const allCardsOpen = nextCards.every(card => card.open); + if (allCardsOpen) { + finishGame(STATUS_WON); + } + } + } + }; + + const clairvoyance = () => { + if (isClairvoyanceUsed) { return; } - // ... игра продолжается + const initiallyOpenCards = cards.filter(card => card.open); + const pairsOpenedCards = initiallyOpenCards.filter(card => + initiallyOpenCards.some( + openCard => openCard.id !== card.id && openCard.suit === card.suit && openCard.rank === card.rank, + ), + ); + + const nextCards = cards.map(card => (card.open ? card : { ...card, open: true })); + setCards(nextCards); + handleIsClairvoyance(true); + + const savedGameEndDate = gameEndDate; + const savedGameStartDate = gameStartDate; + const pauseStartDate = new Date(); + setGameEndDate(pauseStartDate); + + setTimeout(() => { + const pauseEndDate = new Date(); + const pauseDuration = pauseEndDate - pauseStartDate; + setGameStartDate(new Date(savedGameStartDate.getTime() + pauseDuration)); + setGameEndDate(savedGameEndDate); + + const finalCards = nextCards.map(card => + pairsOpenedCards.some(openCard => openCard.id === card.id) ? card : { ...card, open: false }, + ); + setCards(finalCards); + }, 5000); }; + useEffect(() => { + if (status !== STATUS_IN_PROGRESS) return; + + const intervalId = setInterval(() => { + setTimer(getTimerValue(gameStartDate, gameEndDate)); + }, 300); + + return () => clearInterval(intervalId); + }, [gameStartDate, gameEndDate, status]); + const isGameEnded = status === STATUS_LOST || status === STATUS_WON; - // Игровой цикл useEffect(() => { - // В статусах кроме превью доп логики не требуется - if (status !== STATUS_PREVIEW) { - return; - } + if (status !== STATUS_PREVIEW) return; - // В статусе превью мы if (pairsCount > 36) { alert("Столько пар сделать невозможно"); return; } - setCards(() => { - return shuffle(generateDeck(pairsCount, 10)); - }); + setCards(() => shuffle(generateDeck(pairsCount, 10))); const timerId = setTimeout(() => { startGame(); }, previewSeconds * 1000); - return () => { - clearTimeout(timerId); - }; + return () => clearTimeout(timerId); }, [status, pairsCount, previewSeconds]); - // Обновляем значение таймера в интервале useEffect(() => { const intervalId = setInterval(() => { setTimer(getTimerValue(gameStartDate, gameEndDate)); }, 300); - return () => { - clearInterval(intervalId); - }; + return () => clearInterval(intervalId); }, [gameStartDate, gameEndDate]); return ( @@ -185,17 +228,19 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { <>
min
-
{timer.minutes.toString().padStart("2", "0")}
+
{timer.minutes.toString().padStart(2, "0")}
.
sec
-
{timer.seconds.toString().padStart("2", "0")}
+
{timer.seconds.toString().padStart(2, "0")}
)} - {status === STATUS_IN_PROGRESS ? : null} + {status === STATUS_IN_PROGRESS ? ( + + ) : null}
@@ -208,18 +253,22 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { rank={card.rank} /> ))} +
- {isGameEnded ? ( + {isGameEnded && (
- ) : null} + )} ); } diff --git a/src/components/Cards/Cards.module.css b/src/components/Cards/Cards.module.css index 000c5006c..5ebcb2513 100644 --- a/src/components/Cards/Cards.module.css +++ b/src/components/Cards/Cards.module.css @@ -1,8 +1,7 @@ .container { width: 672px; margin: 0 auto; - padding: 26px; - padding-top: 22px; + padding: 22px 26px 26px 26px; box-sizing: border-box; } @@ -28,16 +27,16 @@ .header { display: flex; justify-content: space-between; - align-items: end; + align-items: flex-end; margin-bottom: 35px; } .timer { display: flex; - align-items: end; + align-items: flex-end; color: #fff; - font-family: StratosSkyeng; + font-family: StratosSkyeng, serif; font-size: 64px; font-style: normal; font-weight: 400; @@ -62,7 +61,7 @@ .timerDescription { color: #fff; font-variant-numeric: lining-nums proportional-nums; - font-family: StratosSkyeng; + font-family: StratosSkyeng, serif; font-size: 16px; font-style: normal; font-weight: 400; diff --git a/src/components/EndGameModal/EndGameModal.jsx b/src/components/EndGameModal/EndGameModal.jsx index 722394833..67a342e6c 100644 --- a/src/components/EndGameModal/EndGameModal.jsx +++ b/src/components/EndGameModal/EndGameModal.jsx @@ -1,27 +1,111 @@ import styles from "./EndGameModal.module.css"; - import { Button } from "../Button/Button"; - import deadImageUrl from "./images/dead.png"; import celebrationImageUrl from "./images/celebration.png"; +import { useCustomContext } from "../../hooks/useCustomContext"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { addLeader, getLeaderboard } from "../../api"; -export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick }) { - const title = isWon ? "Вы победили!" : "Вы проиграли!"; +const ACHIEVEMENTS = { + HARD_MODE: 1, + NO_SUPERPOWERS: 2, +}; +export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick, hardGame }) { + const title = isWon ? "Вы победили!" : "Вы проиграли!"; const imgSrc = isWon ? celebrationImageUrl : deadImageUrl; + const imgAlt = isWon ? "celebration emoji" : "dead emoji"; + const { handleLeaderboardChange, isAlahomoraUsed, isClairvoyanceUsed } = useCustomContext(); + const [isHardMode, setIsHardMode] = useState(false); + const resultTime = gameDurationMinutes * 60 + gameDurationSeconds; + const [isInLeaderboard, setIsInLeaderboard] = useState(false); + const [name, setName] = useState(""); + const [achievements, setAchievements] = useState([]); + const navigate = useNavigate(); + + useEffect(() => { + setIsHardMode(hardGame === 9); + }, [hardGame]); + + useEffect(() => { + if (isWon && isHardMode) { + getLeaderboard() + .then(data => { + const leaders = data.leaders.sort((a, b) => a.time - b.time).slice(0, 10); + handleLeaderboardChange(leaders); + const leadersTimes = leaders.map(leader => leader.time); + const isTopTen = leadersTimes.length < 10 || resultTime < Math.max(...leadersTimes); + setIsInLeaderboard(isTopTen); + }) + .catch(error => { + console.error("Ошибка:", error); + }); + } + }, [isWon, resultTime, handleLeaderboardChange, isHardMode]); + + useEffect(() => { + const earnedAchievements = []; + const easyGame = localStorage.getItem("isEasyMode"); - const imgAlt = isWon ? "celebration emodji" : "dead emodji"; + if (easyGame !== "true" && isHardMode) { + earnedAchievements.push(ACHIEVEMENTS.HARD_MODE); + } + + if (!isAlahomoraUsed && !isClairvoyanceUsed && isWon) { + earnedAchievements.push(ACHIEVEMENTS.NO_SUPERPOWERS); + } + + setAchievements(earnedAchievements); + }, [isHardMode, isAlahomoraUsed, isClairvoyanceUsed, isWon]); + + const handleSaveResult = async () => { + const playerName = name.trim() || ""; + try { + const updatedLeaderboard = await addLeader(playerName, resultTime, achievements); + handleLeaderboardChange(updatedLeaderboard.leaders); + navigate("/leaderboard"); + } catch (error) { + console.log("Ошибка:", error); + } + }; + + const handlePlayAgain = async () => { + if (isWon && isHardMode && isInLeaderboard) { + await handleSaveResult(); + } + onClick(); + }; + + const handleGoToLeaderboard = async () => { + navigate("/leaderboard"); + }; return (
{imgAlt} -

{title}

+ {isHardMode && isInLeaderboard && ( + <> +

Вы попали на Лидерборд!

+ setName(e.target.value)} + /> + + + )} + {!isInLeaderboard &&

{title}

}

Затраченное время:

- {gameDurationMinutes.toString().padStart("2", "0")}.{gameDurationSeconds.toString().padStart("2", "0")} + {gameDurationMinutes.toString().padStart(2, "0")}.{gameDurationSeconds.toString().padStart(2, "0")}
- - + +

+ Перейти к лидерборду +

); } diff --git a/src/components/EndGameModal/EndGameModal.module.css b/src/components/EndGameModal/EndGameModal.module.css index 9368cb8b5..3fbc3b521 100644 --- a/src/components/EndGameModal/EndGameModal.module.css +++ b/src/components/EndGameModal/EndGameModal.module.css @@ -1,6 +1,6 @@ .modal { width: 480px; - height: 459px; + min-height: 459px; border-radius: 12px; background: #c2f5ff; display: flex; @@ -12,7 +12,7 @@ .image { width: 96px; height: 96px; - margin-bottom: 8px; + margin: 36px 0 8px 0; } .title { @@ -23,8 +23,8 @@ font-style: normal; font-weight: 400; line-height: 48px; - margin-bottom: 28px; + text-align: center; } .description { @@ -46,6 +46,33 @@ font-style: normal; font-weight: 400; line-height: 72px; - + text-align: center; margin-bottom: 40px; } + +.leader_name { + width: 276px; + height: 45px; + top: 334px; + left: 374px; + border-radius: 10px; + margin-bottom: 28px; + font-family: StratosSkyeng; + + font-size: 24px; + font-weight: 400; + line-height: 32px; + text-align: center; +} + +.link_leader { + font-family: StratosSkyeng; + color: #004980; + font-size: 18px; + font-weight: 400; + line-height: 32px; + text-align: left; + text-decoration: underline; + margin: 18px 0 48px 0; + cursor: pointer; +} diff --git a/src/components/Helps/Helps.jsx b/src/components/Helps/Helps.jsx new file mode 100644 index 000000000..9ca9c404d --- /dev/null +++ b/src/components/Helps/Helps.jsx @@ -0,0 +1,44 @@ +import React from "react"; +import { Button } from "../Button/Button"; +import styles from "./Helps.module.css"; +import ItemWithDescription from "../ItemWithDescription/ItemWithDescription"; + +export function Helps({ resetGame, alahomora, clairvoyance }) { + const descriptions = () => { + return ( + <> +
+

Алохомора

+

Открывается случайная пара карт.

+
+ + ); + }; + + const descriptionCard = () => { + return ( + <> +
+

Прозрение

+

+ На 5 секунд показываются все карты. Таймер длительности игры на это время останавливается. +

+
+ + ); + }; + + return ( + <> +
+ + открыть карты + + + открыть карты + +
+ + + ); +} diff --git a/src/components/Helps/Helps.module.css b/src/components/Helps/Helps.module.css new file mode 100644 index 000000000..5b79021b5 --- /dev/null +++ b/src/components/Helps/Helps.module.css @@ -0,0 +1,22 @@ +.helps_container { + display: flex; + gap: 15px; + cursor: pointer; +} + +.helps_title { + font-family: StratosSkyeng, serif; + font-size: 18px; + font-weight: 700; + line-height: 24px; + text-align: center; +} + +.helps_text { + font-family: StratosSkyeng, serif; + font-size: 18px; + font-weight: 400; + line-height: 24px; + text-align: center; + margin: 10px 20px 20px 20px; +} diff --git a/src/components/ItemWithDescription/ItemWithDescription.js b/src/components/ItemWithDescription/ItemWithDescription.js new file mode 100644 index 000000000..4dbe01f46 --- /dev/null +++ b/src/components/ItemWithDescription/ItemWithDescription.js @@ -0,0 +1,25 @@ +import React, { useState } from "react"; +import styles from "./ItemWithDescription.module.css"; + +const ItemWithDescription = ({ children, description }) => { + const [isHovered, setIsHovered] = useState(false); + const handleMouseEnter = () => { + setIsHovered(true); + }; + + const handleMouseLeave = () => { + setIsHovered(false); + }; + + return ( +
+ {isHovered &&
} +
+ {children} + {isHovered &&
{description}
} +
+
+ ); +}; + +export default ItemWithDescription; diff --git a/src/components/ItemWithDescription/ItemWithDescription.module.css b/src/components/ItemWithDescription/ItemWithDescription.module.css new file mode 100644 index 000000000..103ead715 --- /dev/null +++ b/src/components/ItemWithDescription/ItemWithDescription.module.css @@ -0,0 +1,37 @@ +.itemContainer { + position: relative; + display: inline-block; +} + +.description { + display: block; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + background-color: #c2f5ff; + padding: 10px; + border: 1px solid #ccc; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + z-index: 15; + white-space: wrap; + width: 222px; + border-radius: 12px; + color: #004980; +} + +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgb(0, 73, 128, 0.5); + z-index: 5; + pointer-events: none; +} + +.content { + position: relative; + z-index: 10; +} diff --git a/src/components/LeaderboardBlock/LeaderboardBlock.jsx b/src/components/LeaderboardBlock/LeaderboardBlock.jsx new file mode 100644 index 000000000..b5b03f76d --- /dev/null +++ b/src/components/LeaderboardBlock/LeaderboardBlock.jsx @@ -0,0 +1,141 @@ +import { useCustomContext } from "../../hooks/useCustomContext"; +import styles from "../../pages/Leaderboard/Leaderboard.module.css"; +import { Button } from "../Button/Button"; +import { useNavigate } from "react-router-dom"; +import AchiveDescription from "../AchiveDescription/AchiveDescription"; +import React from "react"; + +const ACHIEVEMENTS = { + HARD_MODE: 1, + NO_SUPERPOWERS: 2, +}; + +const getAchievementIcon = (achievements, id, type) => { + switch (id) { + case ACHIEVEMENTS.HARD_MODE: + return type === "on" ? "frame_on" : "frame_off"; + case ACHIEVEMENTS.NO_SUPERPOWERS: + return type === "on" ? "magic_on" : "magic_off"; + default: + return ""; + } +}; + +export function LeaderboardBlock() { + const { leaderboard } = useCustomContext(); + const navigate = useNavigate(); + const sortedLeaderboard = leaderboard.sort((a, b) => a.time - b.time).slice(0, 10); + console.log(sortedLeaderboard); + + const startGame = () => { + navigate("/"); + }; + + const formatTime = timeInSeconds => { + const minutes = Math.floor(timeInSeconds / 60); + const seconds = timeInSeconds % 60; + return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; + }; + + const descriptions = () => { + return ( +
+ Игра пройден в сложном режиме +
+ ); + }; + + const descriptionCard = () => { + return ( +
+ Игра пройдена без супер-сил +
+ ); + }; + + return ( +
+
+
+ Лидерборд + +
+
    +
  • +
    + Позиция + Пользователь + Достижения + Время +
    +
  • + {sortedLeaderboard.map((leader, index) => ( +
  • +
    + {index + 1} + {leader.name} + + {leader.achievements.includes(ACHIEVEMENTS.HARD_MODE) ? ( + + { + + ) : ( +
    + { +
    + )} + {leader.achievements.includes(ACHIEVEMENTS.NO_SUPERPOWERS) ? ( + + { + + ) : ( +
    + { +
    + )} +
    + {formatTime(leader.time)} +
    +
  • + ))} +
+
+
+ ); +} diff --git a/src/context/Context.jsx b/src/context/Context.jsx new file mode 100644 index 000000000..693842f6b --- /dev/null +++ b/src/context/Context.jsx @@ -0,0 +1,103 @@ +import React, { createContext, useState, useCallback, useEffect } from "react"; +import { getLeaderboard } from "../api"; + +export const Context = createContext(null); + +export const ContextProvider = ({ children }) => { + const ONE_LIFE = 1; + const THREE_LIFE = 3; + + const [attempts, setAttempts] = useState(() => { + const savedAttempts = localStorage.getItem("attempts"); + return savedAttempts ? Number(savedAttempts) : ONE_LIFE; + }); + + const [startLife, setStartLife] = useState(() => { + const savedStartLife = localStorage.getItem("startLife"); + return savedStartLife ? Number(savedStartLife) : ONE_LIFE; + }); + + const [leaderboard, setLeaderboard] = useState([]); + const [isAlahomoraUsed, setIsAlahomoraUsed] = useState(false); + const [isClairvoyanceUsed, setIsClairvoyanceUsed] = useState(false); + + const handleIsAlahomora = useCallback(result => { + setIsAlahomoraUsed(result); + }, []); + + const handleIsClairvoyance = useCallback(result => { + setIsClairvoyanceUsed(result); + }, []); + + const handleLeaderboardChange = useCallback(leaders => { + setLeaderboard(leaders); + }, []); + + const [hardGame, setHardGame] = useState(() => { + const savedHardGame = localStorage.getItem("hardGame"); + return savedHardGame ? Number(savedHardGame) : null; + }); + + useEffect(() => { + getLeaderboard() + .then(result => { + setLeaderboard(result.leaders); + }) + .catch(error => { + console.log("Ошибка:", error); + }); + }, []); + + useEffect(() => { + localStorage.setItem("attempts", attempts); + }, [attempts]); + + useEffect(() => { + localStorage.setItem("startLife", startLife); + }, [startLife]); + + useEffect(() => { + localStorage.setItem("hardGame", hardGame); + }, [hardGame]); + + const handleAttemptsChangeOnStart = useCallback(() => { + const newLife = startLife === ONE_LIFE ? THREE_LIFE : ONE_LIFE; + setAttempts(newLife); + setStartLife(newLife); + }, [startLife]); + + const handleAttemptsChange = useCallback(num => { + setAttempts(num); + }, []); + + const handleStartLifeChange = useCallback(newLife => { + setStartLife(newLife); + setAttempts(newLife); + }, []); + + const handleHardGameChange = useCallback(num => { + setHardGame(num); + }, []); + + return ( + + {children} + + ); +}; diff --git a/src/hooks/useCustomContext.js b/src/hooks/useCustomContext.js new file mode 100644 index 000000000..fed05cce1 --- /dev/null +++ b/src/hooks/useCustomContext.js @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import { Context } from "../context/Context"; + +export const useCustomContext = () => { + return useContext(Context); +}; diff --git a/src/index.js b/src/index.js index f689c5f0b..b5b8a9972 100644 --- a/src/index.js +++ b/src/index.js @@ -3,10 +3,13 @@ import ReactDOM from "react-dom/client"; import "./index.css"; import { RouterProvider } from "react-router-dom"; import { router } from "./router"; +import { ContextProvider } from "./context/Context"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - + + + , ); diff --git a/src/pages/Leaderboard/Leaderboard.jsx b/src/pages/Leaderboard/Leaderboard.jsx new file mode 100644 index 000000000..a2c24e965 --- /dev/null +++ b/src/pages/Leaderboard/Leaderboard.jsx @@ -0,0 +1,5 @@ +import { LeaderboardBlock } from "../../components/LeaderboardBlock/LeaderboardBlock"; + +export function Leaderboard() { + return ; +} diff --git a/src/pages/Leaderboard/Leaderboard.module.css b/src/pages/Leaderboard/Leaderboard.module.css new file mode 100644 index 000000000..d739b856c --- /dev/null +++ b/src/pages/Leaderboard/Leaderboard.module.css @@ -0,0 +1,53 @@ +.container_leader { + width: 100%; + min-height: 100%; + display: flex; + justify-content: center; + color: white; +} + +.container_leader_top { + display: flex; + flex-direction: row; + justify-content: space-between; + margin: 0 40px 40px 40px; +} + +.container_leader_block { + display: flex; + flex-direction: column; + box-sizing: border-box; + margin-top: 52px; + font-family: StratosSkyeng; + font-size: 24px; + font-weight: 400; + line-height: 32px; + text-align: left; + max-width: 944px; + width: 100%; +} + +.position { + height: 64px; + top: 142px; + left: 40px; + border-radius: 12px; + background: #ffffff; + list-style-type: none; + margin-bottom: 15px; + overflow: hidden; +} + +.position_information { + box-sizing: border-box; + display: grid; + grid-template-columns: 2fr 4fr 1fr 1fr; + gap: 66px; + color: #999999; + margin: 16px 20px 16px 20px; +} + +.achive { + position: absolute; + display: inline-block; +} diff --git a/src/pages/SelectLevelPage/LevelStyle.js b/src/pages/SelectLevelPage/LevelStyle.js new file mode 100644 index 000000000..ca7d864c4 --- /dev/null +++ b/src/pages/SelectLevelPage/LevelStyle.js @@ -0,0 +1,8 @@ +export const LevelStyle = (currentLevel, level) => { + const isLevelSelected = currentLevel === level; + + return { + backgroundColor: isLevelSelected ? "#0080c1" : "#fff", + color: isLevelSelected ? "#fff" : "#0080c1", + }; +}; diff --git a/src/pages/SelectLevelPage/SelectLevelPage.jsx b/src/pages/SelectLevelPage/SelectLevelPage.jsx index 758942e51..a4e75c9bc 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.jsx +++ b/src/pages/SelectLevelPage/SelectLevelPage.jsx @@ -1,28 +1,79 @@ -import { Link } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import styles from "./SelectLevelPage.module.css"; +import { useCustomContext } from "../../hooks/useCustomContext"; +import { Button } from "../../components/Button/Button"; +import { useState, useCallback, useEffect } from "react"; +import { LevelStyle } from "./LevelStyle.js"; export function SelectLevelPage() { + const { handleStartLifeChange, handleHardGameChange } = useCustomContext(); + const [gameLevel, setGameLevel] = useState(0); + const [isEasyMode, setIsEasyMode] = useState(() => { + const savedEasyMode = localStorage.getItem("isEasyMode"); + return savedEasyMode ? JSON.parse(savedEasyMode) : false; + }); + const navigate = useNavigate(); + + useEffect(() => { + if (isEasyMode) { + handleStartLifeChange(3); + } else { + handleStartLifeChange(1); + } + }, [isEasyMode, handleStartLifeChange]); + + useEffect(() => { + localStorage.setItem("isEasyMode", JSON.stringify(isEasyMode)); + }, [isEasyMode]); + + const handleLevelChange = useCallback(level => { + setGameLevel(level); + handleHardGameChange(level); + }, []); + + const handleCheckboxChange = useCallback(() => { + setIsEasyMode(prevMode => !prevMode); + }, []); + + const handlePlayClick = useCallback(() => { + if (gameLevel === 0) { + alert(`Не выбрана сложность`); + } else { + navigate(`/game/${gameLevel}`); + } + }, [gameLevel, navigate]); + + const handleLeaderboard = () => { + navigate(`/leaderboard`); + }; + return (
-

Выбери сложность

+

+ Выбери +
+ сложность +

    -
  • - - 1 - +
  • handleLevelChange(3)} style={LevelStyle(gameLevel, 3)}> + 1
  • -
  • - - 2 - +
  • handleLevelChange(6)} style={LevelStyle(gameLevel, 6)}> + 2
  • -
  • - - 3 - +
  • handleLevelChange(9)} style={LevelStyle(gameLevel, 9)}> + 3
+ + +

+ Перейти к лидерборду +

); diff --git a/src/pages/SelectLevelPage/SelectLevelPage.module.css b/src/pages/SelectLevelPage/SelectLevelPage.module.css index 390ac0def..b7182641f 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.module.css +++ b/src/pages/SelectLevelPage/SelectLevelPage.module.css @@ -8,7 +8,7 @@ .modal { width: 480px; - height: 459px; + min-height: 459px; border-radius: 12px; background: #c2f5ff; display: flex; @@ -26,6 +26,7 @@ font-style: normal; font-weight: 400; line-height: 48px; + margin-top: 48px; } .levels { @@ -43,6 +44,15 @@ flex-direction: column; justify-content: center; flex-shrink: 0; + color: #0080c1; + text-align: center; + font-family: StratosSkyeng; + font-size: 64px; + font-style: normal; + font-weight: 400; + line-height: 72px; + text-decoration: none; + cursor: pointer; border-radius: 12px; background: #fff; @@ -62,3 +72,37 @@ .levelLink:visited { color: #0080c1; } + +.checkBox { + cursor: pointer; + width: 30px; + height: 29px; +} + +.labelText { + display: flex; + align-items: center; + font-family: inherit; + color: #004980; + font-size: 24px; + line-height: 32px; + margin-bottom: 38px; +} + +.buttonLink { + text-decoration: none; + color: inherit; + font-variant-numeric: lining-nums proportional-nums; +} + +.leader_link { + font-family: StratosSkyeng; + color: #004980; + font-size: 18px; + font-weight: 400; + line-height: 32px; + text-align: left; + text-decoration: underline; + margin: 18px 0 48px 0; + cursor: pointer; +} diff --git a/src/router.js b/src/router.js index da6e94b51..4826abf63 100644 --- a/src/router.js +++ b/src/router.js @@ -1,6 +1,7 @@ import { createBrowserRouter } from "react-router-dom"; import { GamePage } from "./pages/GamePage/GamePage"; import { SelectLevelPage } from "./pages/SelectLevelPage/SelectLevelPage"; +import { Leaderboard } from "./pages/Leaderboard/Leaderboard"; export const router = createBrowserRouter( [ @@ -12,6 +13,10 @@ export const router = createBrowserRouter( path: "/game/:pairsCount", element: , }, + { + path: "/leaderboard", + element: , + }, ], /** * basename нужен для корректной работы в gh pages