사주고사 제작기 07. 힌트-정답 겹침 수정, 캐릭터 20명 확장, 데이터 통합
이모지/lucide-react를 Phosphor Icons로 통일하고, 랜딩 페이지를 재설계하고, 사주 힌트-정답 키워드 겹침을 수정하고, 캐릭터를 8명에서 20명으로 확장하고, 분산된 데이터 파일을 하나로 합치고, 선택지 변별력을 전수 검수해서 22개 라운드를 일괄 수정했다.
아이콘 마이그레이션
왜 바꿨나
프로젝트 전반에 유니코드 이모지가 106곳 이상 박혀 있었다. 오행 힌트, 등급 표시, 랜딩 피처, 모드 선택, 공유 텍스트 등등. 문제:
- 일관성 없음 — OS/브라우저마다 렌더링이 다르다
- 접근성 부족 — 이모지에 aria-label이 없다
- 디자인 통제 불가 — 크기, 색상, 굵기를 CSS로 제어할 수 없다
lucide-react(4개 파일)도 쓰고 있어서, Phosphor Icons 하나로 통일했다.
마이그레이션 범위
이모지 제거 (7파일):
app/page.tsx — 랜딩 피처 → Phosphor 아이콘
app/onboarding/page.tsx — 온보딩 슬라이드 → Sparkle, PuzzlePiece, Star
components/mode-selector.tsx → MagnifyingGlass, PencilSimpleLine
components/detective/timeline-result.tsx — 등급 → Trophy, Eye, Crosshair, Spiral, Waves
components/detective/detective-share.tsx — 공유 텍스트 → O X
lib/data/detective-scenarios.ts — 오행 이모지 96건 제거
lib/types.ts — 주석 업데이트
lucide-react → Phosphor (4파일):
components/ui/sonner.tsx — 토스트 아이콘 5종
components/result/answer-tabs.tsx — Lock
app/quiz/[questionId]/quiz-client.tsx — RefreshCw → ArrowsClockwise
components/loading-overlay.tsx — Loader2 → SpinnerGapdetective-scenarios.ts에서 💧 수(水) → 수(水)로 바뀌면서, element 필드가 순수 텍스트가 됐다.
랜딩 페이지 재설계
Before → After
Before (8 카드):
Hero(占) → Feature 2열 → Feature 3열 → 진행 방식 카드 → 모드 선택 → CTA
After:
Hero(占 + animated aura) → 진행 방식(타임라인) → 모드 선택 → CTA5개 Feature 카드 + 진행 방식 카드를 타임라인 레일 3스텝으로 압축했다. Phosphor 아이콘(UserCircle, MagnifyingGlass, Trophy)과 세로 연결선으로 시각적 흐름을 만들었다.
Animated Aura
占 문자 뒤에 이중 역회전 conic-gradient + ripple 링 3중 + core pulse를 겹쳤다. 전부 CSS-only.
Layer 1: 외곽 conic-gradient — 25s 정방향 회전
Layer 2: 내곽 conic-gradient — 14s 역방향 회전
Layer 3: Ripple 링 3중 — 3.5s 주기, 1.2s 시차
Layer 4: Core glow — 3s pulse힌트-정답 키워드 겹침 수정
문제 발견
사주 힌트가 정답을 그대로 알려주고 있었다.
힌트: "수(水) 부족 — 감정의 유연성이 없어 몸이 먼저 경고를 보낸다"
정답: "몸이 먼저 신호를 보냈다""몸이 먼저"라는 키워드가 힌트와 정답에 동시에 있으니, 읽는 순간 답이 보였다.
3라운드에 걸친 수정
1차 수정 (44건): 힌트와 키워드가 겹치는 choices[0]을 전부 교체했다. 그런데 수정된 정답이 너무 시적이고 추상적이었다. "쌓아올린 탑이 모래 위에 있었다" 같은 표현이 오답들의 구체적 톤과 달라서, 오히려 정답이 튀었다.
2차 수정 (37건): 모든 정답을 구체적/서술적 톤으로 재작성했다. 핵심 원칙: 4개 선택지의 톤이 동일해야 한다.
수정 전: "쌓아올린 탑이 모래 위에 있었다" (시적 → 정답이 튀어보임)
수정 후: "퇴근길 지하철에서 공황장애가 찾아왔다" (구체적, 오답과 같은 톤)3차 수정: 나머지 추상적 표현들을 가독성 좋은 일상어로 교체.
난이도 균형
힌트가 "몸"을 언급하면 몸 관련 정답을 고르는 건 자연스럽다. 완전히 막을 수는 없지만, 키워드 직접 겹침만 제거해도 "읽자마자 답이 보이는" 문제는 해결됐다. 체감 난이도는 ~30% 쉬움, ~50% 보통, ~20% 어려움. 실제 유저 테스트로 조정할 예정.
캐릭터 8명 → 20명 확장
왜 늘렸나
8명이면 2~3번 플레이하면 본 캐릭터가 나온다. 리플레이 가치가 떨어진다. 20명이면 매번 새 캐릭터를 만날 확률이 높다.
생성 전략
12명을 한 번에 만들면 컨텍스트가 터지니까, 4명씩 3배치로 병렬 생성했다.
batch1 (c9-c12): 체육교사, 커리어여성, 건설업자, PR전문가
batch2 (c13-c16): 투자청년, 여성리더, 자수성가, 약사/연구원
batch3 (c17-c20): 음악인, 인플루언서, 산업화세대, 도예가싱글맘c9~c18은 generated-questions.json에 이미 캐릭터 프로필이 있어서 시나리오만 추가하면 됐다. c19(조현우, 1955년 산업화세대 가부장)과 c20(나은빈, 1975년 도예가 싱글맘)은 캐릭터 자체를 새로 만들었다.
파일 구조
detective-scenarios.ts — c1-c8 (기존) + batch import
detective-scenarios-batch1.ts — c9-c12
detective-scenarios-batch2.ts — c13-c16
detective-scenarios-batch3.ts — c17-c20메인 파일에서 3개 배치를 import해서 spread한다.
import { batch1Scenarios } from './detective-scenarios-batch1';
import { batch2Scenarios } from './detective-scenarios-batch2';
import { batch3Scenarios } from './detective-scenarios-batch3';
export const detectiveScenarios: DetectiveScenario[] = [
// c1-c8 인라인
...batch1Scenarios,
...batch2Scenarios,
...batch3Scenarios,
];캐릭터 다양성
시대, 직업, 성별, 지역, 사주 구성을 의도적으로 분산시켰다.
| 캐릭터 | 시대 | 직업/테마 | 핵심 오행 |
|---|---|---|---|
| c1 김하늘 | 1990 | IT 스타트업 창업자 | 수(水) 과다, 화(火) 부족 |
| c2 이서연 | 1985 | 공무원 | 화(火) 과다, 수(水) 없음 |
| c3~c8 | 1963~1993 | 건설, 회계, 무역, 소설가 등 | 다양 |
| c9 임재혁 | 1983 | 체육교사 | 목(木) 강, 토(土) 흔들림 |
| c10 송미래 | 1994 | 대기업 인사팀 | 화(火)+금(金), 수(水) 부족 |
| c11 오동현 | 1972 | 건설업 자영업 | 금(金)+토(土), 목(木) 부족 |
| c12 배수아 | 1997 | PR 전문가 | 화(火)+토(土), 수(水) 부족 |
| c13 김도윤 | 2005 | 투자/배달 사업 | 토(土) 과다, 수(水) 약함 |
| c14 장하린 | 1998 | 사회적기업/정치 | 화(火)+목(木), 금(金) 부족 |
| c15 홍기태 | 1968 | 운수업→식당 | 금(金)+토(土), 수(水) 약함 |
| c16 윤지아 | 1987 | 약사/연구원 | 목(木)+수(水), 금(金) 약함 |
| c17 서준혁 | 1996 | 인디 뮤지션/프로듀서 | 목(木) 과다, 금(金) 부족 |
| c18 이채원 | 2000 | 인플루언서/뷰티 사업 | 토(土) 과다, 수(水) 부족 |
| c19 조현우 | 1955 | 공장→제조업 창업 | 토(土) 과다, 수(水) 부족 |
| c20 나은빈 | 1975 | 도예가 싱글맘 | 목(木) 과다, 금(金) 부족 |
1955년생부터 2005년생까지. 산업화세대 가부장부터 Z세대 투자청년까지. 같은 오행이라도 시대와 환경이 다르면 전혀 다른 이야기가 된다.
데이터 파일 통합
Before
mock-questions.ts — c1-c2 캐릭터 + q1-q6 문제 (수작업)
generated-questions.json — c3-c18 캐릭터 + q7-q102 문제 (생성)두 파일을 런타임에 합치는 구조였다. c1-c2가 따로 있을 이유가 없었다.
After
generated-questions.json — c1-c20 캐릭터 (20명) + q1-q102 문제 (102개)
mock-questions.ts — import + re-export (4줄)mock-questions.ts는 JSON을 타입 캐스팅해서 내보내는 역할만 한다.
import { type Character, type Question } from '../types';
import generatedData from './generated-questions.json';
export const mockCharacters: Character[] = generatedData.characters as Character[];
export const mockQuestions: Question[] = generatedData.questions as Question[];Node.js 스크립트로 c1-c2 캐릭터와 q1-q6 문제를 JSON에 합쳐서 데이터 무결성을 보장했다.
선택지 변별력 전수 검수
문제 발견
"선택지가 너무 비슷해서 찍기 영역이다"라는 피드백을 받았다. 실제로 20캐릭터 x 5~8라운드를 전수 검토하니, 22개 라운드에서 변별력 부족 패턴이 보였다.
패턴 분류:
| 유형 | 설명 | 예시 |
|---|---|---|
| 동의어 쌍 | 2개 선택지가 표현만 다른 같은 의미 | "교직 선택" vs "다른 직업 고민했지만 교직" |
| 주제 독점 | 3~4개가 같은 주제 변주 | 4개 다 "리더십" 유형 |
| 대칭 중복 | 2+2로 짝이 묶임 | "성공 후 공허" vs "성공 후 고독" |
| 메타/추상 | 구체적 행동이 아닌 추상적 서술 | "변화를 받아들였다" |
수정 원칙
각 선택지는 서로 다른 심리/행동 차원을 대표해야 한다. 사주 힌트가 "물(水)의 기운이 강한 사람"이면, 4개 선택지가 각각 다른 인생 영역(건강/재정/관계/감정)을 건드려야 힌트 기반 추리가 가능하다.
수정 예시: C14 장하린 10대
수정 전:
"학교 축제 기획단에서 리더로 뛰었다" (리더십-이벤트)
"체육대회 응원단장을 자처했다" (리더십-체육)
"학급 반장으로 매 학기 반을 이끌었다" (리더십-학급)
"교내 봉사 동아리를 직접 만들었다" (리더십-봉사)
→ 4개 다 "리더십" 변주. 힌트를 아무리 읽어도 구별 불가.
수정 후:
"학교 축제 기획단에서 리더로 뛰었다" (이벤트/행동)
"체육대회 응원단장을 자처했다" (체육/신체)
"전교 1등을 놓치지 않으며 학업에 올인했다" (학업/성취)
"밴드부에서 기타를 치며 무대에 섰다" (예술/표현)
→ 행동, 신체, 학업, 예술 — 4개 차원이 다르다.수정 범위
detective-scenarios.ts (c1-c8): 5개 라운드
detective-scenarios-batch1.ts (c9-c12): 9개 라운드
detective-scenarios-batch2.ts (c13-c16): 6개 라운드
detective-scenarios-batch3.ts (c17-c20): 1개 라운드
총 22개 라운드, 4파일 수정 (67줄 변경)가장 많이 수정된 캐릭터는 C10 송미래 (4개 라운드). 대기업 커리어우먼 캐릭터라 선택지가 전부 "일-가정 갈등" 프레임에 갇혀 있었다. 각 라운드를 신체 건강, 커리어 전략, 인간관계, 가족 역할 축으로 분산시켰다.
커밋 로그
bfc992b refactor: replace all emojis and lucide-react with Phosphor Icons, redesign landing page
d87a8d4 feat: 캐릭터 20명 확장, 데이터 통합, 힌트-정답 겹침 수정
2c8b11d fix: 추리 모드 선택지 변별력 개선 (22개 라운드)배운 것
-
이모지는 "쉬운 아이콘"이 아니라 "제어할 수 없는 아이콘"이다. 빠르게 넣기엔 좋지만, 크기/색상/굵기를 CSS로 바꿀 수 없고, 플랫폼마다 다르게 보인다. 프로덕션에서는 SVG 아이콘 라이브러리가 맞다.
-
힌트와 정답의 키워드 겹침은 QA로 잡아야 한다. 데이터를 많이 만들면 자연스럽게 힌트 문장과 정답 문장에 같은 단어가 들어간다. 자동 검증 스크립트가 필요할 수도 있다.
-
선택지 톤 통일이 난이도를 결정한다. 정답만 시적이거나, 정답만 구체적이면 톤이 달라서 찍어도 맞힌다. 4개 선택지가 같은 수준의 구체성/추상성을 가져야 진짜 추리가 된다.
-
대량 콘텐츠 생성은 배치 병렬화가 핵심이다. 12캐릭터 x 8라운드 = 96문제를 한 번에 만들면 컨텍스트가 터진다. 4명씩 3배치로 나누면 병렬 실행 가능하고, 각 배치가 독립적이라 품질 검수도 쉽다.
-
데이터 파일은 일찍 통합하는 게 좋다. "수작업 데이터"와 "생성 데이터"를 분리해두면 나중에 어디를 고쳐야 하는지 헷갈린다. 하나의 소스로 합치면 관리 포인트가 줄어든다.
-
선택지 변별력은 "차원"으로 설계해야 한다. 같은 주제의 변주(리더십A, 리더십B, 리더십C, 리더십D)를 나열하면 힌트가 아무 쓸모 없어진다. 각 선택지가 서로 다른 인생 축(건강/재정/관계/감정, 또는 학업/체육/예술/봉사)을 대표해야, 사주 힌트가 특정 축을 가리키는 단서로 기능한다. 변별력 = 차원의 다양성이다.
남은 과제
- 내 사주보기 플로팅 챗봇 (만세력 계산 + Gemini AI 해석)
- 추리 결과 Supabase 저장 (통계/리더보드)
- 추리 결과 전용 URL (공유 링크에서 직접 결과 확인)
- generated-questions analysis 콘텐츠 품질 보강
- 문제/인물 데이터 Supabase 이관
- 힌트-정답 겹침 자동 검증 스크립트