184 lines
17 KiB
Markdown
184 lines
17 KiB
Markdown
# 07. UI/UX 설계
|
||
|
||
## 1. 디자인 원칙
|
||
1. **한 화면에 할 일 하나.** 지금 눌러야 하는 버튼이 가장 크고 진하다.
|
||
2. **글자 + 아이콘.** 아이콘만 있는 버튼 금지(닫기 ✕ 제외).
|
||
3. **지금 상황을 문장으로.** 화면 맨 위에 "내 차례예요. 돌을 놓을 곳을 눌러 주세요."
|
||
4. **할 수 있는 것만 누를 수 있게.** 둘 수 있는 곳·낼 수 있는 카드만 밝게, 나머지는 흐리게. 잘못 누르면 짧게 흔들리고 이유를 한 줄로("같은 색 카드만 낼 수 있어요").
|
||
5. **실수 방지.** 큰 결정(기권, 나가기, 올인, 다이)은 확인. 단, 매 턴 확인창은 금지(느려짐) — 대신 "두 번 눌러 확정" 옵션(바둑·오목 오착 방지, 기본 켜짐: 첫 탭은 미리보기 돌, 같은 곳을 다시 탭하면 착수).
|
||
6. **기다림을 보여 준다.** 남은 시간 링, "OO님이 생각 중…", 연결 상태.
|
||
7. **가볍게.** 장식 이미지 최소, 애니메이션은 짧게(150~250ms), `prefers-reduced-motion` 존중.
|
||
|
||
## 2. 디자인 토큰
|
||
### 2.1 색 (밝은 테마 / 어두운 테마)
|
||
| 토큰 | 밝게 | 어둡게 | 용도 |
|
||
|---|---|---|---|
|
||
| `--bg` | #F7F5F0 | #16181D | 배경(따뜻한 미색) |
|
||
| `--surface` | #FFFFFF | #20232A | 카드, 패널 |
|
||
| `--text` | #1C1E21 | #F2F3F5 | 본문(명암비 15:1 이상) |
|
||
| `--text-sub` | #4A4F57 | #B8BCC4 | 보조 글(4.5:1 이상) |
|
||
| `--primary` | #2563EB | #5B8DEF | 주요 버튼(파랑) |
|
||
| `--primary-text` | #FFFFFF | #0B1020 | 주요 버튼 글자 |
|
||
| `--success` | #15803D | #4ADE80 | 준비, 성공 |
|
||
| `--warning` | #B45309 | #FBBF24 | 시간 부족, 연결 불안정 |
|
||
| `--danger` | #B91C1C | #F87171 | 기권, 나가기 |
|
||
| `--felt` | #1F6B4A | #17513A | 카드 게임 테이블 |
|
||
| `--wood` | #E3B76B | #B98C45 | 바둑판·오목판·장기판 |
|
||
| `--focus` | #F59E0B | #F59E0B | 키보드 포커스 테두리 3px |
|
||
|
||
- 색만으로 정보를 전달하지 않는다(내 차례 = 색 + 글자 + 테두리). 색약 모드: 카드 무늬에 모양 추가(♦에 빗금 등), 팀 색을 파랑/주황 조합으로.
|
||
|
||
### 2.2 글자
|
||
- 글꼴: Pretendard Variable(자체 호스팅, KS X 1001 서브셋 + 필요한 문자).
|
||
- 크기 단계(기본 "보통"): 본문 17px, 작은 글 15px(최소), 버튼 18px, 제목 22/28px. 줄 간격 1.5.
|
||
- 글자 크기 설정: 보통(100%) / 크게(120%) / 아주 크게(140%). 루트 `font-size`만 바꾸고 모든 크기는 `rem`으로.
|
||
- 굵기: 본문 500, 버튼·제목 700. 300 이하 금지.
|
||
|
||
### 2.3 크기·간격
|
||
- 터치 영역 최소 48×48px, 주요 버튼 높이 56px, 버튼 사이 간격 최소 8px.
|
||
- 간격 단위 4px(4/8/12/16/24/32).
|
||
- 모서리 둥글기 12px(버튼), 16px(카드).
|
||
- 바둑판처럼 칸이 작은 보드: 칸 크기가 32px 미만이면 "확대 보기" 버튼 + 두 손가락 확대 지원, "두 번 눌러 확정" 기본 켜짐.
|
||
|
||
## 3. 화면 틀 — PC 중심 (2026-10-04 결정)
|
||
사용자 요청으로 **PC 화면을 기준으로 설계**한다. 휴대폰·태블릿은 같은 화면을 세로로 접어서 지원한다.
|
||
|
||
### 3.1 화면 폭 구간
|
||
| 구간 | 폭 | 배치 |
|
||
|---|---|---|
|
||
| PC (기준) | 1100px 이상 (설계 기준 1280~1920) | 상단 메뉴 + 여러 칸 배치, 채팅·기보 항상 보임 |
|
||
| 태블릿 | 720~1099px | 두 칸 → 한 칸으로 접힘, 오른쪽 패널은 판 아래로 |
|
||
| 휴대폰 | 720px 미만 | 한 칸, 채팅은 버튼으로 여는 시트, 주요 버튼은 아래 고정 |
|
||
|
||
### 3.2 공통 상단 메뉴 (PC)
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────────────┐
|
||
│ ● 같이 놀자 보드게임 홈 게임 목록 [코드 6자리 입력칸][참여] 👤 닉네임 ▾ │ 64px
|
||
└──────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
- 어느 화면에서든 코드를 바로 넣어 참여할 수 있게 상단에 코드 입력칸을 둔다.
|
||
- 오른쪽 끝: 로그인 전에는 [로그인](→ `/login`: 큰 [디스코드로 로그인], 아래 [게스트로 시작하기] → `/login/guest`에서 계정 이름 정하기). 로그인 뒤에는 닉네임(디스코드로 가입했으면 디스코드 이름)을 보이고, 누르면 [⚙️ 설정](→ `/me`)·[🚪 로그아웃] 메뉴가 열린다. 화면·소리 설정은 `/me`에만 있다.
|
||
- 내용 폭은 최대 1280px, 가운데 정렬. 게임 화면만 화면 전체 폭을 쓴다.
|
||
|
||
### 3.3 홈 (PC)
|
||
```
|
||
┌ 상단 메뉴 ───────────────────────────────────────────────────────────────┐
|
||
│ [▶ 하던 게임으로 돌아가기] (있을 때만) │
|
||
│ [+ 방 만들기 (파란 큰 버튼)] [🔢 코드로 참여 → /j] [🤖 컴퓨터와 연습] │
|
||
├──────────────────────────────────────────────────────────────────────────┤
|
||
│ [🎮 게임 고르기] [🚪 지금 열린 방 (n)] ← 탭, 한 번에 하나만 보임 │
|
||
│ 게임 카드 격자 (4~5열, 카드에 인원·시간·난이도) 또는 공개 방 목록(게임·인원·참여) │
|
||
└──────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
- 첫 화면에 다 넣지 않는다. 설명 글은 버튼 아래 한 줄만, 나머지는 탭이나 버튼을 눌러 들어간다.
|
||
|
||
### 3.4 대기실 (PC)
|
||
```
|
||
┌──────────────────────────────┬────────────────────────────────┐
|
||
│ 초대 카드(코드 크게, 복사·QR) │ 자리 목록(2~10칸 격자) │
|
||
│ 게임 설정(펼쳐서 바로 수정 — 방장) │ 채팅(항상 보임) │
|
||
│ 규칙 요약 │ [게임 시작] / [준비하기] │
|
||
└──────────────────────────────┴────────────────────────────────┘
|
||
```
|
||
|
||
### 3.5 게임 화면 (PC)
|
||
```
|
||
┌ 상단 메뉴(얇게 48px: 게임명·코드·메뉴) ─────────────────────────────────────┐
|
||
│ │ 상태: 내 차례예요 ◔ 23초 │
|
||
│ │ 상대 정보 · 시계 │
|
||
│ 게임 판 (화면 높이에 맞춤) │ 내 정보 · 시계 │
|
||
│ │ [무르기][무승부][기권] │
|
||
│ │ 기보(수 목록) │
|
||
│ │ 채팅 + 반응 버튼 │
|
||
└──────────────────────────────────────────────┴──────────────────────────────┘
|
||
```
|
||
- 오른쪽 패널 폭 360px, 판은 남은 공간에서 가장 큰 정사각형.
|
||
- 카드 게임: 판 자리에 원형 테이블, 나는 항상 아래, 내 패는 판 아래쪽 넓게.
|
||
- PC 조작: 마우스를 올리면 미리 보기 돌(반투명), 클릭 한 번에 착수(기본값). "두 번 눌러 확정"은 터치 기기의 기본값이고 설정에서 바꿀 수 있다.
|
||
- 단축키: `Enter` 채팅 입력, `Esc` 창 닫기, 방향키+`Enter` 판 조작.
|
||
|
||
### 3.6 관리자 (PC)
|
||
왼쪽 사이드바(220px)에 메뉴, 오른쪽에 내용. 표는 PC에서 표 형태, 좁은 화면에서는 카드 목록.
|
||
|
||
## 4. 공통 컴포넌트
|
||
| 컴포넌트 | 동작 |
|
||
|---|---|
|
||
| `BigButton` | 56px, 종류 primary/secondary/danger, 로딩 표시, 두 번 눌림 방지 |
|
||
| `CodeDisplay` | `123 456` 큰 글씨 + 복사 |
|
||
| `CopyButton` | 복사 후 1.5초 "복사했어요 ✓", 실패하면 텍스트 선택 상태로 보여 줌 |
|
||
| `ShareButton` | `navigator.share` 있으면 공유 시트, 없으면 링크 복사 |
|
||
| `QrModal` | QR 크게 + 코드 |
|
||
| `CodeInput` | 6칸, 숫자 키패드, 붙여넣기 감지, 다 채우면 자동 제출 |
|
||
| `Seat` | 프로필, 이름, 방장/준비/연결 상태 |
|
||
| `TurnTimer` | 원형 링, 10초 남으면 주황 + 똑딱 소리(설정), 5초 남으면 빨강 + 진동 |
|
||
| `Toast` | 위에서 내려오는 짧은 알림(3초), 스크린리더 `aria-live` |
|
||
| `ConfirmSheet` | 아래에서 올라오는 확인창(큰 버튼 두 개) |
|
||
| `Card` | 트럼프/화투/게임 카드 공용 SVG, 크기 3단계 |
|
||
| `RulesSheet` | 게임 중 규칙 요약(그림), 현재 상황 도움말 |
|
||
| `ConnectionBanner` | "다시 연결하는 중…" 띠, 인터넷이 끊기면 방 밖에서도 "인터넷 연결이 없어요" 띠 |
|
||
|
||
## 5. 초보자 도움
|
||
- 처음 하는 게임이면(기기 기준) 시작할 때 "30초 규칙 요약" 카드 3장(그림 위주), [건너뛰기].
|
||
- 구현: 게임이 진행 중인 화면을 이 기기에서 처음 볼 때(시작하거나 진행 중인 방에 들어올 때, 관전 포함) 카드 3장을 띄운다. [다음]·[건너뛰기], 마지막 장은 [시작하기]. 본 게임은 `localStorage` `bg:qs:<게임id>`에 기억한다. 규칙 페이지의 [30초 요약 보기], 게임 중 [?]의 [30초 요약 다시 보기]로 언제든 다시 본다.
|
||
- 그림은 이미지 파일 없이 기존 카드 컴포넌트(트럼프·화투·마작패 등)나 작은 인라인 SVG·이모지로 그리고, 그림마다 대체 글(`alt`)을 단다.
|
||
- 게임 중 [?] → 지금 상황에서 할 수 있는 것 설명("지금은 낼 카드를 고르는 차례예요. 밝게 보이는 카드만 낼 수 있어요").
|
||
- 구현: 차례 안내 줄 오른쪽의 [? 도움] → "지금 할 수 있는 것" 시트. 게임마다 지금 화면(view)만 보고 문장을 만든다(숨은 정보는 쓰지 않음). 시트에서 [전체 규칙 보기](그 게임의 규칙 설명을 펼침)와 [30초 요약 다시 보기]로 이어진다. 관전자에게는 "구경하고 있어요"를 먼저 알려 준다.
|
||
- 내용은 게임별 `apps/web/src/games/<id>/quick.tsx`(카드 3장)·`help.ts`(`helpNow`)에 있고 게임 화면 묶음과 함께 필요할 때만 내려받는다.
|
||
- 족보·점수가 복잡한 게임(섯다, 포커, 고스톱, 마작)은 내 패 옆에 현재 족보 이름 표시("지금: 7끗", "투페어"), 족보표 버튼.
|
||
- 구현 확인: 섯다·포커는 "지금: <족보>"(방 설정 힌트가 켜졌을 때)와 족보표, 고스톱은 내 점수 내역(광·열끗·띠·피 진행, 점수 항목, 고 횟수)과 족보표, 마작은 대기패·버리면 텐파이 표시(초보 모드)와 역 목록. [?] 설명에도 지금 족보·점수·대기패를 함께 보여 준다.
|
||
- 힌트 옵션(방 설정): 끄면 하이라이트만, 켜면 추천 수 표시(초보 방).
|
||
|
||
## 6. 소리·진동
|
||
- 짧은 효과음만(돌 놓기, 카드 넘기기, 내 차례 알림, 시간 경고, 승리). 합쳐서 100KB 이하, 처음 사용자 조작 후에만 재생(브라우저 정책).
|
||
- 내 차례가 되었는데 탭이 백그라운드면: 탭 제목 깜빡임("● 내 차례예요") + 소리(설정).
|
||
- 진동(`navigator.vibrate`): 내 차례, 시간 5초 남음.
|
||
|
||
## 7. 접근성
|
||
- 키보드만으로 모든 조작 가능(보드는 방향키로 칸 이동 + Enter). 포커스 테두리 항상 보이게.
|
||
- 스크린리더: 보드 칸에 `aria-label`("가로 8, 세로 8, 빈칸"), 상대의 수를 `aria-live`로 읽어 줌("상대가 H8에 두었어요").
|
||
- 명암비 기준 WCAG 2.2 AA 이상, 큰 글자 모드에서 화면이 깨지지 않는지 테스트.
|
||
- 시스템 글자 크기·확대 존중(`user-scalable` 막지 않음).
|
||
|
||
## 8. 성능 예산
|
||
| 항목 | 예산 |
|
||
|---|---|
|
||
| 첫 화면 JS | 150KB gzip 이하 (React + 라우터 + 홈) |
|
||
| 게임 화면 하나 | 추가 80KB gzip 이하(lazy) |
|
||
| 글꼴 | 서브셋 woff2 2개(500/700) 합 400KB 이하, `font-display: swap` |
|
||
| 이미지 | 게임 카드 썸네일은 SVG 또는 WebP 10KB 이하 |
|
||
| 외부 요청 | 0개(디스코드 아바타 제외) |
|
||
|
||
## 9. 문구 규칙
|
||
- 존댓말 해요체, 짧게. "~하시겠습니까?" 대신 "~할까요?".
|
||
- 어려운 말 금지: "테이블" → "방", "세션" → 쓰지 않음, "로비" → "홈".
|
||
- 오류 문구는 원인 + 할 일: "방을 찾을 수 없어요. 코드를 다시 확인해 주세요."
|
||
|
||
## 10. 화면 목록과 상태 (구현 체크용)
|
||
- 홈(로그인 전/후, 진행 중 게임 있음)
|
||
- 닉네임 입력(게스트 시작 / 링크 입장)
|
||
- 코드 입력
|
||
- 게임 상세 + 공개 방 목록
|
||
- 대기실(방장/참가자/관전자, 인원 부족/가득 참)
|
||
- 게임 화면(내 차례/남의 차례/연결 끊김/자리 비움/일시 정지)
|
||
- 결과 화면(승/패/무승부, 한 판 더 투표 중, 처음부터 다시 보기)
|
||
- 계정(게스트/디스코드, 내 게임 기록 목록 + [다시 보기])
|
||
- 다시 보기(`/replay/:gamePk`: 판은 읽기 전용, 처음/이전/재생·멈춤/다음/끝 + 슬라이더. PC는 오른쪽 칸, 폰은 아래 고정 막대. 못 보는 게임은 안내 문구, docs/09 §8)
|
||
- 오류(방 없음, 방 가득 참, 서버 업데이트, 오프라인)
|
||
|
||
## 11. PWA (홈 화면 추가·오프라인 안내)
|
||
- 매니페스트 `/manifest.webmanifest`: 이름 "같이 놀자 보드게임", 짧은 이름 "같이 놀자", `lang: ko`, 시작 `/`, `display: standalone`, 테마·배경 `#F7F5F0`(`--bg`). 아이콘은 "게임천국" 느낌의 게임패드 + 천사 고리 + 반짝이(보라→분홍→주황 배경). `public/icons/`: 192·512(favicon 그대로, 둥근 모서리), 512 maskable(가득 채움, 그림은 가운데 안전 영역 안), apple-touch-icon 180. favicon.svg와 PNG 모두 `bun scripts/pwa-icons.ts`가 한 디자인에서 만든다(Chrome으로 그림).
|
||
- 서비스 워커: 손으로 쓴 `apps/web/sw.js`를 빌드 때 Vite 플러그인이 빌드 ID(파일 이름 해시 기준)를 넣어 `dist/sw.js`로 낸다. 배포 빌드에서만 등록한다(개발 서버에서는 등록 안 함).
|
||
|
||
| 요청 | 처리 |
|
||
|---|---|
|
||
| 페이지 이동(navigate) | 네트워크 먼저, 저장하지 않음. 네트워크 오류면 `/offline.html` |
|
||
| `/assets/*` (해시 파일) | 캐시 먼저(`bg-assets-v1`, 200개까지, 오래된 것부터 지움). 200 응답이고 HTML이 아닐 때만 저장(없는 파일은 서버가 index.html을 돌려주므로) |
|
||
| `/api/*`(관리자 API 포함), `/ws`, `/healthz`, `/internal/*`, 다른 출처, GET 아닌 요청 | 손대지 않음(저장 안 함) |
|
||
|
||
- 캐시: `bg-shell-<빌드ID>`(오프라인 안내 페이지 하나)와 `bg-assets-v1`. 새 워커는 바로 활성화(`skipWaiting` + `clients.claim`)하고, 활성화할 때 이 둘이 아닌 `bg-*` 캐시를 지운다. index.html은 어디에도 저장하지 않으므로 온라인이면 새 배포가 바로 보인다. 해시 파일은 내용이 바뀌지 않아 배포 뒤에도 남겨 둔다. 워커의 저장 방식을 바꾸면 `bg-assets-v1`의 숫자를 올린다.
|
||
- 오프라인 안내 페이지: 다른 파일 없이 혼자 그려지는 HTML(인라인 스타일, 밝게/어둡게). 스크립트는 없다(CSP가 인라인 스크립트를 막음). [다시 시도]는 같은 주소를 다시 연다. 자동 새로고침(meta refresh)은 접근성 검사(axe) 위반이라 쓰지 않는다.
|
||
- 앱 안: 인터넷이 끊기면 `ConnectionBanner`가 위쪽 띠로 알린다(방 안에서는 기존 재연결 문구).
|
||
- 홈 화면에 추가: 홈 아래쪽 카드. `beforeinstallprompt`를 잡아 [추가하기] 버튼을 보이고, iOS(이 이벤트 없음)는 "공유 버튼 → [홈 화면에 추가]" 안내만. 이미 앱으로 열었거나(standalone) [닫기]를 누르면(기기에 기억) 안 보인다.
|
||
- 서버: `/sw.js`·`/manifest.webmanifest`·`/offline.html`은 index.html과 같이 `no-cache`, manifest는 `application/manifest+json`(Bun 기본). CSP `default-src 'self'`가 워커·매니페스트를 허용해 따로 넣은 것은 없다.
|
||
- 확인: `apps/web/src/sw.test.ts`(캐시 규칙), `bun e2e/pwa.e2e.ts`(매니페스트·아이콘, 워커 활성화, 오프라인 띠·안내 페이지, 다시 온라인, 설치 카드).
|