Nihongo Challenge N3 Today

// オプションイベントのバインド (回答ロックされていなければ) if (!answerLocked) const optionDivs = document.querySelectorAll('.option-btn'); optionDivs.forEach(btn => btn.addEventListener('click', (e) => if (answerLocked) return; const idxAttr = btn.getAttribute('data-opt-index'); if (idxAttr !== null) const idxNum = parseInt(idxAttr, 10); if (!isNaN(idxNum)) evaluateAndLock(idxNum, q.correct, q.explanation); ); );

@media (max-width: 550px) .quiz-main padding: 1.5rem; .question-text font-size: 1.3rem; .option-btn padding: 0.8rem 1rem; .header h1 font-size: 1.5rem; </style> </head> <body> <div class="challenge-container" id="quizApp"> <div class="header"> <h1> <span>📘 N3</span> <span>にほんごチャレンジ</span> </h1> <div class="sub">日本語能力試験N3レベル · 文法 & 語彙</div> </div> <div class="stats-panel"> <div class="score-box">🎯 スコア: <span id="scoreValue">0</span></div> <div class="question-counter">📋 問題: <span id="currentQNumber">1</span> / <span id="totalQNumber">0</span></div> </div> <div id="dynamicContent"> <!-- dynamic content injected via js --> </div> <footer>✨ N3チャレンジ — 正解を選んで日本語力を磨こう!</footer> </div>

.question-text background: #fef3da; padding: 1.2rem; border-radius: 2rem; font-size: 1.6rem; font-weight: 600; text-align: center; margin-bottom: 2rem; box-shadow: inset 0 1px 4px #ede0c2, 0 4px 8px rgba(0,0,0,0.05); word-break: break-word; color: #2c221b; line-height: 1.4;

/* main quiz area */ .quiz-main padding: 2rem 2rem 1.8rem; nihongo challenge n3

const optionsHtml = q.options.map((opt, idx) => let additionalClass = ""; let prefixLetter = String.fromCharCode(65 + idx); // A, B, C, D // スタイル判定: // 1) 正解の選択肢は緑ハイライト (正解がどこか示す) // 2) もし間違った選択肢を選んだ場合、その選択肢は赤背景 let isCorrectOption = (idx === correctIdx); let isSelectedWrongOption = (selectedIdx === idx && idx !== correctIdx); let isSelectedCorrect = (selectedIdx === idx && idx === correctIdx);

return ` <div class="option-btn $additionalClass $answerLocked ? 'disabled-opt' : ''" data-opt-index="$idx"> <div class="option-prefix">$prefixLetter</div> <div>$escapeHtml(opt)</div> </div> `; ).join('');

const optionsHtml = q.options.map((opt, idx) => const prefixLetter = String.fromCharCode(65 + idx); return ` <div class="option-btn" data-opt-index="$idx"> <div class="option-prefix">$prefixLetter</div> <div>$escapeHtml(opt)</div> </div> `; ).join(''); if (answerLocked) return

const html = ` <div class="quiz-main"> <div class="question-text">$escapeHtml(q.text)</div> <div class="options-area" id="optionsArea"> $optionsHtml </div> <div class="feedback-area" id="feedbackMsg">$feedbackMsg</div> <button class="next-btn" id="nextButton" disabled>次の問題 ➡</button> <button class="restart-btn" id="restartButton">🔄 チャレンジをやり直す</button> </div> `;

// 通常レンダリング (初期表示、回答前) function renderCurrentQuestion() if (currentIndex >= currentQuestions.length) // クイズ終了: 結果画面を表示 showResultScreen(); return;

.header h1 font-size: 2rem; letter-spacing: 2px; font-weight: 700; display: flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap; const idxAttr = btn.getAttribute('data-opt-index')

.restart-btn background: #e6d5b3; color: #5e3a22; border: none; margin-top: 1rem; padding: 0.7rem; border-radius: 40px; font-weight: 600; width: 100%; cursor: pointer; font-family: inherit; transition: 0.1s;

currentQuestionObj = currentQuestions[currentIndex]; answerLocked = false; selectedOptionIndex = null; const q = currentQuestionObj;

footer font-size: 0.7rem; text-align: center; padding: 1rem; background: #f9f3e2; color: #8b765a;

const resultHtml = ` <div class="quiz-main result-screen"> <div class="badge">🏆 クイズ完了 🏆</div> <div class="result-score">$userScore / $totalQuestions</div> <div style="font-size:1.2rem; margin-bottom: 1rem;">$percentage% 正答率</div> <p style="background:#f2ebd0; padding: 0.8rem; border-radius: 2rem;">$message</p> <button class="restart-btn" id="restartResultBtn" style="margin-top: 2rem; background:#b13e3e; color:white;">🔁 もう一度 N3チャレンジ</button> </div> `; dynamicContainer.innerHTML = resultHtml; const restartResult = document.getElementById('restartResultBtn'); if (restartResult) restartResult.addEventListener('click', () => initGame(); ); // スコアプログレス表示も維持(statsパネルは生きてる) updateProgressUI(); // 最終的に currentIndex が total と同じだが見た目維持