diff --git a/.eslintrc.json b/.eslintrc.json index e37e1e072..d9932d762 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,6 @@ "rules": { "camelcase": ["error", { "properties": "never" }], "prettier/prettier": "error", - "eqeqeq": ["error", "always"], - "no-unused-vars": ["error"] + "eqeqeq": ["error", "always"] } } diff --git a/.gitignore b/.gitignore index 4d29575de..76c22382d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ /build # misc +.idea .DS_Store .env.local .env.development.local diff --git a/README.md b/README.md index 9b90842c4..3b8b6827c 100644 --- a/README.md +++ b/README.md @@ -44,3 +44,18 @@ https://skypro-web-developer.github.io/react-memo/ Запускает eslint проверку кода, эта же команда запускается перед каждым коммитом. Если не получается закоммитить, попробуйте запустить эту команду и исправить все ошибки и предупреждения. + +## Первая домашняя работа (оценка) + +Предполагаемое время: 8 часов. +Фактическое время: 5 часов. + +## Вторая домашняя работа (оценка) + +Предполагаемое время: 8 часов. +Фактическое время: 12 часов. + +## Курсовая работа работа (оценка) + +Предполагаемое время: 9 часов. +Фактическое время: 10 часов. diff --git a/src/api/LeadersAPI.js b/src/api/LeadersAPI.js new file mode 100644 index 000000000..b6c065b3f --- /dev/null +++ b/src/api/LeadersAPI.js @@ -0,0 +1,30 @@ +const topLeadersLink = "https://wedev-api.sky.pro/api/v2/leaderboard"; + +export const getRequest = () => { + return fetch(topLeadersLink, { + method: "GET", + }).then(response => { + if (!response.ok) { + throw new Error("Что-то я заплутал..."); + } + if (response.status === 400) { + throw new Error("Полученные данные не в формате JSON"); + } + return response.json(); + }); +}; + +export const postRequest = record => { + return fetch(topLeadersLink, { + method: "POST", + body: JSON.stringify(record), + }).then(response => { + if (!response.ok) { + throw new Error("Что-то пошло не так"); + } + if (response.status === 400) { + throw new Error("Полученные данные не в формате JSON!"); + } + return response.json(); + }); +}; diff --git a/src/components/AchievementsImages/AchievementsImages.jsx b/src/components/AchievementsImages/AchievementsImages.jsx new file mode 100644 index 000000000..f17e3eed4 --- /dev/null +++ b/src/components/AchievementsImages/AchievementsImages.jsx @@ -0,0 +1,62 @@ +import { useState } from "react"; +import styles from "./AchievementsImages.module.css"; + +import hardLevelImage from "./images/hardLevel.svg"; +import superPowerImage from "./images/superPower.svg"; +import hardLevelGreyImage from "./images/hardLevelGrey.svg"; +import superPowerGreyImage from "./images/superPowerGrey.svg"; + +export function HardLevelImg({ disabled }) { + const [isOpen, setOpen] = useState(false); + + function mouseHover(value) { + if (disabled) return; + + setOpen(value); + } + + return ( +
+ {!disabled && isOpen && ( +

+ Игра пройдена +
в сложном режиме +

+ )} + hard level mouseHover(true)} + onMouseOut={() => mouseHover(false)} + /> +
+ ); +} + +export function SuperPowerImg({ disabled }) { + const [isOpen, setOpen] = useState(false); + + function mouseHover(value) { + if (disabled) return; + + setOpen(value); + } + + return ( +
+ {!disabled && isOpen && ( +

+ Игра пройдена +
+ без супер-сил +

+ )} + hard level mouseHover(true)} + onMouseOut={() => mouseHover(false)} + /> +
+ ); +} diff --git a/src/components/AchievementsImages/AchievementsImages.module.css b/src/components/AchievementsImages/AchievementsImages.module.css new file mode 100644 index 000000000..74883c6aa --- /dev/null +++ b/src/components/AchievementsImages/AchievementsImages.module.css @@ -0,0 +1,39 @@ + +.cntHint { + position: relative; + + img { + display: block; + position: relative; + z-index: 1; + } +} + +.cntInsight { + background: #C2F5FF; + border-radius: 12px; + position: absolute; + bottom: 45px; + left: 3px; + padding: 25px 20px 20px; + width: 222px; + z-index: 1; + box-sizing: border-box; + font-family: "StratosSkyeng", sans-serif; + font-size: 18px; + font-weight: 400; + line-height: 24px; + text-align: center; + color: #004980; + + &:after { + content: ""; + width: 20px; + height: 14px; + position: absolute; + bottom: -9px; + left: 14px; + background: #C2F5FF; + clip-path: polygon(0% 100%, 0% 0%, 100% 0%); + } +} diff --git a/src/components/AchievementsImages/images/hardLevel.svg b/src/components/AchievementsImages/images/hardLevel.svg new file mode 100644 index 000000000..d101cef6d --- /dev/null +++ b/src/components/AchievementsImages/images/hardLevel.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/components/AchievementsImages/images/hardLevelGrey.svg b/src/components/AchievementsImages/images/hardLevelGrey.svg new file mode 100644 index 000000000..9538c3d7d --- /dev/null +++ b/src/components/AchievementsImages/images/hardLevelGrey.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/components/AchievementsImages/images/superPower.svg b/src/components/AchievementsImages/images/superPower.svg new file mode 100644 index 000000000..c74a8b83d --- /dev/null +++ b/src/components/AchievementsImages/images/superPower.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/components/AchievementsImages/images/superPowerGrey.svg b/src/components/AchievementsImages/images/superPowerGrey.svg new file mode 100644 index 000000000..0557f5282 --- /dev/null +++ b/src/components/AchievementsImages/images/superPowerGrey.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Button/Button.module.css b/src/components/Button/Button.module.css index 5d3f1f80e..29e36d8eb 100644 --- a/src/components/Button/Button.module.css +++ b/src/components/Button/Button.module.css @@ -10,7 +10,7 @@ font-variant-numeric: lining-nums proportional-nums; /* Pres → Caption S */ - font-family: StratosSkyeng; + font-family: "StratosSkyeng", sans-serif; font-size: 24px; font-style: normal; font-weight: 400; diff --git a/src/components/Card/Card.module.css b/src/components/Card/Card.module.css index 86c3fbb5b..03390dec4 100644 --- a/src/components/Card/Card.module.css +++ b/src/components/Card/Card.module.css @@ -40,7 +40,7 @@ color: #000; font-variant-numeric: lining-nums proportional-nums; - font-family: StratosSkyeng; + font-family: "StratosSkyeng", sans-serif; font-size: 24px; font-style: normal; font-weight: 400; @@ -75,7 +75,7 @@ /* Анимация переворота */ /* весь контейнер поддерживает перспективу */ .flipContainer { - perspective: 1000; + perspective: 1000px; } .flipContainer:hover .flipper { diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index 7526a56c8..b325c8a59 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -1,10 +1,12 @@ import { shuffle } from "lodash"; -import { useEffect, useState } from "react"; +import { useContext, 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 { Button } from "../Button/Button"; +import { Card } from "../Card/Card"; +import { GameSettingsContext } from "../../context/GameSettingsContext"; +import { InsightHint } from "../Hints/Hints"; // Игра закончилась const STATUS_LOST = "STATUS_LOST"; @@ -13,6 +15,7 @@ const STATUS_WON = "STATUS_WON"; const STATUS_IN_PROGRESS = "STATUS_IN_PROGRESS"; // Начало игры: игрок видит все карты в течении нескольких секунд const STATUS_PREVIEW = "STATUS_PREVIEW"; +const STATUS_FROZEN = "STATUS_FROZEN"; function getTimerValue(startDate, endDate) { if (!startDate && !endDate) { @@ -45,6 +48,8 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { const [cards, setCards] = useState([]); // Текущий статус игры const [status, setStatus] = useState(STATUS_PREVIEW); + // Статус активности проверки открытой пары карт + const [checkPair, setCheckPair] = useState(false); // Дата начала игры const [gameStartDate, setGameStartDate] = useState(null); @@ -57,6 +62,11 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { minutes: 0, }); + const [hasHint, setHasHint] = useState(true); + const { liteVersion, getTriesCount, printTriesText } = useContext(GameSettingsContext); + const [triesCount, setTriesCount] = useState(getTriesCount()); + const [openedCard, setOpenedCard] = useState(null); + function finishGame(status = STATUS_LOST) { setGameEndDate(new Date()); setStatus(status); @@ -67,12 +77,30 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { setGameStartDate(startDate); setTimer(getTimerValue(startDate, null)); setStatus(STATUS_IN_PROGRESS); + + setTriesCount(getTriesCount()); } function resetGame() { setGameStartDate(null); setGameEndDate(null); setTimer(getTimerValue(null, null)); setStatus(STATUS_PREVIEW); + + setCheckPair(false); + setTriesCount(getTriesCount()); + setOpenedCard(null); + setHasHint(true); + } + + function handleInsightClick() { + setStatus(STATUS_FROZEN); + + setTimeout(() => { + setStatus(STATUS_IN_PROGRESS); + setHasHint(false); + + gameStartDate.setMilliseconds(gameStartDate.getMilliseconds() + 5000); + }, 5000); } /** @@ -84,9 +112,14 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { */ const openCard = clickedCard => { // Если карта уже открыта, то ничего не делаем - if (clickedCard.open) { + if (clickedCard.open || checkPair) { return; } + + if (openedCard) { + setCheckPair(true); + } + // Игровое поле после открытия кликнутой карты const nextCards = cards.map(card => { if (card.id !== clickedCard.id) { @@ -127,15 +160,39 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { // "Игрок проиграл", т.к на поле есть две открытые карты без пары if (playerLost) { + if (liteVersion && triesCount > 1) { + setTriesCount(triesCount - 1); + const prevCards = cards.map(card => ({ + ...card, + open: card.open && card.id !== openedCard.id && card.id !== clickedCard.id, + })); + setTimeout(() => { + setCards(prevCards); + setOpenedCard(null); + setCheckPair(false); + }, 500); + return; + } + finishGame(STATUS_LOST); return; } // ... игра продолжается + + if (openCardsWithoutPair.length === 0) { + setOpenedCard(null); + setCheckPair(false); + } else if (openCardsWithoutPair.length === 1) { + setOpenedCard(clickedCard); + } }; const isGameEnded = status === STATUS_LOST || status === STATUS_WON; + const achievements = + status !== STATUS_WON ? [] : pairsCount >= 9 && hasHint ? [1, 2] : pairsCount >= 9 ? [1] : hasHint ? [2] : []; + // Игровой цикл useEffect(() => { // В статусах кроме превью доп логики не требуется @@ -153,6 +210,8 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { return shuffle(generateDeck(pairsCount, 10)); }); + setTriesCount(getTriesCount()); + const timerId = setTimeout(() => { startGame(); }, previewSeconds * 1000); @@ -165,12 +224,14 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { // Обновляем значение таймера в интервале useEffect(() => { const intervalId = setInterval(() => { + if (status === STATUS_LOST || status === STATUS_WON || status === STATUS_FROZEN) return; + setTimer(getTimerValue(gameStartDate, gameEndDate)); }, 300); return () => { clearInterval(intervalId); }; - }, [gameStartDate, gameEndDate]); + }, [gameStartDate, gameEndDate, status]); return (
@@ -195,7 +256,12 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { )}
- {status === STATUS_IN_PROGRESS ? : null} + {status === STATUS_IN_PROGRESS || status === STATUS_FROZEN ? ( + <> + + + + ) : null}
@@ -203,17 +269,20 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { openCard(card)} - open={status !== STATUS_IN_PROGRESS ? true : card.open} + open={status !== STATUS_IN_PROGRESS || status === STATUS_FROZEN ? true : card.open} suit={card.suit} rank={card.rank} /> ))}
+ {liteVersion &&

{printTriesText(triesCount)}

} + {isGameEnded ? (
{ + setLeaders(data.leaders); + setDataIsSent(true); + setError(""); + }); + } + + function handleSubmit() { + sendStatics(); + + onClick(); + } + + function handleLinkToLeaderboard(e) { + e.preventDefault(); + // sendStatics(); + navigate("/leaderboard"); + } + return (
{imgAlt}

{title}

+ {isWon && ( +
+
+ + {dataIsSent || ( + + )} +
+ {error &&

{error}

} +
+ )}

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

{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..25be85b97 100644 --- a/src/components/EndGameModal/EndGameModal.module.css +++ b/src/components/EndGameModal/EndGameModal.module.css @@ -1,6 +1,7 @@ .modal { width: 480px; - height: 459px; + /*height: 459px;*/ + height: 634px; border-radius: 12px; background: #c2f5ff; display: flex; @@ -18,19 +19,19 @@ .title { color: #004980; font-variant-numeric: lining-nums proportional-nums; - font-family: StratosSkyeng; + font-family: "StratosSkyeng", sans-serif; font-size: 40px; font-style: normal; font-weight: 400; line-height: 48px; - + text-align: center; margin-bottom: 28px; } .description { color: #000; font-variant-numeric: lining-nums proportional-nums; - font-family: StratosSkyeng; + font-family: "StratosSkyeng", sans-serif; font-size: 24px; font-style: normal; font-weight: 400; @@ -41,7 +42,7 @@ .time { color: #000; - font-family: StratosSkyeng; + font-family: "StratosSkyeng", sans-serif; font-size: 64px; font-style: normal; font-weight: 400; @@ -49,3 +50,85 @@ margin-bottom: 40px; } + +.link { + color: #004980; + text-align: center; + font-variant-numeric: lining-nums proportional-nums; + font-family: "StratosSkyeng", sans-serif; + font-size: 18px; + font-style: normal; + font-weight: 400; + line-height: 32px; + margin-top: 18px; +} + +.input { + width: 276px; + height: 45px; + top: 334px; + left: 374px; + border-radius: 10px; + background: #ffffff; + font-family: "StratosSkyeng", sans-serif; + font-size: 24px; + font-weight: 400; + line-height: 32px; + text-align: center; + border: none; + outline: none; + + &:read-only { + background: lightcyan; + } +} + +.placeholder { + text-align: center; + font-size: 24px; + font-weight: 400; + line-height: 32px; + font-family: "StratosSkyeng", sans-serif; +} + +.inputData { + margin-bottom: 20px; + + p { + text-align: center; + } +} + +.addNameUser { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 6px; +} + +.btnAddUser { + height: 48px; + border-radius: 12px; + background: #7ac100; + color: #fff; + text-align: center; + font-variant-numeric: lining-nums proportional-nums; + font-family: "StratosSkyeng", sans-serif; + font-size: 24px; + font-style: normal; + font-weight: 400; + line-height: 32px; + border: none; + cursor: pointer; + + &:disabled { + background: cadetblue; + } +} + +.error { + color: lightcoral; + font-family: "StratosSkyeng", sans-serif; + font-size: 18px; + font-weight: 700; +} diff --git a/src/components/Hints/Hints.jsx b/src/components/Hints/Hints.jsx new file mode 100644 index 000000000..b7d5c1724 --- /dev/null +++ b/src/components/Hints/Hints.jsx @@ -0,0 +1,47 @@ +import { useState } from "react"; +import styles from "./Hints.module.css"; + +import cn from "classnames"; + +import eyeImage from "./images/eye.svg"; + +export function InsightHint({ disabled, handleClick }) { + const [isOpen, setOpen] = useState(false); + + function useClick() { + if (disabled) return; + + setOpen(false); + handleClick(); + } + + function mouseHover(value) { + if (disabled) return; + + setOpen(value); + } + + return ( +
+ {isOpen && ( + <> +
+
+

Прозрение

+

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

+
+ + )} + {"eye"} mouseHover(true)} + onMouseOut={() => mouseHover(false)} + /> +
+ ); +} diff --git a/src/components/Hints/Hints.module.css b/src/components/Hints/Hints.module.css new file mode 100644 index 000000000..c129f3733 --- /dev/null +++ b/src/components/Hints/Hints.module.css @@ -0,0 +1,55 @@ + +.cntHint { + position: relative; + + img { + position: relative; + z-index: 1; + } +} + +.disabled { + opacity: 0.4; +} + +.bckColor { + position: absolute; + width: 200vw; + height: 200vh; + background: #004980; + opacity: 0.6; + z-index: 1; + transform: translate(-50%, -50%); +} + +.cntInsight { + display: flex; + flex-direction: column; + align-items: center; + background: #C2F5FF; + border-radius: 12px; + position: absolute; + top: 84px; + left: -77px; + padding: 25px 20px 20px; + width: 222px; + z-index: 1; + box-sizing: border-box; + font-family: "StratosSkyeng", sans-serif; + color: #004980; +} + +.insightTitle { + font-size: 18px; + font-weight: 700; + line-height: 24px; + text-align: center; + margin-bottom: 10px; +} + +.insightDescription { + font-size: 18px; + font-weight: 400; + line-height: 24px; + text-align: center; +} diff --git a/src/components/Hints/images/eye.svg b/src/components/Hints/images/eye.svg new file mode 100644 index 000000000..40a1796f4 --- /dev/null +++ b/src/components/Hints/images/eye.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Hints/images/randomCards.svg b/src/components/Hints/images/randomCards.svg new file mode 100644 index 000000000..40c7ebfc2 --- /dev/null +++ b/src/components/Hints/images/randomCards.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/context/GameSettingsContext.js b/src/context/GameSettingsContext.js new file mode 100644 index 000000000..92b5ce20b --- /dev/null +++ b/src/context/GameSettingsContext.js @@ -0,0 +1,32 @@ +import { createContext, useState } from "react"; + +export const GameSettingsContext = createContext(null); + +export const GameSettingsProvider = ({ children }) => { + const [levelMode, setLevelMode] = useState(1); + const [liteVersion, setLiteVersion] = useState(false); + + function getTriesCount() { + return liteVersion ? 3 : 0; + } + + function printTriesText(count) { + switch (count) { + case 3: + return "У вас есть 3 попытки"; + case 2: + return "У вас осталось 2 попытки"; + case 1: + default: + return "У вас осталась последняя попытка"; + } + } + + return ( + + {children} + + ); +}; diff --git a/src/context/LeaderBoardContext.js b/src/context/LeaderBoardContext.js new file mode 100644 index 000000000..0d102173a --- /dev/null +++ b/src/context/LeaderBoardContext.js @@ -0,0 +1,25 @@ +import { createContext, useEffect, useState } from "react"; +import { getRequest } from "../api/LeadersAPI"; + +export const LeadersContext = createContext(null); +export const leadersTimeSorting = leaders => leaders.sort((a, b) => a.time - b.time); + +async function updateData() { + getRequest().then(leaders => { + const sortingLeaders = leadersTimeSorting(leaders.leaders); + //setLeaders(sortingLeaders.splice(0, 10)); + }); +} + +export const LeadersProvider = ({ children }) => { + const [leaders, setLeaders] = useState([]); + + useEffect(() => { + getRequest().then(leaders => { + const sortingLeaders = leadersTimeSorting(leaders.leaders); + setLeaders(sortingLeaders.splice(0, 10)); + }); + }); + + return {children}; +}; diff --git a/src/index.js b/src/index.js index f689c5f0b..41e0be5da 100644 --- a/src/index.js +++ b/src/index.js @@ -3,10 +3,16 @@ import ReactDOM from "react-dom/client"; import "./index.css"; import { RouterProvider } from "react-router-dom"; import { router } from "./router"; +import { GameSettingsProvider } from "./context/GameSettingsContext"; +import { LeadersProvider } from "./context/LeaderBoardContext"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - + + + + + , ); diff --git a/src/pages/GamePage/GamePage.jsx b/src/pages/GamePage/GamePage.jsx index a4be871db..4e64c13f9 100644 --- a/src/pages/GamePage/GamePage.jsx +++ b/src/pages/GamePage/GamePage.jsx @@ -2,8 +2,8 @@ import { useParams } from "react-router-dom"; import { Cards } from "../../components/Cards/Cards"; -export function GamePage() { - const { pairsCount } = useParams(); +export function GamePage({ otherPairsCount }) { + const { pairsCount = otherPairsCount } = useParams(); return ( <> diff --git a/src/pages/LeaderBoardPage/LeaderBoardPage.jsx b/src/pages/LeaderBoardPage/LeaderBoardPage.jsx new file mode 100644 index 000000000..e5c03359d --- /dev/null +++ b/src/pages/LeaderBoardPage/LeaderBoardPage.jsx @@ -0,0 +1,49 @@ +import { Link } from "react-router-dom"; +import { Button } from "../../components/Button/Button"; +import styles from "./LeaderBoardPage.module.css"; +import { useContext } from "react"; +import { LeadersContext } from "../../context/LeaderBoardContext"; +import cn from "classnames"; + +import { HardLevelImg, SuperPowerImg } from "../../components/AchievementsImages/AchievementsImages"; + +function formatTime(seconds) { + const minutes = Math.floor(seconds / 60); + seconds = seconds % 60; + + return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; +} + +export const LeaderBoardPage = () => { + const { leaders } = useContext(LeadersContext); + + return ( +
+
+

Лидерборд

+ + + +
+
+
+

Позиция

+

Пользователь

+

Достижения

+

Время

+
+ {leaders.map((leader, index) => ( +
+

{index + 1}

+

{leader.name}

+
+ + +
+

{formatTime(leader.time)}

+
+ ))} +
+
+ ); +}; diff --git a/src/pages/LeaderBoardPage/LeaderBoardPage.module.css b/src/pages/LeaderBoardPage/LeaderBoardPage.module.css new file mode 100644 index 000000000..a420b84b7 --- /dev/null +++ b/src/pages/LeaderBoardPage/LeaderBoardPage.module.css @@ -0,0 +1,69 @@ +.container { + padding-left: calc(50% - 472px); + padding-right: calc(50% - 472px); +} + +.header { + display: flex; + justify-content: space-between; + padding-top: 50px; + padding-bottom: 50px; +} + +.title { + font-family: "StratosSkyeng", sans-serif; + font-size: 24px; + font-weight: 400; + line-height: 32px; + text-align: left; + color: #ffffff; +} + +.mainBox { + width: 944px; + height: 64px; + display: flex; + align-items: center; + gap: 66px; + box-sizing: border-box; + padding-inline: 20px; + margin-bottom: 16px; + font-size: 24px; + font-family: "StratosSkyeng", sans-serif; + background-color: #fff; + border-radius: 12px; +} + +.usersPosition { + width: 178px; + font-size: 24px; + font-weight: 400; + line-height: 32px; +} + +.nameUser { + width: 227px; + font-size: 24px; + font-weight: 400; + line-height: 32px; +} + +.achievUser { + width: 194px; + display: flex; + gap: 6px; +} + +.timeRecord { + width: 92px; + font-size: 24px; + font-weight: 400; + line-height: 32px; + box-sizing: border-box; +} + +.headerLine { + color: #999999; +} + + diff --git a/src/pages/SelectLevelPage/SelectLevelPage.jsx b/src/pages/SelectLevelPage/SelectLevelPage.jsx index 758942e51..07bc06995 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.jsx +++ b/src/pages/SelectLevelPage/SelectLevelPage.jsx @@ -1,28 +1,70 @@ -import { Link } from "react-router-dom"; +import { useContext } from "react"; +import { Link, useNavigate } from "react-router-dom"; import styles from "./SelectLevelPage.module.css"; +import { Button } from "../../components/Button/Button"; +import { GameSettingsContext } from "../../context/GameSettingsContext"; + +import cn from "classnames"; export function SelectLevelPage() { + const navigate = useNavigate(); + const { levelMode, setLevelMode, liteVersion, setLiteVersion } = useContext(GameSettingsContext); + + function handleLiteChange() { + setLiteVersion(prev => !prev); + } + + function startGame() { + navigate(`/game/${levelMode * 3}`); + } + return (

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

+ + + + Перейти к лидерборду +
); diff --git a/src/pages/SelectLevelPage/SelectLevelPage.module.css b/src/pages/SelectLevelPage/SelectLevelPage.module.css index 390ac0def..db3a776e7 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.module.css +++ b/src/pages/SelectLevelPage/SelectLevelPage.module.css @@ -21,7 +21,7 @@ color: #004980; text-align: center; font-variant-numeric: lining-nums proportional-nums; - font-family: StratosSkyeng; + font-family: "StratosSkyeng", sans-serif; font-size: 40px; font-style: normal; font-weight: 400; @@ -51,14 +51,46 @@ .levelLink { color: #0080c1; text-align: center; - font-family: StratosSkyeng; + font-family: "StratosSkyeng", sans-serif; font-size: 64px; font-style: normal; font-weight: 400; line-height: 72px; text-decoration: none; + + &:hover { + font-weight: 600; + transform: scale(1.05); + } + + &.selected { + color: #c10080 !important; + } } .levelLink:visited { color: #0080c1; } + +.liteMode { + color: #004980; + font-family: "StratosSkyeng", sans-serif; + margin-bottom: 48px; + font-size: 24px; + font-weight: 400; + line-height: 32px; + text-align: center; + +} + +.link { + color: #004980; + text-align: center; + font-variant-numeric: lining-nums proportional-nums; + font-family: "StratosSkyeng", sans-serif; + font-size: 18px; + font-style: normal; + font-weight: 400; + line-height: 32px; + margin-top: 18px; +} diff --git a/src/router.js b/src/router.js index da6e94b51..acb1ff86b 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 { LeaderBoardPage } from "./pages/LeaderBoardPage/LeaderBoardPage"; export const router = createBrowserRouter( [ @@ -8,10 +9,18 @@ export const router = createBrowserRouter( path: "/", element: , }, + { + path: "/game", + element: , + }, { path: "/game/:pairsCount", element: , }, + { + path: "/leaderboard", + element: , + }, ], /** * basename нужен для корректной работы в gh pages