# 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
``` ## 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.