This commit is contained in:
Dmitry 2025-12-31 20:40:34 +03:00
parent 0422fa6e12
commit 6a94a76c19
13 changed files with 278 additions and 311 deletions

View file

@ -1,29 +1,90 @@
.app {
min-height: 100vh;
height: 100vh;
max-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
padding: clamp(5px, 1vh, 15px);
z-index: 2;
overflow: hidden;
}
.app-content {
width: 100%;
max-width: 1600px;
z-index: 2;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
.app-title-bar {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin-bottom: clamp(5px, 1vh, 15px);
flex-shrink: 0;
gap: clamp(10px, 2vw, 20px);
}
.app-control-buttons {
display: flex;
gap: clamp(5px, 1vw, 10px);
flex-shrink: 0;
}
.control-button {
width: clamp(35px, 5vw, 45px);
height: clamp(35px, 5vw, 45px);
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: clamp(1.2rem, 2.5vw, 1.5rem);
transition: all 0.3s ease;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
padding: 0;
}
.control-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);
}
.control-button:active {
transform: translateY(0) scale(1);
}
.control-button-players:hover {
background: rgba(78, 205, 196, 0.3);
border-color: #4ecdc4;
}
.control-button-questions:hover {
background: rgba(102, 126, 234, 0.3);
border-color: #667eea;
}
.app-title {
text-align: center;
margin-bottom: 20px;
font-size: 6rem;
font-size: clamp(2rem, 5vw, 4rem);
font-weight: bold;
text-shadow:
0 0 10px rgba(255, 215, 0, 0.8),
0 0 20px rgba(255, 215, 0, 0.6),
0 0 30px rgba(255, 215, 0, 0.4);
animation: glow 2s ease-in-out infinite alternate;
flex: 1;
margin: 0;
}
@keyframes glow {
@ -49,28 +110,35 @@
.title-to {
color: #ff6b6b;
font-size: 4.5rem;
font-size: clamp(1.5rem, 3.5vw, 3rem);
}
.question-counter {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 2px solid rgba(255, 215, 0, 0.3);
border-radius: 20px;
padding: clamp(5px, 1vh, 8px) clamp(12px, 2vw, 18px);
color: #ffd700;
font-size: clamp(0.9rem, 2vw, 1.2rem);
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;
}
.app-subtitle {
text-align: center;
color: #fff;
font-size: 2.5rem;
margin-bottom: 40px;
font-size: clamp(1rem, 2vw, 1.5rem);
margin-bottom: clamp(5px, 1vh, 15px);
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
flex-shrink: 0;
}
@media (max-width: 768px) {
.app-title {
font-size: 2.5rem;
}
.title-to {
font-size: 2rem;
}
.app-subtitle {
font-size: 1.2rem;
.app {
padding: clamp(3px, 0.5vh, 10px);
}
}

View file

@ -1,20 +1,81 @@
import { useState } from 'react'
import { useState, useRef } 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 './App.css'
function App() {
const [isQuestionsModalOpen, setIsQuestionsModalOpen] = useState(false)
const [questions, setQuestions] = useState(initialQuestions)
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0)
const gameRef = useRef(null)
const currentQuestion = questions[currentQuestionIndex]
const handleUpdateQuestions = (updatedQuestions) => {
setQuestions(updatedQuestions)
// Если текущий вопрос был удален, сбрасываем индекс
if (currentQuestionIndex >= updatedQuestions.length) {
setCurrentQuestionIndex(0)
}
}
const handleOpenPlayersModal = () => {
if (gameRef.current) {
gameRef.current.openPlayersModal()
}
}
return (
<div className="app">
<Snowflakes />
<div className="app-content">
<h1 className="app-title">
<span className="title-number">100</span>
<span className="title-to">к</span>
<span className="title-number">1</span>
</h1>
<p className="app-subtitle">Новогодняя версия</p>
<Game />
<div className="app-title-bar">
<div className="app-control-buttons">
<button
className="control-button control-button-players"
onClick={handleOpenPlayersModal}
title="Управление участниками"
>
👥
</button>
<button
className="control-button control-button-questions"
onClick={() => setIsQuestionsModalOpen(true)}
title="Управление вопросами"
>
</button>
</div>
<h1 className="app-title">
<span className="title-number">100</span>
<span className="title-to">к</span>
<span className="title-number">1</span>
</h1>
{questions.length > 0 && currentQuestion && (
<div className="question-counter">
{currentQuestionIndex + 1}/{questions.length}
</div>
)}
</div>
<QuestionsModal
isOpen={isQuestionsModalOpen}
onClose={() => setIsQuestionsModalOpen(false)}
questions={questions}
onUpdateQuestions={handleUpdateQuestions}
/>
<Game
ref={gameRef}
questions={questions}
currentQuestionIndex={currentQuestionIndex}
onQuestionIndexChange={setCurrentQuestionIndex}
onQuestionsChange={setQuestions}
/>
</div>
</div>
)

View file

@ -3,14 +3,15 @@
backdrop-filter: blur(10px);
border: 3px solid rgba(255, 255, 255, 0.2);
border-radius: 20px;
padding: 40px 30px;
padding: clamp(8px, 1.5vh, 20px) clamp(12px, 1.5vw, 20px);
cursor: pointer;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 180px;
min-height: 0;
height: 100%;
position: relative;
overflow: hidden;
}
@ -62,18 +63,20 @@
}
.answer-placeholder {
font-size: 3rem;
font-size: clamp(1.2rem, 2.5vw, 2rem);
color: rgba(255, 255, 255, 0.5);
font-weight: bold;
flex-shrink: 0;
}
.answer-points-hidden {
font-size: 4.5rem;
font-size: clamp(1.5rem, 3vw, 2.5rem);
font-weight: bold;
opacity: 0.7;
text-shadow: 0 0 15px currentColor;
filter: blur(1px);
transition: all 0.3s ease;
flex-shrink: 0;
}
.answer-button:hover:not(:disabled) .answer-points-hidden {
@ -83,40 +86,26 @@
}
.answer-text {
font-size: 2.2rem;
font-size: clamp(0.9rem, 1.8vw, 1.4rem);
color: #fff;
font-weight: bold;
margin-bottom: 15px;
margin-bottom: clamp(4px, 1vh, 8px);
text-align: center;
text-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
flex-shrink: 1;
min-height: 0;
}
.answer-points {
font-size: 3.5rem;
font-size: clamp(1.2rem, 2.5vw, 2rem);
font-weight: bold;
text-shadow: 0 0 15px currentColor;
flex-shrink: 0;
}
@media (max-width: 768px) {
.answer-button {
min-height: 80px;
padding: 20px 15px;
}
.answer-text {
font-size: 1.1rem;
}
.answer-points {
font-size: 1.5rem;
}
.answer-points-hidden {
font-size: 2rem;
}
.answer-placeholder {
font-size: 2.5rem;
}
}

