# Dialog
Hộp thoại modal trên nền mờ: hỏi xác nhận ("Đóng thuế 72h?"), tổng kết ngày, và thông báo "Lời nhắn từ Dev".
## Khi nào dùng
- Cần người chơi chọn trước khi game đi tiếp: đóng cửa sớm, nộp thuế, xoá dữ liệu.
- Hiện tổng kết hoặc thông báo lớn (tiêu đề `--display`, nhãn + khối chữ).
- Chỉ báo tin nhanh, không cần chọn: dùng Toast.
## Biến thể
| Biến thể | Lớp | Dùng khi |
|---|---|---|
| Hộp thoại | `tt-dialog` > `tt-dialog__card` | Phủ cả màn hình bằng `scrim`; card `panel`, bo 20, `shadow-modal`, rộng tối đa 400px |
| Tiêu đề | `tt-dialog__title` / `--display` | 1.45rem (h2) hoặc 2rem (h1 tổng kết, lên level; JS: `display: true`) |
| Nút | `tt-dialog__actions` | Cột nút, khe 8px; dùng nút của Button |
| Thông báo Dev | `tt-dialog__badge`, `__close`, `__body` | Nhãn hồng, nút đóng tròn 32px, khối chữ có vạch hồng trái; JS dựng bằng `badge`, `dismissible`, `body` |
| Trưng bày | `tt-dialog tt-dialog--inline` | Chỉ để vẽ mẫu tĩnh trong tài liệu |
## Cấu trúc
```html
Đóng thuế 72h?
Nộp 12% tiền két (28,5k) để được buff khách.
```
## Trạng thái
| Trạng thái | Hiệu ứng |
|---|---|
| Mở | Thêm vào cuối `.tt-app` (hoặc `body`), focus vào nút đầu |
| Đóng | Gỡ phần tử và listener, trả focus về chỗ cũ |
| Nội dung dài | Card cuộn trong (`max-height: 88vh`), scrim cũng cuộn được |
## Hành vi JS
```js
const v = await TTMU.dialog({
title: 'Đóng thuế 72h?',
message: 'Nộp 12% tiền két (28,5k) để được buff khách.',
actions: [
{ label: 'Nộp thuế', value: 'pay', variant: 'primary' },
{ label: 'Để sau', value: 'later' }, // 'ghost' là mặc định
],
}); // 'pay' | 'later' | null
```
- Promise trả về `value` của nút đã bấm (bỏ `value` thì trả `label`); `Esc` hoặc bấm nền mờ trả `null`.
- `variant`: `primary` (nút lớn), `ghost` (nút nhỏ phụ, mặc định), `danger`. Bỏ `actions` thì có một nút "Đóng" trả `true`.
- Khoá focus: `Tab` / `Shift+Tab` xoay vòng trong các nút. Mở dialog mới sẽ đóng dialog cũ với `null`.
- Banner kiểu "Lời nhắn từ Dev":
`TTMU.dialog({ badge: '📢 Lời nhắn từ Dev', title: 'Có bản mới', body: '…', dismissible: true, actions: [{ label: 'Cập nhật', variant: 'primary' }] })`.
`badge` dựng `__badge`, `body` dựng `__body`, `dismissible` dựng nút `__close` (✕, `aria-label="Đóng"`, resolve `null`), `display: true` đổi tiêu đề thành h1 `--display`. Mọi phần này có sẵn Esc, khoá focus, trả focus. Không tự viết tay các phần này.
- Tên hộp thoại: `title` (aria-labelledby) → `label` (aria-label) → `message` → "Hộp thoại". `message` và `body` đều được nối vào `aria-describedby`.
- `title`, `message`, `badge`, `body`, `label`, nút `label` đều gán bằng `textContent`, không nhận HTML (game gốc `ask()` dùng `innerHTML`).
## Token sử dụng
`scrim`, `panel`, `soft`, `ink`, `pink` (vạch bên khối chữ Dev), `fresh-1-bg/ink` (nhãn Dev), `fill-muted` (nút ✕), `shadow-modal`, `shadow-edge` (viền nút ✕ ở `hc`), `radius-12/14/20`, `radius-round`, `space-4/6/8/12/14/16`; nút hành động dùng token của Button.
## Khả năng tiếp cận
- Card có `role="dialog"`, `aria-modal="true"`, luôn có tên (xem trên) và `aria-describedby` trỏ tới `message` và/hoặc `body` (banner chỉ có `body` vẫn có mô tả).
- Toast (z-index 100000) nằm dưới nền mờ của dialog (100050), giống game: hãy gọi `TTMU.toast` sau khi dialog đã resolve, đừng gọi khi dialog còn mở.
- Focus vào nút đầu khi mở, Esc đóng, focus quay về nút đã mở dialog.
- Nền mờ và padding đã cộng `env(safe-area-inset-*)` để không bị tai thỏ / thanh home che.
- Nút đóng `✕` (`dismissible`) có `aria-label="Đóng"` và nằm trong vòng Tab.
## Nên / Không nên
| Nên | Không nên |
|---|---|
| Đặt hành động chính (primary) ở trên, hành động phụ ở dưới | Cho nhiều hơn một nút primary |
| Đặt tên nút bằng động từ ("Nộp thuế", "Để sau") | Dùng "OK / Cancel" chung chung |
| Xử lý `null` như "huỷ" | Giả định luôn có giá trị trả về |
## Nguồn
`.modal`, `.card`, `.card h1/h2/p`, `.askbtns` — css/style.css (dòng 143, 502–504 và `.modal`); `ask()` — js/game.js; `.sp-banner-*` — css/style.css dòng 1011–1130. Màu: `#fffdfa`, `#faf4eb`, `#ffe7ed` đổi thành `panel` hoặc `color-mix` của `pink`; viền `#ecd8c5` bỏ để theo card `.card`; scrim Dev `rgba(20,14,10,.86)` dùng `scrim` chung; animation `spbPop` bỏ cho nhất quán.