class TestPage extends HTMLElement { connectedCallback() { this.className = 'screen'; this.renderQuestion(); } renderHeader(step) { const wrapper = document.createElement('div'); const counter = document.createElement('span'); counter.className = 'muted'; counter.textContent = `${step} / ${state.questions.length}`; const topbar = createTopbar([counter]); topbar.querySelector('.brand').onclick = () => { if (confirm('처음 화면으로 돌아갈까요? 지금까지의 응답은 사라집니다.')) { showStartScreen(); } }; const track = document.createElement('div'); track.className = 'progress-track'; const fill = document.createElement('div'); fill.className = 'progress-fill'; fill.style.width = `${(step / state.questions.length) * 100}%`; track.appendChild(fill); wrapper.append(topbar, track); return wrapper; } renderQuestion() { const index = state.current; const question = state.questions[index]; let selected = state.answers[index] ?? null; let confidence = state.confidences[index] ?? null; this.replaceChildren(this.renderHeader(index + 1)); const card = document.createElement('div'); card.className = 'card'; const title = document.createElement('p'); title.className = 'question-text'; title.textContent = `${index + 1}. ${question.question}`; card.appendChild(title); const list = document.createElement('ul'); list.className = 'options'; const optionButtons = question.options.map((option, i) => { const item = document.createElement('li'); const button = document.createElement('button'); button.type = 'button'; button.className = 'option'; button.setAttribute('aria-pressed', String(option === selected)); const key = document.createElement('span'); key.className = 'key'; key.textContent = String.fromCharCode(65 + i); const text = document.createElement('span'); text.textContent = option; button.append(key, text); button.onclick = () => { selected = option; optionButtons.forEach((b) => b.setAttribute('aria-pressed', 'false')); button.setAttribute('aria-pressed', 'true'); updateSubmitState(); }; item.appendChild(button); list.appendChild(item); return button; }); card.appendChild(list); const confidenceBox = document.createElement('div'); confidenceBox.className = 'confidence'; const confidenceLabel = document.createElement('label'); confidenceLabel.textContent = '이 답에 얼마나 확신하나요?'; confidenceBox.append( confidenceLabel, createChipGroup(CONFIDENCE, { selected: confidence, onSelect: (value) => { confidence = value; updateSubmitState(); }, }) ); card.appendChild(confidenceBox); const actions = document.createElement('div'); actions.className = 'actions'; if (index > 0) { actions.appendChild(createButton('이전 문제', () => history.back(), 'ghost')); } const isLast = index === state.questions.length - 1; const submitButton = document.createElement('button'); submitButton.className = 'primary'; submitButton.textContent = isLast ? '결과 보기' : '다음 문제'; submitButton.onclick = () => { state.answers[index] = selected; state.confidences[index] = confidence; if (isLast) { finishQuiz(); } else { goToQuestion(index + 1); } }; actions.appendChild(submitButton); card.appendChild(actions); const updateSubmitState = () => { submitButton.disabled = !(selected && confidence); }; updateSubmitState(); this.appendChild(card); this.onkeydown = (event) => { if (event.target.tagName === 'INPUT') return; const fromLetter = event.key.toUpperCase().charCodeAt(0) - 65; const fromNumber = Number(event.key) - 1; const at = optionButtons[fromLetter] ?? optionButtons[fromNumber]; if (event.key.length === 1 && at) { at.click(); at.focus(); } }; this.tabIndex = -1; this.focus({ preventScroll: true }); } } customElements.define('test-page', TestPage);