# Countdown Đồng hồ đếm ngược dạng pill đổi màu theo trạng thái: thuế 72h "Còn 23:59:12", hồi chiêu "Hồi chiêu 4:59", ca đêm. ## Khi nào dùng - Có hạn chót hoặc thời gian hồi chiêu cần người chơi nhìn thấy liên tục. - Chỉ cần mốc giờ cố định ("Hạn chót 11:10"): viết chữ thường hoặc dùng Badge. ## Biến thể | Biến thể | Lớp | Dùng khi | |---|---|---| | Còn dư giờ | `tt-countdown tt-countdown--ok` | Nền `timer-ok`, icon ⏳ / ic_hourglass | | Sắp hết | `tt-countdown tt-countdown--soon` | Nền `timer-soon`, icon ⚠ / ic_warn, chữ "Sắp hết · mm:ss" (mặc định khi còn ≤ 1 giờ) | | Quá hạn | `tt-countdown tt-countdown--overdue` | Nền `timer-overdue`, icon ✖ / ic_angry, nhấp nhô `tt-pulse`, chữ "Quá hạn mm:ss" đếm lên | ## Cấu trúc ```html Còn 23:59:12 ``` Thường bạn chỉ cần một `` rồi gọi `TTMU.countdown`, JS dựng phần còn lại. ## Trạng thái | Trạng thái | Hiệu ứng | |---|---| | `--ok` | Nền `timer-ok` | | `--soon` | Nền `timer-soon`, icon ⚠, chữ "Sắp hết · mm:ss"; đổi khi còn ≤ `soonSeconds` | | `--overdue` | Nền `timer-overdue`, nhấp nhô; chữ "Quá hạn mm:ss" đếm lên; `onEnd` gọi một lần | ## Hành vi JS ```js const c = TTMU.countdown(el, { until?, seconds?, soonSeconds?, label?, soonLabel?, overdueLabel?, icons?, onEnd? }); // { destroy, remaining } TTMU.countdown(el, { seconds: 72 * 3600, label: 'Còn', icons: { ok: 'assets/Icons/ic_hourglass.png', soon: 'assets/Icons/ic_hourglass.png', overdue: 'assets/Icons/ic_warn.png' } }); c.destroy(); // dừng timer ``` - `until` nhận `Date`, ms epoch hoặc chuỗi ISO; hoặc `seconds` tính từ lúc gọi. Đếm bằng `Date.now()` nên không trôi khi tab bị treo hoặc thiết bị ngủ. - Định dạng: `23:59:12` khi từ 1 giờ trở lên, `4:59` khi dưới 1 giờ. Chữ số đều cột (`tabular-nums`). - Mỗi phần tử chỉ gắn một lần (`data-tt-bound="1"`); gọi lại trả về cùng API và bỏ qua `opts` mới. Muốn đổi hạn (`until` / `seconds`) hay nhãn thì `destroy()` rồi gọi lại. Hãy `destroy()` khi gỡ phần tử. - `until` không hợp lệ (không phải ngày giờ) được coi như đã hết giờ: hiện "Quá hạn 0:00" và gọi `onEnd`, không bao giờ hiện "NaN". - Chữ gán bằng `textContent`; `icons` là đường dẫn ảnh, bỏ trống thì dùng ký tự ⏳ (ok), ⚠ (soon), ✖ (overdue). ## Token sử dụng `timer-ok`, `timer-soon`, `timer-overdue`, `on-fill`, `radius-round`, `radius-20`, `space-6`, `space-12`. ## Khả năng tiếp cận - `role="timer"` với `aria-live="off"`: screen reader không đọc mỗi giây. `aria-label` được cập nhật thưa (mỗi phút, dưới 1 phút thì mỗi 10 giây, và khi đổi trạng thái): "Còn 23 giờ 59 phút". - Trạng thái không chỉ khác màu: mỗi trạng thái có icon riêng (⏳ ok, ⚠ soon, ✖ overdue) và chữ riêng ("Còn", "Sắp hết ·", "Quá hạn"). - Chữ `on-fill` trên `timer-ok` 5.0:1, `timer-overdue` 4.8:1 (chữ đậm); `timer-soon` chỉ 3.2:1 ở bốn theme gốc (giữ đúng màu game) nên đã có icon ⚠ và chữ "Sắp hết" đi kèm, không dựa vào màu; Tương phản cao: cả ba từ 7.9:1. - Hiệu ứng nhấp nhô tắt khi `prefers-reduced-motion`. ## Nên / Không nên | Nên | Không nên | |---|---| | Đặt cạnh nhãn nói rõ đếm cho việc gì ("Thuế 72h") | Đặt đồng hồ trơ trọi không rõ nghĩa | | Gọi `destroy()` khi gỡ phần tử | Để timer chạy ngầm sau khi phần tử đã rời DOM | ## Nguồn `.tax-timer-badge` và biến thể `.active|.grace|.overdue`, `@keyframes pulseOverdue` — css/style.css dòng 2052–2082; markup `#taxCountdownLive` — js/game.js. `.active` thành `--ok`, `.grace` thành `--soon`. Màu hex (`#15803d`, `#d97706`, `#dc2626`) đổi sang token `timer-*`. Bổ sung có chủ đích: icon, `role="timer"` và hành vi JS đếm theo giờ thật.