Initial commit: Flask quiz game 'Уроки французского'
This commit is contained in:
40
templates/admin.html
Normal file
40
templates/admin.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Админка - Уроки французского</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; background: #1a1a2e; color: #eaeaea; padding: 20px; }
|
||||
h1, h2 { color: #e94560; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
||||
th, td { padding: 10px; border: 1px solid #0f3460; text-align: left; }
|
||||
th { background: #0f3460; }
|
||||
.btn { background: #e94560; color: white; padding: 8px 16px; text-decoration: none; border-radius: 4px; display: inline-block; }
|
||||
.btn:hover { background: #c73e54; }
|
||||
.back { margin-top: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Админка - Редактирование вопросов</h1>
|
||||
<a href="/admin/logout" class="btn" style="float: right;">Выйти</a>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Категория</th>
|
||||
<th>Вопрос</th>
|
||||
<th>Стоимость</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
{% for cat_idx, category in enumerate(questions.categories) %}
|
||||
{% for q_idx, q in enumerate(category.questions) %}
|
||||
<tr>
|
||||
<td>{{ category.name }}</td>
|
||||
<td>{{ q.question }}</td>
|
||||
<td>{{ q.cost }}</td>
|
||||
<td><a href="/admin/edit/{{ cat_idx }}/{{ q_idx }}" class="btn">Редактировать</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
<a href="/" class="btn back">Назад к игре</a>
|
||||
</body>
|
||||
</html>
|
||||
55
templates/edit.html
Normal file
55
templates/edit.html
Normal file
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Редактирование вопроса</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; background: #1a1a2e; color: #eaeaea; padding: 20px; }
|
||||
h1 { color: #e94560; }
|
||||
.form-group { margin: 15px 0; }
|
||||
label { display: block; margin-bottom: 5px; }
|
||||
input, textarea { width: 100%; padding: 8px; background: #0f3460; color: #eaeaea; border: 1px solid #e94560; border-radius: 4px; }
|
||||
.btn { background: #e94560; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
|
||||
.btn:hover { background: #c73e54; }
|
||||
.options { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Редактирование вопроса</h1>
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
<label>Вопрос:</label>
|
||||
<textarea name="question" rows="3" required>{{ question.question }}</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Стоимость:</label>
|
||||
<input type="number" name="cost" value="{{ question.cost }}" required>
|
||||
</div>
|
||||
<div class="form-group options">
|
||||
<div>
|
||||
<label>Вариант 1:</label>
|
||||
<input type="text" name="option1" value="{{ question.options[0] }}" required>
|
||||
</div>
|
||||
<div>
|
||||
<label>Вариант 2:</label>
|
||||
<input type="text" name="option2" value="{{ question.options[1] }}" required>
|
||||
</div>
|
||||
<div>
|
||||
<label>Вариант 3:</label>
|
||||
<input type="text" name="option3" value="{{ question.options[2] }}" required>
|
||||
</div>
|
||||
<div>
|
||||
<label>Вариант 4:</label>
|
||||
<input type="text" name="option4" value="{{ question.options[3] }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Правильный ответ (индекс 0-3):</label>
|
||||
<input type="number" name="answer" value="{{ question.answer }}" min="0" max="3" required>
|
||||
<p>Текущий правильный: {{ question.options[question.answer] }}</p>
|
||||
</div>
|
||||
<button type="submit" class="btn">Сохранить</button>
|
||||
</form>
|
||||
<a href="/admin" class="btn" style="margin-top: 10px;">Назад</a>
|
||||
</body>
|
||||
</html>
|
||||
931
templates/index.html
Normal file
931
templates/index.html
Normal file
@@ -0,0 +1,931 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Уроки французского - Своя игра</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&family=Source+Sans+Pro:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-primary: #1a1a2e;
|
||||
--bg-secondary: #16213e;
|
||||
--surface: #0f3460;
|
||||
--primary: #e94560;
|
||||
--text: #eaeaea;
|
||||
--gold: #ffd700;
|
||||
--success: #4ade80;
|
||||
--error: #f87171;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Source Sans Pro', sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text);
|
||||
min-height: 100vh;
|
||||
background-image:
|
||||
radial-gradient(ellipse at top, #1a1a2e 0%, #0f0f1a 100%),
|
||||
repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(233, 69, 96, 0.03) 10px, rgba(233, 69, 96, 0.03) 20px);
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
padding: 2rem 1rem;
|
||||
background: linear-gradient(180deg, var(--bg-secondary) 0%, transparent 100%);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: 'Playfair Display', serif;
|
||||
font-size: 2.5rem;
|
||||
color: var(--primary);
|
||||
text-shadow: 0 0 30px rgba(233, 69, 96, 0.5);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
color: rgba(234, 234, 234, 0.7);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.score-panel {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
margin: 1.5rem 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.score-item {
|
||||
background: var(--surface);
|
||||
padding: 0.75rem 2rem;
|
||||
border-radius: 50px;
|
||||
font-size: 1.2rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(233, 69, 96, 0.3);
|
||||
}
|
||||
|
||||
.score-value {
|
||||
color: var(--gold);
|
||||
font-weight: 600;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.restart-btn {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.restart-btn:hover {
|
||||
background: #c73e54;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.team-setup {
|
||||
text-align: center;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.team-setup select {
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--primary);
|
||||
border-radius: 4px;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.team-setup select option {
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.team-names {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.team-names input {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.team-names input {
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--primary);
|
||||
border-radius: 4px;
|
||||
margin: 0 0.5rem;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.start-btn {
|
||||
background: var(--success);
|
||||
color: var(--bg-primary);
|
||||
border: none;
|
||||
padding: 0.5rem 1.5rem;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.start-btn:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.score-panel {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin: 1.5rem 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.score-item {
|
||||
background: var(--surface);
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 50px;
|
||||
font-size: 1.2rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(233, 69, 96, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.team-active {
|
||||
border-color: var(--gold);
|
||||
box-shadow: 0 0 15px rgba(255, 215, 0, 0.5);
|
||||
}
|
||||
|
||||
.team-selector {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.team-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
border: 2px solid var(--primary);
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.team-btn.active {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.team-btn:hover {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.game-container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.game-board {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
column-gap: 0.5rem;
|
||||
row-gap: 0.5rem;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.category-header {
|
||||
background: linear-gradient(135deg, var(--primary) 0%, #c73e54 100%);
|
||||
color: white;
|
||||
padding: 0.5rem;
|
||||
text-align: center;
|
||||
font-family: 'Playfair Display', serif;
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
border-radius: 8px 8px 0 0;
|
||||
min-height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 15px rgba(233, 69, 96, 0.4);
|
||||
}
|
||||
|
||||
.question-cell {
|
||||
background: var(--surface);
|
||||
aspect-ratio: 1.4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--gold);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid rgba(233, 69, 96, 0.2);
|
||||
border-radius: 4px;
|
||||
min-height: 50px;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.question-cell:hover:not(.answered) {
|
||||
transform: scale(1.05);
|
||||
background: var(--bg-secondary);
|
||||
box-shadow: 0 0 25px rgba(233, 69, 96, 0.4);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.question-cell.answered {
|
||||
background: rgba(15, 52, 96, 0.5);
|
||||
color: rgba(234, 234, 234, 0.3);
|
||||
cursor: not-allowed;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
z-index: 1000;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-overlay.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: linear-gradient(145deg, var(--bg-secondary) 0%, var(--surface) 100%);
|
||||
border-radius: 20px;
|
||||
padding: 2.5rem;
|
||||
max-width: 600px;
|
||||
width: 90%;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), 0 0 40px rgba(233, 69, 96, 0.2);
|
||||
border: 1px solid rgba(233, 69, 96, 0.3);
|
||||
animation: slideUp 0.4s ease;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(30px); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
.modal h2 {
|
||||
font-family: 'Playfair Display', serif;
|
||||
color: var(--primary);
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.modal-cost {
|
||||
position: absolute;
|
||||
top: -15px;
|
||||
right: 20px;
|
||||
background: var(--gold);
|
||||
color: var(--bg-primary);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 4px 15px rgba(255, 215, 0, 0.4);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.question-text {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 2rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.options {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.option {
|
||||
background: var(--bg-primary);
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.option:hover {
|
||||
border-color: var(--primary);
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.option.correct {
|
||||
background: var(--success);
|
||||
color: var(--bg-primary);
|
||||
border-color: var(--success);
|
||||
}
|
||||
|
||||
.option.wrong {
|
||||
background: var(--error);
|
||||
color: var(--bg-primary);
|
||||
border-color: var(--error);
|
||||
}
|
||||
|
||||
.result-message {
|
||||
text-align: center;
|
||||
padding: 1.5rem;
|
||||
border-radius: 10px;
|
||||
margin-top: 1.5rem;
|
||||
font-weight: 600;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.result-message.show {
|
||||
display: block;
|
||||
animation: popIn 0.3s ease;
|
||||
}
|
||||
|
||||
.result-message.success {
|
||||
background: rgba(74, 222, 128, 0.2);
|
||||
color: var(--success);
|
||||
border: 1px solid var(--success);
|
||||
}
|
||||
|
||||
.result-message.error {
|
||||
background: rgba(248, 113, 113, 0.2);
|
||||
color: var(--error);
|
||||
border: 1px solid var(--error);
|
||||
}
|
||||
|
||||
@keyframes popIn {
|
||||
from { transform: scale(0.8); opacity: 0; }
|
||||
to { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
display: none;
|
||||
margin: 1.5rem auto 0;
|
||||
padding: 0.75rem 2rem;
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.close-btn.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 4px 20px rgba(233, 69, 96, 0.4);
|
||||
}
|
||||
|
||||
.final-screen {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--bg-primary);
|
||||
z-index: 2000;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.final-screen.active {
|
||||
display: flex;
|
||||
animation: fadeIn 0.5s ease;
|
||||
}
|
||||
|
||||
.final-screen h2 {
|
||||
font-family: 'Playfair Display', serif;
|
||||
font-size: 3rem;
|
||||
color: var(--gold);
|
||||
margin-bottom: 1rem;
|
||||
text-shadow: 0 0 30px rgba(255, 215, 0, 0.5);
|
||||
}
|
||||
|
||||
.podium {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
gap: 20px;
|
||||
margin: 2rem 0;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.podium-place {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.podium-team {
|
||||
background: var(--surface);
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
border: 2px solid var(--primary);
|
||||
}
|
||||
|
||||
.podium-team-name {
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.podium-team-score {
|
||||
font-size: 1.5rem;
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
.podium-block {
|
||||
background: linear-gradient(180deg, var(--primary) 0%, #c73e54 100%);
|
||||
width: 100%;
|
||||
border-radius: 8px 8px 0 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.podium-1 .podium-block {
|
||||
height: 150px;
|
||||
background: linear-gradient(180deg, #ffd700 0%, #daa520 100%);
|
||||
}
|
||||
|
||||
.podium-2 .podium-block {
|
||||
height: 100px;
|
||||
background: linear-gradient(180deg, #c0c0c0 0%, #a8a8a8 100%);
|
||||
}
|
||||
|
||||
.podium-3 .podium-block {
|
||||
height: 70px;
|
||||
background: linear-gradient(180deg, #cd7f32 0%, #b87333 100%);
|
||||
}
|
||||
|
||||
.podium-1 { order: 2; }
|
||||
.podium-2 { order: 1; }
|
||||
.podium-3 { order: 3; }
|
||||
|
||||
.all-teams {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.team-result {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: var(--surface);
|
||||
padding: 15px 20px;
|
||||
border-radius: 10px;
|
||||
border: 2px solid var(--primary);
|
||||
}
|
||||
|
||||
.team-place {
|
||||
font-size: 1.5rem;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.team-name {
|
||||
flex: 1;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.team-points {
|
||||
font-size: 1.2rem;
|
||||
color: var(--gold);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.-gradient(180deg, var(--primary) 0%, #c73e54 100%);
|
||||
width: 100%;
|
||||
border-radius: 8px 8px 0 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.podium-1 .podium-block {
|
||||
height: 150px;
|
||||
background: linear-gradient(180deg, #ffd700 0%, #daa520 100%);
|
||||
}
|
||||
|
||||
.podium-2 .podium-block {
|
||||
height: 100px;
|
||||
background: linear-gradient(180deg, #c0c0c0 0%, #a8a8a8 100%);
|
||||
}
|
||||
|
||||
.podium-3 .podium-block {
|
||||
height: 70px;
|
||||
background: linear-gradient(180deg, #cd7f32 0%, #b87333 100%);
|
||||
}
|
||||
|
||||
.podium-1 .podium-block {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.podium-2 .podium-block {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.podium-3 .podium-block {
|
||||
order: 3;
|
||||
}
|
||||
|
||||
.play-again-btn {
|
||||
padding: 1rem 3rem;
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50px;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.play-again-btn:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 10px 30px rgba(233, 69, 96, 0.5);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
h1 { font-size: 1.8rem; }
|
||||
.category-header { font-size: 0.85rem; min-height: 60px; padding: 0.5rem; }
|
||||
.question-cell { font-size: 1.1rem; min-height: 50px; }
|
||||
.modal { padding: 1.5rem; }
|
||||
.question-text { font-size: 1rem; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Своя игра</h1>
|
||||
<p class="subtitle">Рассказ В.Г. Распутина «Уроки французского»</p>
|
||||
<div class="team-setup" id="teamSetup">
|
||||
<label>Количество команд:
|
||||
<select id="teamCount" onchange="setupTeams()">
|
||||
<option value="1">1 команда</option>
|
||||
<option value="2">2 команды</option>
|
||||
<option value="3">3 команды</option>
|
||||
</select>
|
||||
</label>
|
||||
<div id="teamNames"></div>
|
||||
<button class="start-btn" id="startBtn" onclick="startGame()">Начать игру</button>
|
||||
</div>
|
||||
<div class="score-panel" id="scorePanel">
|
||||
<div class="team-selector" id="teamSelector"></div>
|
||||
<div class="score-item">Вопросов: <span class="score-value" id="answered">0</span>/<span id="totalQuestions">30</span></div>
|
||||
<button class="restart-btn" onclick="if(confirm('Вы точно хотите начать заново?')) restartGame()">Начать заново</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="game-container">
|
||||
<div class="game-board" id="gameBoard">
|
||||
{% for cat_idx, category in enumerate(questions.categories) %}
|
||||
<div>
|
||||
<div class="category-header">{{ category.name }}</div>
|
||||
{% for q_idx, q in enumerate(category.questions) %}
|
||||
<div class="question-cell"
|
||||
data-cat="{{ cat_idx }}"
|
||||
data-q="{{ q_idx }}">
|
||||
{{ q.cost }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-overlay" id="modalOverlay">
|
||||
<div class="modal">
|
||||
<div class="modal-body">
|
||||
<span class="modal-cost" id="modalCost">100</span>
|
||||
<h2>Вопрос</h2>
|
||||
<p class="question-text" id="questionText"></p>
|
||||
<div class="options" id="options"></div>
|
||||
<div class="result-message" id="resultMessage"></div>
|
||||
<button class="close-btn" id="closeBtn">Продолжить</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="final-screen" id="finalScreen">
|
||||
<h2>Игра окончена!</h2>
|
||||
<div class="podium" id="podium"></div>
|
||||
<button class="play-again-btn" onclick="location.reload()">Играть снова</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var allQuestions = {{ questions | tojson }};
|
||||
|
||||
var STORAGE_KEY = 'francuzskiy_game';
|
||||
var currentTeam = 0;
|
||||
var teams = [];
|
||||
|
||||
setupTeams();
|
||||
|
||||
function setupTeams() {
|
||||
var count = parseInt(document.getElementById('teamCount').value);
|
||||
var container = document.getElementById('teamNames');
|
||||
container.innerHTML = '';
|
||||
for (var i = 0; i < count; i++) {
|
||||
var input = document.createElement('input');
|
||||
input.placeholder = 'Команда ' + (i + 1);
|
||||
input.value = 'Команда ' + (i + 1);
|
||||
input.id = 'teamName' + i;
|
||||
container.appendChild(input);
|
||||
}
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
document.getElementById('startBtn').style.display = 'none';
|
||||
|
||||
var count = parseInt(document.getElementById('teamCount').value);
|
||||
teams = [];
|
||||
for (var i = 0; i < count; i++) {
|
||||
teams.push({
|
||||
name: document.getElementById('teamName' + i).value || 'Команда ' + (i + 1),
|
||||
score: 0
|
||||
});
|
||||
}
|
||||
|
||||
var totalQuestions = 30;
|
||||
document.getElementById('totalQuestions').textContent = totalQuestions;
|
||||
|
||||
currentTeam = 0;
|
||||
updateScoreDisplay();
|
||||
|
||||
initGame();
|
||||
}
|
||||
|
||||
function selectTeam(index) {
|
||||
currentTeam = index;
|
||||
var btns = document.querySelectorAll('.team-btn');
|
||||
for (var i = 0; i < btns.length; i++) {
|
||||
btns[i].classList.toggle('active', i === index);
|
||||
}
|
||||
}
|
||||
|
||||
function updateScoreDisplay() {
|
||||
var container = document.getElementById('teamSelector');
|
||||
var html = '';
|
||||
for (var i = 0; i < teams.length; i++) {
|
||||
html += '<button class="team-btn' + (i === currentTeam ? ' active' : '') + '" onclick="selectTeam(' + i + ')">' + teams[i].name + ': <span id="score' + i + '">' + teams[i].score + '</span></button>';
|
||||
}
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function loadGameState() {
|
||||
var saved = localStorage.getItem(STORAGE_KEY);
|
||||
if (saved) {
|
||||
return JSON.parse(saved);
|
||||
}
|
||||
return { score: 0, answered: [], userAnswers: {} };
|
||||
}
|
||||
|
||||
function saveGameState(state) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
||||
}
|
||||
|
||||
function initGame() {
|
||||
const state = loadGameState();
|
||||
|
||||
let answeredCount = 0;
|
||||
state.answered.forEach(function(key) {
|
||||
const [cat, q] = key.split('_');
|
||||
const cell = document.querySelector('.question-cell[data-cat="' + cat + '"][data-q="' + q + '"]');
|
||||
if (cell) {
|
||||
cell.classList.add('answered');
|
||||
answeredCount++;
|
||||
}
|
||||
});
|
||||
document.getElementById('answered').textContent = answeredCount;
|
||||
}
|
||||
|
||||
const modalOverlay = document.getElementById('modalOverlay');
|
||||
const questionText = document.getElementById('questionText');
|
||||
const optionsContainer = document.getElementById('options');
|
||||
const modalCost = document.getElementById('modalCost');
|
||||
const resultMessage = document.getElementById('resultMessage');
|
||||
const answeredEl = document.getElementById('answered');
|
||||
const finalScreen = document.getElementById('finalScreen');
|
||||
const finalScore = document.getElementById('finalScore');
|
||||
|
||||
let currentCat = 0;
|
||||
let currentQ = 0;
|
||||
let correctAnswer = 0;
|
||||
let currentCost = 0;
|
||||
let canAnswer = true;
|
||||
|
||||
initGame();
|
||||
|
||||
var cells = document.querySelectorAll('.question-cell');
|
||||
for (var i = 0; i < cells.length; i++) {
|
||||
cells[i].onclick = function() {
|
||||
var cat = parseInt(this.dataset.cat, 10);
|
||||
var q = parseInt(this.dataset.q, 10);
|
||||
var questionData = allQuestions.categories[cat].questions[q];
|
||||
var isAnswered = this.classList.contains('answered');
|
||||
|
||||
openQuestionModal(cat, q, questionData.question, questionData.options, questionData.answer, questionData.cost, this, isAnswered);
|
||||
};
|
||||
}
|
||||
|
||||
function openQuestionModal(catIndex, qIndex, question, options, answer, cost, cell, viewOnly) {
|
||||
currentCat = catIndex;
|
||||
currentQ = qIndex;
|
||||
correctAnswer = answer;
|
||||
currentCost = cost;
|
||||
|
||||
questionText.textContent = question;
|
||||
modalCost.textContent = cost;
|
||||
|
||||
optionsContainer.innerHTML = '';
|
||||
var userAnswerData = null;
|
||||
if (viewOnly) {
|
||||
var state = loadGameState();
|
||||
var key = catIndex + '_' + qIndex;
|
||||
userAnswerData = state.userAnswers[key];
|
||||
}
|
||||
|
||||
options.forEach(function(opt, i) {
|
||||
var btn = document.createElement('div');
|
||||
btn.className = 'option';
|
||||
btn.textContent = opt;
|
||||
|
||||
if (viewOnly) {
|
||||
btn.style.pointerEvents = 'none';
|
||||
if (i === answer) {
|
||||
btn.classList.add('correct');
|
||||
}
|
||||
if (userAnswerData && i === userAnswerData.answer && i !== answer) {
|
||||
btn.classList.add('wrong');
|
||||
}
|
||||
} else {
|
||||
btn.onclick = function() { selectAnswer(i, cell); };
|
||||
}
|
||||
optionsContainer.appendChild(btn);
|
||||
});
|
||||
|
||||
resultMessage.className = 'result-message';
|
||||
resultMessage.textContent = '';
|
||||
if (viewOnly) {
|
||||
resultMessage.textContent = 'Этот вопрос уже отыгран';
|
||||
resultMessage.className = 'result-message show';
|
||||
}
|
||||
var closeBtnEl = document.getElementById('closeBtn');
|
||||
closeBtnEl.className = 'close-btn show';
|
||||
canAnswer = !viewOnly;
|
||||
|
||||
modalOverlay.classList.add('active');
|
||||
}
|
||||
|
||||
function selectAnswer(index, cell) {
|
||||
if (!canAnswer) return;
|
||||
canAnswer = false;
|
||||
|
||||
const options = document.querySelectorAll('.option');
|
||||
options.forEach((opt, i) => {
|
||||
opt.style.pointerEvents = 'none';
|
||||
if (i === correctAnswer) opt.classList.add('correct');
|
||||
});
|
||||
|
||||
const isCorrect = index === correctAnswer;
|
||||
|
||||
if (!isCorrect) {
|
||||
options[index].classList.add('wrong');
|
||||
}
|
||||
|
||||
const state = loadGameState();
|
||||
const key = `${currentCat}_${currentQ}`;
|
||||
|
||||
if (!state.answered.includes(key)) {
|
||||
state.answered.push(key);
|
||||
}
|
||||
state.userAnswers[key] = { team: currentTeam, answer: index };
|
||||
|
||||
if (isCorrect) {
|
||||
teams[currentTeam].score += currentCost;
|
||||
resultMessage.textContent = 'Правильно! ' + teams[currentTeam].name + ' получает +' + currentCost + ' очков';
|
||||
} else {
|
||||
teams[currentTeam].score -= currentCost;
|
||||
resultMessage.textContent = 'Неправильно! ' + teams[currentTeam].name + ' теряет -' + currentCost + ' очков';
|
||||
}
|
||||
|
||||
saveGameState(state);
|
||||
|
||||
updateScoreDisplay();
|
||||
answeredEl.textContent = state.answered.length;
|
||||
|
||||
if (teams.length > 1) {
|
||||
currentTeam = (currentTeam + 1) % teams.length;
|
||||
selectTeam(currentTeam);
|
||||
}
|
||||
|
||||
resultMessage.className = 'result-message show ' + (isCorrect ? 'success' : 'error');
|
||||
|
||||
cell.classList.add('answered');
|
||||
|
||||
var closeBtnEl = document.getElementById('closeBtn');
|
||||
closeBtnEl.classList.add('show');
|
||||
|
||||
setTimeout(function() { checkGameEnd(); }, 500);
|
||||
}
|
||||
|
||||
document.getElementById('closeBtn').onclick = function() {
|
||||
modalOverlay.classList.remove('active');
|
||||
};
|
||||
|
||||
function checkGameEnd() {
|
||||
var totalQuestions = parseInt(document.getElementById('totalQuestions').textContent);
|
||||
var state = loadGameState();
|
||||
if (state.answered.length >= totalQuestions) {
|
||||
var sortedTeams = teams.slice().sort(function(a, b) { return b.score - a.score; });
|
||||
|
||||
var podiumHtml = '<div class="all-teams">';
|
||||
for (var i = 0; i < sortedTeams.length; i++) {
|
||||
var place = i + 1;
|
||||
var medal = '';
|
||||
if (place === 1) medal = '🥇';
|
||||
else if (place === 2) medal = '🥈';
|
||||
else if (place === 3) medal = '🥉';
|
||||
else medal = place + '.';
|
||||
|
||||
podiumHtml += '<div class="team-result">';
|
||||
podiumHtml += '<span class="team-place">' + medal + '</span>';
|
||||
podiumHtml += '<span class="team-name">' + sortedTeams[i].name + '</span>';
|
||||
podiumHtml += '<span class="team-points">' + sortedTeams[i].score + ' очков</span>';
|
||||
podiumHtml += '</div>';
|
||||
}
|
||||
podiumHtml += '</div>';
|
||||
|
||||
document.getElementById('podium').innerHTML = podiumHtml;
|
||||
|
||||
setTimeout(function() {
|
||||
finalScreen.classList.add('active');
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelector('.play-again-btn').onclick = function() {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
location.reload();
|
||||
};
|
||||
|
||||
function restartGame() {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
location.reload();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
28
templates/login.html
Normal file
28
templates/login.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Вход в админку</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; background: #1a1a2e; color: #eaeaea; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
||||
.login-form { background: #0f3460; padding: 30px; border-radius: 10px; text-align: center; }
|
||||
h1 { color: #e94560; margin-bottom: 20px; }
|
||||
input { width: 100%; padding: 10px; margin: 10px 0; background: #1a1a2e; color: #eaeaea; border: 1px solid #e94560; border-radius: 4px; }
|
||||
.btn { background: #e94560; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; width: 100%; }
|
||||
.btn:hover { background: #c73e54; }
|
||||
.error { color: #f87171; margin-top: 10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-form">
|
||||
<h1>Вход в админку</h1>
|
||||
<form method="POST" action="/admin/login">
|
||||
<input type="password" name="password" placeholder="Введите пароль" required>
|
||||
<button type="submit" class="btn">Войти</button>
|
||||
</form>
|
||||
{% if error %}
|
||||
<p class="error">{{ error }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user