diff --git a/app.js b/app.js new file mode 100644 index 0000000..0d73a11 --- /dev/null +++ b/app.js @@ -0,0 +1,475 @@ +const state = { + allQuestions: [], + mode: null, + questionSet: [], + answers: [], + bookmarks: new Set(), + currentIndex: 0, + revealAnswers: false, + finished: false, + showImmediate: false +}; + +const elements = { + questionCount: document.getElementById('questionCount'), + menuView: document.getElementById('menuView'), + questionView: document.getElementById('questionView'), + resultView: document.getElementById('resultView'), + progressBar: document.getElementById('progressBar'), + modeLabel: document.getElementById('modeLabel'), + questionPosition: document.getElementById('questionPosition'), + originalIndex: document.getElementById('originalIndex'), + questionText: document.getElementById('questionText'), + questionImageContainer: document.getElementById('questionImageContainer'), + optionsList: document.getElementById('optionsList'), + feedbackPanel: document.getElementById('feedbackPanel'), + feedbackMessage: document.getElementById('feedbackMessage'), + explanation: document.getElementById('explanation'), + psInfo: document.getElementById('psInfo'), + prevQuestion: document.getElementById('prevQuestion'), + nextQuestion: document.getElementById('nextQuestion'), + finishTest: document.getElementById('finishTest'), + jumpForm: document.getElementById('jumpForm'), + jumpInput: document.getElementById('jumpInput'), + jumpTotal: document.getElementById('jumpTotal'), + statusToggle: document.getElementById('statusToggle'), + bookmarkToggle: document.getElementById('bookmarkToggle'), + themeToggle: document.getElementById('themeToggle'), + backToMenu: document.getElementById('backToMenu'), + statusPanel: document.getElementById('statusPanel'), + statusClose: document.getElementById('statusClose'), + statusList: document.getElementById('statusList'), + rangeModal: document.getElementById('rangeModal'), + rangeClose: document.getElementById('rangeClose'), + rangeCancel: document.getElementById('rangeCancel'), + rangeForm: document.getElementById('rangeForm'), + rangeStart: document.getElementById('rangeStart'), + rangeEnd: document.getElementById('rangeEnd'), + resultScore: document.getElementById('resultScore'), + resultCorrect: document.getElementById('resultCorrect'), + resultIncorrect: document.getElementById('resultIncorrect'), + resultUnanswered: document.getElementById('resultUnanswered'), + reviewButton: document.getElementById('reviewButton'), + resultBackToMenu: document.getElementById('resultBackToMenu') +}; + +const statusItemTemplate = document.getElementById('statusItemTemplate'); + +async function init() { + try { + const response = await fetch('question.json'); + if (!response.ok) throw new Error('無法載入題庫'); + const data = await response.json(); + state.allQuestions = data.map((question, index) => ({ + ...question, + originalIndex: index + })); + elements.questionCount.textContent = `題庫共 ${state.allQuestions.length} 題`; + } catch (error) { + elements.menuView.innerHTML = `

載入題庫發生錯誤:${error.message}

`; + } +} + +function setView(view) { + elements.menuView.classList.toggle('hidden', view !== 'menu'); + elements.questionView.classList.toggle('hidden', view !== 'question'); + elements.resultView.classList.toggle('hidden', view !== 'result'); + elements.backToMenu.classList.toggle('hidden', view === 'menu'); +} + +function resetState() { + state.mode = null; + state.questionSet = []; + state.answers = []; + state.bookmarks.clear(); + state.currentIndex = 0; + state.revealAnswers = false; + state.finished = false; + state.showImmediate = false; +} + +function startPracticeMode() { + if (!state.allQuestions.length) return; + resetState(); + state.mode = 'practice'; + state.questionSet = state.allQuestions.map((q) => ({ ...q })); + state.answers = Array(state.questionSet.length).fill(null); + state.showImmediate = true; + setupQuestionView(); +} + +function startExamMode(limitRange = null) { + if (!state.allQuestions.length) return; + resetState(); + if (limitRange) { + const [start, end] = limitRange; + const available = state.allQuestions.filter((q) => { + const index = q.originalIndex + 1; + return index >= start && index <= end; + }); + state.questionSet = sampleQuestions(available, 50); + } else { + state.questionSet = sampleQuestions(state.allQuestions, 50); + } + if (!state.questionSet.length) { + alert('選定範圍內沒有題目,請重新選擇。'); + return; + } + state.answers = Array(state.questionSet.length).fill(null); + state.mode = limitRange ? 'range' : 'exam'; + setupQuestionView(); +} + +function sampleQuestions(source, amount) { + const copy = [...source]; + const result = []; + const max = Math.min(amount, copy.length); + for (let i = copy.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [copy[i], copy[j]] = [copy[j], copy[i]]; + } + for (let k = 0; k < max; k++) { + result.push({ ...copy[k] }); + } + return result; +} + +function setupQuestionView() { + elements.modeLabel.textContent = getModeLabel(); + elements.jumpTotal.textContent = state.questionSet.length; + elements.jumpInput.setAttribute('max', state.questionSet.length); + updateQuestionDisplay(); + updateProgress(); + updateBookmarkButton(); + updateFinishButton(); + setView('question'); +} + +function getModeLabel() { + switch (state.mode) { + case 'practice': + return '自由參考模式'; + case 'range': + return '自訂範圍測驗'; + case 'exam': + return '模擬考試模式'; + default: + return ''; + } +} + +function updateQuestionDisplay() { + const question = state.questionSet[state.currentIndex]; + if (!question) return; + + elements.questionPosition.textContent = `第 ${state.currentIndex + 1} / ${state.questionSet.length} 題`; + elements.originalIndex.textContent = `原題號:${question.originalIndex + 1}`; + elements.questionText.textContent = question.question; + + elements.questionImageContainer.innerHTML = ''; + if (question.questionimage) { + const img = document.createElement('img'); + img.src = `image/${question.questionimage}`; + img.alt = '題目圖片'; + elements.questionImageContainer.appendChild(img); + } + + elements.optionsList.innerHTML = ''; + question.option.forEach((text, optionIndex) => { + const li = document.createElement('li'); + li.className = 'option'; + li.setAttribute('tabindex', '0'); + li.dataset.index = optionIndex; + const optionContent = `${text}${question.optionend ?? ''}`; + li.textContent = optionContent; + + const selected = state.answers[state.currentIndex]; + if (selected === optionIndex) { + li.classList.add('selected'); + } + + if (shouldRevealAnswers()) { + if (optionIndex === question.answer) { + li.classList.add('correct'); + } else if (selected === optionIndex && selected !== question.answer) { + li.classList.add('incorrect'); + } + } + + li.addEventListener('click', () => selectOption(optionIndex)); + li.addEventListener('keypress', (event) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + selectOption(optionIndex); + } + }); + elements.optionsList.appendChild(li); + }); + + updateFeedback(question); + updateNavigatorButtons(); + updateStatusPanel(); +} + +function selectOption(optionIndex) { + if (state.finished && !state.showImmediate) { + return; + } + state.answers[state.currentIndex] = optionIndex; + updateProgress(); + updateQuestionDisplay(); +} + +function shouldRevealAnswers(index = state.currentIndex) { + if (state.showImmediate) { + return state.answers[index] !== null; + } + return state.revealAnswers; +} + +function updateFeedback(question) { + const selected = state.answers[state.currentIndex]; + const show = shouldRevealAnswers(); + if (show && selected !== null) { + elements.feedbackPanel.classList.remove('hidden'); + const correct = selected === question.answer; + elements.feedbackMessage.textContent = correct ? '答對了!' : '答錯了。'; + elements.feedbackMessage.style.color = correct ? '#22c55e' : '#ef4444'; + elements.explanation.textContent = question.explain ? `解析:${question.explain}` : '此題沒有提供解析。'; + elements.psInfo.textContent = question.ps ? `備註:${question.ps}` : ''; + } else if (show && state.finished && selected === null) { + elements.feedbackPanel.classList.remove('hidden'); + elements.feedbackMessage.textContent = '此題尚未作答。'; + elements.feedbackMessage.style.color = '#f59e0b'; + elements.explanation.textContent = question.explain ? `解析:${question.explain}` : '此題沒有提供解析。'; + elements.psInfo.textContent = question.ps ? `備註:${question.ps}` : ''; + } else if (show) { + elements.feedbackPanel.classList.remove('hidden'); + elements.feedbackMessage.textContent = '請先作答以查看解析。'; + elements.feedbackMessage.style.color = '#f97316'; + elements.explanation.textContent = ''; + elements.psInfo.textContent = ''; + } else { + elements.feedbackPanel.classList.add('hidden'); + elements.feedbackMessage.textContent = ''; + elements.explanation.textContent = ''; + elements.psInfo.textContent = ''; + } +} + +function updateNavigatorButtons() { + elements.prevQuestion.disabled = state.currentIndex === 0; + elements.nextQuestion.disabled = state.currentIndex === state.questionSet.length - 1; +} + +function updateProgress() { + const answeredCount = state.answers.filter((ans) => ans !== null).length; + const total = state.questionSet.length || 1; + const progress = Math.round((answeredCount / total) * 100); + elements.progressBar.style.width = `${progress}%`; +} + +function goToQuestion(index) { + if (index < 0 || index >= state.questionSet.length) return; + state.currentIndex = index; + updateQuestionDisplay(); + updateBookmarkButton(); +} + +function updateBookmarkButton() { + if (state.bookmarks.has(state.currentIndex)) { + elements.bookmarkToggle.textContent = '移除書籤'; + } else { + elements.bookmarkToggle.textContent = '加入書籤'; + } +} + +function toggleBookmark() { + if (state.bookmarks.has(state.currentIndex)) { + state.bookmarks.delete(state.currentIndex); + } else { + state.bookmarks.add(state.currentIndex); + } + updateBookmarkButton(); + updateStatusPanel(); +} + +function updateStatusPanel() { + if (elements.statusPanel.classList.contains('hidden')) return; + renderStatusList(); +} + +function renderStatusList() { + elements.statusList.innerHTML = ''; + state.questionSet.forEach((question, index) => { + const item = statusItemTemplate.content.firstElementChild.cloneNode(true); + item.querySelector('.status-number').textContent = index + 1; + const icons = []; + if (state.answers[index] !== null) icons.push('✔'); + if (state.bookmarks.has(index)) icons.push('★'); + item.querySelector('.status-icons').textContent = icons.join(' '); + item.classList.add(state.answers[index] !== null ? 'answered' : 'unanswered'); + if (state.bookmarks.has(index)) item.classList.add('bookmarked'); + if (index === state.currentIndex) item.classList.add('current'); + item.addEventListener('click', () => { + goToQuestion(index); + closeStatusPanel(); + }); + elements.statusList.appendChild(item); + }); +} + +function openStatusPanel() { + renderStatusList(); + elements.statusPanel.classList.remove('hidden'); +} + +function closeStatusPanel() { + elements.statusPanel.classList.add('hidden'); +} + +function updateFinishButton() { + const shouldShow = state.mode === 'exam' || state.mode === 'range'; + elements.finishTest.classList.toggle('hidden', !shouldShow || state.finished); +} + +function finishTest() { + if (state.finished) return; + state.finished = true; + state.revealAnswers = true; + const { correct, incorrect, unanswered } = calculateResult(); + const score = Math.round((correct / state.questionSet.length) * 100); + elements.resultScore.textContent = `${score} 分`; + elements.resultCorrect.textContent = `${correct}`; + elements.resultIncorrect.textContent = `${incorrect}`; + elements.resultUnanswered.textContent = `${unanswered}`; + updateFinishButton(); + setView('result'); +} + +function calculateResult() { + let correct = 0; + let incorrect = 0; + let unanswered = 0; + state.questionSet.forEach((question, index) => { + const answer = state.answers[index]; + if (answer === null) { + unanswered += 1; + } else if (answer === question.answer) { + correct += 1; + } else { + incorrect += 1; + } + }); + return { correct, incorrect, unanswered }; +} + +function reviewQuestions() { + setView('question'); + updateQuestionDisplay(); + updateBookmarkButton(); + updateProgress(); +} + +function handleThemeToggle() { + const body = document.body; + const current = body.getAttribute('data-theme'); + const next = current === 'light' ? 'dark' : 'light'; + body.setAttribute('data-theme', next); + elements.themeToggle.textContent = next === 'light' ? '🌞 亮色模式' : '🌙 暗色模式'; +} + +function returnToMenu() { + resetState(); + closeStatusPanel(); + elements.rangeModal.classList.add('hidden'); + setView('menu'); +} + +function handleJump(event) { + event.preventDefault(); + const value = Number(elements.jumpInput.value); + if (!Number.isFinite(value)) return; + goToQuestion(value - 1); + elements.jumpInput.value = ''; +} + +document.getElementById('practiceMode').addEventListener('click', startPracticeMode); +document.getElementById('rangeMode').addEventListener('click', () => { + elements.rangeModal.classList.remove('hidden'); +}); +document.getElementById('examMode').addEventListener('click', () => startExamMode()); + +elements.prevQuestion.addEventListener('click', () => goToQuestion(state.currentIndex - 1)); +elements.nextQuestion.addEventListener('click', () => goToQuestion(state.currentIndex + 1)); +elements.finishTest.addEventListener('click', finishTest); + +elements.jumpForm.addEventListener('submit', handleJump); + +elements.statusToggle.addEventListener('click', openStatusPanel); +elements.statusClose.addEventListener('click', closeStatusPanel); + +elements.statusPanel.addEventListener('click', (event) => { + if (event.target === elements.statusPanel) { + closeStatusPanel(); + } +}); + +elements.bookmarkToggle.addEventListener('click', toggleBookmark); + +elements.themeToggle.addEventListener('click', handleThemeToggle); + +elements.backToMenu.addEventListener('click', () => { + if (state.mode === 'exam' || state.mode === 'range') { + const confirmed = confirm('離開將結束此次測驗,是否確定?'); + if (!confirmed) return; + } + returnToMenu(); +}); + +// Range modal controls +elements.rangeClose.addEventListener('click', () => elements.rangeModal.classList.add('hidden')); +elements.rangeCancel.addEventListener('click', () => elements.rangeModal.classList.add('hidden')); +elements.rangeModal.addEventListener('click', (event) => { + if (event.target === elements.rangeModal) { + elements.rangeModal.classList.add('hidden'); + } +}); + +elements.rangeForm.addEventListener('submit', (event) => { + event.preventDefault(); + const start = Number(elements.rangeStart.value); + const end = Number(elements.rangeEnd.value); + if (!Number.isFinite(start) || !Number.isFinite(end) || start <= 0 || end <= 0) { + alert('請輸入有效的題號範圍。'); + return; + } + if (start > end) { + alert('起始題號不可大於結束題號。'); + return; + } + elements.rangeModal.classList.add('hidden'); + startExamMode([start, end]); + elements.rangeStart.value = ''; + elements.rangeEnd.value = ''; +}); + +elements.reviewButton.addEventListener('click', reviewQuestions); + +elements.resultBackToMenu.addEventListener('click', returnToMenu); + +document.addEventListener('keydown', (event) => { + if (event.key === 'Escape') { + closeStatusPanel(); + elements.rangeModal.classList.add('hidden'); + } +}); + +window.addEventListener('beforeunload', (event) => { + if (!state.finished && (state.mode === 'exam' || state.mode === 'range')) { + event.preventDefault(); + event.returnValue = ''; + } +}); + +init(); diff --git a/index.html b/index.html new file mode 100644 index 0000000..ddee65d --- /dev/null +++ b/index.html @@ -0,0 +1,142 @@ + + + + + + 學科刷題系統 + + + + + + +
+
+

