# O/X Quiz Module

재사용 가능한 O/X 단어 자기점검 모듈입니다. VocaScan의 기존 퀴즈 경험에서 확인된 세 가지 응답(`O`=알아요, `X?O`=확실하지 않아요, `X`=몰라요), 키보드 이동, 진행률, 터치 친화적 버튼, 접근성 상태 메시지를 독립 패키지로 분리했습니다.

## 구성

- `ox-quiz-core.mjs`: DOM·프레임워크·네트워크·저장소가 없는 순수 상태 엔진
- `ox-quiz-ui.mjs`: 접근성 있는 화면 렌더러와 키보드/햅틱 선택적 어댑터
- `ox-quiz.css`: `.oxq-` 접두사를 사용하는 충돌 방지 스타일
- `vocascan-adapter.mjs`: 기존 VocaScan 단어와 `o/maybe/x` 값을 연결하는 선택적 어댑터
- `index.mjs`: 위 API의 단일 진입점
- `examples/standalone.html`: 다른 서비스에서 독립적으로 실행하는 최소 예제

## 가장 작은 사용법

```html
<link rel="stylesheet" href="/modules/ox-quiz/ox-quiz.css">
<div id="quiz"></div>
<script type="module">
  import { createOxQuizUI } from '/modules/ox-quiz/index.mjs';
  createOxQuizUI({
    mount: document.querySelector('#quiz'),
    locale: 'ko',
    questions: [
      { id: 'w-1', term: 'resilient', meaning: '회복력이 있는' },
      { id: 'w-2', term: 'fluent', meaning: '유창한' },
    ],
    onStateChange(session, answer, summary) {
      // 호스트 서비스의 저장소/분석 연결 지점. PII를 넣지 마세요.
      console.log(answer.questionId, answer.outcome, summary);
    },
  });
</script>
```

## 상태 계약

`createSession`은 `version`, `id`, `mode`, `status`, `allowMaybe`, `startedAt`, `endedAt`, `index`, `questions`, `answers`를 가진 JSON 직렬화 가능한 상태를 반환합니다. `answerQuestion`은 입력 상태를 바꾸지 않고 `{ session, answer }`를 반환합니다. 완료되면 `status`가 `complete`가 됩니다. `pauseSession`, `resumeSession`, `resetSession`은 호스트가 저장 정책을 정할 수 있도록 순수 복사본을 반환합니다.

질문은 최소 `{ id, term }`이며 `meaning`, `partOfSpeech`, `example`, `metadata`는 선택입니다. 답변에는 `questionId`, `outcome`, `at`, `latencyMs`, `probeCorrect`만 저장합니다. 기본 모듈은 쿠키, localStorage, Firebase, 광고 식별자, 계정 정보 또는 네트워크를 읽지 않습니다.

## UX 계약

- 기본 응답은 세 개이며 라벨은 `ko`, `en`, `th`로 제공됩니다. 호스트는 `labels`로 문구를 바꿀 수 있지만 의미가 섞인 언어를 넣지 않아야 합니다.
- 키보드: `Space`/`Enter`/`ArrowRight`=알아요, `ArrowLeft`=몰라요, `ArrowDown`=확실하지 않아요, 단 입력 요소에서는 가로챔하지 않습니다.
- 버튼은 좁은 화면에서도 48px 이상 터치 영역을 유지하고, `focus-visible`, `aria-label`, `aria-live`, reduced-motion, forced-colors를 지원합니다.
- 선택적 `enableHaptics`는 브라우저의 `navigator.vibrate`가 있을 때만 짧게 진동합니다. 소리·진동이 없어도 흐름은 완성됩니다.
- 버튼을 누르면 다음 문제로 즉시 이동합니다. 마지막 문제 뒤에는 개수 기반 요약과 다시 하기 버튼을 표시합니다.

## 점수와 한계

`heuristicRecallScore`는 과거 VocaScan의 응답·반응시간 휴리스틱을 호환하는 보조 신호입니다. 자기신고 응답만으로 실력, CEFR, 숙련도 또는 정답률을 판정하지 않습니다. `summarizeSession`도 `reportedKnowRate`와 응답 개수만 제공하며, 진단·인증으로 표시해서는 안 됩니다. 외부 정답 평가가 필요한 서비스는 `onAnswer` 또는 별도의 `scorer`를 호스트에서 연결하고 그 근거를 별도로 공개해야 합니다.

## VocaScan 연결

`toOxQuestions(words)`는 `{id|wordId, term|word, meanings, partOfSpeech|pos, example}`를 모듈 질문으로 바꿉니다. `createVocaScanSession`은 그 결과를 `mode: 'vocascan-self-report'`로 표시합니다. `legacyOutcomeToOutcome`와 `outcomeToLegacy`로 기존 `o`, `maybe`, `x` 값을 왕복할 수 있습니다. 어댑터를 사용해도 VocaScan의 SRS·XP·계정·동기화 정책은 호스트 앱에 남아 있습니다.

## 다른 서비스에 넣는 순서

1. 이 폴더를 서비스의 정적 모듈 경로에 복사하거나 빌드 산출물로 포함합니다.
2. `ox-quiz.css`를 한 번 불러오고, `index.mjs`에서 `createOxQuizUI`를 호출합니다.
3. 서비스의 저장 정책에 맞춰 `onStateChange`에서 세션을 저장합니다. 저장 전에 PII와 분석 식별자를 분리합니다.
4. 서비스의 삭제·보존·동의 정책을 모듈 밖에서 연결합니다.
5. `npm test`로 상태·키보드·어댑터 회귀 검사를 실행합니다.

## 버전·권리

현재 모듈 계약 버전은 `1`입니다. 이 저장소의 프로젝트 라이선스와 콘텐츠 권리 고지를 따릅니다. 단어·뜻·예문을 다른 서비스에 재배포할 때는 해당 콘텐츠의 별도 이용 권리를 확인해야 하며, 코드 모듈이 콘텐츠 권리를 부여하지 않습니다. 호스트 서비스는 API 변경을 고정된 버전 경로로 배포하고, 모듈을 계정 정보나 비밀값 저장 용도로 사용하지 않아야 합니다.
