quiz UI 개편 (본격 개편) — 분야 accent 토큰 + 3화면 재설계

요약Sprint 215~219로 기능 완성된 CS 퀴즈(/quiz)의 시작·진행·결과 3화면 UI를 디자인 토큰 기반으로 본격 개편한 프론트엔드 전용 스프린트. 분야별(DS/Algo/Network/OS/DB) 전용 accent 색상 토큰 --quiz-cat-*를 globals.css :root+.dark 양쪽 20개 신규 등록(전 색 WCAG AA 4.5:1+ 실측 — light 4.95~6.05, dark 7.03~10.59)하고, 신규 data/quiz/category-meta.ts SSOT에 분야→{lucide 아이콘, 색상 토큰 var()} 매핑을 모아 5개 컴포넌트가 일관 소비한다. 핵심 절제 결정 3가지: 난이도(EASY/MEDIUM/HARD)는 신규 토큰을 만들지 않고 기존 semantic(success/warning/error) 재사용 → 토큰 0 추가; 신규 components/ui/ 컴포넌트 0(기존 Card/Button/Badge/Progress/Input/ScoreGauge + 인라인 var() 조합); 신규 keyframe 0(기존 fade-in/glow-pulse 재사용). 분야 색은 --diff-*/--lang-* 선례대로 @theme 매핑 없이 raw CSS 변수로 두고 인라인 style var()로 소비(bg-[#...] 하드코딩 금지 준수, react/forbid-dom-props warn은 선례 허용). 분야 pill·진행 칩 아이콘은 전부 aria-hidden으로 accessible name 보존 → getByRole(name) 회귀 0. 테스트 1498→1504(+6), quiz 컴포넌트 5종+category-meta 100/100/100/100, 글로벌 lines 87.67%/branches 79.02%(게이트 83/71). Critic R1 CLEAN(0건). 코드/스키마는 라이브 무관(merge≠라이브, 별도 운영 이월).

날짜
영향도높음

목표

  • Sprint 215~219로 기능 완성·검증된 CS 퀴즈(/quiz)의 시작·진행·결과 3화면 UI를 디자인 토큰 기반으로 본격 재설계한다.
  • 사용자 확정 범위: (1) 3화면 전체 개편, (2) 분야별(DS/Algo/Network/OS/DB) 전용 accent 색상 토큰 신규 도입 + lucide 아이콘 매핑.
  • 프론트엔드 전용 — 데이터/스키마/백엔드 무변경. /quiz 라우트는 인증 게이트이므로 라이브 검증은 별도 운영 이월(merge ≠ 라이브).

배경

215(미니게임 코어)·216(150문항+난이도 필터 UX)·217(로그인 기록 서버 영속화)·218(회귀 안전망)·219(lint 정리)로 퀴즈는 기능적으로 완성됐으나, UI는 기능 구현 위주라 시각적으로 평이했다 — 분야·난이도 선택이 단색 텍스트 pill, 결과 화면이 게이지+텍스트로 밋밋, AlgoSu UI v2(분야/난이도 시각 구분·glassmorphism·애니메이션)를 충분히 활용하지 못했다. 본 스프린트는 토큰 등록 규율(Palette 확정 → Herald 등록 → 소비)을 지키며 3화면을 개편한다.

결정

D1. 분야 색상 = 신규 raw CSS 변수 토큰 (@theme 매핑 없음)

분야 5종 accent를 --quiz-cat-{slug}-color/-bgglobals.css :root(light)+.dark(dark) 양쪽 등록(20개). --diff-*·--lang-* 선례대로 @theme inline Tailwind 매핑은 생략하고 raw CSS 변수로만 둔다 — categorical 다값 색은 Tailwind 유틸 클래스가 아니라 인라인 style={{ color: 'var(--quiz-cat-*-color)' }}로 소비한다(DifficultyBadge 패턴). 이로써 bg-[#...] 하드코딩 금지 규율을 지키면서 라이트/다크 자동 전환을 얻는다.

색상·WCAG AA(텍스트 대비 4.5:1+) 실측:

분야Light(on #FAFAF8)Dark(on #0F0F12)아이콘
DATA_STRUCTURE#2563EB (4.95:1)#60A5FA (7.53:1)Boxes
ALGORITHM#7C3AED (5.45:1)#A78BFA (7.03:1)GitBranch
NETWORK#0E7490 (5.13:1)#22D3EE (10.59:1)Network
OS#A21CAF (6.05:1)#E879F9 (7.78:1)Cpu
DATABASE#047857 (5.25:1)#34D399 (9.95:1)Database

soft bg는 base 색의 light 0.10 / dark 0.14 opacity. 난이도 semantic(success/warning/error)과 hue가 겹치지 않게 blue/violet/cyan/fuchsia/emerald 5색으로 분산.

D2. 난이도 색상 = 기존 semantic 재사용 (신규 토큰 0)

난이도 EASY/MEDIUM/HARD는 신규 토큰을 만들지 않고 기존 semantic 토큰을 재사용한다: EASY→success, MEDIUM→warning, HARD→error, ALL→primary(중립). 근거: 이미 light/dark + WCAG 검증된 토큰이고 의미 매핑이 직관적이며, 토큰 sprawl을 막는다. 이 결정으로 난이도용 Palette wave 자체가 불필요해졌다.

D3. 신규 ui 컴포넌트 0 · 신규 keyframe 0

기존 Card/Button/Badge/Progress/Input/ScoreGauge + 인라인 var() 조합으로 3화면을 모두 커버 → components/ui/ 신규 생성 트리거(Palette UI 가이드) 회피. 애니메이션도 기존 .animate-fade-in·@keyframes glow-pulse(인라인 style={{ animation: 'glow-pulse ...' }} 선례) 재사용 → 신규 keyframe 0. prefers-reduced-motion은 globals.css 전역 미디어쿼리가 이미 모든 CSS 애니메이션을 무력화하므로 추가 가드 불필요.

구현

산출물 (Wave 순서)

Wave에이전트파일내용
W0Palette(가이드)분야 5색 확정 + WCAG AA 실측
W1Heraldfrontend/src/app/globals.css--quiz-cat-* 토큰 20개 등록(light 10 + dark 10)
W2Scribefrontend/src/data/quiz/category-meta.ts(신규)분야→{lucide 아이콘, colorVar, bgVar} SSOT + index.ts 재노출
W2Scribefrontend/src/components/quiz/{QuizStart,QuizPlay,QuizQuestion,QuizFeedback,QuizResult}.tsx3화면 재설계
W3Scribe__tests__/{QuizStart,QuizPlay}.test.tsx, data/quiz/__tests__/category-meta.test.ts(신규)회귀 테스트 +6
W4Librariandocs/adr/sprints/sprint-221.md(본 문서) + EN + docs/adr/README.mdADR + 인덱스 158→159

컴포넌트별 변경

  • category-meta.ts (신규 SSOT): QUIZ_CATEGORY_META: Record<QuizCategory, { icon, colorVar, bgVar }> + getQuizCategoryMeta(). 색상은 var(--quiz-cat-*) 문자열(하드코딩 hex 0). 아이콘 Boxes/GitBranch/Network/Cpu/Database.
  • QuizStart: 분야 pill에 아이콘(aria-hidden)+선택 시 accent 색(인라인 style var: color/bg/border), 난이도 pill을 semantic 톤(DIFFICULTY_TONE 정적 클래스 맵), 헤더 아이콘 칩, 카드 animate-fade-in. resolveCountOptions 로직 불변.
  • QuizPlay: 진행 헤더에 분야 칩(아이콘 aria-hidden + accent 색), animate-fade-in. Progress 바는 primary 유지(ui 미수정).
  • QuizQuestion: 프롬프트를 카드(bg-bg-alt 보더)로 강화. 폼 제출/autoFocus/빈입력 무시 로직 불변.
  • QuizFeedback: 정답/오답에 따라 컨테이너 톤(success-soft/error-soft + 좌측 4px accent 보더), animate-fade-in. role="status"·아이콘·Badge·해설 박스 구조 유지.
  • QuizResult: 신기록 시 Trophy 배지 + 게이지 래퍼 glow-pulse 축하 연출(인라인 style), animate-fade-in. ScoreGauge·정답수·best score 텍스트 유지.

검증

  • tsc: 0 errors. next lint: 0 errors(인라인 var() style react/forbid-dom-props warn 3건은 DifficultyBadge 등 기존 459건 선례와 동일 — next lint는 warn에 비실패, CI 동일).
  • test:coverage: 148 suites / 1504 tests PASS(1498→+6). 글로벌 lines 87.67% / branches 79.02%(게이트 83/71). quiz 컴포넌트 5종 + category-meta.ts 100/100/100/100.
  • next build: ✓. ƒ /[locale]/quiz 39.3 kB(217 기준 37.5kB → +1.8kB, lucide 6아이콘+메타).
  • ADR 게이트: index count(sprint 159, --strict) / adr-en coverage(sprint-221 EN, --strict) / adr-links 0 broken / doc-refs no broken.

Critic 교차 리뷰

  • 대상: frontend/ 11파일 (base 6c2c128..HEAD, 코드+테스트)
  • Codex 명령: codex review --base 6c2c128 -c model=gpt-5.5
  • 세션 ID: 019e9bf6-e285-79a3-84d8-55f3d73a626b

R1 — Critical/High/Medium/Low 0건 (CLEAN): "The changes are UI-focused and the new category metadata mapping is consistently wired into the quiz components with corresponding tokens and tests. I did not identify any discrete correctness, accessibility, or build-breaking issue introduced by the patch."

종합 판정: ✅ 머지 가능 — 단일 라운드 CLEAN. UI 개편 + 메타 매핑이 토큰·테스트와 일관 연결, 접근성/빌드 회귀 없음.