diff --git a/src/App.css b/src/App.css
index 9f065df..213dd55 100644
--- a/src/App.css
+++ b/src/App.css
@@ -74,6 +74,11 @@
border-color: #667eea;
}
+.control-button-new-game:hover {
+ background: rgba(255, 107, 107, 0.3);
+ border-color: #ff6b6b;
+}
+
.app-title {
text-align: center;
font-size: clamp(2rem, 5vw, 4rem);
@@ -113,6 +118,13 @@
font-size: clamp(1.5rem, 3.5vw, 3rem);
}
+.question-counter-wrapper {
+ display: flex;
+ align-items: center;
+ gap: clamp(8px, 1.5vw, 12px);
+ flex-shrink: 0;
+}
+
.question-counter {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
@@ -124,7 +136,32 @@
font-weight: bold;
text-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
- flex-shrink: 0;
+}
+
+.show-all-button-top {
+ background: rgba(255, 107, 107, 0.2);
+ color: #ff6b6b;
+ border: 2px solid rgba(255, 107, 107, 0.5);
+ border-radius: 15px;
+ padding: clamp(5px, 1vh, 8px) clamp(12px, 2vw, 18px);
+ font-size: clamp(0.8rem, 1.5vw, 1rem);
+ font-weight: bold;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ text-shadow: 0 0 10px rgba(255, 107, 107, 0.5);
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
+ white-space: nowrap;
+}
+
+.show-all-button-top:hover {
+ background: rgba(255, 107, 107, 0.4);
+ border-color: #ff6b6b;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 15px rgba(255, 107, 107, 0.4);
+}
+
+.show-all-button-top:active {
+ transform: translateY(0);
}
.app-subtitle {
diff --git a/src/App.jsx b/src/App.jsx
index b471838..13a7013 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,18 +1,37 @@
-import { useState, useRef } from 'react'
+import { useState, useRef, useEffect } from 'react'
import Game from './components/Game'
import Snowflakes from './components/Snowflakes'
import QuestionsModal from './components/QuestionsModal'
import { questions as initialQuestions } from './data/questions'
+import { getCookie, setCookie, deleteCookie } from './utils/cookies'
import './App.css'
function App() {
const [isQuestionsModalOpen, setIsQuestionsModalOpen] = useState(false)
- const [questions, setQuestions] = useState(initialQuestions)
- const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0)
+ const [questions, setQuestions] = useState(() => {
+ const savedQuestions = getCookie('gameQuestions')
+ return savedQuestions || initialQuestions
+ })
+ const [currentQuestionIndex, setCurrentQuestionIndex] = useState(() => {
+ const savedIndex = getCookie('gameQuestionIndex')
+ return savedIndex !== null ? savedIndex : 0
+ })
const gameRef = useRef(null)
const currentQuestion = questions[currentQuestionIndex]
+ // Сохраняем вопросы в cookies при изменении
+ useEffect(() => {
+ if (questions.length > 0) {
+ setCookie('gameQuestions', questions)
+ }
+ }, [questions])
+
+ // Сохраняем индекс вопроса в cookies при изменении
+ useEffect(() => {
+ setCookie('gameQuestionIndex', currentQuestionIndex)
+ }, [currentQuestionIndex])
+
const handleUpdateQuestions = (updatedQuestions) => {
setQuestions(updatedQuestions)
// Если текущий вопрос был удален, сбрасываем индекс
@@ -27,6 +46,31 @@ function App() {
}
}
+ const handleNewGame = () => {
+ if (window.confirm('Начать новую игру? Текущий прогресс будет потерян.')) {
+ deleteCookie('gameQuestions')
+ deleteCookie('gameQuestionIndex')
+ deleteCookie('gamePlayers')
+ deleteCookie('gamePlayerScores')
+ deleteCookie('gameCurrentPlayerId')
+ deleteCookie('gameRevealedAnswers')
+ deleteCookie('gameOver')
+
+ setQuestions(initialQuestions)
+ setCurrentQuestionIndex(0)
+
+ if (gameRef.current) {
+ gameRef.current.newGame()
+ }
+ }
+ }
+
+ const handleShowAll = () => {
+ if (gameRef.current && gameRef.current.showAllAnswers) {
+ gameRef.current.showAllAnswers()
+ }
+ }
+
return (
@@ -47,6 +91,13 @@ function App() {
>
❓
+
@@ -56,8 +107,17 @@ function App() {
{questions.length > 0 && currentQuestion && (
-
- {currentQuestionIndex + 1}/{questions.length}
+
+
+ {currentQuestionIndex + 1}/{questions.length}
+
+
)}
diff --git a/src/components/Answer.css b/src/components/Answer.css
index 87a8d4e..656d4bf 100644
--- a/src/components/Answer.css
+++ b/src/components/Answer.css
@@ -16,6 +16,16 @@
overflow: hidden;
}
+/* Горизонтальный layout для узких кнопок */
+@media (max-width: 1000px) {
+ .answer-button {
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ gap: clamp(8px, 1.5vw, 15px);
+ }
+}
+
.answer-button:hover:not(:disabled) {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(255, 215, 0, 0.4);
@@ -99,6 +109,18 @@
-webkit-box-orient: vertical;
flex-shrink: 1;
min-height: 0;
+ flex: 1;
+}
+
+@media (max-width: 1000px) {
+ .answer-text {
+ margin-bottom: 0;
+ margin-right: clamp(8px, 1.5vw, 15px);
+ text-align: left;
+ -webkit-line-clamp: 2;
+ flex: 1;
+ min-width: 0;
+ }
}
.answer-points {
@@ -108,4 +130,11 @@
flex-shrink: 0;
}
+@media (max-width: 1000px) {
+ .answer-points {
+ font-size: clamp(1.5rem, 3vw, 2.5rem);
+ white-space: nowrap;
+ }
+}
+
diff --git a/src/components/Game.jsx b/src/components/Game.jsx
index a12b081..781d4d4 100644
--- a/src/components/Game.jsx
+++ b/src/components/Game.jsx
@@ -1,8 +1,9 @@
-import { useState, useImperativeHandle, forwardRef } from 'react'
+import { useState, useImperativeHandle, forwardRef, useEffect } from 'react'
import Question from './Question'
import Players from './Players'
import PlayersModal from './PlayersModal'
import QuestionsModal from './QuestionsModal'
+import { getCookie, setCookie, deleteCookie } from '../utils/cookies'
import './Game.css'
const Game = forwardRef(({
@@ -11,22 +12,97 @@ const Game = forwardRef(({
onQuestionIndexChange,
onQuestionsChange,
}, ref) => {
- const [players, setPlayers] = useState([])
- const [currentPlayerId, setCurrentPlayerId] = useState(null)
- const [playerScores, setPlayerScores] = useState({})
- const [gameOver, setGameOver] = useState(false)
- const [revealedAnswers, setRevealedAnswers] = useState([])
+ const [players, setPlayers] = useState(() => {
+ const savedPlayers = getCookie('gamePlayers')
+ return savedPlayers || []
+ })
+ const [currentPlayerId, setCurrentPlayerId] = useState(() => {
+ const savedId = getCookie('gameCurrentPlayerId')
+ return savedId !== null ? savedId : null
+ })
+ const [playerScores, setPlayerScores] = useState(() => {
+ const savedScores = getCookie('gamePlayerScores')
+ return savedScores || {}
+ })
+ const [gameOver, setGameOver] = useState(() => {
+ const savedGameOver = getCookie('gameOver')
+ return savedGameOver === true
+ })
+ const [revealedAnswers, setRevealedAnswers] = useState(() => {
+ const savedAnswers = getCookie('gameRevealedAnswers')
+ return savedAnswers || {}
+ })
+
+ // Получаем открытые ответы для текущего вопроса
+ const getCurrentRevealedAnswers = () => {
+ return revealedAnswers[currentQuestionIndex] || []
+ }
+
+ // Обновляем открытые ответы для текущего вопроса
+ const updateRevealedAnswers = (newAnswers) => {
+ setRevealedAnswers({
+ ...revealedAnswers,
+ [currentQuestionIndex]: newAnswers,
+ })
+ }
const [isPlayersModalOpen, setIsPlayersModalOpen] = useState(false)
const [isQuestionsModalOpen, setIsQuestionsModalOpen] = useState(false)
+ // Сохраняем состояние в cookies при изменении
+ useEffect(() => {
+ if (players.length > 0) {
+ setCookie('gamePlayers', players)
+ } else {
+ deleteCookie('gamePlayers')
+ }
+ }, [players])
+
+ useEffect(() => {
+ if (currentPlayerId !== null) {
+ setCookie('gameCurrentPlayerId', currentPlayerId)
+ } else {
+ deleteCookie('gameCurrentPlayerId')
+ }
+ }, [currentPlayerId])
+
+ useEffect(() => {
+ if (Object.keys(playerScores).length > 0) {
+ setCookie('gamePlayerScores', playerScores)
+ } else {
+ deleteCookie('gamePlayerScores')
+ }
+ }, [playerScores])
+
+ useEffect(() => {
+ setCookie('gameRevealedAnswers', revealedAnswers)
+ }, [revealedAnswers])
+
+ useEffect(() => {
+ setCookie('gameOver', gameOver)
+ }, [gameOver])
+
+ const currentQuestion = questions[currentQuestionIndex]
+ const isLastQuestion = currentQuestionIndex === questions.length - 1
+
+ const handleShowAllAnswers = () => {
+ if (!currentQuestion) return
+ const allAnswerIndices = currentQuestion.answers.map((_, index) => index)
+ updateRevealedAnswers(allAnswerIndices)
+ }
+
useImperativeHandle(ref, () => ({
openPlayersModal: () => setIsPlayersModalOpen(true),
openQuestionsModal: () => setIsQuestionsModalOpen(true),
+ newGame: () => {
+ setPlayers([])
+ setCurrentPlayerId(null)
+ setPlayerScores({})
+ setGameOver(false)
+ setRevealedAnswers({})
+ },
+ showAllAnswers: handleShowAllAnswers,
}))
- const currentQuestion = questions[currentQuestionIndex]
- const isLastQuestion = currentQuestionIndex === questions.length - 1
-
const handleAddPlayer = (name) => {
const newPlayer = {
id: Date.now(),
@@ -76,13 +152,14 @@ const Game = forwardRef(({
}
const handleAnswerClick = (answerIndex, points) => {
- if (revealedAnswers.includes(answerIndex)) return
+ const currentRevealed = getCurrentRevealedAnswers()
+ if (currentRevealed.includes(answerIndex)) return
if (!currentPlayerId) return
if (!currentQuestion) return
- const isLastAnswer = revealedAnswers.length === currentQuestion.answers.length - 1
+ const isLastAnswer = currentRevealed.length === currentQuestion.answers.length - 1
- setRevealedAnswers([...revealedAnswers, answerIndex])
+ updateRevealedAnswers([...currentRevealed, answerIndex])
// Добавляем очки текущему участнику
setPlayerScores({
@@ -121,7 +198,7 @@ const Game = forwardRef(({
if (onQuestionIndexChange) {
onQuestionIndexChange(currentQuestionIndex + 1)
}
- setRevealedAnswers([])
+ // Не сбрасываем открытые ответы - они сохраняются для каждого вопроса отдельно
}
const restartGame = () => {
@@ -129,7 +206,7 @@ const Game = forwardRef(({
onQuestionIndexChange(0)
}
setGameOver(false)
- setRevealedAnswers([])
+ setRevealedAnswers({})
const initialScores = {}
players.forEach(player => {
initialScores[player.id] = 0
@@ -140,6 +217,31 @@ const Game = forwardRef(({
}
}
+ const newGame = () => {
+ setPlayers([])
+ setCurrentPlayerId(null)
+ setPlayerScores({})
+ setGameOver(false)
+ setRevealedAnswers({})
+ if (onQuestionIndexChange) {
+ onQuestionIndexChange(0)
+ }
+ }
+
+ const handlePreviousQuestion = () => {
+ if (currentQuestionIndex > 0 && onQuestionIndexChange) {
+ onQuestionIndexChange(currentQuestionIndex - 1)
+ // Открытые ответы сохраняются для каждого вопроса отдельно
+ }
+ }
+
+ const handleNextQuestion = () => {
+ if (currentQuestionIndex < questions.length - 1 && onQuestionIndexChange) {
+ onQuestionIndexChange(currentQuestionIndex + 1)
+ // Открытые ответы сохраняются для каждого вопроса отдельно
+ }
+ }
+
if (gameOver) {
// Находим победителя(ей)
const scores = Object.values(playerScores)
@@ -215,7 +317,11 @@ const Game = forwardRef(({
question={currentQuestion}
questionNumber={currentQuestionIndex + 1}
onAnswerClick={handleAnswerClick}
- revealedAnswers={revealedAnswers}
+ revealedAnswers={getCurrentRevealedAnswers()}
+ onPreviousQuestion={handlePreviousQuestion}
+ onNextQuestion={handleNextQuestion}
+ canGoPrevious={currentQuestionIndex > 0}
+ canGoNext={currentQuestionIndex < questions.length - 1}
/>
) : (
diff --git a/src/components/Question.css b/src/components/Question.css
index 5b58a95..94f5b14 100644
--- a/src/components/Question.css
+++ b/src/components/Question.css
@@ -19,6 +19,54 @@
flex-shrink: 0;
}
+.question-navigation {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: clamp(10px, 2vw, 20px);
+ margin-bottom: clamp(10px, 2vh, 15px);
+}
+
+.question-nav-button {
+ width: clamp(40px, 6vw, 50px);
+ height: clamp(40px, 6vw, 50px);
+ border-radius: 50%;
+ border: 2px solid rgba(255, 255, 255, 0.3);
+ background: rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(10px);
+ color: #fff;
+ font-size: clamp(1.5rem, 3vw, 2rem);
+ font-weight: bold;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
+}
+
+.question-nav-button:hover {
+ transform: translateY(-2px) scale(1.1);
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
+ border-color: rgba(255, 215, 0, 0.6);
+ background: rgba(255, 255, 255, 0.2);
+}
+
+.question-nav-button:active {
+ transform: translateY(0) scale(1);
+}
+
+.question-nav-button-prev:hover {
+ background: rgba(78, 205, 196, 0.3);
+ border-color: #4ecdc4;
+}
+
+.question-nav-button-next:hover {
+ background: rgba(102, 126, 234, 0.3);
+ border-color: #667eea;
+}
+
.question-number {
display: inline-block;
background: rgba(255, 215, 0, 0.2);
@@ -39,6 +87,33 @@
font-weight: bold;
line-height: 1.3;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
+ margin: 0;
+ flex: 1;
+}
+
+.show-all-button {
+ background: rgba(255, 107, 107, 0.2);
+ color: #ff6b6b;
+ border: 2px solid rgba(255, 107, 107, 0.5);
+ border-radius: 15px;
+ padding: clamp(8px, 1.5vh, 12px) clamp(20px, 3vw, 30px);
+ font-size: clamp(0.9rem, 1.8vw, 1.2rem);
+ font-weight: bold;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ text-shadow: 0 0 10px rgba(255, 107, 107, 0.5);
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
+}
+
+.show-all-button:hover {
+ background: rgba(255, 107, 107, 0.4);
+ border-color: #ff6b6b;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 15px rgba(255, 107, 107, 0.4);
+}
+
+.show-all-button:active {
+ transform: translateY(0);
}
.answers-grid {
diff --git a/src/components/Question.jsx b/src/components/Question.jsx
index cad5675..add548c 100644
--- a/src/components/Question.jsx
+++ b/src/components/Question.jsx
@@ -1,11 +1,44 @@
import Answer from './Answer'
import './Question.css'
-const Question = ({ question, questionNumber, onAnswerClick, revealedAnswers }) => {
+const Question = ({
+ question,
+ questionNumber,
+ onAnswerClick,
+ revealedAnswers,
+ onShowAll,
+ onPreviousQuestion,
+ onNextQuestion,
+ canGoPrevious,
+ canGoNext,
+}) => {
+ const allAnswersRevealed = question.answers.every((_, index) => revealedAnswers.includes(index))
+ const hasUnrevealedAnswers = revealedAnswers.length < question.answers.length
+
return (
-
{question.text}
+
+ {canGoPrevious && onPreviousQuestion && (
+
+ )}
+
{question.text}
+ {canGoNext && onNextQuestion && (
+
+ )}
+
{question.answers.map((answer, index) => (
diff --git a/src/utils/cookies.js b/src/utils/cookies.js
new file mode 100644
index 0000000..cbefda1
--- /dev/null
+++ b/src/utils/cookies.js
@@ -0,0 +1,30 @@
+// Утилиты для работы с cookies
+
+export const setCookie = (name, value, days = 365) => {
+ const date = new Date()
+ date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000)
+ const expires = `expires=${date.toUTCString()}`
+ document.cookie = `${name}=${encodeURIComponent(JSON.stringify(value))};${expires};path=/`
+}
+
+export const getCookie = (name) => {
+ const nameEQ = `${name}=`
+ const ca = document.cookie.split(';')
+ for (let i = 0; i < ca.length; i++) {
+ let c = ca[i]
+ while (c.charAt(0) === ' ') c = c.substring(1, c.length)
+ if (c.indexOf(nameEQ) === 0) {
+ try {
+ return JSON.parse(decodeURIComponent(c.substring(nameEQ.length, c.length)))
+ } catch (e) {
+ return null
+ }
+ }
+ }
+ return null
+}
+
+export const deleteCookie = (name) => {
+ document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;`
+}
+