CODE:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dynamic Quiz (100 Questions)</title>
<style>
/* General Body Styles */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: #f4f4f9;
color: #333;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 20px;
}
/* Main Quiz Container */
#quiz-container {
background-color: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 700px; /* Increased width to accommodate palette */
padding: 2rem;
box-sizing: border-box;
}
/* Question Header */
#question-header {
display: flex;
justify-content: space-between;
align-items: baseline;
border-bottom: 1px solid #e0e0e0;
padding-bottom: 1rem;
margin-bottom: 1.5rem;
}
#question-text {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
}
#question-counter {
font-size: 0.9rem;
color: #888;
white-space: nowrap;
}
/* Options Container & Styling */
#options-container {
display: flex;
flex-direction: column;
gap: 10px;
}
.option-label {
display: flex;
align-items: center;
width: 100%;
padding: 10px;
border: 1px solid #d3d3d3;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.2s ease, border-color 0.2s ease;
box-sizing: border-box;
}
.option-label:hover {
background-color: #f0f8ff;
border-color: #89cff0;
}
/* Hiding the actual radio button */
.option-input { display: none; }
/* Custom radio button circle */
.option-label::before {
content: '';
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid #ccc;
margin-right: 15px;
background-color: #fff;
transition: border-color 0.2s ease, background-color 0.2s ease;
flex-shrink: 0;
}
/* Style for the selected state (blue inner circle) */
.option-input:checked + .option-label::before {
background-color: #2196f3;
border-color: #2196f3;
background-clip: content-box;
padding: 3px;
}
/* Feedback Styling */
.option-label.correct { background-color: #e8f5e9; border-color: #4caf50; }
.option-label.incorrect { background-color: #ffebee; border-color: #f44336; }
.option-label.correct::after, .option-label.incorrect::after { font-size: 1.5rem; margin-left: auto; }
.option-label.correct::after { content: '✔'; color: #4caf50; }
.option-label.incorrect::after { content: '✖'; color: #f44336; }
#options-container.answered .option-label { pointer-events: none; cursor: default; }
/* Feedback and Explanation Text */
#feedback-container { margin-top: 1.5rem; padding: 1rem; border-radius: 8px; min-height: 50px; }
#feedback-text { font-weight: bold; font-size: 1.1rem; margin: 0 0 5px 0; }
#feedback-text.correct { color: #4caf50; }
#feedback-text.incorrect { color: #f44336; }
#explanation-text { color: #555; margin: 0; }
/* Navigation Buttons */
#navigation-container {
display: flex;
justify-content: space-between;
margin-top: 1.5rem;
border-top: 1px solid #e0e0e0;
padding-top: 1rem;
}
.nav-btn {
background-color: #2196f3;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.2s ease, opacity 0.2s ease;
}
.nav-btn:hover { background-color: #1976d2; }
.nav-btn:disabled { background-color: #a0a0a0; cursor: not-allowed; opacity: 0.7; }
/* Final Score Screen */
#final-score-container { text-align: center; }
#final-score-container h2 { font-size: 2rem; color: #333; }
#final-score-container p { font-size: 1.2rem; color: #555; }
/* --- NEW STYLES for Question Palette --- */
#question-palette-container {
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid #e0e0e0;
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
}
.palette-btn {
width: 35px;
height: 35px;
border-radius: 50%; /* Makes it a circle */
border: 1px solid #ccc;
background-color: #f0f0f0; /* Default gray for not visited */
color: #333;
font-size: 0.9rem;
font-weight: bold;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s, border-color 0.2s;
}
.palette-btn:hover {
border-color: #333;
}
.palette-btn.correct {
background-color: #4caf50; /* Green */
border-color: #388e3c;
color: white;
}
.palette-btn.incorrect {
background-color: #f44336; /* Red */
border-color: #d32f2f;
color: white;
}
.palette-btn.current {
border: 3px solid #2196f3; /* Blue border for current */
box-shadow: 0 0 5px rgba(33, 150, 243, 0.5);
}
</style>
</head>
<body>
<div id="quiz-container">
<!-- Question content -->
<div id="question-header">
<h2 id="question-text"></h2>
<p id="question-counter"></p>
</div>
<div id="options-container"></div>
<div id="feedback-container">
<p id="feedback-text"></p>
<p id="explanation-text"></p>
</div>
<div id="navigation-container">
<button id="prev-btn" class="nav-btn">Previous</button>
<button id="next-btn" class="nav-btn">Next</button>
</div>
<!-- Palette will be inserted here by JS -->
<div id="question-palette-container"></div>
</div>
<script>
// --- QUIZ DATA ---
function generateDummyData(count) {
const data = [];
for (let i = 0; i < count; i++) {
const correctIndex = i % 4;
const options = [];
for (let j = 0; j < 4; j++) {
options.push({ value: `Option ${j + 1}`, right: j === correctIndex });
}
data.push({
question: `Question ${i + 1}`,
explanation: `This is the explanation for Question ${i + 1}. The correct answer was Option ${correctIndex + 1}.`,
options: options
});
}
return data;
}
const quizData = generateDummyData(100);
// --- DOM ELEMENT REFERENCES ---
const quizContainer = document.getElementById('quiz-container');
const questionTextEl = document.getElementById('question-text');
const questionCounterEl = document.getElementById('question-counter');
const optionsContainerEl = document.getElementById('options-container');
const feedbackTextEl = document.getElementById('feedback-text');
const explanationTextEl = document.getElementById('explanation-text');
const prevBtn = document.getElementById('prev-btn');
const nextBtn = document.getElementById('next-btn');
const paletteContainerEl = document.getElementById('question-palette-container'); // New reference
// --- QUIZ STATE ---
let currentQuestionIndex = 0;
const userAnswers = new Array(quizData.length).fill(null);
// --- FUNCTIONS ---
/**
* Loads and displays a question and updates the palette.
*/
function loadQuestion() {
clearFeedback();
optionsContainerEl.innerHTML = '';
optionsContainerEl.classList.remove('answered');
const currentQuestion = quizData[currentQuestionIndex];
questionTextEl.textContent = currentQuestion.question;
questionCounterEl.textContent = `Question ${currentQuestionIndex + 1} of ${quizData.length}`;
currentQuestion.options.forEach((option, index) => {
const optionId = `option-${currentQuestionIndex}-${index}`;
const optionWrapper = document.createElement('div');
optionWrapper.innerHTML = `
<input type="radio" name="option-${currentQuestionIndex}" id="${optionId}" class="option-input" value="${index}">
<label for="${optionId}" class="option-label" data-index="${index}">${option.value}</label>
`;
optionsContainerEl.appendChild(optionWrapper);
const label = optionWrapper.querySelector('label');
label.addEventListener('click', handleOptionSelect);
});
if (userAnswers[currentQuestionIndex] !== null) {
restoreAnswerState();
}
updateNavigationButtons();
updateQuestionPalette(); // Update palette on every question load
}
/**
* Handles the user clicking on an option.
*/
function handleOptionSelect(e) {
if (optionsContainerEl.classList.contains('answered')) return;
optionsContainerEl.classList.add('answered');
const selectedLabel = e.target;
const selectedIndex = parseInt(selectedLabel.dataset.index);
const selectedInput = document.getElementById(`option-${currentQuestionIndex}-${selectedIndex}`);
selectedInput.checked = true;
const currentQuestion = quizData[currentQuestionIndex];
const correctIndex = currentQuestion.options.findIndex(opt => opt.right);
const isCorrect = selectedIndex === correctIndex;
userAnswers[currentQuestionIndex] = { selectedIndex, isCorrect };
showFeedback(selectedIndex, correctIndex, isCorrect);
updateQuestionPalette(); // Update palette after an answer
}
/**
* Displays visual feedback (correct/incorrect) and the explanation.
*/
function showFeedback(selectedIndex, correctIndex, isCorrect) {
const labels = optionsContainerEl.querySelectorAll('.option-label');
labels[correctIndex].classList.add('correct');
if (!isCorrect) {
labels[selectedIndex].classList.add('incorrect');
feedbackTextEl.textContent = "Incorrect!";
feedbackTextEl.className = 'incorrect';
} else {
feedbackTextEl.textContent = "Correct!";
feedbackTextEl.className = 'correct';
}
explanationTextEl.textContent = quizData[currentQuestionIndex].explanation || '';
}
function restoreAnswerState() {
optionsContainerEl.classList.add('answered');
const answer = userAnswers[currentQuestionIndex];
if (!answer) return;
const selectedInput = document.getElementById(`option-${currentQuestionIndex}-${answer.selectedIndex}`);
if (selectedInput) selectedInput.checked = true;
const correctIndex = quizData[currentQuestionIndex].options.findIndex(opt => opt.right);
showFeedback(answer.selectedIndex, correctIndex, answer.isCorrect);
}
function clearFeedback() {
feedbackTextEl.textContent = '';
explanationTextEl.textContent = '';
feedbackTextEl.className = '';
}
function updateNavigationButtons() {
prevBtn.disabled = currentQuestionIndex === 0;
nextBtn.disabled = userAnswers[currentQuestionIndex] === null && currentQuestionIndex === quizData.length - 1;
if (currentQuestionIndex === quizData.length - 1) {
nextBtn.textContent = 'Finish Quiz';
} else {
nextBtn.textContent = 'Next';
}
}
/**
* Displays the final score to the user.
*/
function showFinalScore() {
const correctAnswers = userAnswers.filter(answer => answer && answer.isCorrect).length;
const totalQuestions = quizData.length;
// Re-use the existing structure to show the final score
const mainContent = document.querySelector('#quiz-container > div:not(#question-palette-container)');
quizContainer.innerHTML = `
<div id="final-score-container">
<h2>Quiz Completed!</h2>
<p>Your Score: ${correctAnswers} out of ${totalQuestions}</p>
<button class="nav-btn" onclick="location.reload()">Try Again</button>
</div>
`;
// Append the final state of the palette
quizContainer.appendChild(paletteContainerEl);
// Disable buttons in the palette on the final screen
paletteContainerEl.querySelectorAll('.palette-btn').forEach(btn => btn.disabled = true);
}
// --- NEW PALETTE FUNCTIONS ---
/**
* Creates the question number palette buttons once on startup.
*/
function createQuestionPalette() {
quizData.forEach((_, index) => {
const button = document.createElement('button');
button.className = 'palette-btn';
button.textContent = index + 1;
button.dataset.index = index;
button.addEventListener('click', () => jumpToQuestion(index));
paletteContainerEl.appendChild(button);
});
}
/**
* Updates the colors and state of the palette buttons.
*/
function updateQuestionPalette() {
const buttons = paletteContainerEl.querySelectorAll('.palette-btn');
buttons.forEach((button, index) => {
// Reset classes
button.classList.remove('correct', 'incorrect', 'current');
const answer = userAnswers[index];
if (answer) {
button.classList.add(answer.isCorrect ? 'correct' : 'incorrect');
}
if (index === currentQuestionIndex) {
button.classList.add('current');
}
});
}
/**
* Navigates directly to a specific question index.
* @param {number} index - The index of the question to jump to.
*/
function jumpToQuestion(index) {
currentQuestionIndex = index;
loadQuestion();
}
// --- EVENT LISTENERS ---
nextBtn.addEventListener('click', () => {
if (currentQuestionIndex < quizData.length - 1) {
currentQuestionIndex++;
loadQuestion();
} else {
showFinalScore();
}
});
prevBtn.addEventListener('click', () => {
if (currentQuestionIndex > 0) {
currentQuestionIndex--;
loadQuestion();
}
});
// --- INITIALIZATION ---
document.addEventListener('DOMContentLoaded', () => {
createQuestionPalette();
loadQuestion();
});
</script>
</body>
</html>
