타자연습에서 키보드와 손가락 가이드를 다시 만든 이유
타자연습을 처음 만들 때는 “다음에 눌러야 할 키를 보여주면 충분하겠지”라고 생각하기 쉽다. 하지만 실제로 연습 화면 앞에 앉아보면 이야기가 달라진다. 사용자는 화면의 지문, 입력창, 가상 키보드, 손가락 가이드를 거의 동시에 본다. 그중 하나라도 실제 손의 움직임과 어긋나면, 가...
타자연습을 처음 만들 때는 “다음에 눌러야 할 키를 보여주면 충분하겠지”라고 생각하기 쉽다. 하지만 실제로 연습 화면 앞에 앉아보면 이야기가 달라진다. 사용자는 화면의 지문, 입력창, 가상 키보드, 손가락 가이드를 거의 동시에 본다. 그중 하나라도 실제 손의 움직임과 어긋나면, 가이드는 도움이 아니라 방해가 된다.
지난 글에서는 Zero Human Studio의 타자연습을 작은 도구가 아니라 하나의 연습 공간으로 만들기 위해 어떤 흐름을 설계했는지 정리했다. 이후에는 더 세밀한 문제가 보였다. 입력창 포커스는 유지되지만, 가상 키보드가 실제 키보드처럼 보이지 않았다. 쌍자음과 쌍모음 입력에서 Shift 조합이 충분히 설명되지 않았다. 한글 음절 내부에서는 첫 자음만 안내하고, 중성이나 종성으로 자연스럽게 넘어가지 못했다. 심지어 Space 키는 키보드 하단 중앙이 아니라 왼쪽에 붙어 보였다.
이 글은 그 후속 개선을 정리한 기록이다. 다만 “키보드와 손가락 가이드”만 따로 떼어 보면 과정이 너무 좁게 보일 수 있다. 실제로는 그 전에 타자연습을 독립적인 제품 레이어로 세우는 플랜을 만들고, Claude Code를 작업 에이전트처럼 투입해 큰 뼈대를 구현한 뒤, 사람이 화면을 보며 계속 품질 기준을 조정하는 흐름이 있었다. 이후 스프라이트와 이미지 자산을 붙이고, 자리연습 8단계를 다시 정리하고, UX를 여러 차례 다듬으면서 지금의 형태가 되었다.
먼저 플랜을 세우고, 에이전트에게 맡길 수 있는 단위로 쪼갰다
이번 타자연습 작업은 화면을 보다가 즉흥적으로 컴포넌트를 붙인 작업이 아니었다. 처음에는 /typing을 사이트 안의 작은 위젯으로 둘지, 아니면 반복 사용이 가능한 별도 제품 레이어로 만들지부터 정해야 했다. 그래서 먼저 구현 플랜을 문서로 만들었다. 자리연습, 낱말연습, 단문연습, 장문/필사, 속도측정, 그리고 게임형 연습을 각각 어떤 역할로 둘지 나누고, 한국어 입력에서 반드시 막아야 할 문제도 적었다.
특히 한국어 타자연습은 영어권 타자 테스트를 그대로 가져오면 어색해진다. 화면에 보이는 한 글자와 실제 키 입력 횟수가 다르고, IME 조합 중인 글자가 완성된 글자처럼 보일 때도 있다. 그래서 플랜 단계에서부터 한글 자모 단위 메트릭, 두벌식 키보드 매핑, 자리연습의 keydown 기반 처리, 로컬 기록 저장, 입력 중 포커스 유지 같은 기준을 먼저 잡았다.
그 다음에는 Claude Code를 단순 코드 자동완성 도구가 아니라 작업 실행 에이전트처럼 사용했다. 사람이 방향과 품질 기준을 정하고, 에이전트가 큰 구조와 반복적인 구현을 빠르게 만들고, 다시 사람이 화면을 보며 “이건 연습 흐름이 끊긴다”, “이 위치는 너무 크다”, “키보드가 실제 배열처럼 보이지 않는다” 같은 피드백을 주는 방식이었다.
이 과정에서 중요했던 것은 AI에게 한 번에 “타자연습 만들어줘”라고 맡기는 게 아니었다. 먼저 플랜을 세우고, 그 플랜을 기준으로 구현 단위를 쪼개고, 결과물을 실제 브라우저에서 확인하면서 다시 수정하는 반복이었다. 타자연습은 특히 작은 어긋남이 바로 손의 리듬을 깨기 때문에, 기획 문서와 화면 검수가 같이 필요했다.
스프라이트와 이미지 자산도 제품 감각을 바꿨다
초기 타자연습은 기능 위주였다. 모드 카드도 텍스트 중심이었고, 게임형 연습도 코드상으로는 동작할 수 있지만 시각 자산은 비어 있는 상태에 가까웠다. 그러다 보니 “연습 공간”이라기보다는 아직 개발 중인 도구처럼 보였다.
그래서 /public/typing 아래에 모드별 일러스트와 게임 자산을 정리했다. 자리연습, 낱말연습, 단문연습, 장문연습, 게임 모드 카드에 들어갈 이미지를 따로 만들고, 워드 디펜스 게임에는 우주선, 운석, 보석, 폭발, 배경, 효과음 같은 자산을 연결했다. 자산 프롬프트 문서에는 필요한 비율, 출력 크기, 파일명, 후처리 방식까지 적어두었다.
예를 들어 게임 정적 스프라이트는 한 장의 시트에서 운석과 우주선, 보석을 잘라 쓰는 방식으로 계획했다. 폭발은 32프레임 스프라이트 시트로 생각했고, 배경은 parallax에 맞게 레이어를 나누는 방향으로 잡았다. 효과음도 키 입력음, 성공음, 실패음, 게임 충돌음처럼 기능별로 나눴다.
이런 자산 작업은 단순히 보기 좋게 꾸미는 일이 아니었다. 사용자가 /typing에 들어왔을 때 “여기는 그냥 폼 하나가 아니라, 여러 연습 모드를 가진 공간”이라고 느끼게 만드는 장치였다. 텍스트만 있는 모드 카드와 이미지가 붙은 모드 카드는 같은 기능을 가리켜도 제품의 밀도가 다르게 느껴진다.
가상 키보드는 장식이 아니었다
처음의 가상 키보드는 기능적으로는 키를 표시했다. 하지만 실제 키보드처럼 느껴지지는 않았다. 행 간격과 들여쓰기, Shift 키 위치, Space 키의 폭과 중심이 조금씩 어긋나 있었다. 데스크톱에서는 대충 보아 넘길 수 있어도, 모바일 화면에서는 이런 차이가 더 크게 드러난다. 키보드가 왼쪽으로 쏠리거나 카드 밖으로 잘려 보이면, 사용자는 연습보다 레이아웃 문제를 먼저 의식한다.
그래서 가상 키보드를 다시 구성했다. 숫자열, 윗자리, 기본자리, 아랫자리, 스페이스열을 명확히 나누고, Tab, Caps Lock, Backspace, Enter, 좌우 Shift, Space 같은 보조 키를 함께 배치했다. 단순히 글자 키만 나열하는 대신, 사용자가 실제 키보드의 공간감을 떠올릴 수 있게 하려는 의도였다.
특히 Space 키는 중요했다. 스페이스바는 실제 키보드에서 하단 중앙에 있는 가장 긴 키다. 그런데 웹 UI에서 Space를 한 줄짜리 grid item으로만 두면 쉽게 왼쪽에 붙는다. 그래서 명시적인 grid 위치와 span을 부여해 키보드 전체의 하단 중앙에 오도록 조정했다. 작은 수정처럼 보이지만, 키보드 전체가 “실제 키보드처럼” 보이게 만드는 데 큰 차이를 만들었다.
기본자리 8단계도 다시 정리했다
자리연습은 특히 구성 순서가 중요했다. 처음에는 대략적인 키 영역만 나뉘어 있었지만, 실제 연습 메뉴 안에서는 사용자가 지금 어느 단계를 연습하는지, 다음에는 어디로 넘어가야 하는지 더 명확해야 했다. 그래서 자리연습을 1단계부터 8단계까지 다시 정리하고, 해당 메뉴 안에서 바로 이동할 수 있게 했다.
현재 기준은 다음처럼 잡았다.
1단계 기본자리: ㅁ ㄴ ㅇ ㄹ ㅓ ㅏ ㅣ ; :
2단계 오른손 윗자리: ㅕ ㅑ ㅐ ㅒ ㅔ ㅖ
3단계 왼손 윗자리: ㅂ ㅃ ㅈ ㅉ ㄷ ㄸ ㄱ ㄲ
4단계 오른손 아랫자리: ㅡ , < . > / ?
5단계 왼손 아랫자리: ㅋ ㅌ ㅊ ㅍ
6단계 가운뎃자리: ㅅ ㅆ ㅎ ㅠ ㅛ ㅗ ㅜ
7단계 숫자자리: 1! 2@ 3# 4$ 5% 6^ 7& 8* 9( 0) -_ =+
8단계 전체자리: 앞 단계 전체 복습여기서 핵심은 “단계를 늘렸다”가 아니라, 자리연습의 성격을 일반 지문 연습과 다르게 본 것이다. 낱말이나 단문은 완성된 글을 읽고 입력하는 흐름이지만, 자리연습은 지금 눌러야 할 키 하나를 몸에 익히는 과정이다. 그래서 지문 표시 방식도 별도로 바꿨다. 현재 눌러야 할 키를 크게 보여주고, 주변 키들은 좌우로 이어진 preview처럼 보여주며, 진행에 따라 하나씩 시프트되도록 했다.
또 자리연습은 한글 IME 조합에 그대로 맡기면 문제가 생긴다. 예를 들어 ㅁ 다음에 ㅏ를 치면 브라우저 입력값은 마로 조합될 수 있지만, 자리연습 목표는 여전히 ㅁ, ㅏ라는 개별 타건이다. 그래서 이 모드에서는 일반 textarea 변경값만 믿지 않고, KeyboardEvent.code와 Shift 상태를 두벌식 배열로 매핑해 한 타건씩 처리하는 방향으로 안정화했다.
Shift 조합을 표시해야 하는 이유
한국어 두벌식에서는 Shift가 단순히 대문자를 만드는 키가 아니다. ㅂ은 ㅃ이 되고, ㅈ은 ㅉ이 되고, ㅐ는 ㅒ가 된다. 기호 입력에서도 ;는 :, /는 ?, ,는 <, .는 >가 된다. 타자연습에서 이런 입력을 그냥 하나의 문자로만 보여주면 사용자는 어떤 조합을 눌러야 하는지 바로 알기 어렵다.
그래서 키캡 안에 Shift 결과를 함께 표시했다. 예를 들어 q ㅂ 키에는 ⇧ㅃ, o ㅐ 키에는 ⇧ㅒ, / 키에는 ⇧?가 보인다. 다음 입력이 Shift 조합일 때는 문자 키만 강조하지 않고, 필요한 Shift 키도 함께 하이라이트했다.
여기서 기준은 표준 터치타이핑의 반대손 Shift 원칙으로 잡았다. 왼손으로 누르는 문자 키는 오른손 Shift를 쓰고, 오른손으로 누르는 문자 키는 왼손 Shift를 쓴다. 예를 들어 ㅃ은 왼손의 ㅂ/Q 키를 누르므로 오른쪽 Shift를 함께 사용한다. 반대로 ㅖ는 오른손의 ㅔ/P 키를 누르므로 왼쪽 Shift를 함께 사용한다.
이 기준을 UI에만 하드코딩하지 않고, 키보드 메타데이터에 requiredShift, shiftSide, output 같은 힌트로 넣었다. 그래야 가상 키보드와 손가락 가이드가 같은 정보를 바라보고 움직일 수 있다.
한글 음절은 한 글자가 아니라 여러 번의 타건이다
가장 중요한 수정은 한글 음절 내부 안내였다. 화면에는 팀이라는 한 글자가 보이지만, 실제 두벌식 입력은 ㅌ → ㅣ → ㅁ 세 번의 타건으로 이루어진다. 원도 ㅇ → ㅜ → ㅓ → ㄴ 순서로 입력된다. 그런데 기존 로직은 JavaScript 문자열 길이를 기준으로 다음 글자를 찾고 있었다. 그러면 팀을 한 글자로 보고 넘어가기 때문에, 내부의 ㅣ와 ㅁ을 제대로 안내하기 어렵다.
해결 방식은 단순하지만 핵심적이었다. 목표 지문과 사용자가 입력한 값을 모두 같은 방식으로 자모 타건 순서로 분해한 뒤, 입력된 자모 수만큼 진행 위치를 계산했다.
팀원 → ㅌ → ㅣ → ㅁ → ㅇ → ㅜ → ㅓ → ㄴ
예쁜 → ㅇ → ㅖ → ㅃ → ㅡ → ㄴ이제 팀원을 입력할 때 가이드는 ㅌ 다음에 바로 다음 글자로 넘어가지 않는다. ㅣ, ㅁ, ㅇ, ㅜ, ㅓ, ㄴ 순서로 실제 손이 눌러야 할 키를 따라간다. 예쁜처럼 Shift가 들어가는 예시도 마찬가지다. ㅖ는 왼쪽 Shift와 ㅔ/P, ㅃ은 오른쪽 Shift와 ㅂ/Q가 함께 안내된다.
이 변화는 숫자 하나를 바꾸는 수준이 아니었다. 타자연습이 “완성된 글자를 맞히는 게임”이 아니라 “실제 키를 누르는 연습”이라는 점을 다시 확인한 수정이었다.
손가락 가이드도 같은 정보를 봐야 한다
가상 키보드만 정확해도 충분하지 않았다. 화면 아래의 손가락 가이드가 여전히 문자 키 손가락 하나만 표시하면, Shift 조합에서 정보가 반쪽이 된다. 사용자는 ㅃ을 입력해야 한다는 것은 알지만 어느 손으로 Shift를 눌러야 하는지 다시 추측해야 한다.
그래서 손가락 가이드에 Shift 손가락 정보를 전달했다. 왼쪽 Shift가 필요하면 왼손 새끼손가락을, 오른쪽 Shift가 필요하면 오른손 새끼손가락을 함께 강조한다. 라벨도 왼Shift + 오5, 오Shift + 왼5처럼 조합으로 보여준다.
Space 입력도 같은 원칙으로 처리했다. 이전에는 다음 입력이 공백이면 아무 키도 안내하지 않았다. 하지만 띄어쓰기도 명확한 타건이다. 그래서 다음 문자가 공백일 때는 Space를 하나의 KeyHint로 반환하고, 가상 키보드의 Space 키를 하이라이트했다. 손가락 가이드는 왼쪽 엄지와 오른쪽 엄지를 함께 표시하도록 했다. 실제 사용자는 한쪽 엄지만 쓸 수도 있지만, 연습 안내에서는 양쪽 엄지가 Space 담당이라는 점을 보여주는 편이 자연스럽다.
지문 표시 방법도 모드별로 갈라졌다
지문 표시도 한 번 크게 바꿨다. 처음에는 긴 지문이 카드 안에서 스크롤되거나, 현재 위치를 표시하는 주황색 세로선이 입력 커서처럼 보이는 문제가 있었다. 사용자는 실제 입력 커서를 봐야 하는데, 지문 앞의 장식선이 또 다른 커서처럼 보이면 시선이 분산된다. 그래서 그 장식적 세로선을 제거하고, 모드별로 지문 표시 방식을 분리했다.
낱말연습과 속도측정에서는 지문을 최대 두 줄로 제한했다. 첫 번째 줄은 지금 입력해야 하는 현재 라인이고, 두 번째 줄은 다음에 칠 라인을 옅게 보여주는 미리보기다. 현재 줄이 끝나면 두 줄이 위로 밀리듯 다음 묶음으로 넘어간다. 내부 스크롤을 없애고, 연습 흐름이 지문 영역 안에서 계속 이어지게 한 것이다.
반대로 자리연습은 두 줄 문장 표시가 맞지 않았다. 자리연습에서는 문장을 읽는 것이 아니라 현재 키 하나를 익히는 것이 핵심이다. 그래서 “입력할 자리”를 중심으로 현재 키 하나를 크게 강조하고, 나머지는 흐름을 보여주는 보조 정보로 낮췄다. 같은 타자연습이라도 모드가 다르면 지문 UI도 달라져야 했다.
입력 흐름을 방해하지 않는 것
이번 개선의 배경에는 계속 같은 원칙이 있었다. 타자연습에서 사용자의 손이 멈추면 안 된다. 포커스가 입력창에서 벗어나거나, 스페이스바가 페이지를 스크롤하거나, 다음 지문으로 넘어간 직후 습관적으로 누른 Space/Enter가 오타로 들어가면 연습 리듬이 깨진다.
그래서 이전 작업에서는 입력창 포커스와 스크롤 위치를 보존하고, 단문 모드에서 자동 전환 직후의 Space/Enter를 무시하도록 했다. 사용자가 지문을 끝낸 뒤 습관적으로 Space나 Enter를 눌러도 페이지가 스크롤되거나, 다음 지문의 첫 오타로 들어가거나, 버튼으로 포커스가 이동하지 않도록 막았다.
도전단계 UI도 조정했다. 처음에는 목표 단계가 상단에서 너무 크게 보였고, 실제 입력보다 목표 UI가 더 강하게 느껴졌다. 그래서 크기를 줄이고 연습 영역 하단으로 내려, 사용자의 주의가 지문과 입력창에 먼저 머물도록 했다. 입력란은 페이지 진입 시 바로 포커스되도록 하고, 시각적으로도 “여기에 치면 된다”는 느낌이 더 강하게 보이게 했다.
이번 작업은 그 위에 가이드의 정확도를 더한 것이다. 사용자는 계속 같은 입력창에 머물고, 화면은 다음에 누를 키와 손가락만 조용히 알려준다.
특히 모바일 화면에서는 이 원칙이 더 중요하다. 화면 높이가 제한되어 있기 때문에 지문, 입력창, 키보드, 손가락 가이드가 서로 밀어내면 안 된다. 키보드는 카드 안에 들어와야 하고, Space 키는 중앙에 있어야 하며, 하이라이트는 작지만 분명해야 한다.
구현은 큰 커밋 한 번보다 작은 검수의 반복이었다
작업 흐름을 돌아보면, 실제 구현은 여러 단계로 나뉘었다. 먼저 Claude Code를 통해 /typing의 큰 구조를 만들었다. 이때 자리/낱말/단문/장문/속도/게임 모드, 로컬 기록 저장, 자모 기반 메트릭, 워드 디펜스 게임, 결과 패널, 도전단계, 손가락 가이드 같은 뼈대가 들어갔다. 이후 lint와 build를 맞추고, 실제 화면에서 어색한 흐름을 찾아 다시 고쳤다.
그 다음에는 사용 중 발견된 문제를 별도 플랜으로 분리했다. 입력 연속성 플랜에서는 포커스 유지, 스크롤 방지, 지문 2줄 표시, 도전단계 축소, 자리연습 단계 이동, 자리연습 전용 지문 UI를 acceptance criteria처럼 적었다. 그리고 이 기준에 맞춰 다시 구현했다.
이미지와 스프라이트 자산도 같은 방식이었다. 먼저 필요한 자산 목록과 프롬프트를 정리하고, 생성된 이미지와 사운드를 파일명과 경로에 맞춰 후처리했다. 이후 코드에서 해당 자산을 lazy load하거나 public path로 연결하고, 없는 경우에도 앱이 깨지지 않게 했다.
이 방식은 느려 보이지만, 결과적으로는 더 빨랐다. 막연히 “더 예쁘게”라고 말하는 대신, “Space 키는 하단 중앙”, “공백 차례에는 양 엄지”, “낱말/속도 지문은 두 줄”, “자리연습은 현재 키 하나”처럼 검수 가능한 문장으로 바꿨기 때문이다.
구현하면서 확인한 것들
이번 개선은 코드 수정만으로 끝내지 않았다. 변경 후에는 빌드와 브라우저 확인을 반복했다. 예를 들어 팀원과 예쁜이 실제로 어떤 자모 순서로 분해되는지 별도로 출력해 확인했다.
팀원→ㅌ ㅣ ㅁ ㅇ ㅜ ㅓ ㄴ예쁜→ㅇ ㅖ ㅃ ㅡ ㄴㅖ→ 왼쪽 Shift +ㅔ/Pㅃ→ 오른쪽 Shift +ㅂ/Q
브라우저에서는 단문 연습과 자리연습을 직접 열어 확인했다. 자리연습에서는 ㅒ, ㅖ, < 같은 Shift 조합에서 문자 키와 Shift 키가 함께 강조되는지 봤다. 단문 연습에서는 팀워크는까지 입력한 뒤 다음 차례가 공백일 때 Space 키가 하이라이트되고, 손가락 가이드가 왼1 + 오1로 바뀌는지 확인했다.
이런 확인은 번거롭지만 필요하다. 입력 UI는 코드상으로 맞아 보여도 실제 화면에서는 위치가 어긋나거나, 포커스가 예상과 다르게 움직이거나, 모바일 폭에서만 깨질 수 있기 때문이다.
이번에 남은 생각
가상 키보드와 손가락 가이드는 타자연습의 보조 기능처럼 보이지만, 실제로는 연습 품질을 결정하는 핵심 피드백이다. 지문이 무엇인지 알려주는 것만큼, 다음에 어떤 손가락을 움직여야 하는지 알려주는 일도 중요하다.
이번 개선을 통해 Zero Human Studio의 타자연습은 조금 더 “입력 결과를 채점하는 화면”에서 “입력 과정을 안내하는 화면”에 가까워졌다. 처음에는 타자 속도를 재는 기능처럼 출발했지만, 지금은 플랜, 모드 구성, 자산, 게임, 단계, 입력 UX, 지문 표시, 키보드/손가락 가이드가 서로 맞물린 작은 제품에 가까워졌다. 아직 더 할 일은 많다. 약점 자모를 기반으로 연습을 추천하거나, 모바일 전용 레이아웃을 더 다듬거나, 장문 필사에서 문단 단위 흐름을 개선할 수도 있다.
하지만 이번 단계에서 얻은 기준은 분명하다. 타자연습은 완성된 글자보다 실제 타건을 기준으로 설계해야 한다. 특히 한국어에서는 한 음절이 여러 번의 키 입력으로 만들어진다는 사실을 UI가 끝까지 따라가야 한다. 그 작은 차이가 연습을 훨씬 더 믿을 만하게 만든다.
Related posts
Read →Related tools