View file

@ -2,75 +2,39 @@
width: 100%;
max-width: 1400px;
margin: 0 auto;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
position: relative;
}
.game-header {
margin-bottom: 30px;
margin-bottom: clamp(5px, 1vh, 15px);
display: flex;
flex-direction: column;
gap: 15px;
gap: clamp(5px, 1vh, 10px);
align-items: center;
flex-shrink: 0;
}
.manage-players-button {
background: linear-gradient(135deg, #4ecdc4 0%, #44a08d 100%);
color: white;
border: none;
padding: 12px 30px;
font-size: 1rem;
font-weight: bold;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(78, 205, 196, 0.3);
white-space: nowrap;
}
.manage-players-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(78, 205, 196, 0.5);
}
.manage-players-button:active {
transform: translateY(0);
}
.game-header-buttons {
.game-content {
flex: 1;
display: flex;
gap: 15px;
flex-wrap: wrap;
justify-content: center;
}
.manage-questions-button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 30px;
font-size: 1rem;
font-weight: bold;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
white-space: nowrap;
}
.manage-questions-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
}
.manage-questions-button:active {
transform: translateY(0);
flex-direction: column;
overflow: hidden;
min-height: 0;
}
.game-over {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
padding: 40px;
flex: 1;
padding: clamp(10px, 2vh, 20px);
overflow-y: auto;
min-height: 0;
}
.game-over-content {
@ -78,15 +42,17 @@
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 60px 40px;
padding: clamp(15px, 3vh, 30px) clamp(20px, 3vw, 30px);
border: 2px solid rgba(255, 215, 0, 0.3);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
width: 100%;
max-width: 90%;
}
.game-over-title {
font-size: 5rem;
font-size: clamp(1.5rem, 4vw, 3rem);
color: #ffd700;
margin-bottom: 30px;
margin-bottom: clamp(10px, 2vh, 20px);
text-shadow: 0 0 20px rgba(255, 215, 0, 0.8);
}
@ -101,8 +67,8 @@
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
color: white;
border: none;
padding: 25px 60px;
font-size: 2rem;
padding: clamp(10px, 2vh, 15px) clamp(30px, 5vw, 50px);
font-size: clamp(1rem, 2vw, 1.5rem);
border-radius: 35px;
cursor: pointer;
font-weight: bold;
@ -121,29 +87,36 @@
.no-players-message {
text-align: center;
padding: 60px 20px;
padding: clamp(20px, 4vh, 40px) clamp(15px, 2vw, 20px);
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
border: 2px solid rgba(255, 215, 0, 0.3);
flex: 1;
display: flex;
align-items: center;
justify-content: center;
min-height: 0;
}
.no-players-message p {
color: #fff;
font-size: 1.5rem;
font-size: clamp(1rem, 2vw, 1.3rem);
margin: 0;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}
.final-scores {
width: 100%;
margin: 30px 0;
margin: clamp(10px, 2vh, 20px) 0;
max-height: 40vh;
overflow-y: auto;
}
.final-scores-title {
color: #fff;
font-size: 2rem;
margin-bottom: 20px;
font-size: clamp(1.2rem, 2.5vw, 1.8rem);
margin-bottom: clamp(10px, 2vh, 15px);
text-align: center;
}
@ -151,8 +124,8 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 25px;
margin: 10px 0;
padding: clamp(8px, 1.5vh, 12px) clamp(15px, 2vw, 20px);
margin: clamp(5px, 1vh, 8px) 0;
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
border: 2px solid rgba(255, 255, 255, 0.2);
@ -167,13 +140,13 @@
.final-score-name {
color: #fff;
font-size: 1.5rem;
font-size: clamp(1rem, 2vw, 1.3rem);
font-weight: 500;
}
.final-score-value {
color: #ffd700;
font-size: 1.5rem;
font-size: clamp(1rem, 2vw, 1.3rem);
font-weight: bold;
text-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
}
@ -184,50 +157,4 @@
text-shadow: 0 0 15px rgba(255, 215, 0, 0.8);
}
@media (max-width: 768px) {
.game-over-title {
font-size: 2rem;
}
.game-over-score {
font-size: 1.5rem;
}
.restart-button {
font-size: 1.1rem;
padding: 12px 30px;
}
.no-players-message p {
font-size: 1.2rem;
}
.final-scores-title {
font-size: 1.5rem;
}
.final-score-item {
padding: 12px 20px;
}
.final-score-name,
.final-score-value {
font-size: 1.2rem;
}
.manage-players-button,
.manage-questions-button {
font-size: 0.9rem;
padding: 10px 20px;
}
.game-header-buttons {
flex-direction: column;
width: 100%;
}
.game-header {
margin-bottom: 20px;
}
}

