같은 글자 크기가 31가지였다
11월 8일에는 화면 하나를 고치지 않고, 화면을 그리는 규칙을 고쳤습니다. 시작한 이유는 intent 에 문제로 적은 세 가지였습니다.
첫째는 큰 글자입니다. iOS 텍스트 크기를 접근성 크기(accessibility-large)로 올리면 탭 레이블, 배지, 제목이 고정 높이에 걸려 잘렸습니다. 최대 크기에서는 홈과 단어장 글자 대부분이 반쯤 잘려 보였다고 그날 시뮬레이터 기록에 남아 있습니다. 글자가 커지는 상한을 정하는 maxFontSizeMultiplier 는 앱 전체에 한 곳도 없었습니다.
둘째는 같은 것이 화면마다 조금씩 달랐다는 점입니다. 글자 크기를 text-[13.5px] 처럼 숫자로 바로 쓴 곳이 31종, 826곳이었습니다. 눌림 투명도 5종, 비활성 투명도 4종, 모달 뒤 막 17곳, 애니메이션 길이 15종 이상. 새 화면을 만들 때마다 값을 새로 고르고 있었던 것입니다.
셋째는 강조가 너무 많았다는 것입니다. 홈 첫 화면에 주황(accent) 테두리 카드가 셋, 주황 버튼이 셋 있었고, 목록 안의 작은 시작 버튼까지 주황 빛 그림자를 달고 있었습니다. DESIGN.md 에는 강조가 셋이면 강조가 없는 것과 같다고 적었습니다.
규칙은 가져오고 숫자는 가져오지 않았다
기준으로 삼은 것은 Astryx 의 디자인 규칙입니다. 간격, 리듬, 모양, 높이, 글자, 색, 움직임 일곱 가지 기초와 상태 표현 규칙을 가져왔습니다. 다만 숫자는 버렸습니다. 웹 컨트롤 28/32/36px, 본문 14px 같은 값은 데스크톱 웹 기준이라, 44pt 터치와 한국어·일본어가 섞인 글자에 맞게 다시 정했습니다. 패키지도 설치하지 않았습니다. Astryx 는 웹 React 와 StyleX 기반이고, 네이티브 쪽은 canary 실험이라 React Native 바인딩이 없다는 것이 DESIGN.md 머리에 남긴 이유입니다. 색과 글꼴은 기존 팔레트 그대로 두었습니다.
글자는 10개 역할로 모았습니다. 핵심은 크기와 줄 높이를 따로 둔 것입니다.
// 글자 크기는 역할로 고른다(DESIGN.md §3). 크기만 담고 줄 높이는 넣지 않는다 — 한 줄짜리
// 고정 높이 요소(버튼·배지·칩)는 줄 높이 없이 높이 + 가운데 정렬로 맞추고, 여러 줄 본문만
// 같은 이름의 `leading-*` 를 붙인다.
fontSize: {
micro: "11px",
caption: "12px",
footnote: "13px",
body: "15px",
callout: "17px",
// … title3 20 · title2 24 · title1 28 · display 40
hero: "56px",
},
lineHeight: {
micro: "16px",
caption: "18px",
// …
hero: "82px",
},
이렇게 나눈 데에는 이 앱이 줄 높이 때문에 두 번 반대 방향으로 넘어진 이력이 있습니다. 6월 19일(81a09b8)에는 줄 상자가 너무 좁아서 카탈로그 탭 글자의 아래 획이 잘렸습니다. 커밋에 "그→ㄱ, 가→기"라고 적혀 있습니다. 7월 9일(fd97b4a)에는 반대로 자연스러운 크기보다 작은 줄 높이를 고정했더니 iOS 에서 Noto Sans KR 글자가 줄 위쪽으로 떴고, 버튼과 배지를 고정 높이 + 가운데 정렬로 다시 짰습니다. 그래서 규칙을 둘로 갈랐습니다. 한 줄짜리 고정 높이 요소에는 줄 높이를 아예 주지 않고, 여러 줄 본문에는 글자 크기의 1.45배 이상(Noto Sans KR·JP 의 자연 줄 상자)을 줍니다.
이 규칙은 문서로만 두지 않고 테스트에 넣었습니다.
it('keeps each role leading at or above the Noto Sans natural line box (1.45×)', () => {
// 자연 줄 상자보다 작게 고정하면 iOS 에서 글자가 위로 뜬다(memory notosanskr-vertical-centering).
for (const [role, size] of Object.entries(sizes)) {
expect(leads[role]).toBeDefined();
expect(px(leads[role])).toBeGreaterThanOrEqual(Math.ceil(px(size) * 1.45));
}
});
plan 에 따르면 이 테스트가 바로 제 첫 값을 잡았습니다. title1·display·hero 의 줄 높이가 모자라서 42·58·82 로 올렸습니다.
옮기는 일은 두 번에 나눴습니다. 1차는 값이 같거나 반 픽셀 차이인 618곳을 기계적으로, 2차는 나머지 197곳을 규칙대로 옮겼습니다. 14px 은 보조 색이면 footnote(13), 아니면 body(15)로 보내 위계를 갈랐습니다. 후리가나가 얹히는 가사 줄과 획순 번호, 가나 격자, 한 글자 퀴즈는 예외로 남겼습니다.
버튼 하나에 모은 판단
공용 Button 에 여러 결정이 한꺼번에 들어갔습니다. 빛 그림자(glow)는 이제 한 조건에서만 붙습니다.
const glow = variant === 'primary' && size === 'lg';
size="lg" primary 가 그 화면의 주인공이고, 한 화면에 그런 버튼을 둘 두지 않는다는 규칙입니다. 그래서 단어장의 작은 시작 버튼에는 빛이 없고, 학습 탭 오늘 학습 시작 에는 있습니다. 홈의 게스트 가입 배너도 주황 바탕과 테두리를 걷어 내고, 강조는 무료 가입 버튼 하나에만 남겼습니다.
누를 때의 움직임은 Squish Button 프리셋을 쓰되 크기를 바꿨습니다. 원본의 0.88 은 단독 데모 버튼용이라 화면 폭 CTA 에서 12% 가 줄면 과하다고 보고 0.97 로 두었습니다. 동작 줄이기를 켜면 투명도만 바뀝니다.
큰 글자 상한은 1.3배로 정했고, 버튼·배지·칩·탭 레이블·세그먼트처럼 높이가 고정된 컨트롤에만 겁니다. 본문은 제한 없이 커지게 두었습니다.
단어 카드 왼쪽의 상태 띠는 고민 끝에 남겼습니다. DESIGN.md 는 한쪽 변에만 두꺼운 색 띠를 두지 않는다고 정했는데, 단어장은 목록을 세로로 훑는 화면이라 그 열이 읽기에 도움이 됩니다. 그래서 띠는 남기고 3px 를 2px 로, 색은 투명도 60% 로 줄였습니다.
Utakoto 단어장(스토어 스크린샷, 이번 정리 전 모습)입니다. 단어 카드 왼쪽 상태 띠가 이번에 2px·투명도 60% 로 가늘어졌습니다.
하나 더 찾은 것이 있습니다. 이 앱의 Text 에는 기본 글꼴이 없어서, 글꼴 클래스를 빠뜨린 340여 곳이 iOS 시스템 글꼴로 그려지고 있었습니다. 전부 채우되, 일본어 읽기와 한국어 뜻이 한 줄에 섞인 5곳만 일부러 시스템 글꼴에 맡겼습니다. Noto Sans JP 에는 한글이 없기 때문입니다.
확인한 것과 남은 것
plan 에 적힌 검증은 타입 검사 오류 0, 테스트 71 스위트 743개 통과, eslint 91건(기존 main 과 같은 수)입니다. 화면은 iOS 시뮬레이터와 Android 에뮬레이터에 1.11.0 release 빌드를 설치해 봤고, 기본 크기·다크·accessibility-large·최대 접근성 크기(앱 재시작 뒤)에서 버튼·배지·칩·탭 레이블·학습 트랙이 잘리지 않았다고 기록했습니다. 그 확인 중에 1.45배 밑이던 줄 높이 42곳과 Android 에서 처지던 레벨 아이콘 같은 것을 더 잡았습니다. 발행은 같은 날 OTA 7bb4ed01(runtime 1.11.0, iOS·Android)이었습니다.
남은 것도 있습니다. 출시 조건에 적은 "Android 실기기에서 Squish 눌림과 토스트 overshoot 확인"은 확인했다는 기록이 없습니다. Android 에서 1을 넘는 베지어 곡선이 어떻게 도는지 보려던 항목입니다. 애니메이션 길이가 55~1100ms 로 흩어진 것은 DESIGN.md 에 표를 만들었을 뿐 아직 모으는 중이고, 탭바 접힘의 높이 애니메이션도 예외로 남아 있습니다. intent 의 설계 질문 중 공용 Text 래퍼를 만들지 클래스 규칙으로 갈지는, 결과적으로 "바깥 Text 에는 꼭 글꼴 클래스를 단다"는 규칙이 되었지만 래퍼를 버린 이유는 기록에 남아 있지 않습니다. 홈 강조를 줄인 것이 가입 전환에 닿는지는 plan 의 판정일인 11월 22일에 게스트 배너 탭률과 AI 대화 입구 탭률을 출시 전 14일과 비교해 봅니다.
Utakoto 는 J-POP·애니송 가사로 일본어를 배우는 앱입니다. 가사마다 후리가나·한국어 발음·뜻이 붙고, 노래 속 단어가 JLPT 단어장으로 모입니다. iOS·Android 에서 무료입니다.