내 작은 왕국 기획 위키

UI 선언 시스템

03 만드는 법

이 문서 안에서

게임 화면은 ui/ 폴더의 선언 파일 25개에 들어 있습니다. 이 문서는 파일 문법, 렌더러 절차, 테마 토큰, 새 화면 추가 절차를 정합니다. 마운트 시점과 오버레이 생명주기는 화면 구성이 다룹니다.

한 줄 요약 — 마크업과 CSS 는 ui/*.ui.json 에, TypeScript 는 데이터 바인딩과 이벤트만 — 두 세계를 잇는 유일한 계약은 ref 문자열입니다.


왜 JSON 인가

마켓 목업(src/market/)이 shared만 import 할 수 있어 렌더러가 src/shared/ui/에 있어야 합니다. 그 목업 렌더러가 같은 .ui.json을 같은 헬퍼로 써서 스크린샷과 실제 화면이 일치합니다(마케팅 소재). JSON에 함수를 담을 수 없으므로 events 사용 횟수는 0입니다.


문서 하나의 모양

파일 하나 = UIDefinition(src/shared/types/ui.ts). 키 순서: namecsstemplatesui.

필드필수
name필수문서 이름. 빈 불허. 파일명과 동일
ui필수루트 노드 하나
css선택문서 전용 스타일시트. 자동 스코핑
templates선택리스트 바인딩용 조각

css는 한 줄 JSON 문자열. resolveJsonModule static import → src/ui-json.d.ts가 타입 지정. 빌드 시점 번들.

노드 속성 목록

재귀 타입 UINode(src/shared/ui/dom/types.ts).

속성사용 횟수
type태그 이름881
class문자열 또는 문자열 배열818
refTS 가 이 노드를 찾는 이름436
texttextContent397
children자식 노드 배열335
attrDOM 속성208
style인라인 스타일10
key저작용 식별자(렌더 무관)0
css노드 단위 스코프 CSS0
events이벤트 핸들러0

ElementTag 33개 + (string & {}). 빈 문자열만 거부, 실패 시 div 대체. 실 사용 11개: div(481) span(176) button(104) img(100) input(6) li(5) p(5) 외 4개(각 1).

class — 공백 split → classList.add. attrsetAttribute, undefined/false 건너뜀, 값 문자열·불리언·유한수. texttextContent. styleObject.assign, camelCase. 인라인 style 용도: 초기 접힘(display:"none")과 진행 막대(width:"100%")뿐.

검증 규칙

src/shared/data/parsers.tsvalidateUiNodeAt / validateUiDefinitionAt:

대상규칙
노드객체
깊이MAX_UI_DEPTH = 100
순환WeakSet 감지
type문자열, 빈 불허
key문자열, 빈 불허
class문자열(빈 허용) 또는 문자열 배열
text css문자열, 빈 허용
ref문자열, 빈 불허
style객체, 모든 값 문자열
attr객체, 값 문자열·불리언·undefined·유한수
events객체, 값 함수
children배열, 재귀 검증
문서name 비어 있지 않은 문자열, ui 유효 노드, templates 값마다 검증

검증은 작업대가 문서를 세울 때(양식 보고) 동작. 계약 테스트(tests/data-contracts.test.ts): 중첩 통과, 빈 type 거부, 순환 거부(빌드와 테스트).

ref 계약

render(){ el, refs }. refsMap<string, HTMLElement>. 436개.

규약설명
이름camelCase. 예외: tut-title tut-text tut-next
중복나중 것이 덮어씀
병합자식 refs → 부모로 올라옴
setRefText/setRefAttrref 없으면 조용히 지나감
uiTemplate(def, name)없는 이름 → throw

ref 최다: villager-detail(87), settings-screen(47), seal-office-screen(44).

렌더러 · 마운트 헬퍼

렌더 단계 (src/shared/ui/dom/render.ts)동작
1 createElement실패 시 div 대체
2 classclassList.add
3 styleObject.assign
4 attrsetAttribute
5 textel.textContent
6 children재귀 + refs 병합
7 eventsaddEventListener
8 refrefs.set
9 css스코프 스타일 주입, 래퍼 display:contents
마운트 함수 (src/game/screens/ui-mount.ts)동작
cloneUiNodestructuredClone
mountUiDef(def, parent?)clone + css 렌더
clearAndMount(parent, def)부모 비우고 재마운트, 스크롤 복원
repeatTemplate(host, tpl, items, bind)host 비우고 item마다 clone → bind
uiTemplate(def, name)조회, 없으면 throw
setRefText / setRefAttrref 있을 때만 설정

css 있으면 반환 el은 래퍼. 루트에 flex:1; min-height:0 필수. 리스트 렌더는 repeatTemplate+uiTemplate만 허용. 22개 문서 51개 템플릿: affinity-chart(chartRow chartChip) · blocked-dialog(blockedRow) · chat-screen(msgRow) · collect-all(resourceRow) · confirm-dialog(qtyPct) · craft(recipe material) · craft-job(material) · daily-wheel(slotLabel oddsRow) · inventory-screen(chip item) · kingdom-info(line) · kingdom-screen(menuBtn) · kingdom-sheet(row cost piece chip) · mission-screen(loginDay missionCard empty) · monster-info(statRow dropChip) · patch-notes(noteCard) · pen-detail(line) · recruit(jobCard villagerCard statChip) · recruit-detail(statChip yieldRow) · reward-popup(itemCell) · seal-office-screen(packRow packageRow passActive fundPlan fundChip shelfNote fundStepRow) · villager-detail(satchelRow craftJobRow spoilsRow equipSlot bagRow menuItem foodRow mineRow buffChip) · villagers(villagerCard empty chip).

테마 토큰

src/game/screens/theme.ts PANEL_THEME_CSS. openGameOverlay()가 화면별 CSS 앞에 주입.

클래스핵심 값
.mlk-overlayposition:fixed; inset:0; z-index:150; 스크림 rgba(6,6,14,0.82)
.mlk-containerwidth:100%; margin:auto; 세로 flex, Galmuri, #f5e6cc
.mlk-shell세로 flex + min-height:0
.mlk-scrollflex:1 1 auto; min-height:0; overflow-y:auto
.mlk-panel배경 #1a1a2e, 테두리 3px solid #5c4033, 그림자 inset 0 0 0 2px #8b6914
.mlk-btn-primary배경 #c4a35a, 글자 #1a1a0d, 테두리 #8b6914
.mlk-btn-secondary배경 #5c4033, 글자 #f5e6cc, 테두리 #8b6914
.mlk-icon-close글자 #b8a088 20px
.mlk-toast#6ac86a 12px 인라인 알림 줄
용도
본문 / 제목 금 / 수치 금#f5e6cc / #e8c84a / #f5d76e
주 버튼 hover / 보조 hover#d4b36a / #6b4c3d
카드 배경·테두리 / 흐린 글자#0d0d1a·#3a2a1a / #8a7a6a #b8a088
성공 / 경고·위험 / 인장 보라#6ac86a / #e8a84a #d68a8a #7a2a2a / #c9a0ff #6b4fa0

토스트가 두 종류인 이유

패널 안 .mlk-toast는 CSS 클래스 하나(craft mission-screen recruit villager-detail), 패널과 수명 동일. 플로팅(showToast())은 document.body z-200 호스트, 화면이 닫히는 액션의 확인용. 기준: 메시지가 패널보다 오래 살면 플로팅 — 그리고 패널 안에서도 플레이어가 보고 있지 않은 곳에 답하게 되면 플로팅입니다(영입의 금화·인장 부족). 플로팅은 pointer-events: none — 화면 위에 떠 있으므로 탭을 먹지 않고 뒤쪽 버튼/탭으로 흘려보냅니다.


UI 문서 37개

파일화면루트 class노드reftpl
bottom-tab-bar하단 탭바sdv-bar2230
villagers주민 탭v-container33223720px
inventory-screen가방 탭sdv-inv-screen64222420px
kingdom-screen왕국 탭kd-container24151500px
mission-screen미션 탭mi-container68443500px
seal-office-screen인장소 탭sh-container193597480px
settings-screen설정 탭s-container82520420px
chat-fab채팅 버튼mlk-chat-fab430
chat-screen반투명 채팅창mlk-chat20131
villager-detail주민 상세mlk-panel mlk-shell1861249
monster-info적 정보sdv-monster-info27162360px
affinity-chart속성 상성표sdv-affinity-chart24112360px
recruit영입mlk-panel mlk-shell48253
collect-all일괄 수거mlk-panel mlk-shell23151
craft제작mlk-panel mlk-shell53392
craft-job제작 중 항목mlk-panel24141
confirm-dialog확인 대화상자sdv-confirm28171360px
recruit-detail후보 상세sdv-recruit-detail38222380px
shop-info상품 설명sdv-shop-info17100360px
kingdom-sheet왕국 하위 시트 5종ks-shell mlk-shell49314
kingdom-info건축·행사 상세sdv-kingdom-info26161360px
pen-detail우리 상세sdv-pen-detail28171380px
daily-wheel행운의 돌림판dw-root24132360px
patch-notes패치 노트mlk-panel mlk-shell1791
patch-note-detail패치 노트 상세mlk-panel mlk-shell1590
feedback-dialog피드백mlk-panel mlk-shell18110
blocked-dialog차단 해제mlk-panel mlk-shell1691
reward-popup획득 팝업mlk-panel mlk-shell rw-panel35141
seal-exchange인장 교환sdv-seal-exchange1560360px
intro-root인트로 셸mlk-intro16140
name-input이름 입력name-modal1070min(400px,88%)
tutorial-coach코치 카드tut-card640
hud미사용sdv-hud1000
status-bars미사용sdv-status1100360px
daily-login미사용dl-container4500480px
action-menu미사용sdv-menu2600320px
dialog-sample미사용sdv-dialog700480px

합계 1,352 노드, 686 ref, 51 템플릿, 8,119줄. 32개 static import, 5개 미참조.


새 화면 추가 절차

  1. ui/<이름>.ui.json 만들기 (작업대의 UI 탭에서 새 파일 → 연필로 원문 편집).
  2. 루트 클래스. 오버레이: mlk-panel mlk-shell, 스크롤: mlk-scroll.
  3. CSS. 색은 위 팔레트, 여백 var(--mlk-screen-pad).
  4. TS 참조 노드에만 ref(camelCase).
  5. 반복 행 → templates. 루트에 프리뷰 샘플만.
  6. src/game/screens/overlays/에 TS, 문서 static import.
  7. openGameOverlayclearAndMount → refs 바인딩.
  8. setRefText(refs, name, t("…")).
  9. 아이콘 → public/ui/icons/, 문서에 /ui/icons/….
  10. game.html?ui로 그림 확인 → pnpm typecheckpnpm testpnpm build.

탭 화면: 루트 flex:1; min-height:0 필수. SCREEN_KEYS 키, SCREEN_COLORS 색, bottom-tab-bar.ui.jsondata-screen 버튼, main.ts 등록. styleId=mlk-<이름>-css, singletonKey 필수. 열쇠 규칙 다국어 텍스트, 셸 규약 화면 구성, 게이트 빌드와 테스트.

떠 있는 버튼(ui/chat-fab.ui.json): 바닥 기준을 화면과 똑같이 max(var(--mlk-bar-h), var(--kbi, 0px)) 로 잡으세요. 그러면 소프트 키보드가 올라올 때 버튼도 같이 올라갑니다. z-index 는 탭바(100)와 오버레이(150) 사이. 겹쳐 뜨는 대가로 스크롤 목록의 오른쪽 끝 버튼을 가릴 수 있다는 점은 알고 있는 트레이드오프입니다.


이어서 읽기

이 문서를 가리키는 곳