The quiz questions are under: // --- Quiz Data (Story Bible Theme) ---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Code Syntax Quiz</title>
<!-- Tailwind CSS -->
<script src="<https://cdn.tailwindcss.com>"></script>
<!-- CodeMirror CSS -->
<link rel="stylesheet" href="<https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.css>">
<link rel="stylesheet" href="<https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/theme/material-ocean.min.css>">
<!-- CodeMirror Core JS -->
<script src="<https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.js>"></script>
<!-- CodeMirror Modes (Syntax Highlighting Languages) -->
<script src="<https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/mode/multiplex.min.js>"></script>
<script src="<https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/yaml/yaml.min.js>"></script>
<!-- Confetti for correct answers -->
<script src="<https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js>"></script>
<style>
body {
background-color: #f3f4f6;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
/* CodeMirror Customization to fit Tailwind design */
.CodeMirror {
height: 150px;
border-radius: 0.5rem;
font-family: 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', "Liberation Mono", "Courier New", monospace;
font-size: 16px;
padding: 0.5rem;
box-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
}
.CodeMirror-focused {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
/* Smooth fade transitions */
.fade-enter {
opacity: 0;
transform: translateY(10px);
}
.fade-enter-active {
opacity: 1;
transform: translateY(0);
transition: opacity 300ms, transform 300ms;
}
</style>
</head>
<body class="min-h-screen flex items-center justify-center p-4 text-gray-800">
<div class="max-w-xl w-full bg-white rounded-2xl shadow-xl overflow-hidden">
<!-- Header -->
<div class="bg-indigo-600 p-6 text-white text-center relative">
<h1 class="text-2xl font-bold tracking-tight">Syntax Master</h1>
<p class="text-indigo-200 mt-1">Test your coding syntax knowledge</p>
<div class="absolute top-4 right-4 text-sm font-semibold bg-indigo-800 px-3 py-1 rounded-full">
<span id="progress-text">1 / 5</span>
</div>
</div>
<!-- Quiz Container -->
<div class="p-6 md:p-8">
<!-- Question area -->
<div class="mb-6">
<h2 id="step-title" class="text-xl font-semibold text-gray-800 mb-2">Loading...</h2>
<p id="step-text" class="text-gray-700 mb-3"></p>
<p id="step-hint" class="text-sm text-gray-500">Mode: <span class="font-mono bg-gray-100 px-1 rounded">...</span></p>
</div>
<!-- Code Editor area -->
<div class="mb-6 relative">
<textarea id="code-editor"></textarea>
</div>
<!-- Action buttons & Feedback -->
<div class="flex flex-col sm:flex-row items-center justify-between gap-4">
<button id="submit-btn" class="w-full sm:w-auto px-6 py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors focus:ring-4 focus:ring-indigo-300">
Check Answer
</button>
<button id="next-btn" class="hidden w-full sm:w-auto px-6 py-2.5 bg-green-600 hover:bg-green-700 text-white font-medium rounded-lg transition-colors focus:ring-4 focus:ring-green-300 flex items-center justify-center gap-2">
<span id="next-btn-text">Next</span>
<svg xmlns="<http://www.w3.org/2000/svg>" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</button>
</div>
<!-- Feedback Message -->
<div id="feedback-container" class="hidden mt-6 p-4 rounded-lg flex items-start gap-3">
<div id="feedback-icon" class="mt-0.5"></div>
<div>
<h3 id="feedback-title" class="font-semibold"></h3>
<p id="feedback-desc" class="text-sm mt-1"></p>
</div>
</div>
</div>
<!-- Completion Screen -->
<div id="completion-screen" class="hidden p-10 text-center">
<div class="w-20 h-20 bg-green-100 text-green-600 rounded-full flex items-center justify-center mx-auto mb-4">
<svg xmlns="<http://www.w3.org/2000/svg>" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/></svg>
</div>
<h2 class="text-3xl font-bold text-gray-800 mb-2">Story Bible Complete!</h2>
<p class="text-gray-600 mb-6">You've successfully mastered the basics of YAML world-building.</p>
<button id="restart-btn" class="px-6 py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors">
Play Again
</button>
</div>
</div>
<script>
// --- Quiz Data (Story Bible Theme) ---
const steps = [
{
type: "example",
title: "Lesson 1: Character Basics",
text: "In a story bible, you often start with basic character profiles. YAML uses key-value pairs separated by a colon and a space.",
mode: "Example (Read-Only)",
code: "name: Kaelen\\nrole: Protagonist\\nage: 28\\nrace: Elf"
},
{
type: "challenge",
title: "Challenge 1: Create a Character",
text: "Create a character profile. Set the key 'name' to 'Lyra' and 'role' to 'Mage'.",
mode: "Challenge (Editable)",
initialCode: "",
validate: (code) => {
const lines = code.trim().split('\\n').map(l => l.trim());
if (!/:/.test(code)) return { isCorrect: false, message: "Remember to use a colon ':' to separate keys and values." };
if (!lines.some(l => /^name:\\s+Lyra['"]?$/.test(l))) return { isCorrect: false, message: "Make sure you have a line that says exactly 'name: Lyra'." };
if (!lines.some(l => /^role:\\s+Mage['"]?$/.test(l))) return { isCorrect: false, message: "Make sure you have a line that says exactly 'role: Mage'." };
return { isCorrect: true };
},
successMsg: "Perfect! You've started your character database."
},
{
type: "example",
title: "Lesson 2: Lists of Items",
text: "Characters have inventories or belong to factions. In YAML, arrays (lists) are created using hyphens on new lines.",
mode: "Example (Read-Only)",
code: "character: Kaelen\\ninventory:\\n - Iron Sword\\n - Health Potion\\n - Map of Eldoria"
},
{
type: "challenge",
title: "Challenge 2: Update the Inventory",
text: "Kaelen just found a 'Magic Ring'. Add it to his inventory list below. Keep the existing items!",
mode: "Challenge (Editable)",
initialCode: "character: Kaelen\\ninventory:\\n - Iron Sword\\n - Health Potion\\n",
validate: (code) => {
const trimmed = code.trim();
if (!/-\\s+Iron Sword/.test(trimmed)) return { isCorrect: false, message: "Don't delete the Iron Sword!" };
if (!/-\\s+Health Potion/.test(trimmed)) return { isCorrect: false, message: "Don't delete the Health Potion!" };
if (!/-\\s+Magic Ring/.test(trimmed)) return { isCorrect: false, message: "Add the '- Magic Ring' as a new list item." };
if (!/^ -\\s/m.test(trimmed)) return { isCorrect: false, message: "Make sure your list items are indented with spaces." };
return { isCorrect: true };
},
successMsg: "Great! Lists are super useful for tracking items, factions, or spells."
},
{
type: "example",
title: "Lesson 3: Nested Data (Locations)",
text: "Story bibles require deep lore. You can nest properties by indenting them (always use spaces, not tabs!).",
mode: "Example (Read-Only)",
code: "location: Eldoria\\ntype: Capital City\\nstats:\\n population: 50000\\n defense: High\\n magic_level: Medium"
},
{
type: "challenge",
title: "Challenge 3: Modify the Lore",
text: "A plague has struck Eldoria! Change the 'population' in the nested stats from 50000 to 25000.",
mode: "Challenge (Editable)",
initialCode: "location: Eldoria\\ntype: Capital City\\nstats:\\n population: 50000\\n defense: High\\n",
validate: (code) => {
if (!/population:\\s+25000/.test(code)) return { isCorrect: false, message: "You need to change the population value to 25000." };
if (!/^ population:/m.test(code)) return { isCorrect: false, message: "Make sure 'population' stays indented under 'stats:'." };
if (/50000/.test(code)) return { isCorrect: false, message: "Remove the old 50000 value." };
return { isCorrect: true };
},
successMsg: "Tragic, but correctly formatted! Nesting lets you organize complex world-building."
}
];
// --- Application State ---
let currentStepIndex = 0;
let editor;
// --- DOM Elements ---
const stepTitleEl = document.getElementById('step-title');
const stepTextEl = document.getElementById('step-text');
const stepHintEl = document.querySelector('#step-hint span');
const progressTextEl = document.getElementById('progress-text');
const submitBtn = document.getElementById('submit-btn');
const nextBtn = document.getElementById('next-btn');
const nextBtnText = document.getElementById('next-btn-text');
const feedbackContainer = document.getElementById('feedback-container');
const feedbackIcon = document.getElementById('feedback-icon');
const feedbackTitle = document.getElementById('feedback-title');
const feedbackDesc = document.getElementById('feedback-desc');
const quizContainer = document.querySelector('.p-6.md\\\\:p-8');
const completionScreen = document.getElementById('completion-screen');
const restartBtn = document.getElementById('restart-btn');
// --- SVGs for Feedback ---
const successIconSVG = `<svg class="text-green-600" xmlns="<http://www.w3.org/2000/svg>" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/></svg>`;
const errorIconSVG = `<svg class="text-red-600" xmlns="<http://www.w3.org/2000/svg>" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>`;
// --- Initialization ---
function initApp() {
// Initialize CodeMirror
editor = CodeMirror.fromTextArea(document.getElementById('code-editor'), {
lineNumbers: true, // Helpful for YAML
mode: 'yaml',
theme: 'material-ocean',
viewportMargin: Infinity,
lineWrapping: true,
});
// Handle Shift-Enter to submit (fixing Enter so it can make newlines!)
editor.setOption("extraKeys", {
"Shift-Enter": function(cm) {
if(!nextBtn.classList.contains('hidden')) {
nextStep();
} else if (steps[currentStepIndex].type === 'challenge') {
checkAnswer();
}
}
});
loadStep(0);
}
// --- Core Functions ---
function loadStep(index) {
currentStepIndex = index;
const step = steps[index];
// Update UI
stepTitleEl.textContent = step.title;
stepTextEl.textContent = step.text;
stepHintEl.textContent = step.mode;
progressTextEl.textContent = `${index + 1} / ${steps.length}`;
// Reset Editor
editor.setValue(step.type === 'example' ? step.code : (step.initialCode || ""));
editor.setOption('readOnly', step.type === 'example' ? 'nocursor' : false);
// Configure buttons based on step type
if (step.type === 'example') {
submitBtn.classList.add('hidden');
nextBtn.classList.remove('hidden');
nextBtnText.textContent = 'Start Challenge';
// Make Next button blue for Examples
nextBtn.classList.remove('bg-green-600', 'hover:bg-green-700', 'focus:ring-green-300');
nextBtn.classList.add('bg-indigo-600', 'hover:bg-indigo-700', 'focus:ring-indigo-300');
} else {
editor.focus();
// Move cursor to end of text for prefilled challenges
if (step.initialCode) {
editor.setCursor(editor.lineCount(), 0);
}
submitBtn.classList.remove('hidden');
nextBtn.classList.add('hidden');
nextBtnText.textContent = 'Next Lesson';
// Make Next button green for Challenge Success
nextBtn.classList.remove('bg-indigo-600', 'hover:bg-indigo-700', 'focus:ring-indigo-300');
nextBtn.classList.add('bg-green-600', 'hover:bg-green-700', 'focus:ring-green-300');
}
hideFeedback();
}
function checkAnswer() {
const step = steps[currentStepIndex];
if (step.type !== 'challenge') return;
const userAnswer = editor.getValue();
// Do not validate if empty
if (!userAnswer.trim()) {
showFeedback(false, "Input Required", "Please type some code before checking.");
return;
}
const result = step.validate(userAnswer);
if (result.isCorrect) {
showFeedback(true, "Correct!", step.successMsg);
submitBtn.classList.add('hidden');
nextBtn.classList.remove('hidden');
nextBtn.focus();
triggerConfetti();
} else {
showFeedback(false, "Try Again", result.message);
}
}
function nextStep() {
if (currentStepIndex + 1 < steps.length) {
loadStep(currentStepIndex + 1);
} else {
showCompletion();
}
}
function showCompletion() {
quizContainer.classList.add('hidden');
completionScreen.classList.remove('hidden');
triggerConfetti(true);
}
function restartQuiz() {
completionScreen.classList.add('hidden');
quizContainer.classList.remove('hidden');
loadStep(0);
}
// --- UI Helpers ---
function showFeedback(isSuccess, title, desc) {
feedbackContainer.classList.remove('hidden', 'bg-green-50', 'bg-red-50', 'text-green-800', 'text-red-800');
if (isSuccess) {
feedbackContainer.classList.add('bg-green-50', 'text-green-800');
feedbackIcon.innerHTML = successIconSVG;
} else {
feedbackContainer.classList.add('bg-red-50', 'text-red-800');
feedbackIcon.innerHTML = errorIconSVG;
}
feedbackTitle.textContent = title;
feedbackDesc.textContent = desc;
// Simple animation
feedbackContainer.classList.remove('fade-enter-active');
feedbackContainer.classList.add('fade-enter');
requestAnimationFrame(() => {
feedbackContainer.classList.add('fade-enter-active');
feedbackContainer.classList.remove('fade-enter');
});
}
function hideFeedback() {
feedbackContainer.classList.add('hidden');
}
function triggerConfetti(massive = false) {
if (massive) {
var duration = 3000;
var end = Date.now() + duration;
(function frame() {
confetti({
particleCount: 5,
angle: 60,
spread: 55,
origin: { x: 0 },
colors: ['#4f46e5', '#10b981', '#3b82f6']
});
confetti({
particleCount: 5,
angle: 120,
spread: 55,
origin: { x: 1 },
colors: ['#4f46e5', '#10b981', '#3b82f6']
});
if (Date.now() < end) {
requestAnimationFrame(frame);
}
}());
} else {
confetti({
particleCount: 100,
spread: 70,
origin: { y: 0.6 },
colors: ['#4f46e5', '#10b981', '#3b82f6']
});
}
}
// --- Event Listeners ---
submitBtn.addEventListener('click', checkAnswer);
nextBtn.addEventListener('click', nextStep);
restartBtn.addEventListener('click', restartQuiz);
// Bootstrap App
window.addEventListener('DOMContentLoaded', initApp);
</script>
</body>
</html>