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 @@
+
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 @@
+
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 @@
+
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 @@
+
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 @@
+
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 @@
+
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 (

-
{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 (
+
+
+
+ Лидерборд
+
+
+
+
+
+ );
+}
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