히라가나·가타카나 학습관 개선하기
가나 연습 화면을 만든 지 10일쯤 됐다. 그동안 매일 썼다. 만든 사람이 매일 쓰는 건 좋은 일인데, 좋은 이유가 내가 생각한 것과 달랐다. 잘 만들어서가 아니라 어디가 걸리는지 몸이 먼저 알게 돼서다.
이 화면은 대충 이렇다. 맨 위에 오늘 할 일이 있고, 그 아래 히라가나·가타카나 탭, 그 아래 행 목록이 27줄. 행마다 버튼이 둘 붙어 있다 — 「학습」에서 다섯 자쯤을 넘겨보며 소리를 붙이고, 「연습」에서 그걸 문제로 푼다. 배운 글자는 다음 행을 풀 때 섞여 다시 나온다.
오늘은 그렇게 걸리던 것들을 쭉 적어봤다. 여럿이었는데 다 적고 보니 하나로 모였다. 매일 오는 사람이 매일 지나쳐야 하는 것들.
같은 일을 하는 버튼은 한 자리로 모은다
맨 위 칸에 있는 「오늘 복습」은 정해진 몫이다. 오늘 물어봐야 할 글자만 낸다. 그걸 다 하고도 더 하고 싶은 날이 있어서, 오늘 몫과 별개로 더 연습하는 방법을 따로 뒀다.
그런데 그 방법이 두 개였다.
| 자리 | 내는 범위 | 한 번에 | |
|---|---|---|---|
| 섞어 풀기 | 오늘 할 일 칸 안, 진도 막대 아래 | 히라가나 + 가타카나 | 30자 |
| 연습하기 | 그 칸 밖 탭 줄 오른쪽 | 탭에서 보고 있는 쪽만 | 25자 |
만들 때는 각각 이유가 있었다. 연습하기는 탭 줄에 붙어 있으니 탭이 정하는 범위가 맞다. 섞어 풀기는 둘을 다 배운 뒤에 하는 마무리라 조금 더 길게 낸다. 따로 보면 둘 다 말이 된다고 생각했다.
문제는 하는 일이 하나라는 거였다. 둘 다 오늘 몫 말고 조금 더 연습하자는 것뿐인데, 쓰는 사람은 자리도 범위도 분량도 두 벌을 외워야 했다.
게다가 위아래로 붙어 있었다. 한 화면에 같이 보이는데도 서로 모르는 사이처럼 굴었다.
내 손이 어느 쪽으로 갔는지가 답이었다. 붙여둔 지 일주일이 넘도록 「연습하기」는 한 번도 안 눌러보게 되더라. 더 하고 싶은 날은 있었는데, 그때마다 위에 있는 「섞어 풀기」를 눌렀다.
그러면 하나로 합치는 게 맞다. 남은 건 그 하나를 어디에 둘까였는데, 그건 다음 문제가 정해줬다.
할 수 없는 일은 버튼으로 내놓지 않는다
두 자리로 갈린 것보다 더 걸리는 게 있었다.
연습하기는 오늘 복습이 남아 있어도 눌렸다. 그런데 누르면 연습이 시작되지 않고 「오늘 복습 25자를 먼저 하세요」 하는 안내가 떴다.
이 안내를 여러 번 고쳤다. 문구를 다듬고, 숫자를 넣고, 뺐다. 매번 조금 나아지는 것 같았는데 계속 걸렸다.
고쳐볼수록 분명해진 건, 문제가 말에 있지 않다는 것이었다. 버튼이 거기 있다는 것 자체가 「지금 이걸 할 수 있다」는 약속인데 누르면 못 한다고 하니, 할 수 없는 일을 버튼으로 내놓고 말로 되돌리는 모양이 된다. 그 어긋남은 문구로 메울 수가 없다.
그래서 버튼을 옮겼다. 오늘 복습을 다 끝내면 그 자리가 「더 연습하기」로 바뀐다.
같은 자리를 이어 쓰는 게 요점이다. 할 일이 남았으면 복습 버튼이고, 끝냈으면 그 자리가 더 연습하는 방법이 된다. 눈이 가는 데는 한 곳이고, 순서는 안내문이 아니라 화면이 생긴 모양으로 지켜진다.

