본문으로 건너뛰기

사주고사 제작기 07. 힌트-정답 겹침 수정, 캐릭터 20명 확장, 데이터 통합

·16 min read·7 / 12

이모지/lucide-react를 Phosphor Icons로 통일하고, 랜딩 페이지를 재설계하고, 사주 힌트-정답 키워드 겹침을 수정하고, 캐릭터를 8명에서 20명으로 확장하고, 분산된 데이터 파일을 하나로 합치고, 선택지 변별력을 전수 검수해서 22개 라운드를 일괄 수정했다.


아이콘 마이그레이션

왜 바꿨나

프로젝트 전반에 유니코드 이모지가 106곳 이상 박혀 있었다. 오행 힌트, 등급 표시, 랜딩 피처, 모드 선택, 공유 텍스트 등등. 문제:

  1. 일관성 없음 — OS/브라우저마다 렌더링이 다르다
  2. 접근성 부족 — 이모지에 aria-label이 없다
  3. 디자인 통제 불가 — 크기, 색상, 굵기를 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 → SpinnerGap

detective-scenarios.ts에서 💧 (水)(水)로 바뀌면서, element 필드가 순수 텍스트가 됐다.


랜딩 페이지 재설계

Before → After

Before (8 카드):
  Hero(占) → Feature 2열 → Feature 3열 → 진행 방식 카드 → 모드 선택 → CTA
 
After:
  Hero(占 + animated aura) → 진행 방식(타임라인) → 모드 선택 → CTA

5개 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 김하늘1990IT 스타트업 창업자수(水) 과다, 화(火) 부족
c2 이서연1985공무원화(火) 과다, 수(水) 없음
c3~c81963~1993건설, 회계, 무역, 소설가 등다양
c9 임재혁1983체육교사목(木) 강, 토(土) 흔들림
c10 송미래1994대기업 인사팀화(火)+금(金), 수(水) 부족
c11 오동현1972건설업 자영업금(金)+토(土), 목(木) 부족
c12 배수아1997PR 전문가화(火)+토(土), 수(水) 부족
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개 라운드)

배운 것

  1. 이모지는 "쉬운 아이콘"이 아니라 "제어할 수 없는 아이콘"이다. 빠르게 넣기엔 좋지만, 크기/색상/굵기를 CSS로 바꿀 수 없고, 플랫폼마다 다르게 보인다. 프로덕션에서는 SVG 아이콘 라이브러리가 맞다.

  2. 힌트와 정답의 키워드 겹침은 QA로 잡아야 한다. 데이터를 많이 만들면 자연스럽게 힌트 문장과 정답 문장에 같은 단어가 들어간다. 자동 검증 스크립트가 필요할 수도 있다.

  3. 선택지 톤 통일이 난이도를 결정한다. 정답만 시적이거나, 정답만 구체적이면 톤이 달라서 찍어도 맞힌다. 4개 선택지가 같은 수준의 구체성/추상성을 가져야 진짜 추리가 된다.

  4. 대량 콘텐츠 생성은 배치 병렬화가 핵심이다. 12캐릭터 x 8라운드 = 96문제를 한 번에 만들면 컨텍스트가 터진다. 4명씩 3배치로 나누면 병렬 실행 가능하고, 각 배치가 독립적이라 품질 검수도 쉽다.

  5. 데이터 파일은 일찍 통합하는 게 좋다. "수작업 데이터"와 "생성 데이터"를 분리해두면 나중에 어디를 고쳐야 하는지 헷갈린다. 하나의 소스로 합치면 관리 포인트가 줄어든다.

  6. 선택지 변별력은 "차원"으로 설계해야 한다. 같은 주제의 변주(리더십A, 리더십B, 리더십C, 리더십D)를 나열하면 힌트가 아무 쓸모 없어진다. 각 선택지가 서로 다른 인생 축(건강/재정/관계/감정, 또는 학업/체육/예술/봉사)을 대표해야, 사주 힌트가 특정 축을 가리키는 단서로 기능한다. 변별력 = 차원의 다양성이다.


남은 과제

  • 내 사주보기 플로팅 챗봇 (만세력 계산 + Gemini AI 해석)
  • 추리 결과 Supabase 저장 (통계/리더보드)
  • 추리 결과 전용 URL (공유 링크에서 직접 결과 확인)
  • generated-questions analysis 콘텐츠 품질 보강
  • 문제/인물 데이터 Supabase 이관
  • 힌트-정답 겹침 자동 검증 스크립트