HTML 직접 보기
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>우리 반 퀴즈쇼</title>
<meta name="description" content="교실 전자칠판과 태블릿을 위한 화려하고 재미있는 우리 반 퀴즈쇼!">
<style>
button:focus-visible, input:focus-visible, textarea:focus-visible {
outline: 3px solid #facc15;
outline-offset: 3px;
}
:root {
--bg-deep: #0f172a;
--header-bg: #1e293b;
--text-yellow: #facc15;
--text-white: #ffffff;
--coral: #fb7185;
--mint: #34d399;
--blue: #60a5fa;
--lavender: #a78bfa;
--success: #22c55e;
--danger: #ef4444;
--panel-bg: rgba(30, 41, 59, 0.95);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Pretendard', 'Apple SD Gothic Neo', sans-serif;
-webkit-tap-highlight-color: transparent;
user-select: none;
}
body {
background-color: var(--bg-deep);
color: var(--text-white);
min-height: 100vh;
overflow-x: hidden; overflow-y: auto;
display: flex;
flex-direction: column;
}
/* Confetti Canvas */
#confetti {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1000;
}
/* Header */
header {
padding: 15px 25px;
background: var(--header-bg);
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 4px 10px rgba(0,0,0,0.3);
z-index: 10;
}
.logo-area {
display: flex;
align-items: center;
gap: 12px;
}
.cloud-logo {
position: relative;
width: 40px;
height: 25px;
background: var(--lavender);
border-radius: 50px;
}
.cloud-logo::before, .cloud-logo::after {
content: '';
position: absolute;
background: var(--lavender);
border-radius: 50%;
}
.cloud-logo::before { width: 20px; height: 20px; top: -10px; left: 6px; }
.cloud-logo::after { width: 15px; height: 15px; top: -6px; left: 20px; }
.logo-text {
font-weight: 900;
font-size: 1.4rem;
color: var(--lavender);
letter-spacing: -1px;
}
.tool-bar {
display: flex;
gap: 10px;
}
.tool-btn {
background: rgba(255,255,255,0.1);
border: 1px solid rgba(255,255,255,0.2);
color: white;
padding: 8px 15px;
border-radius: 50px;
cursor: pointer;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 5px;
transition: all 0.2s;
}
.tool-btn:hover { background: rgba(255,255,255,0.2); }
.tool-btn.active { background: var(--lavender); border-color: var(--lavender); }
/* Main Content */
main {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
position: relative;
z-index: 5;
}
.quiz-container {
width: 100%;
max-width: 1000px;
text-align: center;
}
.title-board {
margin-bottom: 30px;
animation: bounceIn 0.8s ease;
}
.quiz-title {
font-size: 4.5rem;
font-weight: 900;
color: var(--text-yellow);
text-shadow: 4px 4px 0px rgba(0,0,0,0.5), 0 0 20px rgba(250, 204, 21, 0.3);
margin-bottom: 10px;
letter-spacing: -2px;
}
.status-line {
display: flex;
justify-content: center;
gap: 30px;
font-size: 1.8rem;
font-weight: 700;
}
.status-badge {
background: rgba(0,0,0,0.3);
padding: 10px 25px;
border-radius: 100px;
border: 2px solid var(--text-yellow);
color: var(--text-yellow);
}
.question-card {
background: rgba(255,255,255,0.05);
padding: 40px;
border-radius: 40px;
border: 4px solid rgba(255,255,255,0.1);
margin-bottom: 40px;
backdrop-filter: blur(10px);
}
.question-text {
font-size: 3rem;
line-height: 1.2;
word-break: keep-all;
margin-bottom: 40px;
}
/* Options */
.options-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 25px;
}
.option-btn {
position: relative;
padding: 40px 20px;
font-size: 2.2rem;
font-weight: 900;
color: white;
border: none;
border-radius: 30px;
cursor: pointer;
transition: all 0.1s;
box-shadow: 0 12px 0 var(--shadow-color);
transform: translateY(0);
}
.option-btn:active:not(:disabled) {
transform: translateY(8px);
box-shadow: 0 4px 0 var(--shadow-color);
}
.option-btn.selected {
outline: 6px solid var(--text-yellow);
outline-offset: 4px;
}
.option-btn.selected::after {
content: '✓';
position: absolute;
top: 10px;
right: 15px;
font-size: 2rem;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.btn-coral { background: var(--coral); --shadow-color: #e11d48; }
.btn-mint { background: var(--mint); --shadow-color: #059669; }
.btn-blue { background: var(--blue); --shadow-color: #2563eb; }
.btn-lavender { background: var(--lavender); --shadow-color: #7c3aed; }
.option-btn:disabled {
cursor: default;
opacity: 0.6;
}
/* Actions */
.action-bar {
margin-top: 30px;
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.reveal-btn {
background: var(--text-yellow);
color: #000;
font-size: 2.5rem;
font-weight: 900;
padding: 20px 80px;
border-radius: 100px;
border: none;
cursor: pointer;
box-shadow: 0 8px 0 #ca8a04, 0 10px 25px rgba(250, 204, 21, 0.4);
transition: all 0.1s;
}
.reveal-btn:active {
transform: translateY(4px);
box-shadow: 0 4px 0 #ca8a04;
}
.reveal-btn:disabled {
background: #94a3b8;
box-shadow: 0 8px 0 #64748b;
cursor: not-allowed;
color: #475569;
}
/* Result Overlay */
.result-panel {
background: var(--panel-bg);
border: 4px solid var(--text-yellow);
padding: 40px;
border-radius: 40px;
display: none;
flex-direction: column;
align-items: center;
animation: popIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
max-width: 800px;
width: 100%;
}
.result-panel.show { display: flex; }
.result-title {
font-size: 4rem;
font-weight: 900;
margin-bottom: 20px;
}
.explanation {
font-size: 2rem;
color: #cbd5e1;
line-height: 1.4;
margin-bottom: 30px;
word-break: keep-all;
}
/* Final View */
.final-view {
display: none;
flex-direction: column;
align-items: center;
gap: 30px;
}
.final-view.show { display: flex; }
.final-score {
font-size: 8rem;
font-weight: 900;
color: var(--text-yellow);
text-shadow: 0 0 40px rgba(250, 204, 21, 0.5);
}
/* Editor Modal */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
z-index: 2000;
display: none;
justify-content: center;
align-items: center;
padding: 20px;
}
.modal-content {
background: var(--header-bg);
width: 100%;
max-width: 900px;
max-height: 90vh;
border-radius: 40px;
padding: 40px;
overflow-y: auto;
border: 4px solid var(--lavender);
}
.editor-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
}
.q-edit-card {
background: rgba(255,255,255,0.05);
padding: 25px;
border-radius: 20px;
margin-bottom: 20px;
border-left: 10px solid var(--lavender);
}
.input-group {
margin-bottom: 15px;
}
.input-group label {
display: block;
margin-bottom: 8px;
font-weight: 700;
color: var(--lavender);
}
.input-field {
width: 100%;
background: rgba(0,0,0,0.3);
border: 2px solid rgba(255,255,255,0.1);
color: white;
padding: 12px 15px;
border-radius: 12px;
font-size: 1.1rem;
}
.input-field:focus {
border-color: var(--lavender);
outline: none;
}
.error-msg {
color: var(--danger);
font-weight: 700;
margin-bottom: 15px;
padding: 10px;
background: rgba(239, 68, 68, 0.1);
border-radius: 10px;
display: none;
}
/* Footer */
footer {
padding: 15px;
text-align: right;
z-index: 10;
}
/* Animations */
@keyframes bounceIn {
0% { transform: scale(0.3); opacity: 0; }
50% { transform: scale(1.05); opacity: 1; }
70% { transform: scale(0.9); }
100% { transform: scale(1); }
}
@keyframes popIn {
0% { transform: scale(0.8); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
.sparkle {
animation: sparkleAnim 0.6s ease-in-out infinite;
}
@keyframes sparkleAnim {
0%, 100% { transform: scale(1); filter: brightness(1); }
50% { transform: scale(1.1); filter: brightness(1.5); }
}
/* Tablet & Mobile adjustment */
@media (max-width: 850px) {
.quiz-title { font-size: 3rem; }
.question-text { font-size: 2rem; }
.option-btn { font-size: 1.6rem; padding: 30px 15px; }
.reveal-btn { font-size: 2rem; padding: 15px 50px; }
.status-line { font-size: 1.3rem; }
}
@media (max-height: 700px) {
.quiz-title { font-size: 2.5rem; margin-bottom: 5px; }
.question-card { padding: 20px; margin-bottom: 15px; }
.question-text { font-size: 1.8rem; margin-bottom: 20px; }
.option-btn { padding: 20px 10px; font-size: 1.4rem; }
header { padding: 8px 15px; }
}
/* Scrollbar */
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--lavender); border-radius: 10px; }
body.reduce-motion *, body.reduce-motion *::before, body.reduce-motion *::after { animation: none !important; transition: none !important; }
.option-btn { color: #102038; min-height: 64px; overflow-wrap: anywhere; }
.option-btn:disabled { opacity: .9; }
.tool-btn { min-height: 44px; }
main { padding-bottom: 72px; }
@media (max-width: 1100px) {
.quiz-title { font-size: clamp(2rem, 5vw, 3rem); }
.question-text { font-size: clamp(1.6rem, 3.5vw, 2.2rem); margin-bottom: 24px; }
.question-card { padding: 24px; margin-bottom: 20px; }
.option-btn { padding: 22px 14px; font-size: 1.7rem; }
.title-board { margin-bottom: 22px; }
}
@media (max-width: 600px) {
header { flex-wrap: wrap; gap: 16px; padding: 18px; }
.status-line { gap: 12px; }
.status-badge { padding: 8px 16px; }
.options-grid { gap: 18px; }
.question-card { padding: 18px; }
.reveal-btn { max-width: 100%; padding: 15px 26px; }
}
</style>
</head>
<body>
<canvas id="confetti"></canvas>
<header>
<div class="logo-area">
<div class="cloud-logo"></div>
<div class="logo-text">에듀잇티</div>
</div>
<div class="tool-bar">
<button id="soundToggle" class="tool-btn" onclick="toggleSound()">🔇 소리 켜기</button>
<button id="motionToggle" class="tool-btn" onclick="toggleMotion()">✨ 움직임 줄이기</button>
<button id="fullscreenBtn" class="tool-btn" onclick="toggleFullscreen()">🖥️ 전체화면</button>
</div>
</header>
<main>
<div id="quizView" class="quiz-container">
<div class="title-board">
<h1 class="quiz-title">우리 반 퀴즈쇼</h1>
<div class="status-line">
<div class="status-badge">문제 <span id="qIdx">1</span> / <span id="qTotal">3</span></div>
<div class="status-badge" style="border-color: white; color: white;">점수: <span id="currentScore">0</span></div>
</div>
</div>
<div id="quizContent">
<div class="question-card">
<h2 id="questionText" class="question-text">질문을 불러오는 중...</h2>
<div id="selectionAlert" style="color: var(--danger); font-weight: 900; margin-bottom: 15px; visibility: hidden; font-size: 1.5rem;">보기를 먼저 선택해 주세요!</div>
<div id="optionsGrid" class="options-grid">
<!-- Buttons Injected -->
</div>
</div>
<div class="action-bar">
<button id="revealBtn" class="reveal-btn" onclick="handleReveal()">정답 공개</button>
<div id="resultPanel" class="result-panel" aria-live="polite">
<div id="resultTitle" class="result-title">정답입니다!</div>
<p id="explanationText" class="explanation">해설이 표시됩니다.</p>
<button id="nextBtn" class="reveal-btn" onclick="goToNext()">다음 문제</button>
</div>
</div>
</div>
<div id="finalView" class="final-view">
<h2 style="font-size: 4rem;">축하합니다!</h2>
<div class="final-score"><span id="finalScoreDisplay">0</span>점</div>
<button class="reveal-btn" onclick="restartQuiz()">다시 시작하기</button>
</div>
</div>
</main>
<footer>
<button class="tool-btn" onclick="openEditor()">⚙️ 문제 편집</button>
</footer>
<!-- Editor Modal -->
<div id="editorModal" class="modal">
<div class="modal-content">
<div class="editor-header">
<h2 style="font-size: 2.5rem; color: var(--text-yellow);">문제 편집기</h2>
<button class="tool-btn" onclick="closeEditor()">닫기</button>
</div>
<div id="editorError" class="error-msg">빈칸 없이 보기 4개와 정답 번호 1~4를 입력해주세요!</div>
<div id="editorList">
<!-- Questions List -->
</div>
<div style="display: flex; gap: 20px; justify-content: center; margin-top: 30px;">
<button class="reveal-btn" style="background: var(--lavender); box-shadow: 0 8px 0 #7c3aed; font-size: 1.8rem;" onclick="applyEdits()">변경사항 적용</button>
<button class="reveal-btn" style="background: var(--header-bg); border: 2px solid white; color: white; box-shadow: 0 8px 0 #000; font-size: 1.8rem;" onclick="closeEditor()">취소</button>
</div>
<p style="margin-top: 20px; text-align: center; color: var(--secondary);">* 새로고침하면 기본 문제로 돌아갑니다.</p>
</div>
</div>
<script>
// --- Audio System ---
const AudioCtx = window.AudioContext || window.webkitAudioContext;
let audioCtx = null;
let isSoundOn = false;
function playTone(freq, duration, type = 'sine', volume = 0.1) {
if (!isSoundOn || !audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
gain.gain.setValueAtTime(volume, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + duration);
}
function soundSelect() { playTone(660, 0.1, 'square', 0.05); }
function soundCorrect() {
playTone(523.25, 0.2, 'triangle', 0.1);
setTimeout(() => playTone(659.25, 0.2, 'triangle', 0.1), 100);
setTimeout(() => playTone(783.99, 0.4, 'triangle', 0.1), 200);
}
function soundIncorrect() {
playTone(220, 0.3, 'sawtooth', 0.1);
setTimeout(() => playTone(110, 0.5, 'sawtooth', 0.1), 100);
}
function soundDrumroll() {
if (!isSoundOn) return;
const duration = 1.5;
const bufferSize = audioCtx.sampleRate * duration;
const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1;
const noise = audioCtx.createBufferSource();
noise.buffer = buffer;
const gain = audioCtx.createGain();
gain.gain.setValueAtTime(0.01, audioCtx.currentTime);
gain.gain.linearRampToValueAtTime(0.1, audioCtx.currentTime + duration);
gain.gain.linearRampToValueAtTime(0.01, audioCtx.currentTime + duration + 0.1);
noise.connect(gain);
gain.connect(audioCtx.destination);
noise.start();
}
function soundFanfare() {
const notes = [523, 659, 784, 1046];
notes.forEach((f, i) => setTimeout(() => playTone(f, 0.4, 'square', 0.05), i * 150));
}
// --- Confetti System ---
const canvas = document.getElementById('confetti');
const ctx = canvas.getContext('2d');
let particles = [];
let isMotionOn = true;
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
class Particle {
constructor() {
this.x = Math.random() * canvas.width;
this.y = -10;
this.size = Math.random() * 10 + 5;
this.color = [`#fb7185`, `#34d399`, `#60a5fa`, `#facc15`][Math.floor(Math.random() * 4)];
this.speedX = Math.random() * 4 - 2;
this.speedY = Math.random() * 5 + 3;
this.rot = Math.random() * Math.PI * 2;
this.rotSpeed = Math.random() * 0.2 - 0.1;
}
update() {
this.x += this.speedX;
this.y += this.speedY;
this.rot += this.rotSpeed;
}
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rot);
ctx.fillStyle = this.color;
ctx.fillRect(-this.size/2, -this.size/2, this.size, this.size);
ctx.restore();
}
}
function spawnConfetti() {
if (!isMotionOn) return;
particles = [];
for (let i = 0; i < 100; i++) particles.push(new Particle());
animateConfetti();
}
function animateConfetti() {
if (particles.length === 0) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach((p, i) => {
p.update();
p.draw();
if (p.y > canvas.height) particles.splice(i, 1);
});
requestAnimationFrame(animateConfetti);
}
// --- Quiz Data & Logic ---
const defaultData = [
{
question: "정삼각형의 변은 몇 개일까요?",
options: ["2개", "3개", "4개", "5개"],
correct: 1,
explanation: "정삼각형도 변이 3개인 다각형입니다!"
},
{
question: "1/2과 크기가 같은 분수는?",
options: ["1/3", "2/4", "2/3", "3/4"],
correct: 1,
explanation: "분모와 분자에 같은 수를 곱하면 2/4가 돼요!"
},
{
question: "1m는 몇 cm일까요?",
options: ["10", "50", "100", "1000"],
correct: 2,
explanation: "1미터는 정확히 100센티미터입니다!"
}
];
let questions = JSON.parse(JSON.stringify(defaultData));
let currentIdx = 0;
let score = 0;
let selectedIdx = null;
let isRevealed = false;
let revealVersion = 0;
const colors = ['coral', 'mint', 'blue', 'lavender'];
function renderQuestion() {
revealVersion++;
const q = questions[currentIdx];
document.getElementById('qIdx').innerText = currentIdx + 1;
document.getElementById('qTotal').innerText = questions.length;
document.getElementById('currentScore').innerText = score;
document.getElementById('questionText').innerText = q.question;
document.getElementById('selectionAlert').style.visibility = 'hidden';
document.getElementById('resultPanel').classList.remove('show');
document.getElementById('quizContent').style.display = 'block';
document.getElementById('finalView').classList.remove('show');
document.getElementById('revealBtn').style.display = 'block';
document.getElementById('revealBtn').disabled = false;
const grid = document.getElementById('optionsGrid');
grid.innerHTML = '';
q.options.forEach((opt, i) => {
const btn = document.createElement('button');
btn.className = `option-btn btn-${colors[i % 4]}`;
btn.innerText = opt;
btn.onclick = () => selectOption(i, btn);
grid.appendChild(btn);
});
isRevealed = false;
selectedIdx = null;
}
function selectOption(idx, btn) {
if (isRevealed) return;
selectedIdx = idx;
soundSelect();
document.getElementById('selectionAlert').style.visibility = 'hidden';
document.querySelectorAll('.option-btn').forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
}
async function handleReveal() {
if (isRevealed) return;
if (selectedIdx === null) {
document.getElementById('selectionAlert').style.visibility = 'visible';
return;
}
const btn = document.getElementById('revealBtn');
btn.disabled = true;
isRevealed = true;
const version = revealVersion;
document.querySelectorAll('.option-btn').forEach(b => b.disabled = true);
soundDrumroll();
// Short delay for drama
await new Promise(r => setTimeout(r, 1500));
if (version !== revealVersion) return;
const q = questions[currentIdx];
const isCorrect = (selectedIdx === q.correct);
if (isCorrect) {
score += 100;
soundCorrect();
spawnConfetti();
} else {
soundIncorrect();
}
document.getElementById('currentScore').innerText = score;
btn.style.display = 'none';
const panel = document.getElementById('resultPanel');
const title = document.getElementById('resultTitle');
title.innerText = isCorrect ? "정답입니다! 🌟" : "아쉬워요! 💦";
title.className = isCorrect ? 'result-title sparkle' : 'result-title';
title.style.color = isCorrect ? 'var(--text-yellow)' : 'var(--danger)';
document.getElementById('explanationText').innerText = q.explanation;
panel.classList.add('show');
// Visual feedback on options
document.querySelectorAll('.option-btn').forEach((b, i) => {
b.disabled = true;
if (i === q.correct) {
b.style.border = '6px solid gold';
b.style.filter = 'brightness(1.2)';
} else if (i === selectedIdx && !isCorrect) {
b.style.opacity = '0.3';
}
});
document.getElementById('nextBtn').innerText = (currentIdx === questions.length - 1) ? "최종 결과" : "다음 문제";
}
function goToNext() {
if (currentIdx < questions.length - 1) {
currentIdx++;
renderQuestion();
} else {
showFinal();
}
}
function showFinal() {
document.getElementById('quizContent').style.display = 'none';
document.getElementById('finalView').classList.add('show');
document.getElementById('finalScoreDisplay').innerText = score;
soundFanfare();
spawnConfetti();
}
function restartQuiz() {
currentIdx = 0;
score = 0;
renderQuestion();
}
// --- UI Controls ---
function toggleSound() {
if (!audioCtx) audioCtx = new AudioCtx();
isSoundOn = !isSoundOn;
const btn = document.getElementById('soundToggle');
btn.innerText = isSoundOn ? "🔊 소리 끄기" : "🔇 소리 켜기";
btn.classList.toggle('active', isSoundOn);
if (isSoundOn && audioCtx.state === 'suspended') audioCtx.resume();
}
function toggleMotion() {
isMotionOn = !isMotionOn;
document.body.classList.toggle('reduce-motion', !isMotionOn);
if (!isMotionOn) { particles.length = 0; ctx.clearRect(0, 0, canvas.width, canvas.height); }
const btn = document.getElementById('motionToggle');
btn.innerText = isMotionOn ? "✨ 움직임 줄이기" : "🚫 움직임 켜기";
btn.classList.toggle('active', !isMotionOn);
}
function toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
document.getElementById('fullscreenBtn').innerText = "📴 화면 축소";
} else {
document.exitFullscreen();
document.getElementById('fullscreenBtn').innerText = "🖥️ 전체화면";
}
}
// --- Editor Modal ---
function openEditor() {
if (isRevealed && document.getElementById("revealBtn").style.display !== "none") return;
const escapeAttr = value => String(value).replaceAll('&', '&').replaceAll('"', '"').replaceAll('<', '<').replaceAll('>', '>');
const list = document.getElementById('editorList');
list.innerHTML = '';
questions.forEach((q, i) => {
const card = document.createElement('div');
card.className = 'q-edit-card';
card.innerHTML = `
<div class="input-group">
<label>문제 ${i+1} 질문</label>
<input type="text" class="input-field q-txt" value="${escapeAttr(q.question)}">
</div>
<div class="input-group">
<label>보기 (쉼표로 구분)</label>
<input type="text" class="input-field q-opts" value="${escapeAttr(q.options.join(', '))}">
</div>
<div style="display: grid; grid-template-columns: 1fr 2fr; gap: 15px;">
<div class="input-group">
<label>정답 번호 (1-4)</label>
<input type="number" class="input-field q-ans" value="${q.correct + 1}" min="1" max="4">
</div>
<div class="input-group">
<label>해설</label>
<input type="text" class="input-field q-exp" value="${escapeAttr(q.explanation)}">
</div>
</div>
`;
list.appendChild(card);
});
document.getElementById('editorError').style.display = 'none';
document.getElementById('editorModal').style.display = 'flex';
}
function closeEditor() {
document.getElementById('editorModal').style.display = 'none';
}
function applyEdits() {
const cards = document.querySelectorAll('.q-edit-card');
const newQs = [];
let isValid = true;
cards.forEach(card => {
const q = card.querySelector('.q-txt').value.trim();
const optsRaw = card.querySelector('.q-opts').value.split(',').map(s => s.trim());
const ans = Number(card.querySelector('.q-ans').value) - 1;
const exp = card.querySelector('.q-exp').value.trim();
if (!q || optsRaw.length !== 4 || optsRaw.some(v => !v) || !Number.isInteger(ans) || ans < 0 || ans > 3 || !exp) isValid = false;
newQs.push({
question: q,
options: optsRaw,
correct: ans,
explanation: exp
});
});
if (!isValid) {
document.getElementById('editorError').style.display = 'block';
return;
}
questions = newQs;
closeEditor();
restartQuiz();
}
// Init
renderQuestion();
</script>
</body>
</html>