없는 버튼은 설명할 것도 없다. 안내문도, 이 버튼이 왜 이렇게 동작하는지 코드에 적어둔 주석도 없어졌다. 그 자리를 차지하던 섞어 풀기 설명 두 문단까지 빠지면서, 같은 높이에 행이 셋 들어왔다.
감추는 대신 잠가서 화면을 누구에게나 같게 둔다
「더 연습하기」를 누르면 무엇을 풀지 고르는 화면이 뜬다. 히라가나만, 가타카나만, 둘을 섞어서. 이 셋을 갈래라고 부르자.
문제는 아직 가타카나를 시작 안 한 사람이다. 가타카나는 낼 카드가 아예 없고, 섞은 갈래는 둘 다 시작해야 열리게 해뒀다. 그 사람에게는 셋 중 둘이 잠긴다. 감출까 싶었는데 안 감췄다.
감추면 화면이 사람마다 다른 개수로 뜬다. 히라가나만 한 사람은 한 칸, 둘 다 한 사람은 세 칸을 본다. 그러면 어제 본 화면과 오늘 본 화면이 같은 화면인지 알 수 없다. 무엇보다 가타카나라는 게 있다는 사실 자체가 안 보인다.
이 화면이 진도와 상관없이 늘 같은 모양이었으면 했다. 셋이 같은 자리에 같은 크기로 서 있고, 사람마다 달라지는 건 그중 무엇이 열려 있느냐뿐인 것.