學科刷題系統

+ +
+
+ + +
+
+ +
+ + + + + +
+ + + + + + + + + + diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..657b3c5 --- /dev/null +++ b/styles.css @@ -0,0 +1,547 @@ +:root { + --background: #f5f7fb; + --card-background: #ffffff; + --surface: #e8ecf5; + --text-primary: #1f2937; + --text-secondary: #4b5563; + --accent: #2563eb; + --accent-hover: #1d4ed8; + --border: #d1d5db; + --shadow: 0 18px 40px rgba(15, 23, 42, 0.12); +} + +body[data-theme="dark"] { + --background: #0f172a; + --card-background: #1e293b; + --surface: #1e293b; + --text-primary: #f8fafc; + --text-secondary: #cbd5f5; + --accent: #60a5fa; + --accent-hover: #3b82f6; + --border: #334155; + --shadow: 0 20px 45px rgba(15, 23, 42, 0.4); +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + background: var(--background); + font-family: 'Noto Sans TC', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + color: var(--text-primary); + display: flex; + flex-direction: column; +} + +.app-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1.25rem clamp(1rem, 4vw, 2.5rem); + background: transparent; +} + +.header-left { + display: flex; + align-items: center; + gap: 1rem; +} + +.app-title { + margin: 0; + font-size: clamp(1.25rem, 3vw, 1.75rem); + font-weight: 700; +} + +.question-count { + font-size: 0.95rem; + color: var(--text-secondary); +} + +.header-actions { + display: flex; + align-items: center; + gap: 0.5rem; +} + +button { + font-family: inherit; + border: none; + border-radius: 999px; + cursor: pointer; + font-weight: 600; + transition: transform 0.15s ease, box-shadow 0.15s ease, background-color 0.2s ease; +} + +button:focus-visible { + outline: 3px solid var(--accent); + outline-offset: 2px; +} + +button.primary { + padding: 0.65rem 1.5rem; + background: var(--accent); + color: #fff; + box-shadow: 0 6px 16px rgba(37, 99, 235, 0.25); +} + +button.primary:hover { + background: var(--accent-hover); + transform: translateY(-1px); +} + +button.secondary { + padding: 0.55rem 1.25rem; + background: var(--surface); + color: var(--text-primary); + border: 1px solid var(--border); +} + +button.secondary:hover { + transform: translateY(-1px); + box-shadow: 0 4px 10px rgba(15, 23, 42, 0.15); +} + +button.icon-button { + background: transparent; + border: none; + color: var(--text-primary); + padding: 0.25rem; + font-size: 1.2rem; +} + +button.icon-button:hover { + color: var(--accent); +} + +.app-main { + flex: 1; + width: min(1100px, 95vw); + margin: 0 auto clamp(1rem, 3vw, 2.5rem); + display: flex; + justify-content: center; + align-items: flex-start; +} + +.card { + background: var(--card-background); + border-radius: 24px; + padding: clamp(1rem, 5vw, 2.5rem); + width: 100%; + box-shadow: var(--shadow); + transition: background-color 0.3s ease, color 0.3s ease; +} + +.hidden { + display: none !important; +} + +.menu-actions { + display: grid; + gap: 1rem; + margin: 1.5rem 0; +} + +.menu-subtitle { + color: var(--text-secondary); +} + +.menu-info ul { + padding-left: 1rem; + color: var(--text-secondary); + line-height: 1.7; +} + +.progress-wrapper { + width: 100%; + height: 8px; + background: var(--surface); + border-radius: 999px; + overflow: hidden; + margin-bottom: 1.5rem; +} + +.progress-bar { + height: 100%; + width: 0%; + background: linear-gradient(90deg, var(--accent), var(--accent-hover)); + transition: width 0.25s ease; +} + +.question-header { + display: flex; + justify-content: space-between; + gap: 1rem; + flex-wrap: wrap; + align-items: center; + margin-bottom: 1rem; +} + +.question-meta { + display: flex; + flex-wrap: wrap; + gap: 0.5rem 1rem; + align-items: center; +} + +.mode-label { + padding: 0.25rem 0.75rem; + border-radius: 999px; + background: rgba(37, 99, 235, 0.12); + color: var(--accent); + font-weight: 600; +} + +.question-position, +.question-original { + color: var(--text-secondary); + font-size: 0.95rem; +} + +.question-text { + margin-top: 0; + font-size: clamp(1.1rem, 3vw, 1.35rem); + line-height: 1.6; +} + +.question-image img { + max-width: min(100%, 480px); + width: 100%; + border-radius: 16px; + margin: 1rem 0; + box-shadow: 0 8px 24px rgba(15, 23, 42, 0.25); +} + +.options { + list-style: none; + margin: 0; + padding: 0; + display: grid; + gap: 0.75rem; +} + +.option { + padding: 0.85rem 1rem; + border-radius: 18px; + border: 1px solid var(--border); + background: var(--surface); + color: var(--text-primary); + cursor: pointer; + transition: border 0.2s ease, background-color 0.2s ease, transform 0.15s ease; +} + +.option:hover { + transform: translateY(-1px); + border-color: var(--accent); +} + +.option.selected { + border-color: var(--accent); + background: rgba(37, 99, 235, 0.16); +} + +.option.correct { + border-color: #22c55e; + background: rgba(34, 197, 94, 0.18); +} + +.option.incorrect { + border-color: #ef4444; + background: rgba(239, 68, 68, 0.18); +} + +.feedback { + margin-top: 1.5rem; + border-radius: 16px; + padding: 1rem 1.25rem; + border: 1px solid var(--border); + background: var(--surface); +} + +.feedback p { + margin: 0 0 0.75rem; + font-weight: 600; +} + +.explanation, +.ps { + color: var(--text-secondary); + line-height: 1.6; + white-space: pre-line; +} + +.question-footer { + margin-top: 2rem; + display: flex; + flex-wrap: wrap; + gap: 1rem; + align-items: center; + justify-content: space-between; +} + +.navigator { + display: flex; + gap: 0.75rem; +} + +.jump-form { + display: flex; + align-items: center; + gap: 0.5rem; + flex-wrap: wrap; + color: var(--text-secondary); +} + +.jump-form input { + width: 4rem; + padding: 0.4rem; + border-radius: 12px; + border: 1px solid var(--border); + background: var(--card-background); + color: var(--text-primary); +} + +.status-panel { + position: fixed; + inset: 0; + background: rgba(15, 23, 42, 0.45); + display: flex; + align-items: center; + justify-content: center; + padding: 1.5rem; + z-index: 20; +} + +.status-panel-content { + background: var(--card-background); + padding: 1.5rem; + border-radius: 20px; + width: min(90vw, 520px); + box-shadow: var(--shadow); + display: flex; + flex-direction: column; + gap: 1rem; +} + +.status-panel-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.status-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); + gap: 0.75rem; + max-height: 60vh; + overflow-y: auto; + padding-right: 0.25rem; +} + +.status-item { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.25rem; + padding: 0.75rem 0.5rem; + border-radius: 16px; + background: var(--surface); + border: 1px solid transparent; + color: var(--text-secondary); + font-weight: 600; +} + +.status-item.current { + box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.35); + border-color: var(--accent); + color: var(--text-primary); +} + +.status-item.answered { + border-color: rgba(34, 197, 94, 0.6); + color: var(--text-primary); +} + +.status-item.unanswered { + border-color: rgba(148, 163, 184, 0.6); +} + +.status-item.bookmarked { + box-shadow: 0 0 0 2px rgba(234, 179, 8, 0.3); +} + +.status-number { + font-size: 1rem; +} + +.status-icons { + font-size: 0.85rem; + color: var(--text-secondary); +} + +.status-legend { + display: flex; + gap: 1rem; + color: var(--text-secondary); + font-size: 0.9rem; +} + +.status-legend .legend::before { + content: ''; + display: inline-block; + width: 10px; + height: 10px; + border-radius: 50%; + margin-right: 0.35rem; +} + +.status-legend .answered::before { + background: #22c55e; +} + +.status-legend .unanswered::before { + background: #94a3b8; +} + +.status-legend .bookmarked::before { + background: #facc15; +} + +.modal { + position: fixed; + inset: 0; + background: rgba(15, 23, 42, 0.45); + display: flex; + align-items: center; + justify-content: center; + z-index: 30; + padding: 1.5rem; +} + +.modal-content { + width: min(92vw, 420px); + background: var(--card-background); + border-radius: 20px; + box-shadow: var(--shadow); + display: flex; + flex-direction: column; +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1.25rem 1.5rem 0.75rem; +} + +.modal-body { + display: flex; + flex-direction: column; + gap: 0.85rem; + padding: 0 1.5rem 1.5rem; +} + +.field { + display: flex; + flex-direction: column; + gap: 0.4rem; + color: var(--text-secondary); +} + +.field input { + padding: 0.6rem 0.75rem; + border-radius: 12px; + border: 1px solid var(--border); + background: var(--surface); + color: var(--text-primary); +} + +.modal-tip { + color: var(--text-secondary); + font-size: 0.9rem; + margin-top: 0.5rem; +} + +.modal-actions { + display: flex; + justify-content: flex-end; + gap: 0.75rem; +} + +.error { + color: #ef4444; + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.35); + padding: 1rem 1.25rem; + border-radius: 16px; + line-height: 1.6; + text-align: center; +} + +.result-summary { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: 1rem; + margin: 1.5rem 0; +} + +.result-number { + display: block; + font-size: clamp(1.8rem, 5vw, 2.6rem); + font-weight: 700; + color: var(--accent); +} + +.result-label { + color: var(--text-secondary); +} + +@media (max-width: 768px) { + .app-header { + flex-direction: column; + align-items: flex-start; + gap: 0.75rem; + } + + .header-actions { + align-self: stretch; + justify-content: space-between; + } + + .question-footer { + flex-direction: column; + align-items: stretch; + } + + .navigator { + justify-content: space-between; + } + + .jump-form { + width: 100%; + justify-content: space-between; + } +} + +@media (max-width: 480px) { + button.primary, + button.secondary { + width: 100%; + } + + .options { + gap: 0.6rem; + } + + .option { + font-size: 0.95rem; + } + + .status-list { + grid-template-columns: repeat(auto-fill, minmax(56px, 1fr)); + } +}