View file

@ -1,23 +1,29 @@
import { useState } from 'react'
import { useState, useImperativeHandle, forwardRef } from 'react'
import Question from './Question'
import Players from './Players'
import PlayersModal from './PlayersModal'
import QuestionsModal from './QuestionsModal'
import Score from './Score'
import { questions as initialQuestions } from '../data/questions'
import './Game.css'
const Game = () => {
const Game = forwardRef(({
questions = [],
currentQuestionIndex = 0,
onQuestionIndexChange,
onQuestionsChange,
}, ref) => {
const [players, setPlayers] = useState([])
const [currentPlayerId, setCurrentPlayerId] = useState(null)
const [playerScores, setPlayerScores] = useState({})
const [questions, setQuestions] = useState(initialQuestions)
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0)
const [gameOver, setGameOver] = useState(false)
const [revealedAnswers, setRevealedAnswers] = useState([])
const [isPlayersModalOpen, setIsPlayersModalOpen] = useState(false)
const [isQuestionsModalOpen, setIsQuestionsModalOpen] = useState(false)
useImperativeHandle(ref, () => ({
openPlayersModal: () => setIsPlayersModalOpen(true),
openQuestionsModal: () => setIsQuestionsModalOpen(true),
}))
const currentQuestion = questions[currentQuestionIndex]
const isLastQuestion = currentQuestionIndex === questions.length - 1
@ -112,12 +118,16 @@ const Game = () => {
}
const nextQuestion = () => {
setCurrentQuestionIndex(currentQuestionIndex + 1)
if (onQuestionIndexChange) {
onQuestionIndexChange(currentQuestionIndex + 1)
}
setRevealedAnswers([])
}
const restartGame = () => {
setCurrentQuestionIndex(0)
if (onQuestionIndexChange) {
onQuestionIndexChange(0)
}
setGameOver(false)
setRevealedAnswers([])
const initialScores = {}
@ -130,15 +140,6 @@ const Game = () => {
}
}
const handleUpdateQuestions = (updatedQuestions) => {
setQuestions(updatedQuestions)
// Если текущий вопрос был удален, сбрасываем индекс
if (currentQuestionIndex >= updatedQuestions.length) {
setCurrentQuestionIndex(0)
setRevealedAnswers([])
}
}
if (gameOver) {
// Находим победителя(ей)
const scores = Object.values(playerScores)
@ -189,25 +190,10 @@ const Game = () => {
isOpen={isQuestionsModalOpen}
onClose={() => setIsQuestionsModalOpen(false)}
questions={questions}
onUpdateQuestions={handleUpdateQuestions}
onUpdateQuestions={onQuestionsChange}
/>
<div className="game-header">
<div className="game-header-buttons">
<button
className="manage-players-button"
onClick={() => setIsPlayersModalOpen(true)}
>
{players.length === 0 ? 'Добавить участников' : 'Управление участниками'}
</button>
<button
className="manage-questions-button"
onClick={() => setIsQuestionsModalOpen(true)}
>
Управление вопросами
</button>
</div>
{players.length > 0 && (
<Players
players={players}
@ -219,31 +205,24 @@ const Game = () => {
</div>
{players.length > 0 && currentPlayerId ? (
<>
<div className="game-content">
{questions.length === 0 ? (
<div className="no-players-message">
<p>Добавьте вопросы, чтобы начать игру</p>
</div>
) : currentQuestion ? (
<>
<Score
score={playerScores[currentPlayerId] || 0}
questionNumber={currentQuestionIndex + 1}
totalQuestions={questions.length}
/>
<Question
question={currentQuestion}
questionNumber={currentQuestionIndex + 1}
onAnswerClick={handleAnswerClick}
revealedAnswers={revealedAnswers}
/>
</>
<Question
question={currentQuestion}
questionNumber={currentQuestionIndex + 1}
onAnswerClick={handleAnswerClick}
revealedAnswers={revealedAnswers}
/>
) : (
<div className="no-players-message">
<p>Ошибка: вопрос не найден</p>
</div>
)}
</>
</div>
) : (
<div className="no-players-message">
<p>Добавьте участников, чтобы начать игру</p>
@ -251,7 +230,9 @@ const Game = () => {
)}
</div>
)
}
})
Game.displayName = 'Game'
export default Game

View file

@ -1,5 +1,6 @@
.players-container {
margin-bottom: 20px;
margin-bottom: clamp(5px, 1vh, 10px);
flex-shrink: 0;
}
.players-list {

View file

@ -17,10 +17,10 @@
background: rgba(20, 20, 30, 0.95);
backdrop-filter: blur(20px);
border-radius: 20px;
padding: 30px;
padding: clamp(15px, 3vh, 25px);
max-width: 500px;
width: 100%;
max-height: 80vh;
max-height: 90vh;
overflow-y: auto;
border: 2px solid rgba(255, 215, 0, 0.3);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
@ -31,12 +31,12 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
margin-bottom: clamp(15px, 2vh, 20px);
}
.players-modal-title {
color: #ffd700;
font-size: 2rem;
font-size: clamp(1.3rem, 2.5vw, 1.8rem);
margin: 0;
text-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
}

View file

@ -1,16 +1,22 @@
.question-container {
width: 100%;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
}
.question-box {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 25px;
padding: 50px;
margin-bottom: 40px;
padding: clamp(15px, 3vh, 30px);
margin-bottom: clamp(5px, 1vh, 15px);
border: 3px solid rgba(255, 215, 0, 0.3);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
text-align: center;
flex-shrink: 0;
}
.question-number {
@ -18,9 +24,9 @@
background: rgba(255, 215, 0, 0.2);
border: 2px solid rgba(255, 215, 0, 0.5);
border-radius: 15px;
padding: 12px 30px;
margin-bottom: 25px;
font-size: 1.8rem;
padding: clamp(6px, 1vh, 10px) clamp(15px, 2vw, 25px);
margin-bottom: clamp(10px, 2vh, 15px);
font-size: clamp(1rem, 2vw, 1.4rem);
color: #ffd700;
font-weight: bold;
text-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
@ -29,29 +35,23 @@
.question-text {
color: #fff;
font-size: 3.5rem;
font-size: clamp(1.2rem, 3vw, 2.5rem);
font-weight: bold;
line-height: 1.4;
line-height: 1.3;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}
.answers-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 30px;
grid-auto-rows: minmax(0, 1fr);
gap: clamp(10px, 2vw, 20px);
flex: 1;
min-height: 0;
overflow: hidden;
}
@media (max-width: 768px) {
.question-number {
font-size: 1.3rem;
padding: 10px 20px;
margin-bottom: 20px;
}
.question-text {
font-size: 1.5rem;
}
.answers-grid {
grid-template-columns: 1fr;
}

View file

@ -5,7 +5,6 @@ const Question = ({ question, questionNumber, onAnswerClick, revealedAnswers })
return (
<div className="question-container">
<div className="question-box">
<div className="question-number">Вопрос {questionNumber}</div>
<h2 className="question-text">{question.text}</h2>
</div>
<div className="answers-grid">

View file

@ -17,7 +17,7 @@
background: rgba(20, 20, 30, 0.95);
backdrop-filter: blur(20px);
border-radius: 20px;
padding: 30px;
padding: clamp(15px, 3vh, 25px);
max-width: 800px;
width: 100%;
max-height: 90vh;
@ -31,12 +31,12 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
margin-bottom: clamp(15px, 2vh, 20px);
}
.questions-modal-title {
color: #ffd700;
font-size: 2rem;
font-size: clamp(1.3rem, 2.5vw, 1.8rem);
margin: 0;
text-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
}

View file

@ -1,45 +0,0 @@
.score-container {
display: flex;
justify-content: space-around;
margin-bottom: 40px;
gap: 30px;
}
.score-item {
flex: 1;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 35px;
text-align: center;
border: 3px solid rgba(255, 215, 0, 0.3);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.score-label {
display: block;
color: rgba(255, 255, 255, 0.8);
font-size: 1.8rem;
margin-bottom: 15px;
text-transform: uppercase;
letter-spacing: 2px;
}
.score-value {
display: block;
color: #ffd700;
font-size: 4.5rem;
font-weight: bold;
text-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
}
@media (max-width: 768px) {
.score-container {
flex-direction: column;
}
.score-value {
font-size: 2rem;
}
}

View file

@ -1,21 +0,0 @@
import './Score.css'
const Score = ({ score, questionNumber, totalQuestions }) => {
return (
<div className="score-container">
<div className="score-item">
<span className="score-label">Счёт:</span>
<span className="score-value">{score}</span>
</div>
<div className="score-item">
<span className="score-label">Вопрос:</span>
<span className="score-value">
{questionNumber}/{totalQuestions}
</span>
</div>
</div>
)
}
export default Score

View file

@ -12,11 +12,18 @@ body {
-moz-osx-font-smoothing: grayscale;
background: linear-gradient(135deg, #0a0e27 0%, #1a1f3a 100%);
min-height: 100vh;
overflow-x: hidden;
max-height: 100vh;
overflow: hidden;
margin: 0;
padding: 0;
}
#root {
min-height: 100vh;
max-height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Новогодние снежинки */