그래서 셋을 늘 세워두고, 못 고르는 것에는 자물쇠와 여는 법을 적었다. 문장은 이렇다 — 「가타카나를 한 글자 이상 풀면 열립니다」.
여기 두 가지가 들어 있다. 무엇이 막고 있는지, 그리고 그걸 어떻게 여는지.
「두 글자를 모두 시작하면 열립니다」라고 두면 읽고 나서도 물어볼 게 남는다. 두 글자가 뭔지, 시작한다는 게 어디까지인지.
「익히면」이 아니라 「풀면」인 것도 이유가 있다. 이 앱에서 「익힘」은 뜻이 정해진 낱말이다. 여러 날에 걸쳐 같은 글자를 몇 번씩 맞혀야 겨우 붙는다.
그런데 실제로 잠금이 열리는 조건은 한 문제만 풀면 된다. 맞히든 틀리든 상관없다. 한 문제면 되는 일에 며칠이 걸린다고 말하면 읽는 사람은 그럼 나중에 오자 하고 접는다. 화면이 실제보다 인색하게 말하는 것도 틀린 안내다.
자물쇠를 고른 데는 사심도 조금 있었다. 잠긴 걸 하나씩 여는 맛이 있으면 좋겠다 싶었다.
거창한 장치를 붙일 생각은 없었다. 가타카나를 한 글자 풀고 돌아왔더니 잠겨 있던 칸이 열려 있는, 그 정도면 충분하다고 봤다. 어차피 여는 조건은 이미 있던 것이고, 그걸 감추는 대신 보이게 둔 것뿐이다.
잠글 때도 접근성을 챙긴다
여기서 하나 놓칠 뻔했다. 버튼을 잠그는 방법을 고르는 일이었는데, 고르기에 따라 방금 적은 그 문장이 통째로 사라질 수 있었다.
버튼을 못 누르게 하는 방법이 두 가지다. HTML 의 disabled 를 걸거나, aria-disabled 로 「지금은 못 쓴다」고 알리기만 하고 실제로 막는 일은 코드가 하거나.
disabled 가 편하다. 브라우저가 알아서 막아주고 흐리게도 만들어준다. 그런데 그러면 그 버튼이 키보드 탭 순서에서 통째로 빠지고, 화면을 소리로 읽어주는 스크린 리더도 건너뛴다.
그러면 방금 공들여 적은 「가타카나를 한 글자 이상 풀면 열립니다」가 스크린 리더로 읽는 사람에게는 아예 없는 문장이 된다. 잠긴 이유를 화면에 적어두는 게 이 화면 설계의 전부인데, 그게 눈으로 보는 사람에게만 닿으면 절반만 만든 셈이다.
그래서 aria-disabled 쪽으로 갔다. 대신 막는 일을 코드가 해야 한다.
aria-disabled={locked}
onClick={() => {
if (locked) return;
onPick(scope);
}}이 if 한 줄은 꾸밈이 아니다. 섞은 갈래는 히라가나만 해둔 상태에서도 낼 카드가 있다 — 히라가나 카드가 남아 있으니까. 이 줄이 없으면 「히라가나 + 가타카나」를 눌렀는데 히라가나만 나온다. 고른 것과 나온 것이 다른, 제일 나쁜 종류의 버그다.
바꾸고 나니 가타카나를 아직 시작 안 한 사람에게는 이 화면에서 키보드로 닿는 곳이 둘에서 넷으로 늘었다. 갈래 셋과 「닫기」다.
못 고르는 것은 덮지 말고 빼서 가라앉힌다
하려던 일은 분명했다. 고를 수 있는 카드가 먼저 보이고, 못 고르는 카드는 뒤로 물러나 있을 것. 그런데 그걸 어떻게 그릴지에서 한참 헤맸다.
처음엔 카드 위에 어두운 막을 씌웠다. 게임에서 잠긴 스테이지를 그렇게 그리니까 자연스러워 보였다.
띄워놓고 보니 이상했다. 못 고르는 카드가 제일 눈에 띄었다.
이유는 단순했다. 밝은 바탕에 어두운 막을 얹는 건 대비를 더하는 일이다. 잠긴 카드는 짙은 회색 덩어리가 되고, 정작 고를 수 있는 카드는 흰 종이로 남는다. 가라앉히려던 것이 제일 무거워졌다.
어두운 화면에서는 정반대로 어긋났다. 바탕이 이미 거의 검정이라, 막을 덧씌우면 카드가 통째로 뚫린 구멍처럼 보였다. 같은 코드가 밝은 화면과 어두운 화면에서 서로 다른 방향으로 틀린 것이다.
막의 짙기를 이리저리 조절해 두 쪽을 다 맞춰보려다가, 결국 방향을 바꿨다.
덮는 대신 빼면 된다. 카드 하나를 통째로 옅게 하면 테두리도 글자도 자물쇠도 같은 만큼 물러난다. 무엇을 덧대 가릴지 자리마다 고를 필요가 없고, 밝은 쪽에서도 어두운 쪽에서도 똑같이 동작한다. 옅어지는 건 바탕이 무슨 색이든 뒤로 가는 일이니까.
- <span className="absolute inset-0 bg-scrim opacity-60" /> // 카드 위에 어두운 막을 한 겹
+ locked ? 'cursor-not-allowed opacity-50' : … // 카드 자체를 옅게덧대던 것이 없어졌고, 씨름하던 문제도 같이 사라졌다.
같은 이유로 정한 숫자는 하나로 합친다
한 번에 내는 최대 장수가 둘이었다. 오늘 복습 25자, 섞어 풀기 30자.
자리가 멀리 떨어져 있을 때는 티가 안 났다. 갈래 셋을 한 화면에 나란히 세우고 나니 옆 칸과 숫자가 다른 것이 무슨 뜻이 있어 보였다. 둘 다 「한 자리에서 끝낼 만큼」이라는 같은 이유로 정한 수인데, 이유가 같으면 숫자도 같아야 한다.
합치면서 20자로 내렸다. 25자를 매일 풀어 보니 끝이 조금 지겨웠기 때문이다.
상한은 못 견딜 선이 아니라 내일 또 올 만한 선으로 잡아야 한다. 같은 글자를 사이를 두고 다시 묻는 방식이라, 오늘 백 자를 몰아치는 것보다 내일도 오는 쪽이 낫기 때문이다.
창 높이를 붙들어 글자만 바뀌게 한다
학습과 연습은 화면을 덮고 뜨는 창 안에서 돌아간다. 창을 띄우지 않고 행 목록 자리에 문제를 그릴 수도 있었는데, 그 아래에 오십음도 전체 표가 깔려 있다. 스크롤만 내리면 답이 다 보이는 자리에서 문제를 낼 수는 없었다.
그 창이 며칠째 어수선했다. 문제를 풀 때마다 느꼈는데, 어디가 어수선한지는 끝내 짚이지 않았다.
재봤다. 창의 높이다.
| 화면 | 창 높이 |
|---|---|
| 학습 · 행 설명 없는 행 | 362px |
| 연습 · 쓰기 문제 | 365px |
| 연습 · 읽기 문제 | 377px |
| 학습 · 행 설명 있는 행 | 424px |
| 학습 · 로마자 표기가 한 줄 더 붙는 글자 | 448px |
가장 낮을 때와 높을 때가 86px 차이다. 더 나쁜 건 언제 움직이냐였다. さ행을 한 장씩 넘겨보니 이랬다.
424 → 448 → 424 → 424 → 424
두 번째 장에서만 튀었다가 돌아온다. 그 장이 し 다. し 는 로마자로 shi 라고 쓰는데 표기법에 따라 si 로도 쓴다. 그 한 줄이 이 글자에만 붙는다. 줄이 생겼다 사라지면서 창이 24px 자랐다 줄었다.
연습에서는 더 잦다. 읽기 문제는 가나를 크게 내고 쓰기 문제는 로마자를 낸다. 둘의 글자 크기가 달라서(72px 와 60px) 문제를 넘길 때마다 12px씩 오르내렸다.
보러 온 것은 글자인데 눈이 먼저 잡은 건 그걸 담은 틀이었다. 어디가 어수선한지 못 짚은 것도 그래서다. 내용만 들여다보고 있었으니 답이 나올 리가 없었다.
고치는 방법을 둘로 나눴다.
작은 자리는 미리 비운다. si 줄이 없는 글자에도 그 한 줄의 자리를 남겨둔다. 읽기·쓰기 글자가 앉을 칸도 큰 쪽에 맞춰 고정하고 그 안에서 가운데 세운다. 작은 글자가 나올 때 위아래로 6px 이 남지만, 글자가 제자리에 서는 값이 그보다 크다.
큰 자리는 하한으로 받는다. 행 설명은 미리 비워두기엔 컸다 — 62px 인데 27개 행 중 16개 행에만 있다. 그래서 창에 높이 하한을 주고, 가운데를 늘어나는 칸으로 뒀다. 설명이 없는 행에서는 그만큼을 가운데 여백이 대신 먹는다. 하한은 표에서 가장 높던 448px 로 잡았다.
결과는 이렇다.
448 448 448 448 448
다섯 장을 넘기는 동안 글자가 앉는 자리도 1px 안 움직인다. 행 설명이 있는 행과 없는 행도, 학습에서 연습으로 넘어갈 때도 창 밑변이 그대로다.
높이를 고정이 아니라 하한으로 둔 건 일부러다. 폰에서 행 설명이 넉 줄로 접히는 경우처럼 진짜로 더 필요할 때는 늘어나야 한다.
테두리는 누를 수 있는 것에만 두른다
학습 화면에서 행 설명이 테두리 상자에 담겨 있었다. 바로 아래에 ← 와 「다음」 버튼이 있고, 그것들도 테두리를 두르고 있었다.
한 창 안에서 테두리를 가진 것이 둘이었다. 하나는 누를 수 있는 것, 하나는 읽는 글.
그러면 눈이 훑을 때 「테두리 있는 것 = 누를 것」이라는 규칙을 못 세운다. 매번 읽어서 판단해야 한다.
테두리를 벗기고 왼쪽에 선 하나만 세웠다. 글에서 곁말을 다는 표시이고, 이 사이트의 인용문이 이미 같은 모양을 쓴다. 획이 하나라 버튼과 헷갈릴 일이 없다.
같은 이유로 갈래 고르는 화면에서도 글자를 감싸던 상자를 뺐다. 카드에 테두리가 있고 창에도 테두리가 있는데 글자까지 담아두니 액자 안의 액자 안의 액자였다. 글자는 그 자체로 충분히 덩어리라 담을 것이 없었다.
떠 있는 카드 대신 밑변에 붙은 시트로 만든다
폰에서 이 창들은 화면 아래쪽에 뜬다. 그런데 사방에 여백을 두고 네 귀가 둥근 카드 모양이라, 떠 있는 카드가 하필 밑변에 닿아 있는 꼴이었다.
자리도 손해였다. 390px 짜리 화면에서 창이 342px 로 물러나 있었다. 양옆 24px 씩 합쳐 48px 을 잃는 셈인데, 보기 넷을 늘어놓는 연습 화면에는 그 48px 이 아쉽다.
좌·우·아래 여백과 테두리를 없애고 위 두 귀만 둥글렸다. 아래에서 올라와 밑변에 붙은 시트로 읽히고, 그게 실제로 이것이 있는 자리다.

