Skip to content

Instantly share code, notes, and snippets.

@ochaton
Created November 16, 2025 10:31
Show Gist options
  • Select an option

  • Save ochaton/88de622ff8d10cea59796b132305ce10 to your computer and use it in GitHub Desktop.

Select an option

Save ochaton/88de622ff8d10cea59796b132305ce10 to your computer and use it in GitHub Desktop.
quiz-site
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Тестики :)</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="page">
<header class="header">
<h1>Мини-тест</h1>
<p class="subtitle">
Выбери <strong>ровно один</strong> правильный вариант в каждом вопросе и нажми «Проверить».
</p>
</header>
<main>
<form id="quiz-form" class="quiz"></form>
<div class="actions">
<button type="button" id="check-btn">Проверить</button>
<button type="button" id="reset-btn" class="secondary">Сбросить</button>
</div>
<div id="result" class="result" aria-live="polite"></div>
</main>
<footer class="footer">
<small>Просто статический сайтик без бэкенда :)</small>
</footer>
</div>
<script src="script.js"></script>
</body>
</html>
// Здесь описываем вопросы
const questions = [
{
id: 1,
text: "Какой тип страницы мы сейчас делаем?",
options: [
"Тяжёлое SPA с бэкендом и БД",
"Простой статический сайт на HTML/JS/CSS",
"Нативное мобильное приложение"
],
correctIndex: 1,
explanation: "Это обычный статический сайт: файлики HTML + CSS + JS, без сервера и БД."
},
{
id: 2,
text: "Сколько правильных ответов в каждом вопросе?",
options: [
"Зависит от настроения автора",
"Ровно один",
"Ровно два"
],
correctIndex: 1,
explanation: "По условию теста: «ровно один» правильный вариант."
},
{
id: 3,
text: "Что произойдёт при выкладке такого сайта на Netlify?",
options: [
"Ничего, нужен обязательно Node.js сервер",
"Сайт будет работать как есть, просто как набор статических файлов",
"Netlify автоматически перепишет код на Go"
],
correctIndex: 1,
explanation: "Netlify отлично хостит чистую статику — HTML/JS/CSS без каких-либо доработок."
}
];
// Генерация формы
function renderQuiz() {
const form = document.getElementById("quiz-form");
form.innerHTML = "";
questions.forEach((q, qIndex) => {
const questionDiv = document.createElement("section");
questionDiv.className = "question";
questionDiv.dataset.questionId = q.id;
const title = document.createElement("h2");
title.className = "question-title";
title.textContent = `${qIndex + 1}. ${q.text}`;
questionDiv.appendChild(title);
const list = document.createElement("ul");
list.className = "options";
q.options.forEach((optionText, optIndex) => {
const li = document.createElement("li");
li.className = "option";
const label = document.createElement("label");
const input = document.createElement("input");
input.type = "radio";
input.name = `question-${qIndex}`;
input.value = String(optIndex);
const span = document.createElement("span");
span.textContent = optionText;
label.appendChild(input);
label.appendChild(span);
li.appendChild(label);
list.appendChild(li);
});
questionDiv.appendChild(list);
const explanation = document.createElement("div");
explanation.className = "question-explanation";
explanation.style.display = "none";
questionDiv.appendChild(explanation);
form.appendChild(questionDiv);
});
}
function checkAnswers() {
const form = document.getElementById("quiz-form");
const resultBlock = document.getElementById("result");
let correctCount = 0;
let unansweredCount = 0;
// Сначала убираем старые подсветки
form.querySelectorAll(".option").forEach(opt => {
opt.classList.remove("correct", "incorrect");
});
form.querySelectorAll(".question").forEach(q => {
q.classList.remove("unanswered");
const expl = q.querySelector(".question-explanation");
if (expl) expl.style.display = "none";
});
questions.forEach((q, qIndex) => {
const questionSection = form.querySelector(
`.question[data-question-id="${q.id}"]`
);
const selected = form.querySelector(
`input[name="question-${qIndex}"]:checked`
);
if (!selected) {
unansweredCount++;
questionSection.classList.add("unanswered");
return;
}
const selectedIndex = Number(selected.value);
const optionsEls = questionSection.querySelectorAll(".option");
optionsEls.forEach((optEl, optIndex) => {
if (optIndex === q.correctIndex) {
optEl.classList.add("correct");
}
if (optIndex === selectedIndex && selectedIndex !== q.correctIndex) {
optEl.classList.add("incorrect");
}
});
const explanationBlock = questionSection.querySelector(
".question-explanation"
);
if (explanationBlock && q.explanation) {
explanationBlock.textContent = q.explanation;
explanationBlock.style.display = "block";
}
if (selectedIndex === q.correctIndex) {
correctCount++;
}
});
const total = questions.length;
let message = `Результат: ${correctCount} из ${total}.`;
if (unansweredCount > 0) {
message += ` Не отвечено: ${unansweredCount}.`;
}
if (correctCount === total && unansweredCount === 0) {
message += " Отлично! 🎉";
} else if (correctCount === 0) {
message += " Попробуй ещё раз 🙂";
}
resultBlock.textContent = message;
}
function resetQuiz() {
const form = document.getElementById("quiz-form");
const resultBlock = document.getElementById("result");
form.reset();
resultBlock.textContent = "";
form.querySelectorAll(".option").forEach(opt => {
opt.classList.remove("correct", "incorrect");
});
form.querySelectorAll(".question").forEach(q => {
q.classList.remove("unanswered");
const expl = q.querySelector(".question-explanation");
if (expl) {
expl.style.display = "none";
expl.textContent = "";
}
});
}
// Инициализация
document.addEventListener("DOMContentLoaded", () => {
renderQuiz();
document
.getElementById("check-btn")
.addEventListener("click", checkAnswers);
document
.getElementById("reset-btn")
.addEventListener("click", resetQuiz);
});
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f4f5fb;
color: #222;
}
.page {
min-height: 100vh;
max-width: 800px;
margin: 0 auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.header {
text-align: center;
margin-bottom: 8px;
}
.subtitle {
margin: 0;
color: #555;
font-size: 0.95rem;
}
.quiz {
display: flex;
flex-direction: column;
gap: 16px;
}
.question {
background: #fff;
border-radius: 12px;
padding: 16px 18px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
}
.question-title {
margin: 0 0 8px;
font-size: 1.02rem;
}
.options {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.option {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 6px;
border-radius: 8px;
cursor: pointer;
transition: background 0.15s ease;
}
.option:hover {
background: #f1f3ff;
}
.option input[type="radio"] {
cursor: pointer;
}
.option.correct {
background: #e6f7e9;
outline: 1px solid #3aa952;
}
.option.incorrect {
background: #ffe9e9;
outline: 1px solid #d93c3c;
}
.question.unanswered {
outline: 1px dashed #f0a500;
}
.question-explanation {
margin-top: 8px;
font-size: 0.85rem;
color: #555;
}
.actions {
display: flex;
gap: 8px;
margin-top: 8px;
}
button {
border: none;
border-radius: 999px;
padding: 10px 18px;
font-size: 0.95rem;
cursor: pointer;
background: #4b6bff;
color: #fff;
font-weight: 500;
transition: background 0.15s ease, transform 0.05s ease;
}
button:hover {
background: #3954d9;
}
button:active {
transform: scale(0.98);
}
button.secondary {
background: #e0e2f5;
color: #222;
}
button.secondary:hover {
background: #cdd1f0;
}
.result {
margin-top: 12px;
padding: 10px 14px;
border-radius: 10px;
font-size: 0.95rem;
background: #fff;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
}
.footer {
margin-top: auto;
text-align: center;
color: #888;
font-size: 0.8rem;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment