프로필 UI 글자 크기를 네 단계와 공통 축소 규칙으로 통일한다

This commit is contained in:
2026-09-29 09:41:39 +00:00
parent 5bafdfc1e7
commit 7c65cf60f3
43 changed files with 1347 additions and 299 deletions
+23 -82
View File
@@ -132,84 +132,25 @@ The CSS variables contain `/image/game/*` URLs but do not import or copy image
files. Caddy continues to own `/image/*`; Vite must not rewrite the image tree
as application assets.
## Game typography tiers
The game UI uses four starting sizes from `assets/styles/tokens.css`:
All profile pages share the four sizes in `assets/styles/tokens.css`, applied by
page styles and `assets/styles/typography.css`. Gateway is outside this policy.
This is an intentional Core UX decision, expanded on 2026-09-29 from the initial
2026-09-15 policy. See [the typography contract](frontend-typography.md) for the
step-down rule, measured fitting, content exceptions, and browser audit command.
| Token suffix (`--sammo-font-size-`) | CSS size | Role |
| --- | --- | --- |
| `small` | `normal × 12 / 14` (12px) | Compact controls, metadata, secondary labels |
| `normal` | 14px | Body, tables, ordinary controls |
| `emphasis` | 16px | Emphasized names and section labels |
| `title` | 24px | Page and major section headings |
| Token suffix (`--sammo-font-size-`) | Size | Shared class |
| ----------------------------------- | ---- | --------------------- |
| `title` | 24px | `sammo-text-title` |
| `emphasis` | 16px | `sammo-text-emphasis` |
| `normal` | 14px | `sammo-text-normal` |
| `small` | 12px | `sammo-text-small` |
This is an intentional Core UX policy adopted on 2026-09-15. It is a starting
policy for verified UI, not a claim that every rendered glyph has one of four
computed sizes. Existing geometry and media queries remain owned by each page.
A breakpoint can choose another tier (for example, the personnel nation heading
uses 16px on its narrow layout and 24px on its wide layout).
Use the tokens for new UI. Do not round arbitrary content or all remaining
relative sizes automatically. Before moving an existing label to a tier, compare
actual Chromium screenshots and text geometry at 500px and 1000px, with the
same font, data, DPR and zoom. Also check the real mobile viewport modes: a
390px-wide device scales a 500px layout to approximately 78% and a 1000px layout
to 39%; these do not change the CSS token values. A page with a fixed 1000px
minimum width can still render at about 39% in 500px mode because Chromium
fits the overflowing content. Record `visualViewport.scale` for each route;
do not infer every route's scale from the selected mode alone.
### Preserved exceptions
- Battle log `.small_war_log .name_plate` remains `0.75em`, and `.crew_plate`
remains `90%`. Legacy inline `0.9em` conversions and zero-sized hidden markers
remain unchanged. At a 14px parent these are 10.5px and 12.6px respectively;
nested content must be calculated from its actual parent.
- NPC possession names retain their existing length rule: 4 and 7 characters
render at 16px, and the existing `length >= 9` branch renders at 12px. Do not
remove the smaller branch or enlarge long names to match adjacent labels.
- Player HTML, editor size choices, and personal CSS remain content/user settings.
Scoped `small` rules normalize UI metadata without rewriting injected HTML.
### Deferred conversions
| Area | Preserved size | Reason |
| --- | --- | --- |
| Chief overview compact rows and turn indices | `0.55rem` (8.8px) | 12px text overlaps the existing 11.25px rows |
| Chief compact header/name | `0.65rem` / `0.6rem` | Preserve the same dense card contract |
| Narrow personnel chief name / lock label | 15px / 10px | Enlarging them reduces visible maximum-length names |
| Best generals / hall name and nation cells | 11px; inner `small` 95% | 12px worsens overflow in the fixed name cells |
| General selection and main nation basic card | Existing local/inherited sizes | Maximum-width names already overflow; conversion is deferred |
User-created general names and founded/renamed nation names are limited by
legacy width 18 (CJK counts 2, ASCII counts 1): test both 9 CJK characters and
18 wide ASCII characters. Selection-pool and scenario names have a different
source contract and are not proven bounded by that user-input guard.
Existing ellipsis, horizontal scrolling, and pre-existing maximum-name overflow
are not fixed by these tokens. The deferred areas need a separate layout decision
before their fonts are enlarged. The `typographyPolicy.spec.ts` fixture tests
protect the safe main labels, personnel breakpoints, dense chief rows, NPC
length branches, and battle log ratios. They use mocked read responses and do
not constitute live game or public deployment verification.
### Relative small tier and exception variables (2026-09-15)
`small` is `calc(var(--sammo-font-size-normal) * 12 / 14)`. The reference is the
14px normal tier, not the immediate parent. An 80% declaration on nested `small`
elements would shrink repeatedly and would yield 12.8px inside a 16px parent;
the token keeps UI metadata at 12px regardless of nesting.
The retained exceptions are also named in `tokens.css`: `nation-card`,
`chief-compact-{base,row,header,name}`, `hall-{name,secondary}`,
`personnel-mobile-{name,lock}`, `bracket-bet-button`, and `war-{name,crew}`
(all with the `--sammo-font-size-` prefix). Their values preserve the previous
rendering, including rem/em/% semantics. Zero-sized hidden markers and arbitrary
user HTML remain content contracts. The betting page and bracket now reference
the shared tier variables directly instead of the redundant `--bet-font-*` aliases.
The historical deferral reasons above describe the original 12px proposal.
`small`, `sub`, `sup`, and semantic log annotations move down one tier:
24 → 16 → 14 → 12 → 10px. The last size is only for subordinate text inside the
smallest tier; repeated nesting stops at 10px. Do not reintroduce scoped `small`
resets, per-page exception tokens, or implicit percentage reductions.
## Text alignment on both axes (2026-09-29)
@@ -218,14 +159,14 @@ height. Horizontal alignment remains independent: numerical values can stay
right-aligned and dates or field labels left-aligned. This policy does not change
game state, field order, font tiers, cell dimensions, or the meaning of Ref data.
| Content | Vertical alignment |
| --- | --- |
| A short heading, status, button/link label, identity or scalar value | Center within its own cell when it fits |
| A short label that wraps on a narrow viewport | Center the complete label if it fits; allow natural height and preserve wrapping |
| A form label next to one input | Center against the input; if the field also has help/error text, align to the input rather than the entire help block |
| Prose, logs, notices, editor content, multi-paragraph detail, card collections | Preserve the reading start at the top |
| A table cell with text/numbers/actions | Use table-cell `vertical-align: middle`; retain explicit top alignment for narrative/detail cells |
| A column flex container | The vertical axis is `justify-content`, not `align-items` |
| Content | Vertical alignment |
| ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| A short heading, status, button/link label, identity or scalar value | Center within its own cell when it fits |
| A short label that wraps on a narrow viewport | Center the complete label if it fits; allow natural height and preserve wrapping |
| A form label next to one input | Center against the input; if the field also has help/error text, align to the input rather than the entire help block |
| Prose, logs, notices, editor content, multi-paragraph detail, card collections | Preserve the reading start at the top |
| A table cell with text/numbers/actions | Use table-cell `vertical-align: middle`; retain explicit top alignment for narrative/detail cells |
| A column flex container | The vertical axis is `justify-content`, not `align-items` |
Choose the smallest owner of the text. For a block or grid-item cell with inline
markup, `align-content: safe center` preserves inline wrapping, ellipsis and the
+69
View File
@@ -0,0 +1,69 @@
# 전체 프로필 페이지 typography audit
실제 Chromium에서 router의 모든 페이지와 주요 열린 상태를 조회한다. 기존 E2E의
합성 fixture를 재사용하며 실제 게임 API와 계정에는 접속하지 않는다.
`load-fixtures.mjs`는 TypeScript compiler로 fixture 선언만 추출한다. test 등록,
`beforeEach`, 테스트 본문은 실행하지 않는다. fixture 구조가 바뀌면 audit도 함께
수정하고, 빈 화면이나 오류 화면을 정상 페이지 근거로 삼지 않는다.
## 실행
Core root에서 Node 24와 설치된 Playwright Chromium을 사용한다. prefix에 맞게
production frontend를 build하고 preview를 먼저 띄운다. frontend 준비는
[테스트 정책](testing-policy.md)을 따른다.
```sh
VITE_APP_BASE_PATH=/che VITE_GAME_API_URL=/che/api/trpc \
VITE_GAME_PROFILE=che:default pnpm --filter @sammo-ts/game-frontend build
VITE_APP_BASE_PATH=/che VITE_GAME_API_URL=/che/api/trpc \
VITE_GAME_PROFILE=che:default pnpm --filter @sammo-ts/game-frontend preview --host 127.0.0.1 --port 15331
```
다른 terminal에서:
```sh
CAPTURE_ORIGIN=http://127.0.0.1:15331 PHASE=che-cjk \
node tools/typography-audit/capture.mjs
CAPTURE_ORIGIN=http://127.0.0.1:15331 PHASE=che-ascii NAME_KIND=ascii \
node tools/typography-audit/capture.mjs
CAPTURE_ORIGIN=http://127.0.0.1:15331 PHASE=che-mobile MOBILE=1 \
node tools/typography-audit/capture.mjs
```
HWE는 build의 세 변수를 `/hwe`, `/hwe/api/trpc`, `hwe:default`로 바꾸고,
audit에도 `PLAYWRIGHT_GAME_BASE_PATH=hwe PLAYWRIGHT_GAME_PROFILE=hwe:default`를
지정한다. 같은 dist를 쓰는 profile build는 직렬로 실행한다. 활성 공개 profile을
새로 배포하거나 서버 설정을 바꾸는 명령이 아니다.
| 환경 변수 | 의미 |
| ---------------------------- | ----------------------------------------------------------------- |
| `CAPTURE_ORIGIN` | 이미 실행 중인 frontend origin |
| `PHASE` | 결과 하위 디렉터리 이름 |
| `TYPOGRAPHY_OUTPUT_DIR` | 기본 `test-results/typography-audit` |
| `TYPOGRAPHY_FONT_ROOT` | 선택적 Pretendard cache: `source.css`와 CSS가 참조하는 woff2 파일 |
| `FRONTEND_PARITY_IMAGE_ROOT` | 기본 workspace의 `image/` |
| `NAME_KIND` | 기본 `cjk`, 또는 `ascii`, `short4`, `mid7` |
| `WIDTHS` | 기본 `500,1000` CSS px |
| `MOBILE` | `1`이면 실제 390×844 touch 기기에서 500/1000 화면 모드 |
| `SCENES` | 쉼표로 나눈 일부 scene만 실행; 전체 페이지 검증으로 보고하지 않음 |
각 실행은 현재 router의 `path` 목록과 전체 scene 목록의 대응을 먼저 검사한다.
font 로딩 실패, fixture/page 오류, 설명할 수 없는 computed 크기는 실패로 기록한다.
자동 축소는 `data-font-fit-max`와 10px 하한을 함께 검사한다. 원본 screenshot,
DOM, computed style, text range, viewport scale, 이미지 natural size/object-fit,
canvas font와 합성 응답이 scene별 파일로 남는다. 실패 manifest와 성공 수를 따로
확인한다. snapshot 개수만으로 성공이라고 판단하지 않는다.
`clipped`는 overflow를 가진 조상의 경계를 검사한다. overflow가 visible인 글자가
옆 칸을 침범하는 경우까지 자동 판정하지 못하므로 이름의 range와 element bounds,
screenshot도 비교한다. 긴 반각 이름의 기존 문제를 숨기려고 glyph mask를 늘리지
않는다. 별도의 Playwright interaction suite로 dropdown, dialog, hover/focus,
active, disabled 상태를 확인한다.
## 검증 경계
이 도구는 fixture production Chromium의 화면 근거다. 운영 DB/계정, 공개 HTTPS,
개인 CSS나 임의의 저장 HTML 전체를 검사했다는 뜻이 아니다. 감사 화면의 tab,
국가 회의실/기밀실, 동적 왕조 상세, 명령·인사 선택창을 별도 scene으로 포함한다.
새 사용자 페이지가 추가되면 router coverage 오류를 해결하며 scene과 fixture를
추가한다. 생성물은 Git에 넣지 않는다.
+68
View File
@@ -0,0 +1,68 @@
# 프로필 UI 글자 크기
Gateway를 제외한 모든 게임 profile의 공통 정책이다. 2026-09-29 사용자 요청에
따른 의도적 Core UX 차이이며 Ref의 작은 글자를 그대로 유지하는 계약을 대체한다.
권한, 게임 데이터, 로그 내용·색상, 이미지와 사용자 작성 HTML 저장값은 바꾸지 않는다.
## 기본 단계와 작은 설명
| 역할 | 토큰 / 공통 class | 최대 기본 크기 |
| ------------------ | ---------------------------------- | -------------- |
| 큰 제목 | `title` / `sammo-text-title` | 24px |
| 강조, 소제목 | `emphasis` / `sammo-text-emphasis` | 16px |
| 본문, 일반 조작 | `normal` / `sammo-text-normal` | 14px |
| 보조 정보, 밀집 표 | `small` / `sammo-text-small` | 12px |
토큰 접두사는 `--sammo-font-size-`이다. 화면 selector도 같은 토큰을 쓴다.
반응형 breakpoint는 위 네 단계 중 하나를 선택한다.
공통 `typography.css`는 `small`, `sub`, `sup`, `.sammo-text-smaller`, 게임 로그의
`.legacy-small`에 바로 아래 단계를 적용한다. 24→16→14→12px 순서이며 12px 안의
작은 설명만 10px를 허용한다. 중첩해도 10px 아래로 내려가지 않는다. 일반 본문이나
조작 버튼에 다섯 번째 단계를 직접 지정하지 않는다. 개별 화면의 `small` reset은
추가하지 않는다. 로그 formatter의 semantic 옵션은 game frontend에서만 사용하므로
Gateway 등 다른 소비자의 기존 상대 크기는 유지된다.
사령부 12턴 요약은 12px, 행 높이 16px를 사용한다. 기존 11.25px 행에서 글자만
키우면 겹치므로 두 줄 header는 32px, 카드 224px, 전체 요약 448px로 함께 조정했다.
인사부 모바일 이름은 16px, 명장·명예의 전당 이름과 토너먼트 작은 조작은 12px다.
전투 요약의 이름·병력도 12px로 표시하며 의미와 색상은 유지한다.
## 칸에 맞춘 축소
자동 축소의 최대값도 위 네 단계 중 하나여야 한다. 짧은 글자를 임의의 크기로
확대하지 않는다. 기존 빙의 이름은 16px를 기본으로 긴 이름을 12px로 낮춘다.
명장·명예의 전당의 고정 폭 이름칸은 공통 `vFitText` directive와
`.sammo-fit-text`를 사용한다. CSS의 12px에서 실제 로드된 폰트의 text range를
측정하고 칸보다 길 때만 축소한다. 너비·텍스트·font 로딩이 바뀌면 최대 단계부터
다시 계산하므로 짧아진 이름은 원래 크기를 회복한다. 10px에서도 넘치는 극단적인
반각 이름은 말줄임하며 원문을 DOM과 `title`에 남긴다. 이 경우 10~12px 사이의
실측값은 고정 예외 크기가 아니라 네 단계에 상한을 둔 자동 축소 결과다.
## 별도 콘텐츠 경계
- 국가 소개·임관 권유문 등 사용자 작성 HTML의 명시적 크기와 개인 CSS는 사용자
설정이다. 저장된 HTML이나 editor의 글자 크기 선택지는 변환하지 않는다.
editor의 선택지 UI 자체는 일반 크기를 상속한다.
- 위 HTML을 미리 보여 주는 축소 preview의 transform과 사용자가 고른 화면 배율은
글자 크기 토큰과 별개다. audit에서 computed 크기와 viewport scale을 함께 기록한다.
- 숨김·복사용 marker는 0px를 유지한다. 화면 본문 단계로 세지 않는다.
- Canvas 차트의 축·범례·tooltip도 공통 small 토큰과 화면 font family를 사용한다.
새 고정 크기 예외는 화면 목적과 geometry 근거, 후속 설계 필요성을 문서화한다.
단순히 칸이 좁다는 이유로 11px, 15px 같은 별도 토큰을 만들지 않는다.
## 검증
`typographyPolicy.spec.ts`는 production Chromium에서 실제 폰트 로딩, 단계별 중첩,
최대 이름, 사령부 행 높이, 자동 축소 상한과 말줄임, 로그 크기를 확인한다.
전체 페이지 audit는 [audit 실행 안내](frontend-typography-audit.md)를 따른다.
router의 모든 선언을 scene 목록과 대조하고, 각 scene의 screenshot·DOM·computed
style·text range·이미지 크기와 fixture 응답을 남긴다. 500/1000px와 실제 390px
기기의 화면 모드를 별도로 실행한다. 9개 전각·18개 반각 이름을 사용한다.
API 응답을 고정한 frontend 검증이다. 운영 DB, 실제 계정의 임의 HTML, 공개 HTTPS나
배포 성공의 증거로 확대하지 않는다. 값이 바뀔 수 있는 예외는 고정 토큰으로
위장하지 않고 생성 이유와 상한을 함께 검사한다.