아래 여백은 그냥 0 이 아니라 아이폰이 알려주는 안전 영역만큼 남겼다. 밑변에 딱 붙이고 나면 화면 맨 아래 홈 인디케이터가 버튼 위에 겹치기 때문이다.
세로로 설 때는 내용도 같이 눕힌다
시트로 바꾸고 나서 하나가 더 걸렸다. 갈래 고르는 화면이 폰에서 화면을 거의 다 덮었다.
넓은 화면에서 셋을 가로로 펴는 카드 배치를 만들었는데, 폰에서 그게 그대로 세로로 쌓였다. 갈래는 셋뿐인데 시트가 594px 까지 늘어났다.
세로로 설 때는 내용도 같이 눕혀야 했다. 가나 글자를 왼쪽 끝에, 갈래 이름과 장수를 그 옆에, 화살표를 오른쪽 끝에. 세로로 쌓인 항목은 왼쪽 끝이 나란해야 목록으로 읽힌다.
390×717 짜리 화면에서 시트가 594px 에서 404px 로 줄었다.

높이 상한도 걸었다. 넘칠 때는 제목과 「닫기」가 붙박이로 남고 가운데 목록만 굴러간다. 시트에서 제일 헷갈리는 건 어디까지가 시트고 어디부터 뒤 페이지인지인데, 위아래가 고정돼 있으면 그 경계가 안 흔들린다.
한 번 읽으면 되는 글은 접어 둔다
오늘 할 일 칸에 「익힘은 어떻게 세나요」라는 설명이 펼쳐진 채로 있었다.
꼭 필요한 글이다. 같은 글자를 1일 → 2일 → 4일 간격으로 네 번 맞혀야 익힌 것으로 치기 때문에, 아무리 자주 들어와도 7일이 걸린다. 이 규칙을 모르면 며칠을 해도 익힘이 0 인 것을 고장으로 읽는다.
펴 두는 조건은 「익힘이 하나도 없으면」이었다. 처음 쓰는 사람에게 보여주자는 뜻이었다.
그런데 바로 그 7일이 문제였다. 연습을 시작하고 일주일 동안은 아무리 매일 해도 익힘이 0 이라, 그 사이 이 글은 계속 펼쳐진 채로 있었다. 내가 매일 지나친 게 그거다.
조건이 겨눈 자리도 빗나가 있었다. 진도가 하나도 없으면 이 설명 덩이가 화면에 나오지도 않게 돼 있었다. 처음 쓰는 사람에게 보여주려고 만든 조건인데, 정작 그 사람에게는 규칙이 통째로 가려져 있던 셈이다.
기준을 진도로 바꿨다. 진도가 없으면 아직 읽은 적 없을 테니 펴 두고, 한 문제라도 풀었으면 접는다. 처음 온 사람에게는 이제 가려지지 않고 펼쳐진 채로 나오고, 한 번 풀고 온 사람에게는 한 줄로 접혀 있다.

