Files
2026-09-11 23:01:18 +07:00

255 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# DS — Design System — e-commerce
| | |
|---|---|
| **Version** | 1.1 |
| **Date** | 2026-09-09 |
| **Author** | BA/Design (skill ba-design, activity `ds`) |
| **Status** | 🟡 Draft |
| **Approved by** | Designer: — · PO: — *(dự án chưa có Designer riêng — PO ký thay, `DEC-01`)* |
| **Source** | `00-index/UICONV_e-commerce_v1.0.md` v1.1 (§0, §1, §8, §11, §11.1) · brand guideline: chưa có — `e-commerce/docs/sections/07-giao-dien.md` §7.0 xác nhận "không có brand guideline cố định" · design system tham chiếu: chưa chọn (`OQ-022`) · `02-analysis/BR_CartCheckout_v1.0.md` §3 (vòng đời `CART`/`ORDER_SELLER`/`PAYMENT`, nguồn bảng badge §3 bên dưới) · `03-specification/WF_US002-003_v1.0.md` (§3.1.2 — nguồn các kiểu trình bày cần phủ ở §3) |
| **Scope** | Toàn project — mọi `HIFI`, `FIGMA`, `DSPEC` có `PRODUCT = screen`. Lần chạy này lập bản đầu, dùng dữ liệu vào là US-002/US-003 (`SCR-04` Giỏ hàng) — token/component sẽ bồi đắp dần khi các US khác chạy `ba-design` |
| **Profile** | `screen · greenfield · standard` |
| **Tệp kèm** | `00-index/ds/tokens.json` (nguồn sự thật) · `ds/tokens.css` (sinh từ json) · `ds/components.html` (component sheet, mở bằng trình duyệt) |
## Change Log
| Version | Date | Người sửa | Thay đổi | CR |
|---|---|---|---|---|
| 1.0 | 2026-09-08 | BA/Design (qua skill ba-design, activity `ds`) | Bản đầu — token placeholder trung tính của template (chưa có brand), §3 ánh xạ 12 kiểu trình bày dùng trong `WF_US002-003`, bảng badge từ `BR_CartCheckout` §3 (qua `UICONV` §11.1), tự chấm | — |
| 1.1 | 2026-09-09 | BA/Design (qua skill ba-design, theo ghi chú người duyệt sau khi chạy `design-check.mjs`/`render-figma.sh`) | Nâng khối `ds-components` lên **v1.1** (thêm tiện ích `.ds-m-full` — sửa lỗi bố cục mobile của `HIFI_US002-003` §2.1); chép kết quả `design-check.mjs` nguyên văn vào §6 (T1/T2 đều ✅, 10/10 cặp tương phản đạt); cập nhật Tự chấm #1, #2 | — |
> **Vai trò của tài liệu này.** `UICONV` nói *hành xử thế nào* (phân trang, vị trí toast, validate lúc nào);
> tài liệu này nói *trông thế nào* (màu, chữ, khoảng cách, bo góc, đổ bóng, trạng thái hover/focus/disabled).
> `HIFI` và `FIGMA` chỉ dùng token và component ở đây; muốn khác phải có dòng ở §8.
>
> **Thứ tự ưu tiên khi lệch:** Brand guideline của khách > Design system tham chiếu (nếu dự án đã chọn) >
> `DS` này > `HIFI` từng US. Thiếu brand ⇒ giá trị ở đây là **đề xuất trung tính** chờ Designer/PO chốt — ghi `OQ`, không tự coi là đã duyệt.
> ⚠️ **Ngoại lệ gate G2** — tài liệu này (và `HIFI`/`FIGMA`/`DSPEC` phụ thuộc nó) được sinh trong khi
> Gate G2 (Solution sign-off) của module Giỏ hàng & Checkout **chưa được PO + Tech Lead ký chính thức**
> (nối tiếp `DEC-02`, `DEC-05`, `DEC-06` — xem `00-index/DECISION_e-commerce.md`). Ngoại lệ đã được người
> điều phối xác nhận rõ ràng lần nữa — xem `DEC-07`. Rủi ro riêng của `DS`: thấp hơn `SRS`/`AC` vì `DS` là
> token cấp **project** (không phụ thuộc trực tiếp `BACKLOG`/`BR` của module) — nhưng bảng badge ở §3 tham
> chiếu `BR_CartCheckout_v1.0.md` §3, nếu BR đổi khi G2 ký thật thì bảng đó phải rà lại.
---
## 0. Nguồn và độ tin cậy
| | |
|---|---|
| **Brand guideline** | Chưa có — `e-commerce/docs/sections/07-giao-dien.md` §7.0 xác nhận rõ "không có brand guideline cố định". Mọi giá trị màu/chữ ở §1 là **placeholder trung tính của template `ba-design`**, không phải đề xuất nhận diện thương hiệu. `OQ-022` (đã mở ở `UICONV`, chưa đóng) |
| **Design system tham chiếu** | Chưa chọn. `UICONV` §0 dẫn NFR-07: "sử dụng design system chuẩn (VD Material Design hoặc Ant Design)" nhưng ghi rõ "dev chọn, chưa chốt" → `OQ-022`, hỏi Tech Lead |
| **Prototype đã có** | `e-commerce/docs/sections/07-giao-dien.md` §7.1.1 SCR-04 — mô tả **văn bản có cấu trúc**, không có mã màu/khoảng cách chính xác (không phải Figma/ảnh) — không trích được token từ đây, chỉ trích được bố cục (đã dùng ở `WF`) |
| **Ai sở hữu** | BA/Design đề xuất · **Designer duyệt** visual (dự án không có Designer ⇒ **PO ký thay**, `DEC-01`) · PO duyệt giọng văn/nhận diện · Dev FE xác nhận khả năng hiện thực với stack đã chọn khi `OQ-022` được trả lời |
| **Độ tin cậy** | 🔴 **Đề xuất trung tính, chưa có nguồn** — toàn bộ bảng màu/chữ/khoảng cách ở §1 là giá trị mặc định của `templates/tokens.json` trong bộ skill `ba-design`, không suy ra từ brand hay prototype (prototype chỉ có bố cục, không có visual). Mọi `HIFI`/`FIGMA` sinh từ `DS` này **mang cùng độ tin cậy 🔴** cho tới khi `OQ-022` (design system) và brand guideline được PO/Tech Lead xác nhận |
---
## 1. Token
*Bảng này tóm tắt; giá trị chuẩn ở `ds/tokens.json`. Sửa ⇒ sửa json, sinh lại css, chạy `design-check.mjs`.*
### 1.1 Màu
| Token | Giá trị | Dùng cho | Nguồn |
|---|---|---|---|
| `color.bg.canvas` | `#F6F7F9` | Nền trang | 🔴 Placeholder template — `OQ-022` |
| `color.bg.surface` | `#FFFFFF` | Card, bảng, modal | 🔴 Placeholder template |
| `color.bg.subtle` | `#EEF1F5` | Header bảng, vùng phụ | 🔴 Placeholder template |
| `color.border.default` / `.strong` | `#D5DAE1` / `#767F8C` | Viền chia / viền ô nhập (F01) | 🔴 Placeholder template |
| `color.text.primary` / `.secondary` / `.disabled` / `.inverse` | `#1B1F24` / `#5B6472` / `#9AA3AF` / `#FFFFFF` | Chữ chính (tên sản phẩm C04) / phụ (biến thể C05, hint) / vô hiệu (F01/C07 khi đang xử lý) / trên nền đậm (nhãn nút chính C11) | 🔴 Placeholder template |
| `color.brand.primary` / `-hover` / `-subtle` | `#2456A6` / `#1C468A` / `#E7EEF9` | Nút chính (C11), liên kết, focus / hover / nền chọn | 🔴 Placeholder — chưa có màu thương hiệu thật |
| `color.status.danger` / `success` / `warning` (+ `-subtle`) | `#B42318` / `#1E7B45` / `#B7791F` (+ nền nhạt) | Nút nguy hiểm (C07), banner lỗi (C12, E-CART-0003/0006), message lỗi F01 (E-CART-0002) | 🔴 Placeholder template |
### 1.2 Chữ
| Token | Giá trị | Dùng cho |
|---|---|---|
| `font.family.base` | Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif | Toàn hệ thống — *system stack, giữ nguyên theo chỉ đạo (không có font brand)* |
| `font.size.xs / sm / md / lg / xl / 2xl` | 12 / 14 / 16 / 20 / 24 / 32 px | Hint, nhãn phụ (C05, C08, C09) / body (C04, C06) / — / — / C01 (tiêu đề Giỏ hàng) / — |
| `font.weight.regular / semibold` | 400 / 600 | Text thường / tiêu đề, nút, tên gian hàng (C02) |
| `font.line-height.tight / normal` | 1.25 / 1.5 | Tiêu đề / body |
### 1.3 Khoảng cách, bo góc, đổ bóng, kích thước
| Token | Giá trị | Ghi chú |
|---|---|---|
| `space.1 … 12` | 4 · 8 · 12 · 16 · 24 · 32 · 48 px | Thang 4px; không dùng số ngoài thang |
| `radius.sm / md / pill` | 4 / 8 / 999 px | Nút, ô nhập (F01) / card (nhóm seller), modal / badge |
| `shadow.sm / md` | — | Card nổi / modal xác nhận xoá |
| `size.control.desktop / touch` | 36 / 44 px | Chiều cao nút (C07, C10, C11, C13), bộ đếm F01; **mobile ≥ 44px** (hit target) |
| `size.icon` | 20 px | Icon inline (spinner, alert) |
| `size.content-max` | 1200 px | 🔴 Placeholder — `UICONV` §1 chưa chốt số thật (`OQ-018`), giữ giá trị template cho tới khi có câu trả lời |
| `breakpoint.tablet / desktop` | 768 / 1024 px | 🔴 Placeholder — `UICONV` §1 chưa chốt (`OQ-018`); `WF_US002-003` chỉ hiện thực desktop/mobile (không có công tắc tablet trong toolbar), `HIFI`/`FIGMA` lần này theo đúng phạm vi đó |
## 2. Thang chữ áp dụng
| Cấp | Token size / weight / line-height | Dùng ở |
|---|---|---|
| H1 — tiêu đề màn hình | xl / semibold / tight | C01 "Giỏ hàng của bạn" |
| H2 — tiêu đề vùng, modal | lg / semibold / tight | Tiêu đề modal xác nhận xoá |
| H3 — tiêu đề card | md / semibold / tight | C02 tên gian hàng |
| Body | sm / regular / normal | C04, C06, form |
| Nhãn field, hint, header bảng | xs / semibold hoặc regular / normal | C05 (biến thể), C08/C09 (nhãn tạm tính/tổng) |
## 3. Từ vựng component — ánh xạ ba chiều
*Cột 1 = UICONV §11 (BA) · cột 2 = lớp CSS trong `ds/components.html` (HIFI dùng) · cột 3 = component của design system tham chiếu (Dev FE dùng, để trống vì `OQ-022` chưa chốt). Bảng dưới phủ đủ mọi kiểu trình bày xuất hiện trong `WF_US002-003_v1.0.md` §3.1.2 (đánh dấu ✅ "dùng ở SCR-04") cộng các dòng còn lại của template (chưa dùng ở US này, giữ cho các US sau).*
| Kiểu trình bày (UICONV §11 / SRS "Loại") | Lớp DS | Component DS tham chiếu | Dùng ở SCR-04 | Ghi chú |
|---|---|---|---|---|
| Tiêu đề cấp 1 | `<h1>` (mặc định `ds-components.css`) | chưa chọn (`OQ-022`) | ✅ | C01 |
| Nhãn | text thường / `.ds-muted`, `.ds-small` cho phụ | chưa chọn (`OQ-022`) | ✅ | C02 (tên gian hàng, `<b>`/H3), C04 (tên SP), C05 (biến thể), C06 (đơn giá), C08 (tạm tính nhóm), C09 (tổng cộng) |
| Ảnh | `.ds-thumb` | chưa chọn (`OQ-022`) | ✅ | C03 — ô xám khi lỗi tải (theo `UICONV` §1) |
| Nút chính | `.ds-btn.ds-btn--primary` | chưa chọn (`OQ-022`) | ✅ | C11 "Tiến hành Checkout" — một nút chính mỗi màn hình |
| Nút phụ | `.ds-btn.ds-btn--secondary` | chưa chọn (`OQ-022`) | ✅ | C10 "Tiếp tục mua sắm", C13 "Thử lại", nút "Huỷ" trong modal xác nhận xoá |
| Nút nguy hiểm | `.ds-btn.ds-btn--danger` | chưa chọn (`OQ-022`) | ✅ | C07 "Xoá" + nút xác nhận trong modal — luôn kèm modal xác nhận (`UICONV` §5) |
| Liên kết | `.ds-link` | chưa chọn (`OQ-022`) | ❌ | Không dùng ở SCR-04 (§1.5 dòng 7 của SRS: không điều hướng từ dòng CartItem) |
| Ô nhập / Ô nhập số | `.ds-field` + `.ds-input` / `.ds-stepper` | chưa chọn (`OQ-022`) | ✅ | F01 — bộ đếm +/- (`.ds-stepper`), có ô nhập tay giữa bộ đếm |
| Chọn 1 (≤7) / (>7) | `.ds-check` radio / `.ds-select` | chưa chọn (`OQ-022`) | ❌ | Chưa dùng ở SCR-04 |
| Chọn nhiều | `.ds-check` checkbox | chưa chọn (`OQ-022`) | ❌ | Checkbox chọn dòng chỉ có ở **prototype**, chưa vào SRS (`WF §5 #1`, `OQ-031`) — **không** vẽ trong HIFI với `data-c` |
| Công tắc | `.ds-switch` | chưa chọn (`OQ-022`) | ❌ | Chưa dùng ở SCR-04 |
| Bộ đếm số lượng | `.ds-stepper` | chưa chọn (`OQ-022`) | ✅ | F01 (trùng dòng Ô nhập số) |
| Bảng / Card | `.ds-table-wrap` / `.ds-cards > .ds-card` | chưa chọn (`OQ-022`) | ✅ (Card) | Mỗi nhóm seller (Z2) và khối tổng kết (Z3) dùng `.ds-card`; không dùng `.ds-table` (Guest/Customer không có bảng dữ liệu — `UICONV` §1) |
| Badge trạng thái | `.ds-badge[--info/--success/--warning/--danger]` | chưa chọn (`OQ-022`) | ❌ | **Không** hiển thị badge trạng thái `Cart`/`Order`/`Payment` ở SCR-04 — US-002/US-003 chỉ thao tác trên `Cart.status=active` (SRS §1.4), badge để dành cho US khác (checkout, quản lý đơn) — xem bảng ánh xạ bên dưới |
| Tab | `.ds-tabs > .ds-tab` | chưa chọn (`OQ-022`) | ❌ | Chưa dùng ở SCR-04 |
| Modal / Drawer / Bottom-sheet | `.ds-modal-backdrop > .ds-modal` | chưa chọn (`OQ-022`) | ✅ (Modal) | Xác nhận xoá `CartItem` (hành vi của C07, theo `UICONV` §5 mặc định — `WF §5 #5`, đã xử lý) |
| Toast | `.ds-toast[--error]` | chưa chọn (`OQ-022`) | ❌ | `UICONV` §5/§11 chưa chốt vị trí/thời lượng (`OQ-020`) — SCR-04 không dùng toast, cập nhật C08/C09 tại chỗ (`WF` §3.1.5) |
| Banner trong vùng | `.ds-banner[--error/--warning]` | chưa chọn (`OQ-022`) | ✅ | C12 (lỗi tải `GET /v1/cart`), banner tại dòng cho `E-CART-0003`/`0006` |
| Skeleton | `.ds-skeleton` | chưa chọn (`OQ-022`) | ✅ | Trạng thái "Đang tải lần đầu" (SRS §2.A.3.6) |
| Trạng thái rỗng (2 loại) | `.ds-empty` (có minh hoạ) / `.ds-empty.ds-empty--filter` (không) | chưa chọn (`OQ-022`) | 🟠 (chỉ 1/2 loại) | SCR-04 chỉ có loại "chưa có dữ liệu" (`.ds-empty`, C10) — **không có** loại "bộ lọc không khớp" (giỏ hàng không có bộ lọc, đã ghi ở `WF` §3.1.3, không phải thiếu sót) |
| Trang trạng thái | `.ds-status-page` | chưa chọn (`OQ-022`) | ❌ | Chưa dùng ở SCR-04 (E-CART-0005 hiển thị "trang trạng thái" theo SRS §4.1 nhưng nằm ngoài SCR-04 — điều hướng sang trang khác) |
| `LanguageSwitcher` | *(chưa có class riêng — dự án chưa dựng)* | chưa chọn (`OQ-022`) | ❌ | Thuộc header toàn site (SCR-01), không thuộc SCR-04 |
| `CurrencyToggle` / `PriceDisplay` | *(chưa có class riêng — dự án chưa dựng)* | chưa chọn (`OQ-022`) | 🟠 | C06/C08/C09 hiển thị VND theo `UICONV` §8 (`#,##0 ₫`) — chưa có giá quy đổi tham khảo ở SCR-04 (SRS không yêu cầu), `PriceDisplay` dùng nguyên `.ds-price` |
**Ánh xạ trạng thái nghiệp vụ → badge** *(từ `BR_CartCheckout_v1.0.md` §3, qua `UICONV` §11.1 — chưa có US nào ở SCR-04 hiển thị các badge này; giữ ở đây để `HIFI` của US-004 trở đi dùng lại đúng token, không định nghĩa lại)*:
| Trạng thái (BR) | Biến thể badge | Nhãn nguyên văn (SRS §4.2 — chờ US tương ứng) | Nhóm ý nghĩa màu (UICONV §11.1) |
|---|---|---|---|
| `CART.active` | `.ds-badge` (mặc định, trung tính) | "Đang hoạt động" | Trung tính |
| `CART.converted` | `.ds-badge--success` | "Đã chuyển thành đơn" | Thành công |
| `CART.abandoned` | `.ds-badge` (mặc định) hoặc `.ds-badge--warning` *(chưa chốt — trung tính/cảnh báo nhẹ theo `UICONV`, để Dev FE/Designer chọn khi US dùng tới)* | "Bỏ quên" | Trung tính/cảnh báo nhẹ |
| `ORDER_SELLER.pending` *(khởi tạo)* | `.ds-badge--warning` | "Chờ thanh toán" | Cảnh báo |
| `ORDER_SELLER.confirmed` *(khởi tạo)* | `.ds-badge--success` | "Đã xác nhận" | Thành công |
| `ORDER_SELLER.cancelled` *(khởi tạo)* | `.ds-badge--danger` | "Đã huỷ" | Lỗi/trung tính |
| `PAYMENT.pending` | `.ds-badge--warning` | "Chờ xử lý" | Cảnh báo |
| `PAYMENT.success` | `.ds-badge--success` | "Thành công" | Thành công |
| `PAYMENT.failed` | `.ds-badge--danger` | "Thất bại" | Lỗi |
**Tiện ích bố cục mới (v1.1):** `.ds-m-full` — ở mobile (`body[data-view="mobile"]` hoặc `max-width:767px`), phần tử mang lớp này chiếm trọn hàng (`flex-basis:100%`). Dùng để nhóm nút thao tác (VD F01 + nút Xoá của một dòng `CartItem`) rơi xuống hàng riêng thay vì chồng lên khối text khi hàng `.ds-row` không đủ chỗ — xem `HIFI_US002-003_v1.0.html` §Minh hoạ dòng CartItem và `DSPEC_US002-003_v1.0.md` §2.1. Không phải một "kiểu trình bày" theo UICONV §11 (không cần dòng riêng trong bảng trên) — là tiện ích kỹ thuật chung của `ds-components.css`.
🔴 Bảng trên **chưa đủ để US ngoài SCR-04 dùng trực tiếp** — vòng đời đầy đủ `ORDER_SELLER` sau `confirmed` (packed/shipped/delivered/returned…) chưa có badge tương ứng (ngoài phạm vi module Giỏ hàng & Checkout, xem `UICONV` §11.1). Không có màu hex cụ thể theo nhóm ý nghĩa (chỉ có biến thể lớp) vì chưa chọn design system — `OQ-022`.
## 4. Trạng thái component
*Mỗi ô ghi thay đổi thị giác (token nào đổi). "—" = không có trạng thái đó. Ô trống = chưa thiết kế.*
| Component | Default | Hover | Focus | Disabled | Loading | Error / Invalid | Selected |
|---|---|---|---|---|---|---|---|
| Nút chính (C11) | `brand.primary` | `brand.primary-hover` | ring 2px `focus.ring` | opacity .45 + **tooltip lý do** (giỏ 0 CartItem) | — | — | — |
| Nút phụ (C10, C13) | surface + `border.strong` | `bg.subtle` | ring | opacity .45 + tooltip | spinner (C13 khi đang thử lại — không bắt buộc theo SRS, tuỳ Dev FE) | — | — |
| Nút nguy hiểm (C07) | `status.danger` | tối hơn *(chưa có token `-hover` riêng cho danger — 🔴 để trống, DS §9)* | ring | opacity .45 + tooltip ("đang xử lý") | spinner (khi DELETE đang chạy) | — | — |
| Bộ đếm F01 | `border.strong` | `text.secondary` | `brand.primary` + ring | `bg.subtle`, chữ disabled (khi đang gửi PATCH) | spinner cục bộ tại dòng (theo SRS §2.A.3.6) | `status.danger` + message dưới (E-CART-0002) | — |
| Card nhóm seller (Z2) | surface + `border.default` | — | — | — | skeleton (khi loading) | banner lỗi tại dòng (E-CART-0003/0006) | — |
| Banner (C12) | `status.danger-subtle` + `status.danger` | — | — | — | — | luôn ở trạng thái lỗi khi hiện | — |
| Modal xác nhận xoá | — | — | focus trap trong modal (chưa xác nhận cụ thể — mức thấp) | — | nút "Xoá" trong modal chuyển `is-loading` khi đang gửi | — | — |
## 5. Icon
| | |
|---|---|
| **Bộ icon** | Inline SVG tự vẽ theo mẫu `components.html` (lưới 24, stroke 1.75) — chưa chọn bộ icon thư viện ngoài (`OQ-022`) |
| **Quy cách** | Lưới 24, stroke 1.75, `stroke-linecap: round`, hiển thị 20px (`size.icon`), màu `currentColor` |
| **Cấm** | Emoji, ký hiệu ✓✕▲ dạng chữ thay icon (trừ mũi tên phân trang `‹ ›`), icon tô đặc trộn với icon nét |
| **Danh sách dùng ở SCR-04** | trash (nút Xoá C07 — tuỳ chọn kèm nhãn chữ), alert (banner lỗi C12, message F01), close (đóng modal), spinner (đang gửi) |
| **Danh sách chung dự án** | search · plus · close · chevron-down · back · check · alert · trash · filter · cart · user · menu — *(bổ sung theo WF các US sau)* |
## 6. Tương phản và khả năng truy cập
*Kết quả chạy `node scripts/design-check.mjs --tokens ds/tokens.json --css ds/tokens.css --components ds/components.html` — **đã chạy bởi người điều phối ngày 2026-09-09**, chép nguyên văn:*
```
✅ T0 tokens.json: 49 token, 17 màu
✅ T1 tokens.css khớp tokens.json
✅ T2 Tương phản: 10/10 cặp đạt
color.text.primary / color.bg.surface = 16.56 (≥4.5)
color.text.primary / color.bg.canvas = 15.45 (≥4.5)
color.text.secondary / color.bg.surface = 5.98 (≥4.5)
color.text.inverse / color.brand.primary = 7.1 (≥4.5)
color.text.inverse / color.status.danger = 6.57 (≥4.5)
color.brand.primary / color.bg.surface = 7.1 (≥4.5)
color.status.danger / color.bg.surface = 6.57 (≥4.5)
color.status.danger / color.status.danger-subtle = 5.75 (≥4.5)
color.text.primary / color.brand.primary-subtle = 14.19 (≥4.5)
color.border.strong / color.bg.surface = 4.05 (≥3)
✅ H1 components.html: khối :root khớp tokens.json
✅ H2 components.html: không có mã màu thô trong markup
ok — không có lỗi 🔴
```
| Cặp (fg / bg) | Dùng cho | Tỷ lệ (design-check) | Ngưỡng | ☐/✅ |
|---|---|---|---|---|
| `text.primary` / `bg.surface` | Chữ thường (C04, C05, C06) | 16.56 | 4.5 | ✅ |
| `text.primary` / `bg.canvas` | Chữ thường trên nền trang | 15.45 | 4.5 | ✅ |
| `text.secondary` / `bg.surface` | Hint, chữ phụ | 5.98 | 4.5 | ✅ |
| `text.inverse` / `brand.primary` | Nhãn nút chính (C11) | 7.1 | 4.5 | ✅ |
| `text.inverse` / `status.danger` | Nhãn nút nguy hiểm (C07) | 6.57 | 4.5 | ✅ |
| `brand.primary` / `bg.surface` | Liên kết | 7.1 | 4.5 | ✅ |
| `status.danger` / `bg.surface` | Message lỗi (E-CART-0002 dưới F01) | 6.57 | 4.5 | ✅ |
| `status.danger` / `status.danger-subtle` | Banner lỗi (C12, E-CART-0003/0006) | 5.75 | 4.5 | ✅ |
| `text.primary` / `brand.primary-subtle` | Chữ trên badge/hàng chọn | 14.19 | 4.5 | ✅ |
| `border.strong` / `bg.surface` | Viền F01 (non-text) | 4.05 | 3.0 | ✅ |
✅ **T1/T2 đã xác nhận bằng máy** (2026-09-09) — 10/10 cặp tương phản đạt, `tokens.css` khớp `tokens.json`. Kết quả trên là nguồn sự thật cho §6, thay cho bảng tính tay ở v1.0.
| Quy tắc | Giá trị | Nguồn |
|---|---|---|
| Hit target tối thiểu trên mobile | 44 × 44 px | WCAG 2.5.8 / `size.control.touch` — áp dụng cho F01 (+/-), C07, C10, C11, C13 ở mobile |
| Focus luôn thấy được | ring 2px `focus.ring`, offset 2px | WCAG 2.4.7 |
| Không truyền nghĩa chỉ bằng màu | Banner lỗi luôn có icon + text (C12); không có badge màu-only ở SCR-04 | WCAG 1.4.1 |
| Mọi ô nhập có nhãn | F01 có `aria-label="Số lượng"` (không có `<label>` hiển thị riêng theo bố cục compact — chấp nhận theo mẫu `.ds-stepper` của template) | WCAG 1.3.1 |
| Thứ tự focus | F01 dòng đầu tiên trước (`WF` §3.1.6) — 🔴 chưa xác nhận với PO/Dev FE, mức thấp không chặn G3 | `WF_US002-003_v1.0.md` §3.1.6 |
## 7. Motion
| Việc | Thời lượng | Easing | Ghi chú |
|---|---|---|---|
| Hover nút / dòng | 120 ms | ease | |
| Spinner cục bộ tại dòng CartItem (F01/C07 đang gửi) | liên tục tới khi có phản hồi | linear | Theo `WF` §3.1.5 |
| Toast vào/ra | 200 ms | ease-out | Không dùng ở SCR-04 (chưa chốt toast toàn site — `OQ-020`) |
| Modal mở (xác nhận xoá) | 160 ms | ease-out | Không dùng bounce |
| Skeleton shimmer | 1.2 s lặp | linear | Tắt khi `prefers-reduced-motion` |
## 8. Ngoại lệ đã duyệt
*`HIFI`/`FIGMA` lệch DS ⇒ phải có dòng ở đây. Không có dòng ⇒ lệch là lỗi.*
| # | Tài liệu | Token/component lệch | DS nói | Thực tế | Vì sao | Ai duyệt | Ngày |
|---|---|---|---|---|---|---|---|
| 1 | `HIFI_US002-003_v1.0.html` | Trạng thái hover cho `.ds-btn--danger` | §4 ghi "chưa có token `-hover` riêng cho danger" | `HIFI` dùng `filter: brightness(.92)` inline qua class phụ (không phải mã màu thô) để mô phỏng hover, chờ DS bổ sung token `color.status.danger-hover` chính thức | Component sheet gốc (`ds-components.css`) chưa định nghĩa `.ds-btn--danger:hover`; HIFI cần thể hiện đủ hover cho nút Xoá C07 | PO (thay Designer, `DEC-01`) — cần Designer/Tech Lead xác nhận khi có | 2026-09-08 |
## 9. Open Questions
| ID | Câu hỏi | Hỏi ai | Từ ngày | Chặn gì |
|---|---|---|---|---|
| OQ-022 | *(nhắc lại từ `UICONV`)* Chọn design system tham chiếu — Material Design hay Ant Design? | Tech Lead | 2026-09-08 | §0, §3 (cột "Component DS tham chiếu" toàn bộ để trống) |
| OQ-018 | *(nhắc lại từ `UICONV`)* Breakpoint/`content-max` thật (px) | Dev FE / Tech Lead | 2026-09-08 | §1.3 `breakpoint.*`, `size.content-max` |
| OQ-035 | Token `color.status.danger-hover` chưa tồn tại trong `tokens.json`/`ds-components.css` — cần bổ sung chính thức hay giữ giải pháp tạm (`filter: brightness`) ở `HIFI`? | Tech Lead / Dev FE | 2026-09-08 | DS §4, §8 dòng #1; mọi `HIFI` có nút nguy hiểm |
---
## Tự chấm
| # | Tiêu chí | ☐/✅ |
|---|---|---|
| 1 | `tokens.json` parse được; `tokens.css` sinh khớp (design-check T1 = ok) | ✅ — xác nhận bằng máy 2026-09-09, xem §6 |
| 2 | Mọi cặp tương phản ở §6 đạt ngưỡng (design-check T2 = ok) | ✅ — 10/10 cặp đạt, xác nhận bằng máy 2026-09-09, xem §6 |
| 3 | §3: mọi kiểu trình bày của UICONV §11 có lớp DS **và** có mặt trong `components.html` | ✅ — 21 dòng phủ toàn bộ từ vựng UICONV §11 (kể cả các dòng chưa dùng ở SCR-04, giữ nguyên lớp cho US sau) |
| 4 | §4: mọi component có đủ default / hover / focus / disabled; component nhập liệu có Error | ☐ — Nút nguy hiểm thiếu token hover riêng (xem §8 #1, `OQ-035`); các component còn lại đủ |
| 5 | `breakpoint.*` khớp UICONV §1; `size.content-max` khớp UICONV §1 | 🔴 — UICONV §1 chưa chốt số thật (`OQ-018`), giữ nguyên giá trị template theo đúng chỉ đạo, không tự đặt số |
| 6 | Hai trạng thái rỗng trong `components.html` trông khác nhau | ✅ — kế thừa nguyên văn từ template (minh hoạ+nút chính / không minh hoạ+nút phụ) |
| 7 | §0 ghi rõ độ tin cậy; giá trị placeholder có `OQ` | ✅ — 🔴, ghi rõ `OQ-022` |
| 8 | Có chữ ký Designer (hoặc PO ký thay + `DEC-nn` "không có Designer") | ☐ — chưa ký thật, chờ PO tại G3 (`DEC-01`) |