From b81af609482abf253ad8b4693405620b9c0e2d47 Mon Sep 17 00:00:00 2001 From: apple246680 <97295135+apple246680@users.noreply.github.com> Date: Fri, 31 Oct 2025 20:36:38 +0800 Subject: [PATCH] feat: build responsive quiz interface --- app.js | 561 +++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 42 ++++ styles.css | 579 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1182 insertions(+) create mode 100644 app.js create mode 100644 index.html create mode 100644 styles.css diff --git a/app.js b/app.js new file mode 100644 index 0000000..edbb407 --- /dev/null +++ b/app.js @@ -0,0 +1,561 @@ +const app = document.getElementById('app'); +const progressContainer = document.getElementById('progressContainer'); +const progressBar = document.getElementById('progressBar'); +const statusPanel = document.getElementById('statusPanel'); +const statusList = document.getElementById('statusList'); +const statusPanelClose = document.getElementById('statusPanelClose'); +const resultOverlay = document.getElementById('resultOverlay'); +const themeToggle = document.getElementById('themeToggle'); + +let questions = []; + +const state = { + mode: null, + questionIndices: [], + currentQuestion: 0, + answers: {}, + revealed: {}, + bookmarks: new Set(), + completed: false, + customRange: null, + score: null +}; + +async function init() { + setupTheme(); + try { + const response = await fetch('question.json'); + questions = await response.json(); + renderMenu(); + } catch (error) { + renderError(error); + } +} + +function setupTheme() { + const saved = localStorage.getItem('quiz-theme'); + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + const root = document.documentElement; + + const applyTheme = (mode) => { + root.classList.toggle('dark', mode === 'dark'); + themeToggle.textContent = mode === 'dark' ? '☀️' : '🌙'; + localStorage.setItem('quiz-theme', mode); + }; + + const initial = saved || (prefersDark ? 'dark' : 'light'); + applyTheme(initial); + + themeToggle.addEventListener('click', () => { + const next = root.classList.contains('dark') ? 'light' : 'dark'; + applyTheme(next); + }); +} + +function resetState() { + state.mode = null; + state.questionIndices = []; + state.currentQuestion = 0; + state.answers = {}; + state.revealed = {}; + state.bookmarks = new Set(); + state.completed = false; + state.customRange = null; + state.score = null; +} + +function renderMenu() { + resetState(); + progressContainer.hidden = true; + progressBar.style.width = '0%'; + progressBar.setAttribute('aria-valuenow', '0'); + progressBar.setAttribute('aria-valuetext', '尚未開始作答'); + app.innerHTML = ` +
+
+

選擇練習模式

+

題庫共 ${questions.length} 題,挑選適合自己的練習方式開始刷題吧!

+
+ +
+ `; + + app.querySelectorAll('button[data-mode]').forEach((btn) => { + btn.addEventListener('click', () => { + const mode = btn.dataset.mode; + if (mode === 'practice') { + startPracticeMode(); + } else if (mode === 'exam') { + startExamMode(); + } + }); + }); + + const customForm = document.getElementById('customForm'); + customForm.addEventListener('submit', (event) => { + event.preventDefault(); + const start = Number(document.getElementById('rangeStart').value); + const end = Number(document.getElementById('rangeEnd').value); + if (Number.isNaN(start) || Number.isNaN(end)) { + alert('請輸入有效的題號範圍'); + return; + } + if (start < 1 || end < 1 || start > questions.length || end > questions.length || start > end) { + alert('題號範圍無效,請重新輸入。'); + return; + } + startCustomMode(start, end); + }); +} + +function prepareSession(mode, indices, customRange = null) { + state.mode = mode; + state.questionIndices = indices; + state.currentQuestion = 0; + state.answers = {}; + state.revealed = {}; + state.bookmarks = new Set(); + state.completed = false; + state.customRange = customRange; + state.score = null; +} + +function startPracticeMode() { + const indices = questions.map((_, index) => index); + prepareSession('practice', indices); + renderQuestionView(); +} + +function startCustomMode(start, end) { + const startIndex = start - 1; + const endIndex = end - 1; + const range = []; + for (let i = startIndex; i <= endIndex; i += 1) { + range.push(i); + } + const indices = pickRandom(range, Math.min(50, range.length)); + prepareSession('custom', indices, { start, end }); + renderQuestionView(); +} + +function startExamMode() { + const total = Math.min(50, questions.length); + const pool = questions.map((_, index) => index); + const indices = pickRandom(pool, total); + prepareSession('exam', indices); + renderQuestionView(); +} + +function pickRandom(list, count) { + const cloned = [...list]; + for (let i = cloned.length - 1; i > 0; i -= 1) { + const j = Math.floor(Math.random() * (i + 1)); + [cloned[i], cloned[j]] = [cloned[j], cloned[i]]; + } + return cloned.slice(0, count); +} + +function getModeBadgeText() { + switch (state.mode) { + case 'practice': + return '自由參考模式'; + case 'custom': + return '自訂範圍測驗'; + case 'exam': + return '模擬考試模式'; + default: + return ''; + } +} + +function getModeSubtext() { + switch (state.mode) { + case 'practice': + return `題庫總題數:${questions.length}`; + case 'custom': + if (state.customRange) { + return `題號範圍 ${state.customRange.start} - ${state.customRange.end},隨機抽出 ${state.questionIndices.length} 題`; + } + return ''; + case 'exam': + return `整份題庫隨機抽出 ${state.questionIndices.length} 題`; + default: + return ''; + } +} + +function renderQuestionView() { + const total = state.questionIndices.length; + if (!total) { + app.innerHTML = '

沒有符合條件的題目。

'; + document.getElementById('backToMenu').addEventListener('click', renderMenu); + return; + } + + progressContainer.hidden = false; + updateProgressBar(); + + const actualIndex = state.questionIndices[state.currentQuestion]; + const question = questions[actualIndex]; + const selectedOption = state.answers[actualIndex]; + const isRevealed = state.mode === 'practice' ? Boolean(state.revealed[actualIndex]) : state.completed; + + const answeredCount = state.questionIndices.filter((idx) => state.answers[idx] !== undefined).length; + + app.innerHTML = ` +
+
+
${getModeBadgeText()}
+ ${getModeSubtext() ? `
${getModeSubtext()}
` : ''} +
+ 第 ${state.currentQuestion + 1} / ${total} 題 + (原題號 #${actualIndex + 1}) +
+
已作答 ${answeredCount} 題,共 ${total} 題
+

${formatText(question.question)}

+ ${question.questionimage ? `題目圖片` : ''} +
+
+ ${question.option.map((_, idx) => renderOptionButton(question, actualIndex, idx, selectedOption, isRevealed)).join('')} +
+ ${renderFeedback(question, actualIndex, selectedOption, isRevealed)} + ${renderExplanation(question, isRevealed)} + ${renderPs(question)} +
+ + +
+ + + +
+ + +
+ ${state.mode !== 'practice' && !state.completed ? '' : ''} + +
+
+ `; + + app.querySelectorAll('.option-button').forEach((btn) => { + btn.addEventListener('click', () => { + const optionIndex = Number(btn.dataset.index); + handleOptionSelect(actualIndex, optionIndex); + }); + }); + + document.getElementById('prevBtn').addEventListener('click', () => { + if (state.currentQuestion > 0) { + state.currentQuestion -= 1; + renderQuestionView(); + } + }); + + document.getElementById('nextBtn').addEventListener('click', () => { + if (state.currentQuestion < total - 1) { + state.currentQuestion += 1; + renderQuestionView(); + } + }); + + const jumpInput = document.getElementById('jumpInput'); + document.getElementById('jumpBtn').addEventListener('click', () => { + const target = Number(jumpInput.value); + jumpToQuestion(target); + }); + + jumpInput.addEventListener('keydown', (event) => { + if (event.key === 'Enter') { + event.preventDefault(); + const target = Number(jumpInput.value); + jumpToQuestion(target); + } + }); + + document.getElementById('bookmarkBtn').addEventListener('click', () => { + toggleBookmark(actualIndex); + }); + + document.getElementById('statusBtn').addEventListener('click', () => { + openStatusPanel(); + }); + + document.getElementById('menuBtn').addEventListener('click', () => { + renderMenu(); + }); + + const submitBtn = document.getElementById('submitBtn'); + if (submitBtn) { + submitBtn.addEventListener('click', () => { + handleSubmit(); + }); + } + + updateStatusPanel(); +} + +function renderOptionButton(question, actualIndex, optionIndex, selectedOption, isRevealed) { + const baseClass = ['option-button']; + const isSelected = selectedOption === optionIndex; + if (isSelected) baseClass.push('selected'); + + const isCorrect = question.answer === optionIndex; + + if (isRevealed) { + if (isCorrect) { + baseClass.push('correct'); + } else if (isSelected) { + baseClass.push('incorrect'); + } + } + + const suffix = question.optionend || ''; + return ` + + `; +} + +function renderFeedback(question, actualIndex, selectedOption, isRevealed) { + if (!isRevealed) { + if (state.mode === 'practice' && state.answers[actualIndex] !== undefined) { + const isCorrect = question.answer === selectedOption; + return `
${isCorrect ? '答對了!' : '答錯了,再接再厲!'} 正確答案為 ${String.fromCharCode(65 + question.answer)} 選項。
`; + } + return ''; + } + + const isCorrect = question.answer === selectedOption; + const message = selectedOption === undefined ? '此題未作答。' : (isCorrect ? '答對了!' : '答錯了,來看看解析。'); + return `
${message} 正確答案為 ${String.fromCharCode(65 + question.answer)} 選項。
`; +} + +function renderExplanation(question, isRevealed) { + if (!isRevealed) return ''; + const text = question.explain?.trim(); + if (!text) return ''; + return `
解析
${formatText(text)}
`; +} + +function renderPs(question) { + const note = question.ps?.trim(); + if (!note) return ''; + return `
${formatText(note)}
`; +} + +function handleOptionSelect(actualIndex, optionIndex) { + state.answers[actualIndex] = optionIndex; + if (state.mode === 'practice') { + state.revealed[actualIndex] = true; + } + if (state.mode !== 'practice' && state.completed) { + state.revealed[actualIndex] = true; + } + renderQuestionView(); +} + +function toggleBookmark(actualIndex) { + if (state.bookmarks.has(actualIndex)) { + state.bookmarks.delete(actualIndex); + } else { + state.bookmarks.add(actualIndex); + } + renderQuestionView(); +} + +function jumpToQuestion(target) { + const total = state.questionIndices.length; + if (!target || target < 1 || target > total) { + alert('請輸入 1 到 ' + total + ' 之間的題號'); + return; + } + state.currentQuestion = target - 1; + renderQuestionView(); +} + +function openStatusPanel() { + statusPanel.classList.remove('hidden'); + statusPanel.setAttribute('aria-hidden', 'false'); +} + +function closeStatusPanel() { + statusPanel.classList.add('hidden'); + statusPanel.setAttribute('aria-hidden', 'true'); +} + +statusPanelClose.addEventListener('click', closeStatusPanel); +statusPanel.addEventListener('click', (event) => { + if (event.target === statusPanel) { + closeStatusPanel(); + } +}); + +resultOverlay.addEventListener('click', (event) => { + if (event.target === resultOverlay) { + hideResultOverlay(); + } +}); + +function updateStatusPanel() { + const total = state.questionIndices.length; + statusList.innerHTML = state.questionIndices.map((idx, order) => { + const answered = state.answers[idx] !== undefined; + const bookmarked = state.bookmarks.has(idx); + const classes = ['status-item']; + if (answered) classes.push('answered'); + if (bookmarked) classes.push('bookmarked'); + if (order === state.currentQuestion) classes.push('current'); + return ` +
+ ${order + 1} + 原題號 #${idx + 1} + ${bookmarked ? '' : ''} + ${answered ? '' : ''} +
+ `; + }).join(''); + + statusList.querySelectorAll('.status-item').forEach((item) => { + item.addEventListener('click', (event) => { + const order = Number(event.currentTarget.dataset.order); + state.currentQuestion = order; + closeStatusPanel(); + renderQuestionView(); + }); + }); +} + +function updateProgressBar() { + const total = state.questionIndices.length; + if (!total) { + progressBar.style.width = '0%'; + return; + } + const answered = state.questionIndices.filter((idx) => state.answers[idx] !== undefined).length; + const percent = Math.round((answered / total) * 100); + progressBar.style.width = `${percent}%`; + progressBar.dataset.label = `已作答 ${answered}/${total}`; + progressBar.setAttribute('aria-valuenow', String(percent)); + progressBar.setAttribute('aria-valuetext', `已作答 ${answered} 題,共 ${total} 題`); +} + +function handleSubmit() { + const total = state.questionIndices.length; + const answered = state.questionIndices.filter((idx) => state.answers[idx] !== undefined).length; + if (answered < total) { + const proceed = confirm(`仍有 ${total - answered} 題未作答,確定要交卷嗎?`); + if (!proceed) { + return; + } + } + + state.completed = true; + state.questionIndices.forEach((idx) => { + state.revealed[idx] = true; + }); + + const correct = state.questionIndices.reduce((acc, idx) => acc + (state.answers[idx] === questions[idx].answer ? 1 : 0), 0); + const score = Math.round((correct / total) * 100); + state.score = { total, correct, answered, unanswered: total - answered, score }; + + showResultOverlay(); + renderQuestionView(); +} + +function showResultOverlay() { + const { total, correct, answered, unanswered, score } = state.score; + resultOverlay.innerHTML = ` +
+
+

測驗成績

+

恭喜完成本次${state.mode === 'exam' ? '模擬考試' : '自訂測驗'}!以下是你的作答結果:

+
+
+
+ ${score} + 總分 +
+
+ ${correct} + 答對題數 +
+
+ ${answered - correct} + 答錯題數 +
+
+ ${unanswered} + 未作答 +
+
+
+ + +
+
+ `; + resultOverlay.classList.remove('hidden'); + resultOverlay.setAttribute('aria-hidden', 'false'); + + document.getElementById('reviewBtn').addEventListener('click', () => { + hideResultOverlay(); + }); + + document.getElementById('backBtn').addEventListener('click', () => { + hideResultOverlay(); + renderMenu(); + }); + +} + +function hideResultOverlay() { + resultOverlay.classList.add('hidden'); + resultOverlay.setAttribute('aria-hidden', 'true'); +} + +function renderError(error) { + app.innerHTML = ` +
+

載入題庫失敗

+

無法讀取題庫資料,請稍後再試。

+
${error.message}
+
+ `; +} + +function formatText(text = '') { + return text.replace(/\n/g, '
'); +} + +function sanitizeImagePath(path) { + if (!path) return ''; + if (path.startsWith('http')) return path; + return path.startsWith('./') ? path : `./${path.replace(/^\/?/, '')}`; +} + +init(); diff --git a/index.html b/index.html new file mode 100644 index 0000000..4b89df2 --- /dev/null +++ b/index.html @@ -0,0 +1,42 @@ + + + + + + 學科刷題系統 + + + +
+
+

學科刷題系統

+
+
+ +
+
+ +
+ + + + + + + + diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..121e316 --- /dev/null +++ b/styles.css @@ -0,0 +1,579 @@ +:root { + color-scheme: light dark; + --bg: #f8f9fb; + --card-bg: #ffffff; + --text: #1a1a1a; + --subtext: #555555; + --border: #e0e0e0; + --accent: #2f7dff; + --accent-soft: rgba(47, 125, 255, 0.12); + --danger: #e74c3c; + --success: #27ae60; + --shadow: 0 10px 30px rgba(0, 0, 0, 0.1); +} + +:root.dark { + --bg: #121417; + --card-bg: #1f2329; + --text: #f1f5f9; + --subtext: #cbd5f5; + --border: #2b3037; + --accent: #6d9bff; + --accent-soft: rgba(109, 155, 255, 0.16); + --danger: #ff6b6b; + --success: #4cd964; + --shadow: 0 10px 30px rgba(15, 23, 42, 0.35); +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: "Noto Sans TC", "PingFang TC", "Microsoft JhengHei", sans-serif; + background: var(--bg); + color: var(--text); + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.top-bar { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1rem clamp(1rem, 5vw, 3rem); + background: var(--card-bg); + border-bottom: 1px solid var(--border); + position: sticky; + top: 0; + z-index: 100; +} + +.branding h1 { + font-size: clamp(1.25rem, 2vw + 0.8rem, 2rem); + margin: 0; +} + +.actions { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.icon-button { + border: none; + background: transparent; + color: var(--text); + font-size: 1.25rem; + cursor: pointer; + border-radius: 50%; + width: 2.5rem; + height: 2.5rem; + display: grid; + place-items: center; + transition: background 0.2s ease, transform 0.2s ease; +} + +.icon-button:hover, +.icon-button:focus { + background: var(--accent-soft); + outline: none; + transform: translateY(-1px); +} + +.progress-container { + height: 6px; + background: var(--border); + margin: 0 clamp(1rem, 5vw, 3rem); + border-radius: 999px; + overflow: hidden; +} + +.progress-bar { + height: 100%; + background: linear-gradient(90deg, var(--accent), #6ee7b7); + width: 0%; + transition: width 0.3s ease; +} + +.app { + flex: 1; + padding: clamp(1rem, 4vw, 3rem); + display: flex; + justify-content: center; +} + +.card { + width: min(960px, 100%); + background: var(--card-bg); + border-radius: 1.25rem; + padding: clamp(1.5rem, 5vw, 3rem); + box-shadow: var(--shadow); + border: 1px solid var(--border); + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.card h2 { + margin: 0; +} + +.text-muted { + color: var(--subtext); + line-height: 1.6; +} + +.menu-grid { + display: grid; + gap: 1.5rem; +} + +.mode-card { + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: 1rem; + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 0.75rem; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.mode-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow); +} + +.mode-card h3 { + margin: 0; +} + +.mode-card p { + margin: 0; + color: var(--subtext); + line-height: 1.6; +} + +.custom-form { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.form-row { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} + +.form-row label { + flex: 1 1 180px; + display: flex; + flex-direction: column; + gap: 0.5rem; + font-weight: 600; +} + +.form-row input { + padding: 0.6rem 0.75rem; + border-radius: 0.75rem; + border: 1px solid var(--border); + background: transparent; + color: inherit; +} + +.form-row input:focus { + outline: 2px solid var(--accent); +} + +.primary-button, +.secondary-button, +.ghost-button { + cursor: pointer; + border-radius: 0.75rem; + padding: 0.75rem 1.5rem; + font-size: 1rem; + border: none; + transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; +} + +.primary-button { + background: var(--accent); + color: #fff; + box-shadow: var(--shadow); +} + +.primary-button:hover, +.primary-button:focus { + transform: translateY(-1px); +} + +.controls button[disabled] { + opacity: 0.5; + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +.secondary-button { + background: var(--accent-soft); + color: var(--accent); +} + +.ghost-button { + background: transparent; + border: 1px solid var(--border); + color: var(--text); +} + +.controls { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + align-items: center; +} + +.controls .spacer { + flex: 1 1 auto; +} + +.question-meta { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.session-badge { + align-self: flex-start; + padding: 0.35rem 0.75rem; + border-radius: 999px; + background: var(--accent-soft); + color: var(--accent); + font-size: 0.85rem; + font-weight: 600; +} + +.session-subtext { + color: var(--subtext); + font-size: 0.9rem; +} + +.question-info { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + font-size: 1rem; + align-items: baseline; +} + +.question-info span { + color: var(--subtext); +} + +.question-progress { + color: var(--subtext); + font-size: 0.95rem; +} + +.question-title { + font-size: clamp(1.1rem, 1vw + 1rem, 1.4rem); + line-height: 1.7; + white-space: pre-wrap; +} + +.question-image { + width: 100%; + max-height: 360px; + object-fit: contain; + border-radius: 0.75rem; + border: 1px solid var(--border); + background: #fff; +} + +.options { + display: grid; + gap: 0.75rem; +} + +.option-button { + text-align: left; + display: flex; + gap: 0.75rem; + align-items: flex-start; + border-radius: 1rem; + padding: 1rem; + border: 1px solid var(--border); + background: transparent; + color: inherit; + cursor: pointer; + transition: transform 0.15s ease, border 0.15s ease, box-shadow 0.15s ease, background 0.15s ease; +} + +.option-button strong { + font-size: 1.1rem; + min-width: 2rem; +} + +.option-button span { + flex: 1; +} + +.option-button:hover, +.option-button:focus { + transform: translateY(-1px); + box-shadow: var(--shadow); + outline: none; +} + +.option-button.selected { + border-color: var(--accent); + background: var(--accent-soft); +} + +.option-button.correct { + border-color: var(--success); + background: rgba(39, 174, 96, 0.12); +} + +.option-button.incorrect { + border-color: var(--danger); + background: rgba(231, 76, 60, 0.12); +} + +.feedback { + border-radius: 1rem; + padding: 1rem; + background: var(--accent-soft); + color: var(--accent); +} + +.feedback.correct { + background: rgba(39, 174, 96, 0.12); + color: var(--success); +} + +.feedback.incorrect { + background: rgba(231, 76, 60, 0.12); + color: var(--danger); +} + +.explanation { + background: var(--card-bg); + border-radius: 1rem; + border: 1px solid var(--border); + padding: 1rem; + line-height: 1.6; +} + +.ps-note { + background: var(--accent-soft); + color: var(--accent); + border-radius: 0.75rem; + padding: 0.75rem; +} + +.status-panel { + position: fixed; + inset: 0; + background: rgba(15, 23, 42, 0.45); + display: flex; + justify-content: center; + align-items: flex-end; + padding: 1.5rem; + transition: opacity 0.2s ease; +} + +.status-panel.hidden { + opacity: 0; + pointer-events: none; +} + +.status-panel__content { + width: min(720px, 100%); + background: var(--card-bg); + border-radius: 1.25rem; + padding: 1.5rem; + border: 1px solid var(--border); + box-shadow: var(--shadow); + max-height: 85vh; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.status-panel__header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.status-panel__legend { + display: flex; + gap: 1rem; + flex-wrap: wrap; + font-size: 0.9rem; +} + +.legend-item { + padding: 0.25rem 0.75rem; + border-radius: 999px; + border: 1px solid var(--border); +} + +.legend-answered { + background: rgba(39, 174, 96, 0.12); + color: var(--success); +} + +.legend-unanswered { + background: rgba(231, 76, 60, 0.12); + color: var(--danger); +} + +.legend-bookmarked { + background: var(--accent-soft); + color: var(--accent); +} + +.status-panel__list { + display: grid; + gap: 0.75rem; + grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); + overflow-y: auto; + padding-right: 0.25rem; +} + +.status-item { + border-radius: 0.75rem; + border: 1px solid var(--border); + padding: 0.75rem; + display: flex; + flex-direction: column; + gap: 0.25rem; + cursor: pointer; + transition: transform 0.15s ease, box-shadow 0.15s ease, border 0.15s ease; + text-align: center; +} + +.status-item:hover { + transform: translateY(-2px); + box-shadow: var(--shadow); +} + +.status-item.current { + border-color: var(--accent); + box-shadow: var(--shadow); +} + +.status-item.answered { + background: rgba(39, 174, 96, 0.12); + border-color: var(--success); +} + +.status-item.bookmarked { + background: var(--accent-soft); + border-color: var(--accent); +} + +.status-item .status-label { + font-size: 0.9rem; + color: var(--subtext); +} + +.jump-input { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.jump-input input { + width: 80px; + padding: 0.5rem 0.75rem; + border-radius: 0.75rem; + border: 1px solid var(--border); + background: transparent; + color: inherit; +} + +.jump-input input:focus { + outline: 2px solid var(--accent); +} + +.result-overlay { + position: fixed; + inset: 0; + background: rgba(15, 23, 42, 0.6); + display: flex; + align-items: center; + justify-content: center; + padding: 1.5rem; +} + +.result-overlay.hidden { + opacity: 0; + pointer-events: none; +} + +.result-card { + background: var(--card-bg); + border-radius: 1.5rem; + padding: 2rem; + width: min(520px, 100%); + border: 1px solid var(--border); + box-shadow: var(--shadow); + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.result-summary { + display: grid; + gap: 1rem; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); +} + +.result-box { + border-radius: 1rem; + padding: 1rem; + background: var(--accent-soft); + color: var(--accent); + text-align: center; +} + +.result-box strong { + display: block; + font-size: 1.5rem; + margin-bottom: 0.25rem; +} + +@media (max-width: 768px) { + .card { + padding: 1.25rem; + border-radius: 1rem; + } + + .controls { + flex-direction: column; + align-items: stretch; + } + + .jump-input { + width: 100%; + } + + .jump-input input { + flex: 1; + } +} + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001ms !important; + scroll-behavior: auto !important; + } +}