고치고 나니 하나가 더 보였다. 사실 나는 이게 접힌다는 것도 몰랐다. 접는 표시로 「펴기」라는 작은 글자를 제목 옆에 뒀는데, 그 글자를 접혀 있을 때만 보이게 해뒀다. 펼쳐져 있는 동안에는 접을 수 있다는 표시가 화면에 하나도 없었다는 뜻이다. 늘 펼쳐져 있었으니 나는 그 글자를 본 적이 없었다.
그래서 제목 앞에 화살표를 하나 붙였다. 접고 펴는 자리에서 흔히 보는 모양이라, 글자를 읽기 전에 눈에 먼저 걸리지 않을까 싶었다. 자리도 늘 지킨다 — 접혀 있든 펼쳐져 있든 그대로 있고 방향만 돌아가니, 펼쳐 놓고도 접을 수 있다는 게 보인다.
「펴기」라는 말은 「펼치기」로 바꿨다. 큰 차이는 아닌데 개인적으로 이쪽이 더 자연스럽게 읽혔다. 이 글자는 접혀 있을 때만 보인다. 접는 건 화면이 알아서 하니 안내가 필요한 쪽은 펼치는 쪽뿐이다. 한 번 눌러 펼쳐 본 사람은 그게 토글이라는 걸 자연스럽게 알 테니, 「접기」까지 두는 건 오히려 불필요해 보였다.
하나씩 고치는 재미
고친 것 대부분은 만들 때 각각 이유가 있던 것들이다. 연습하기가 탭 줄에 붙어 있던 것도, 섞어 풀기를 마무리로 따로 둔 것도, 상한이 25자와 30자로 갈린 것도.
각각은 지금 봐도 말이 된다. 다만 그 이유들이 한 화면 안에서 서로 만난 적이 없었다. 만들 때는 하나씩 정하는데 쓸 때는 한꺼번에 만난다. 그 차이는 며칠 매일 써보고 나서야 눈에 들어왔다.
돌아보니 하나하나가 다 작은 것들이었다. 버튼 하나를 옮기고, 숫자 둘을 하나로 하고, 설명 한 덩이를 접어 두고. 다 해놓고 화면을 여니 꽤 달라져 있었다. 몇 개는 테스트로 남겨뒀다 — 코드 한 줄이라 다음에 조용히 사라지기 쉬운 것들이라서.
매일 쓰는 화면이 조금씩 나아지는 게 좋다. 어제 걸리던 자리가 오늘은 안 걸리고, 그런 게 하나씩 늘어난다. 새로 만들 때랑은 또 다른 재미다.