Autosave: 20260305-195720
This commit is contained in:
parent
4227c05243
commit
aae6b26656
@ -49,6 +49,29 @@ a {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quiz-progress-wrap {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #fff7ef;
|
||||||
|
padding: 0.8rem 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quiz-progress-bar {
|
||||||
|
width: 100%;
|
||||||
|
height: 13px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #f3dfcc;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quiz-progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
width: 0;
|
||||||
|
border-radius: inherit;
|
||||||
|
background: linear-gradient(90deg, #f59e0b, #22c55e);
|
||||||
|
transition: width 0.35s ease;
|
||||||
|
}
|
||||||
|
|
||||||
.question-area {
|
.question-area {
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
@ -99,6 +122,11 @@ a {
|
|||||||
background: #f0fdf4;
|
background: #f0fdf4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.option-item-correct-pop {
|
||||||
|
animation: correctPop 0.9s cubic-bezier(0.2, 0.9, 0.28, 1.2);
|
||||||
|
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
.option-item-wrong {
|
.option-item-wrong {
|
||||||
border-color: #dc2626;
|
border-color: #dc2626;
|
||||||
background: #fef2f2;
|
background: #fef2f2;
|
||||||
@ -247,6 +275,15 @@ body.quiz-live #quiz #revealBtn {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.quiz-live #quiz .quiz-progress-wrap {
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
padding: 0.95rem 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.quiz-live #quiz .quiz-progress-bar {
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
body.quiz-live #quiz .quiz-actions {
|
body.quiz-live #quiz .quiz-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -325,3 +362,47 @@ body.lightbox-open {
|
|||||||
height: 2.4rem;
|
height: 2.4rem;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.correct-spark {
|
||||||
|
position: fixed;
|
||||||
|
width: var(--size, 14px);
|
||||||
|
height: var(--size, 14px);
|
||||||
|
border-radius: 5px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1100;
|
||||||
|
box-shadow: 0 0 14px rgba(255, 180, 75, 0.75);
|
||||||
|
transform: translate(-50%, -50%) scale(0.55);
|
||||||
|
animation: sparkBurst 1.15s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes correctPop {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.42);
|
||||||
|
}
|
||||||
|
35% {
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 0 0 28px rgba(34, 197, 94, 0);
|
||||||
|
}
|
||||||
|
65% {
|
||||||
|
transform: scale(1.04);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sparkBurst {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(-50%, -50%) scale(0.45) rotate(0deg);
|
||||||
|
}
|
||||||
|
18% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(0.25) rotate(var(--rot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -78,14 +78,14 @@ const quizData = [
|
|||||||
imageAlt: "Город Витебск"
|
imageAlt: "Город Витебск"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
question: "Белорусский народный инструмент — это…",
|
question: "Как называется белорусский струнный ударный музыкальный инструмент?",
|
||||||
options: ["Цимбалы", "Скрипка", "Дудка", "Сопилка"],
|
options: ["Цимбалы", "Скрипка", "Дудка", "Сопилка"],
|
||||||
answer: 0,
|
answer: 0,
|
||||||
fact: "Цимбалы — струнный инструмент, по ним ударяют молоточками.",
|
fact: "Цимбалы — это инструмент со струнами, по которым играют маленькими молоточками.",
|
||||||
facts: [
|
facts: [
|
||||||
"Цимбалы относятся к струнным ударным инструментам.",
|
"Цимбалы похожи на большую деревянную трапецию со струнами.",
|
||||||
"Музыкант играет на цимбалах двумя маленькими молоточками.",
|
"По струнам ударяют двумя маленькими молоточками, и получается звонкий звук.",
|
||||||
"Звук цимбал часто можно услышать в белорусской народной музыке."
|
"Цимбалы часто звучат в белорусской народной музыке."
|
||||||
],
|
],
|
||||||
imagePath: "assets/pasted-20260305-190344-ec46e879.jpg",
|
imagePath: "assets/pasted-20260305-190344-ec46e879.jpg",
|
||||||
imageAlt: "Традиционный музыкальный инструмент"
|
imageAlt: "Традиционный музыкальный инструмент"
|
||||||
@ -100,7 +100,7 @@ const quizData = [
|
|||||||
"Замок сочетает элементы готики, ренессанса и барокко.",
|
"Замок сочетает элементы готики, ренессанса и барокко.",
|
||||||
"Комплекс Мирского замка входит в список Всемирного наследия ЮНЕСКО."
|
"Комплекс Мирского замка входит в список Всемирного наследия ЮНЕСКО."
|
||||||
],
|
],
|
||||||
imagePath: "assets/images/quiz/q08-castle.jpg",
|
imagePath: "assets/pasted-20260305-193451-5d1a5abc.jpg",
|
||||||
imageAlt: "Мирский замок"
|
imageAlt: "Мирский замок"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -139,7 +139,7 @@ const quizData = [
|
|||||||
"В городе находится Софийский собор — важный исторический памятник.",
|
"В городе находится Софийский собор — важный исторический памятник.",
|
||||||
"Полоцк был крупным культурным и торговым центром в Средние века."
|
"Полоцк был крупным культурным и торговым центром в Средние века."
|
||||||
],
|
],
|
||||||
imagePath: "assets/images/quiz/q11-polotsk.jpg",
|
imagePath: "assets/pasted-20260305-193618-2a0dd0c5.jpg",
|
||||||
imageAlt: "Город Полоцк"
|
imageAlt: "Город Полоцк"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -161,6 +161,8 @@ const startBtn = document.getElementById("startQuizBtn");
|
|||||||
const quizSection = document.getElementById("quiz");
|
const quizSection = document.getElementById("quiz");
|
||||||
const summarySection = document.getElementById("summary");
|
const summarySection = document.getElementById("summary");
|
||||||
const progressEl = document.getElementById("questionProgress");
|
const progressEl = document.getElementById("questionProgress");
|
||||||
|
const progressLabel = document.getElementById("progressLabel");
|
||||||
|
const progressFill = document.getElementById("progressFill");
|
||||||
const questionText = document.getElementById("questionText");
|
const questionText = document.getElementById("questionText");
|
||||||
const optionList = document.getElementById("optionList");
|
const optionList = document.getElementById("optionList");
|
||||||
const totalQuestions = document.getElementById("totalQuestions");
|
const totalQuestions = document.getElementById("totalQuestions");
|
||||||
@ -185,37 +187,65 @@ const state = {
|
|||||||
score: 0,
|
score: 0,
|
||||||
revealed: false,
|
revealed: false,
|
||||||
selectedOption: null,
|
selectedOption: null,
|
||||||
locked: false
|
locked: false,
|
||||||
|
lastAnswerCorrect: false,
|
||||||
|
optionOrders: []
|
||||||
};
|
};
|
||||||
|
|
||||||
totalQuestions.textContent = quizData.length.toString();
|
totalQuestions.textContent = quizData.length.toString();
|
||||||
|
|
||||||
|
function updateProgressBar() {
|
||||||
|
const completed = state.index + (state.revealed ? 1 : 0);
|
||||||
|
const percent = Math.max(0, Math.min(100, Math.round((completed / quizData.length) * 100)));
|
||||||
|
progressLabel.textContent = `${completed} из ${quizData.length}`;
|
||||||
|
progressFill.style.width = `${percent}%`;
|
||||||
|
progressFill.parentElement?.setAttribute("aria-valuenow", String(percent));
|
||||||
|
}
|
||||||
|
|
||||||
|
function shuffleArray(items) {
|
||||||
|
const arr = items.slice();
|
||||||
|
for (let i = arr.length - 1; i > 0; i -= 1) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[arr[i], arr[j]] = [arr[j], arr[i]];
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildOptionOrders() {
|
||||||
|
return quizData.map((item) => shuffleArray(item.options.map((_, idx) => idx)));
|
||||||
|
}
|
||||||
|
|
||||||
function renderQuestion() {
|
function renderQuestion() {
|
||||||
const current = quizData[state.index];
|
const current = quizData[state.index];
|
||||||
|
const currentOrder = state.optionOrders[state.index] || current.options.map((_, idx) => idx);
|
||||||
progressEl.textContent = `Вопрос ${state.index + 1} из ${quizData.length}`;
|
progressEl.textContent = `Вопрос ${state.index + 1} из ${quizData.length}`;
|
||||||
|
updateProgressBar();
|
||||||
questionText.textContent = current.question;
|
questionText.textContent = current.question;
|
||||||
optionList.innerHTML = "";
|
optionList.innerHTML = "";
|
||||||
|
optionList.classList.toggle("d-none", state.revealed);
|
||||||
|
optionList.setAttribute("aria-hidden", state.revealed ? "true" : "false");
|
||||||
|
|
||||||
current.options.forEach((option, idx) => {
|
currentOrder.forEach((originalIdx, displayIdx) => {
|
||||||
|
const option = current.options[originalIdx];
|
||||||
const li = document.createElement("li");
|
const li = document.createElement("li");
|
||||||
li.className = "option-item";
|
li.className = "option-item";
|
||||||
li.setAttribute("role", "button");
|
li.setAttribute("role", "button");
|
||||||
li.setAttribute("tabindex", "0");
|
li.setAttribute("tabindex", "0");
|
||||||
li.dataset.index = String(idx);
|
li.dataset.index = String(originalIdx);
|
||||||
|
|
||||||
if (idx === state.selectedOption) {
|
if (originalIdx === state.selectedOption) {
|
||||||
li.classList.add("option-item-selected");
|
li.classList.add("option-item-selected");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.revealed) {
|
if (state.revealed) {
|
||||||
if (idx === current.answer) {
|
if (originalIdx === current.answer) {
|
||||||
li.classList.add("option-item-correct");
|
li.classList.add("option-item-correct");
|
||||||
} else if (idx === state.selectedOption) {
|
} else if (originalIdx === state.selectedOption) {
|
||||||
li.classList.add("option-item-wrong");
|
li.classList.add("option-item-wrong");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
li.innerHTML = `<span class="option-index">${String.fromCharCode(65 + idx)}</span><span>${option}</span>`;
|
li.innerHTML = `<span class="option-index">${String.fromCharCode(65 + displayIdx)}</span><span>${option}</span>`;
|
||||||
optionList.appendChild(li);
|
optionList.appendChild(li);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -274,6 +304,8 @@ startBtn.addEventListener("click", () => {
|
|||||||
state.revealed = false;
|
state.revealed = false;
|
||||||
state.selectedOption = null;
|
state.selectedOption = null;
|
||||||
state.locked = false;
|
state.locked = false;
|
||||||
|
state.lastAnswerCorrect = false;
|
||||||
|
state.optionOrders = buildOptionOrders();
|
||||||
renderQuestion();
|
renderQuestion();
|
||||||
quizSection.scrollIntoView({ behavior: "smooth" });
|
quizSection.scrollIntoView({ behavior: "smooth" });
|
||||||
});
|
});
|
||||||
@ -284,6 +316,7 @@ function goToNextQuestion() {
|
|||||||
state.revealed = false;
|
state.revealed = false;
|
||||||
state.selectedOption = null;
|
state.selectedOption = null;
|
||||||
state.locked = false;
|
state.locked = false;
|
||||||
|
state.lastAnswerCorrect = false;
|
||||||
renderQuestion();
|
renderQuestion();
|
||||||
} else {
|
} else {
|
||||||
showSummary();
|
showSummary();
|
||||||
@ -297,12 +330,46 @@ restartBtn.addEventListener("click", () => {
|
|||||||
state.revealed = false;
|
state.revealed = false;
|
||||||
state.selectedOption = null;
|
state.selectedOption = null;
|
||||||
state.locked = false;
|
state.locked = false;
|
||||||
|
state.lastAnswerCorrect = false;
|
||||||
|
state.optionOrders = buildOptionOrders();
|
||||||
summarySection.classList.add("d-none");
|
summarySection.classList.add("d-none");
|
||||||
quizSection.classList.remove("d-none");
|
quizSection.classList.remove("d-none");
|
||||||
renderQuestion();
|
renderQuestion();
|
||||||
quizSection.scrollIntoView({ behavior: "smooth" });
|
quizSection.scrollIntoView({ behavior: "smooth" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function playCorrectClickEffect() {
|
||||||
|
const selectedEl = optionList.querySelector(`.option-item[data-index="${state.selectedOption}"]`);
|
||||||
|
if (!selectedEl) return;
|
||||||
|
|
||||||
|
selectedEl.classList.remove("option-item-correct-pop");
|
||||||
|
void selectedEl.offsetWidth;
|
||||||
|
selectedEl.classList.add("option-item-correct-pop");
|
||||||
|
|
||||||
|
const rect = selectedEl.getBoundingClientRect();
|
||||||
|
const burstCount = 28;
|
||||||
|
const colors = ["#f59e0b", "#f97316", "#fb7185", "#22c55e", "#facc15"];
|
||||||
|
|
||||||
|
for (let i = 0; i < burstCount; i += 1) {
|
||||||
|
const spark = document.createElement("span");
|
||||||
|
spark.className = "correct-spark";
|
||||||
|
spark.style.left = `${rect.left + rect.width / 2}px`;
|
||||||
|
spark.style.top = `${rect.top + rect.height / 2}px`;
|
||||||
|
spark.style.background = colors[i % colors.length];
|
||||||
|
|
||||||
|
const angle = ((Math.PI * 2) / burstCount) * i + (Math.random() * 0.3 - 0.15);
|
||||||
|
const distance = 88 + Math.random() * 148;
|
||||||
|
const size = 12 + Math.random() * 18;
|
||||||
|
spark.style.setProperty("--dx", `${Math.cos(angle) * distance}px`);
|
||||||
|
spark.style.setProperty("--dy", `${Math.sin(angle) * distance}px`);
|
||||||
|
spark.style.setProperty("--rot", `${Math.random() * 280 - 140}deg`);
|
||||||
|
spark.style.setProperty("--size", `${size}px`);
|
||||||
|
|
||||||
|
document.body.appendChild(spark);
|
||||||
|
setTimeout(() => spark.remove(), 1250);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function closeLightbox() {
|
function closeLightbox() {
|
||||||
imageLightbox.classList.add("d-none");
|
imageLightbox.classList.add("d-none");
|
||||||
document.body.classList.remove("lightbox-open");
|
document.body.classList.remove("lightbox-open");
|
||||||
@ -334,13 +401,17 @@ function selectOption(index) {
|
|||||||
if (state.revealed || state.locked) return;
|
if (state.revealed || state.locked) return;
|
||||||
const current = quizData[state.index];
|
const current = quizData[state.index];
|
||||||
state.selectedOption = index;
|
state.selectedOption = index;
|
||||||
if (state.selectedOption === current.answer) {
|
state.lastAnswerCorrect = state.selectedOption === current.answer;
|
||||||
|
|
||||||
|
if (state.lastAnswerCorrect) {
|
||||||
|
playCorrectClickEffect();
|
||||||
state.score += 1;
|
state.score += 1;
|
||||||
}
|
}
|
||||||
state.revealed = true;
|
state.revealed = true;
|
||||||
state.locked = true;
|
state.locked = true;
|
||||||
renderQuestion();
|
renderQuestion();
|
||||||
renderAnswer();
|
renderAnswer();
|
||||||
|
|
||||||
nextBtn.classList.remove("d-none");
|
nextBtn.classList.remove("d-none");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
assets/pasted-20260305-193451-5d1a5abc.jpg
Normal file
BIN
assets/pasted-20260305-193451-5d1a5abc.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 212 KiB |
BIN
assets/pasted-20260305-193618-2a0dd0c5.jpg
Normal file
BIN
assets/pasted-20260305-193618-2a0dd0c5.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 337 KiB |
BIN
assets/vm-shot-2026-03-05T19-56-11-555Z.jpg
Normal file
BIN
assets/vm-shot-2026-03-05T19-56-11-555Z.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
11
index.php
11
index.php
@ -85,6 +85,15 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
|||||||
<span class="fs-4 fw-semibold" id="classScore">0</span>
|
<span class="fs-4 fw-semibold" id="classScore">0</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="quiz-progress-wrap mb-3" aria-live="polite">
|
||||||
|
<div class="d-flex justify-content-between align-items-center gap-2 mb-2">
|
||||||
|
<span class="small text-muted">Пройдено</span>
|
||||||
|
<span class="small fw-semibold" id="progressLabel">0 из 12</span>
|
||||||
|
</div>
|
||||||
|
<div class="quiz-progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" aria-label="Прогресс по вопросам">
|
||||||
|
<div class="quiz-progress-fill" id="progressFill"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="question-area">
|
<div class="question-area">
|
||||||
<div class="question-label text-uppercase small text-muted mb-2">Вопрос</div>
|
<div class="question-label text-uppercase small text-muted mb-2">Вопрос</div>
|
||||||
@ -140,7 +149,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
|||||||
<ol class="text-muted">
|
<ol class="text-muted">
|
||||||
<li>Нажмите «Начать викторину» и выводите вопросы на экран.</li>
|
<li>Нажмите «Начать викторину» и выводите вопросы на экран.</li>
|
||||||
<li>Дайте детям 20–30 секунд на обсуждение.</li>
|
<li>Дайте детям 20–30 секунд на обсуждение.</li>
|
||||||
<li>Выберите вариант ответа, затем нажмите «Показать ответ».</li>
|
<li>Выберите вариант ответа — карточка с правильным ответом откроется сразу.</li>
|
||||||
<li>В конце покажите итоги и обсудите факты.</li>
|
<li>В конце покажите итоги и обсудите факты.</li>
|
||||||
</ol>
|
</ol>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user