KBO 덕력은 원래 토스 미니앱으로 따로 만들어져 있던 프로젝트다. 규칙 300문항, 구단 500문항, 기록 300문항, 역사 300문항, 총 1400문항짜리 야구 퀴즈 은행에 XP·레벨·업적·오답노트·주간 리포트까지 붙어 있다. 토스 앱 세 개 중에서 콘텐츠가 가장 두껍다. 이걸 토스 안에서만 도는 미니앱이 아니라 누구나 브라우저로 바로 여는 웹 페이지로 옮기는 작업이었다. 문제를 새로 쓰는 게 아니라 옮기기만 하면 되니 금방 끝날 줄 알았다.
화면이 그냥 하얗게 떴다
토스 SDK 호출부터 걷어냈다. 광고·공유·분석 로그는 이미 "지원되는 환경인지" 확인과 try/catch로 감싸져 있어서 브라우저에서는 조용히 넘어갔다. 문제는 라우팅이었다. 옮긴 그대로 띄웠더니 상단 바와 하단 탭만 보이고 본문이 비어 있었다. React Router에 basename을 안 줬던 게 원인이다. 원래 앱은 도메인 루트에서 돌았지만 이번엔 play.ghmun.com/kbo-deok/ 밑에서 도는데, 라우터는 여전히 /만 찾고 있었다. basename="/kbo-deok" 한 줄로 해결됐다. 한 줄 고치는 데 원인 찾는 시간이 훨씬 더 걸렸다.
뒤로가기 버튼이 앱 전체를 죽였다
basename을 고치고 나니 이번엔 상단 바도 없는 완전한 흰 화면이었다. 안드로이드 뒤로가기 버튼을 앱 안 이동에 연결하는 graniteEvent.addEventListener("backEvent", ...) 호출이 범인이었다. 이 함수는 실패를 콜백으로 알려주지 않고, 토스 앱 밖에서는 호출하는 순간 바로 오류를 던진다. 다른 토스 SDK 호출은 전부 try/catch로 감싸져 있었는데 이 한 군데만 빠져 있었다. 화면이 그려지기도 전에 앱 전체가 멈춘 이유다. 호출을 try/catch로 감싸고, 실패하면 아무것도 구독하지 않게 고쳤다. 방어 코드를 열 군데 잘 짜놓고 한 군데를 빼먹으면 이렇게 된다.
진행 상황 저장도 손봐야 했다
사용자 데이터는 토스가 발급하는 익명 식별키로 구분해서 토스 저장소에 넣던 구조였다. 웹에는 그런 식별키가 없으니 브라우저 localStorage로 바꿨다. 기존 비동기 방식의 함수 모양은 그대로 둬서 나머지 코드는 거의 안 건드렸다. 대신 기록은 그 기기, 그 브라우저에만 남는다.
만렙까지 7년 걸리게 설계돼 있었다
옮기면서 레벨 파일을 열어봤다가 좀 놀랐다. 레벨은 Lv.0부터 Lv.99까지고, 10레벨마다 "야구 입문", "직관러", "스탯 덕후"를 거쳐 "KBO 고인물"까지 칭호가 바뀐다. 필요 XP는 제곱 곡선이라 초반은 1주 만에 Lv.10이 되지만, 모든 모드를 매일 다 챙기는 사람도 Lv.99까지 약 7년이 걸린다. 계산 근거까지 주석에 적혀 있다. 찐팬 테스트, 오늘의 퀴즈, 구단 집중, 분야 마스터, 고인물 챌린지, 공유 보너스를 하루도 안 빼고 다 하면 하루 약 858 XP다. 858 곱하기 365 곱하기 7, 만렙 XP는 219만이다. Lv.90까지만 해도 약 5.8년이다. 오늘의 퀴즈만 푸는 사람은 이보다 훨씬 오래 걸린다. 주석에는 "아직 실제 유저가 없는 시점이라 예전 XP 호환은 신경 쓰지 않았다"는 말도 있다. 유저가 없을 때 7년짜리 계획부터 세운 셈이다. 그 자신감은 존중한다. 업적은 "첫 삼진", "복습왕", "위클리 덕후" 등 12개, 찐팬 테스트 결과로 나오는 팬 유형은 "규칙 박사형"부터 "고인물형"까지 6개다. 웹으로 옮기면서 이 수치는 하나도 안 바꿨다.
사용법
홈의 "10문제로 덕력 확인하기"를 누르면 네 분야에서 무작위로 뽑힌 10문제짜리 찐팬 테스트가 시작된다. 하단 "탐험" 탭에서는 오늘의 퀴즈·구단별 퀴즈·분야별 퀴즈·고인물 챌린지를 따로 고를 수 있다. 틀린 문제는 오답노트에 쌓여 홈에서 바로 복습된다. 응원 구단을 정하면 그 구단 문제가 더 자주 나오고 홈에 구단 카드가 뜬다. 결과 화면의 광고 자리는 웹에서는 비어 있다.