update skill ba

This commit is contained in:
Canhchimlac
2026-09-11 23:01:18 +07:00
parent 2c7bcde741
commit d169c85dd4
51 changed files with 4111 additions and 30 deletions

View File

@@ -2,12 +2,12 @@
| | |
|---|---|
| **Version** | 1.3 |
| **Version** | 1.4 |
| **Date** | 2026-09-08 |
| **Author** | BA (qua skill ba-lifecycle / ba-2-analysis / ba-3-specification) |
| **Author** | BA (qua skill ba-lifecycle / ba-2-analysis / ba-3-specification / ba-design) |
| **Status** | 🟡 Draft |
| **Approved by** | — *(sổ quyết định, cập nhật liên tục, không cần baseline riêng)* |
| **Source** | Ghi chú từ người duyệt trong prompt khởi tạo dự án; `references/workflow.md` §3 "Dự án không có Designer"; prompt điều phối GĐ2 (ngoại lệ gate, trả lời `OQ-004`/`OQ-008`); ghi chú từ người duyệt trong prompt điều phối GĐ3 (sửa DEC-01, chốt chế độ WF); prompt điều phối GĐ3 (ngoại lệ gate G2); prompt điều phối GĐ3 lần chạy US-002/US-003 (ngoại lệ gate G2 nối tiếp) |
| **Source** | Ghi chú từ người duyệt trong prompt khởi tạo dự án; `references/workflow.md` §3 "Dự án không có Designer"; prompt điều phối GĐ2 (ngoại lệ gate, trả lời `OQ-004`/`OQ-008`); ghi chú từ người duyệt trong prompt điều phối GĐ3 (sửa DEC-01, chốt chế độ WF); prompt điều phối GĐ3 (ngoại lệ gate G2); prompt điều phối GĐ3 lần chạy US-002/US-003 (ngoại lệ gate G2 nối tiếp); prompt điều phối `ba-design` lần chạy US-002/US-003 (ngoại lệ gate G2 nối tiếp lần ba) |
| **Scope** | Toàn dự án e-commerce |
## Change Log
@@ -18,6 +18,7 @@
| 1.1 | 2026-09-08 | BA (qua skill ba-2-analysis) | Thêm DEC-02 (ngoại lệ gate G1 khi chạy GĐ2), DEC-03 (xác nhận điểm tích hợp Khuyến mãi/Loyalty — trả lời `OQ-004`), DEC-04 (thứ tự ưu tiên khi cắt phạm vi — trả lời `OQ-008`) | — |
| 1.2 | 2026-09-08 | BA (qua skill ba-3-specification, activity `uiconv`) | Sửa nội dung `DEC-01` theo ghi chú người duyệt: dự án **CÓ** prototype tham chiếu dạng văn bản (`e-commerce/docs/sections/07-giao-dien.md`, SAD §7, status approved) — `WF` sẽ chạy chế độ 🎨 (mức phủ khác nhau theo từng màn hình) thay vì ✏️ hoàn toàn như ghi trước đây; phần PO ký thay Designer giữ nguyên. Thêm `DEC-05` (ngoại lệ gate G2 khi chạy GĐ3 `uiconv`) | — |
| 1.3 | 2026-09-08 | BA (qua skill ba-3-specification, activity `srs`/`ac`/`nfr`/`api`/`wf`) | Thêm `DEC-06` (ngoại lệ gate G2 nối tiếp khi viết `SRS`/`AC`/`NFR`/`API`/`WF` của US-002/US-003) | — |
| 1.4 | 2026-09-08 | BA/Design (qua skill ba-design, activity `ds`/`hifi`/`figma`/`dspec`) | Thêm `DEC-07` (ngoại lệ gate G2 nối tiếp lần ba khi chạy `ba-design` cho US-002/US-003 — sinh `DS_e-commerce_v1.0.md`, `HIFI_US002-003_v1.0.html`, `FIGMA_US002-003_v1.0/`, `DSPEC_US002-003_v1.0.md`) | — |
## Danh sách quyết định
@@ -29,3 +30,4 @@
| DEC-03 | Xác nhận điểm nối với module Khuyến mãi & Loyalty (trả lời `OQ-004`, mở từ GĐ1): áp dụng mã coupon (FR-13) và điểm thưởng (FR-14) là **điểm tích hợp**, không thuộc phạm vi module Giỏ hàng & Checkout. Module Khuyến mãi/Loyalty **sở hữu logic tính toán**; module Giỏ hàng & Checkout **chỉ hiển thị kết quả đã tính và gọi API áp dụng**. | PO sàn (trả lời qua người điều phối) | 2026-09-08 | Phạm vi §5.2 của `BRIEF` được xác nhận đúng như đề xuất ban đầu của BA; `BACKLOG_CartCheckout_v1.0.md` không có US riêng cho coupon/loyalty; `IMPACT_CartCheckout_v1.0.md` §1.5 ghi nhận đây là một điểm tích hợp cần API contract trước GĐ3 (`OQ-017` mới phát sinh: xử lý khi API này lỗi/chậm) | `BRIEF_CartCheckout_v1.0.md` §5.2, `OQ-004`; trả lời của người dùng trong prompt điều phối GĐ2 |
| DEC-06 | Chạy `ba-3-specification` các activity `srs`/`part2`/`ac`/`errors`/`nfr`/`api`/`wf` để đặc tả US-002 (Xem giỏ hàng nhóm theo seller) và US-003 (Sửa số lượng / xoá sản phẩm trong giỏ) — sinh `SRS_US002-003_v1.0.md`, `AC_US-002_v1.0.md`, `AC_US-003_v1.0.md`, `NFR_US002-003_v1.0.md`, `API_US002-003_v1.0.md`, `WF_US002-003_v1.0.md`+`.html` — **trong khi Gate G2 của module Giỏ hàng & Checkout vẫn chưa được PO + Tech Lead ký chính thức** (nối tiếp `DEC-02`, `DEC-05`). Đây là ngoại lệ gate nối tiếp, chấp nhận cho mục đích đánh giá bộ skill BA. Rủi ro cao hơn `DEC-05` (UICONV cấp project) vì `SRS`/`AC` lần này bám trực tiếp vào `BACKLOG`/`BR` của module — nếu `BACKLOG_CartCheckout`/`BR_CartCheckout` đổi khi G2 ký thật, phần bị ảnh hưởng nhiều nhất là §1.3 (hiện chưa có `BR-CART-nn` áp dụng trực tiếp cho US-002/US-003) và mọi AC nhóm 1 dựa trên "không có BR ràng buộc". | Người điều phối, xác nhận rõ ràng và khẳng định lại sau khi được cảnh báo rủi ro | 2026-09-08 | `SRS_US002-003_v1.0.md` và các artifact phụ thuộc giữ `Status: 🟡 Draft`, không thể `✅ Baselined` cho tới khi G2 module được ký thật; đã ghi 7 `OQ` mới (`OQ-028`…`OQ-034`) vào `OQ_e-commerce.md` | Prompt điều phối GĐ3 (lần chạy US-002/US-003) "Ngoại lệ gate"; `DEC-02`, `DEC-05` |
| DEC-04 | Xác nhận thứ tự ưu tiên khi phải cắt phạm vi (trả lời `OQ-008`, mở từ GĐ1): giữ **RQ-001 (giỏ hàng đa seller) và RQ-002 (tách đơn theo seller)** làm ưu tiên trước; **RQ-003 phần thanh toán ví điện tử (VNPay/Momo) có thể lùi triển khai sau COD** nếu thiếu thời gian. | PO sàn (trả lời qua người điều phối) | 2026-09-08 | `BACKLOG_CartCheckout_v1.0.md` §8 (đề xuất thứ tự thực hiện): US-001…US-006 (giỏ hàng + checkout + Guest) và US-007 (COD) ưu tiên đợt 1–3; US-008 (VNPay/Momo) xếp đợt 4, có thể lùi tiếp nếu `ASM-01` chưa xác minh kịp | `BRIEF_CartCheckout_v1.0.md` §6, `OQ-008`; trả lời của người dùng trong prompt điều phối GĐ2 |
| DEC-07 | Chạy skill `ba-design` (activity `ds`, `hifi`, `figma`, `dspec`) cho US-002 (Xem giỏ hàng nhóm theo seller) và US-003 (Sửa số lượng / xoá sản phẩm trong giỏ) — sinh `00-index/DS_e-commerce_v1.0.md` (+ `ds/tokens.json`, `ds/tokens.css`, `ds/components.html`), `03-specification/design/HIFI_US002-003_v1.0.html`, `03-specification/design/FIGMA_US002-003_v1.0/`, `03-specification/design/DSPEC_US002-003_v1.0.md` — **trong khi Gate G2 của module Giỏ hàng & Checkout vẫn chưa được PO + Tech Lead ký chính thức** (nối tiếp `DEC-02`, `DEC-05`, `DEC-06`). Người dùng đã được cảnh báo rủi ro gate G2 chưa qua và **khẳng định lại rõ ràng** muốn tiếp tục, cho mục đích đánh giá bộ skill BA. Rủi ro riêng của lần chạy này: `HIFI` chép trực tiếp thành phần/text từ `SRS_US002-003_v1.0.md` (vốn đã mang rủi ro của `DEC-06`) — nếu `SRS` phải viết lại khi G2 ký thật, `DS`/`HIFI`/`FIGMA`/`DSPEC` của `SCR-04` phải chạy lại toàn bộ theo đúng chuỗi phụ thuộc `artifact-map.md` §5 (`SRS`→`HIFI`→`FIGMA`→`DSPEC`). Rủi ro thấp hơn ở phần `DS` (token cấp project, không phụ thuộc trực tiếp `BACKLOG`/`BR` của module) trừ bảng ánh xạ badge ở `DS` §3 (tham chiếu `BR_CartCheckout` §3). | Người điều phối, xác nhận rõ ràng và khẳng định lại sau khi được cảnh báo rủi ro | 2026-09-08 | `DS_e-commerce_v1.0.md`, `HIFI_US002-003_v1.0.html`, `FIGMA_US002-003_v1.0/`, `DSPEC_US002-003_v1.0.md` giữ `Status: 🟡 Draft`, không thể `✅ Baselined` cho tới khi G2 module được ký thật **và** G3 tự nó được ký; đã ghi ngoại lệ vào Open Questions của `DSPEC_US002-003_v1.0.md` §8 (dòng `DS-EX-01`) | Prompt điều phối `ba-design` (lần chạy US-002/US-003) "Ngoại lệ gate"; `DEC-02`, `DEC-05`, `DEC-06` |

View File

@@ -0,0 +1,254 @@
# 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`) |

View File

@@ -0,0 +1,332 @@
<!--
components.html — component sheet của DS_e-commerce, sinh bởi skill ba-design (activity ds). Tự chứa, mở bằng trình duyệt.
QUY TẮC: <style> = tokens.css (khối :root) + ds-components.css NGUYÊN VĂN. Mỗi component có data-component và data-uiconv (khớp UICONV §11).
Đủ trạng thái: default · hover (mô tả) · focus · disabled (+ tooltip lý do) · loading · error. Không mã màu thô ngoài biến --ds-*.
Kiểm: node .claude/skills/ba-design/scripts/design-check.mjs --tokens tokens.json --css tokens.css --html components.html
Nguồn: DS_e-commerce_v1.1.md · UICONV_e-commerce_v1.0.md v1.1 §11 (chưa chốt design system tham chiếu — OQ-022, chưa chốt brand — SAD §7.0)
v1.1: nâng khối ds-components lên v1.1 (thêm .ds-m-full — tiện ích xếp chồng thao tác ở mobile, dùng để sửa lỗi bố cục HIFI_US002-003 mobile).
-->
<!doctype html>
<html lang="vi">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>DS — e-commerce — Components</title>
<style>
:root{
--ds-color-bg-canvas:#F6F7F9;
--ds-color-bg-surface:#FFFFFF;
--ds-color-bg-subtle:#EEF1F5;
--ds-color-border-default:#D5DAE1;
--ds-color-border-strong:#767F8C;
--ds-color-text-primary:#1B1F24;
--ds-color-text-secondary:#5B6472;
--ds-color-text-disabled:#9AA3AF;
--ds-color-text-inverse:#FFFFFF;
--ds-color-brand-primary:#2456A6;
--ds-color-brand-primary-hover:#1C468A;
--ds-color-brand-primary-subtle:#E7EEF9;
--ds-color-status-danger:#B42318;
--ds-color-status-danger-subtle:#FDECEA;
--ds-color-status-success:#1E7B45;
--ds-color-status-success-subtle:#E6F4EC;
--ds-color-status-warning:#B7791F;
--ds-color-status-warning-subtle:#FFF4DE;
--ds-color-focus-ring:#2456A6;
--ds-font-family-base:"Segoe UI","Roboto","Helvetica Neue","Arial",sans-serif;
--ds-font-family-mono:"SFMono-Regular","Consolas","Menlo",monospace;
--ds-font-size-xs:12px;
--ds-font-size-sm:14px;
--ds-font-size-md:16px;
--ds-font-size-lg:20px;
--ds-font-size-xl:24px;
--ds-font-size-2xl:32px;
--ds-font-weight-regular:400;
--ds-font-weight-semibold:600;
--ds-font-line-height-tight:1.25;
--ds-font-line-height-normal:1.5;
--ds-space-1:4px;
--ds-space-2:8px;
--ds-space-3:12px;
--ds-space-4:16px;
--ds-space-6:24px;
--ds-space-8:32px;
--ds-space-12:48px;
--ds-radius-sm:4px;
--ds-radius-md:8px;
--ds-radius-pill:999px;
--ds-shadow-sm:0px 1px 2px 0px #1B1F2414;
--ds-shadow-md:0px 4px 12px 0px #1B1F2426;
--ds-size-control-desktop:36px;
--ds-size-control-touch:44px;
--ds-size-icon:20px;
--ds-size-content-max:1200px;
--ds-breakpoint-tablet:768px;
--ds-breakpoint-desktop:1024px;
}
/* ds-components v1.1 — BEGIN
CSS component của DS_<PROJECT>. Chỉ dùng biến --ds-* (tokens.css); cấm mã màu/px thô ngoài khối này.
Từ vựng lớp = cột "Kiểu trình bày" của UICONV §11 (xem DS §3). Khối này được chép NGUYÊN VĂN vào
components.html và mọi HIFI_*.html — kiểm khớp bằng scripts/design-check.mjs. */
*,*::before,*::after{box-sizing:border-box}
html{font-family:var(--ds-font-family-base);font-size:var(--ds-font-size-sm);line-height:var(--ds-font-line-height-normal);color:var(--ds-color-text-primary);background:var(--ds-color-bg-canvas);-webkit-font-smoothing:antialiased}
body{margin:0}
h1,h2,h3{margin:0;line-height:var(--ds-font-line-height-tight);font-weight:var(--ds-font-weight-semibold)}
h1{font-size:var(--ds-font-size-xl)}h2{font-size:var(--ds-font-size-lg)}h3{font-size:var(--ds-font-size-md)}
a,.ds-link{color:var(--ds-color-brand-primary);text-decoration:underline;text-underline-offset:2px;cursor:pointer}
a:hover,.ds-link:hover{color:var(--ds-color-brand-primary-hover)}
:focus-visible{outline:2px solid var(--ds-color-focus-ring);outline-offset:2px}
.ds-muted{color:var(--ds-color-text-secondary)}
.ds-small{font-size:var(--ds-font-size-xs)}
.ds-icon{width:var(--ds-size-icon);height:var(--ds-size-icon);flex:none;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round}
.ds-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
/* Layout */
.ds-page{max-width:var(--ds-size-content-max);margin:0 auto;padding:var(--ds-space-6) var(--ds-space-4);display:grid;gap:var(--ds-space-4)}
.ds-row{display:flex;gap:var(--ds-space-2);align-items:center;flex-wrap:wrap}
.ds-row--between{justify-content:space-between}.ds-row--end{justify-content:flex-end}
.ds-stack{display:grid;gap:var(--ds-space-3)}
.ds-cols{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,1fr);gap:var(--ds-space-4);align-items:start}
.ds-header{position:sticky;top:0;z-index:5;background:var(--ds-color-bg-surface);border-bottom:1px solid var(--ds-color-border-default);padding:var(--ds-space-3) var(--ds-space-4);display:flex;align-items:center;gap:var(--ds-space-4);min-height:56px}
.ds-breadcrumb{display:flex;gap:var(--ds-space-2);align-items:center;font-size:var(--ds-font-size-xs);color:var(--ds-color-text-secondary)}
.ds-breadcrumb a{text-decoration:none}
/* Nút — UICONV §11: nút chính / nút phụ / nút nguy hiểm / liên kết */
.ds-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--ds-space-2);height:var(--ds-size-control-desktop);padding:0 var(--ds-space-4);border-radius:var(--ds-radius-sm);border:1px solid transparent;font:inherit;font-weight:var(--ds-font-weight-semibold);cursor:pointer;white-space:nowrap;text-decoration:none;transition:background .12s}
.ds-btn--primary{background:var(--ds-color-brand-primary);color:var(--ds-color-text-inverse)}
.ds-btn--primary:hover{background:var(--ds-color-brand-primary-hover)}
.ds-btn--secondary{background:var(--ds-color-bg-surface);color:var(--ds-color-text-primary);border-color:var(--ds-color-border-strong)}
.ds-btn--secondary:hover{background:var(--ds-color-bg-subtle)}
.ds-btn--danger{background:var(--ds-color-status-danger);color:var(--ds-color-text-inverse)}
.ds-btn--ghost{background:transparent;color:var(--ds-color-brand-primary)}
.ds-btn--icon{width:var(--ds-size-control-desktop);padding:0}
.ds-btn[aria-disabled="true"],.ds-btn:disabled{opacity:.45;cursor:not-allowed;pointer-events:none}
.ds-btn.is-loading{pointer-events:none}
.ds-spinner{width:16px;height:16px;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:ds-spin .8s linear infinite;flex:none}
@keyframes ds-spin{to{transform:rotate(360deg)}}
/* Field — UICONV §4: nhãn trên field, * sau nhãn, lỗi dưới field thay hint */
.ds-field{display:grid;gap:var(--ds-space-1)}
.ds-field__label{font-size:var(--ds-font-size-xs);font-weight:var(--ds-font-weight-semibold);color:var(--ds-color-text-primary)}
.ds-field__label .ds-req{color:var(--ds-color-status-danger);margin-left:2px}
.ds-field__hint{font-size:var(--ds-font-size-xs);color:var(--ds-color-text-secondary)}
.ds-field__error{font-size:var(--ds-font-size-xs);color:var(--ds-color-status-danger);display:flex;gap:var(--ds-space-1);align-items:center}
.ds-input,.ds-select,.ds-textarea{width:100%;height:var(--ds-size-control-desktop);padding:0 var(--ds-space-3);border:1px solid var(--ds-color-border-strong);border-radius:var(--ds-radius-sm);background:var(--ds-color-bg-surface);color:var(--ds-color-text-primary);font:inherit}
.ds-textarea{height:auto;min-height:88px;padding:var(--ds-space-2) var(--ds-space-3)}
.ds-input::placeholder{color:var(--ds-color-text-secondary)}
.ds-input:hover,.ds-select:hover{border-color:var(--ds-color-text-secondary)}
.ds-input:focus,.ds-select:focus{border-color:var(--ds-color-brand-primary);outline:2px solid var(--ds-color-focus-ring);outline-offset:0}
.ds-input.is-invalid,.ds-select.is-invalid{border-color:var(--ds-color-status-danger)}
.ds-input[disabled],.ds-select[disabled]{background:var(--ds-color-bg-subtle);color:var(--ds-color-text-disabled);cursor:not-allowed}
.ds-input[readonly]{border-color:transparent;background:transparent;padding-left:0}
.ds-input-wrap{position:relative}
.ds-input-wrap .ds-icon{position:absolute;left:var(--ds-space-3);top:50%;transform:translateY(-50%);color:var(--ds-color-text-secondary)}
.ds-input-wrap .ds-input{padding-left:calc(var(--ds-space-3) + var(--ds-size-icon) + var(--ds-space-2))}
.ds-select{appearance:none;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%235B6472' stroke-width='1.75'><path d='m4 6 4 4 4-4'/></svg>");background-repeat:no-repeat;background-position:right var(--ds-space-3) center;padding-right:calc(var(--ds-space-3) + 24px)}
.ds-check{display:inline-flex;align-items:center;gap:var(--ds-space-2);cursor:pointer;min-height:var(--ds-size-control-desktop)}
.ds-check input{width:18px;height:18px;margin:0;accent-color:var(--ds-color-brand-primary)}
.ds-switch{position:relative;width:40px;height:22px;border-radius:var(--ds-radius-pill);background:var(--ds-color-border-strong);display:inline-block;flex:none}
.ds-switch::after{content:"";position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;background:var(--ds-color-bg-surface)}
.ds-switch[aria-checked="true"]{background:var(--ds-color-brand-primary)}
.ds-switch[aria-checked="true"]::after{left:20px}
.ds-stepper{display:inline-flex;border:1px solid var(--ds-color-border-strong);border-radius:var(--ds-radius-sm);height:var(--ds-size-control-desktop);overflow:hidden}
.ds-stepper button{width:var(--ds-size-control-desktop);border:0;background:var(--ds-color-bg-surface);font:inherit;cursor:pointer;color:var(--ds-color-text-primary)}
.ds-stepper input{width:48px;text-align:center;border:0;border-left:1px solid var(--ds-color-border-default);border-right:1px solid var(--ds-color-border-default);font:inherit}
/* Bảng / Card — UICONV §3, §11 */
.ds-table-wrap{overflow-x:auto;border:1px solid var(--ds-color-border-default);border-radius:var(--ds-radius-md);background:var(--ds-color-bg-surface)}
.ds-table{width:100%;border-collapse:collapse;font-size:var(--ds-font-size-sm)}
.ds-table th,.ds-table td{padding:var(--ds-space-3) var(--ds-space-4);text-align:left;border-bottom:1px solid var(--ds-color-border-default);vertical-align:middle}
.ds-table th{background:var(--ds-color-bg-subtle);font-weight:var(--ds-font-weight-semibold);font-size:var(--ds-font-size-xs);color:var(--ds-color-text-secondary);white-space:nowrap}
.ds-table th[aria-sort]{cursor:pointer}
.ds-table tbody tr:hover{background:var(--ds-color-bg-canvas)}
.ds-table tbody tr[aria-selected="true"]{background:var(--ds-color-brand-primary-subtle)}
.ds-table tbody tr:last-child td{border-bottom:0}
.ds-table td.ds-num{text-align:right;font-variant-numeric:tabular-nums}
.ds-card{background:var(--ds-color-bg-surface);border:1px solid var(--ds-color-border-default);border-radius:var(--ds-radius-md);padding:var(--ds-space-4);display:grid;gap:var(--ds-space-2)}
.ds-cards{display:grid;gap:var(--ds-space-3)}
.ds-badge{display:inline-flex;align-items:center;height:22px;padding:0 var(--ds-space-2);border-radius:var(--ds-radius-pill);font-size:var(--ds-font-size-xs);font-weight:var(--ds-font-weight-semibold);background:var(--ds-color-bg-subtle);color:var(--ds-color-text-primary)}
.ds-badge--info{background:var(--ds-color-brand-primary-subtle)}
.ds-badge--success{background:var(--ds-color-status-success-subtle);color:var(--ds-color-status-success)}
.ds-badge--warning{background:var(--ds-color-status-warning-subtle);color:var(--ds-color-status-warning)}
.ds-badge--danger{background:var(--ds-color-status-danger-subtle);color:var(--ds-color-status-danger)}
.ds-tabs{display:flex;gap:var(--ds-space-4);border-bottom:1px solid var(--ds-color-border-default)}
.ds-tab{padding:var(--ds-space-2) 0;border-bottom:2px solid transparent;color:var(--ds-color-text-secondary);cursor:pointer;font-weight:var(--ds-font-weight-semibold)}
.ds-tab[aria-selected="true"]{color:var(--ds-color-brand-primary);border-bottom-color:var(--ds-color-brand-primary)}
.ds-pagination{display:flex;gap:var(--ds-space-2);align-items:center;font-size:var(--ds-font-size-xs);color:var(--ds-color-text-secondary)}
.ds-price{font-variant-numeric:tabular-nums;font-weight:var(--ds-font-weight-semibold)}
.ds-thumb{width:64px;height:64px;border-radius:var(--ds-radius-sm);background:var(--ds-color-bg-subtle);border:1px solid var(--ds-color-border-default);display:grid;place-items:center;color:var(--ds-color-text-secondary);flex:none}
/* Phản hồi hệ thống — UICONV §5, §6 */
.ds-toast{position:fixed;top:var(--ds-space-4);right:var(--ds-space-4);z-index:20;min-width:280px;max-width:420px;padding:var(--ds-space-3) var(--ds-space-4);border-radius:var(--ds-radius-md);background:var(--ds-color-text-primary);color:var(--ds-color-text-inverse);box-shadow:var(--ds-shadow-md);display:flex;gap:var(--ds-space-3);align-items:flex-start}
.ds-toast--error{background:var(--ds-color-status-danger)}
.ds-banner{display:flex;gap:var(--ds-space-3);align-items:flex-start;padding:var(--ds-space-3) var(--ds-space-4);border-radius:var(--ds-radius-md);border:1px solid var(--ds-color-border-default);background:var(--ds-color-bg-subtle)}
.ds-banner--error{background:var(--ds-color-status-danger-subtle);border-color:var(--ds-color-status-danger);color:var(--ds-color-status-danger)}
.ds-banner--warning{background:var(--ds-color-status-warning-subtle);border-color:var(--ds-color-status-warning)}
.ds-empty{display:grid;justify-items:center;text-align:center;gap:var(--ds-space-3);padding:var(--ds-space-12) var(--ds-space-4);color:var(--ds-color-text-secondary)}
.ds-empty__illus{width:120px;height:88px;color:var(--ds-color-border-strong)}
.ds-empty--filter{padding:var(--ds-space-8) var(--ds-space-4)}
.ds-skeleton{height:14px;border-radius:var(--ds-radius-sm);background:linear-gradient(90deg,var(--ds-color-bg-subtle),var(--ds-color-bg-canvas),var(--ds-color-bg-subtle));background-size:200% 100%;animation:ds-shimmer 1.2s infinite}
@keyframes ds-shimmer{to{background-position:-200% 0}}
.ds-modal-backdrop{position:fixed;inset:0;background:#1B1F2480;display:grid;place-items:center;z-index:10;padding:var(--ds-space-4)}
.ds-modal{width:100%;max-width:520px;background:var(--ds-color-bg-surface);border-radius:var(--ds-radius-md);box-shadow:var(--ds-shadow-md);display:grid}
.ds-modal__head{display:flex;justify-content:space-between;align-items:center;padding:var(--ds-space-4) var(--ds-space-6);border-bottom:1px solid var(--ds-color-border-default)}
.ds-modal__body{padding:var(--ds-space-6);display:grid;gap:var(--ds-space-4)}
.ds-modal__foot{display:flex;justify-content:flex-end;gap:var(--ds-space-2);padding:var(--ds-space-4) var(--ds-space-6);border-top:1px solid var(--ds-color-border-default)}
.ds-status-page{display:grid;justify-items:center;gap:var(--ds-space-3);padding:var(--ds-space-12);text-align:center}
/* Trạng thái ẩn/khoá/read-only — W7 */
[data-vis="hidden"]{display:none!important}
[data-vis="disabled"]{opacity:.45;pointer-events:none}
[data-vis="readonly"] .ds-input{border-color:transparent;background:transparent}
/* Responsive — breakpoint theo UICONV §1; body[data-view] ép khung nhìn khi xem trong HIFI */
@media (max-width:1023px){.ds-cols{grid-template-columns:1fr}}
@media (max-width:767px){
.ds-page{padding:var(--ds-space-4) var(--ds-space-3)}
.ds-btn{height:var(--ds-size-control-touch)}
.ds-input,.ds-select{height:var(--ds-size-control-touch)}
.ds-table-wrap{display:none}.ds-cards{display:grid}
.ds-hide-m{display:none!important}
}
@media (min-width:768px){.ds-cards{display:none}.ds-hide-d{display:none!important}}
body[data-view="mobile"] .ds-cols{grid-template-columns:1fr}
body[data-view="mobile"] .ds-table-wrap{display:none}body[data-view="mobile"] .ds-cards{display:grid}
body[data-view="mobile"] .ds-hide-m{display:none!important}body[data-view="mobile"] .ds-hide-d{display:revert}
body[data-view="mobile"] .ds-btn,body[data-view="mobile"] .ds-input,body[data-view="mobile"] .ds-select{height:var(--ds-size-control-touch)}
body[data-view="mobile"] .ds-page{padding:var(--ds-space-4) var(--ds-space-3)}
body[data-view="tablet"] .ds-cols{grid-template-columns:1fr}
/* Tiện ích xếp chồng ở mobile: phần tử trong .ds-row chiếm trọn hàng (nhóm nút thao tác của một dòng danh sách/CartItem…) */
.ds-m-full{}
@media (max-width:767px){.ds-m-full{flex-basis:100%;min-width:0}}
body[data-view="mobile"] .ds-m-full{flex-basis:100%;min-width:0}
/* ds-components v1.1 — END */
</style>
</head>
<body>
<header class="ds-header">
<h1 style="font-size:var(--ds-font-size-md)">DS_e-commerce · Component sheet v1.1</h1>
<span class="ds-muted ds-small">Mỗi component = một dòng UICONV §11 · đủ trạng thái default / hover / focus / disabled / loading / error · chỉ dùng biến <code>--ds-*</code> · design system tham chiếu chưa chọn (OQ-022)</span>
</header>
<main class="ds-page">
<section class="ds-card" data-component="Nút" data-uiconv="Nút chính · Nút phụ · Nút nguy hiểm · Liên kết">
<h2>Nút</h2>
<div class="ds-row">
<button class="ds-btn ds-btn--primary">Tạo mới</button>
<button class="ds-btn ds-btn--secondary">Huỷ</button>
<button class="ds-btn ds-btn--danger">Xoá</button>
<button class="ds-btn ds-btn--ghost">Xoá lọc</button>
<a class="ds-link" href="#">Chi tiết</a>
</div>
<div class="ds-row">
<button class="ds-btn ds-btn--primary" aria-disabled="true" title="Chọn ít nhất 1 dòng để bật">Disabled + tooltip lý do (UICONV §7)</button>
<button class="ds-btn ds-btn--primary is-loading"><span class="ds-spinner"></span>Đang lưu…</button>
<button class="ds-btn ds-btn--secondary ds-btn--icon" aria-label="Đóng"><svg class="ds-icon" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg></button>
</div>
<p class="ds-small ds-muted">Hover: nền tối hơn một bậc (<code>--ds-color-brand-primary-hover</code>). Focus: viền 2px <code>--ds-color-focus-ring</code>. Chiều cao 36px desktop / 44px mobile.</p>
</section>
<section class="ds-card" data-component="Field" data-uiconv="Ô nhập · Ô nhập số · Chọn 1 · Chọn nhiều · Chọn ngày · Công tắc · Bộ đếm số lượng">
<h2>Field</h2>
<div class="ds-cols" style="grid-template-columns:1fr 1fr">
<label class="ds-field"><span class="ds-field__label">Mã <b class="ds-req">*</b></span><input class="ds-input" placeholder="3–20 ký tự, A–Z 0–9 -"><span class="ds-field__hint">Không trùng với mã đã có</span></label>
<label class="ds-field"><span class="ds-field__label">Số lượng (F01) <b class="ds-req">*</b></span>
<span class="ds-input is-invalid" style="display:flex;align-items:center;padding:0">
<span class="ds-stepper" style="border:0"><button aria-label="Giảm">−</button><input value="0" aria-label="Số lượng"><button aria-label="Tăng">+</button></span>
</span>
<span class="ds-field__error"><svg class="ds-icon" style="width:14px;height:14px" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 8v5m0 3h.01"/></svg>Số lượng phải là số nguyên từ 1 trở lên.</span>
</label>
<label class="ds-field"><span class="ds-field__label">Loại</span><select class="ds-select"><option>Thường</option></select></label>
<label class="ds-field"><span class="ds-field__label">Tên (read-only)</span><input class="ds-input" readonly value="Cửa hàng A"></label>
<label class="ds-field"><span class="ds-field__label">Tên (disabled)</span><input class="ds-input" disabled value="Cửa hàng A"></label>
<div class="ds-field"><span class="ds-field__label">Tìm kiếm</span><div class="ds-input-wrap"><svg class="ds-icon" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg><input class="ds-input" placeholder="Nhập mã hoặc tên…"></div></div>
</div>
<div class="ds-row">
<label class="ds-check"><input type="checkbox" checked> Chọn nhiều</label>
<label class="ds-check"><input type="radio" name="r" checked> Chọn 1 (≤ 7 lựa chọn)</label>
<span class="ds-row"><span class="ds-switch" role="switch" aria-checked="true"></span> Công tắc</span>
<span class="ds-stepper"><button aria-label="Giảm">−</button><input value="2" aria-label="Số lượng"><button aria-label="Tăng">+</button></span>
<span class="ds-stepper"><button aria-label="Giảm" disabled>−</button><input value="1" aria-label="Số lượng" disabled><button aria-label="Tăng" disabled></button></span>
</div>
<p class="ds-small ds-muted">Bộ đếm số lượng (dùng cho F01 — CartItem): nút "−" disable khi giá trị = ngưỡng dưới (theo OQ-028, chưa chốt hành vi); ô nhập giữa cho nhập tay.</p>
</section>
<section class="ds-card" data-component="Ảnh / Card sản phẩm" data-uiconv="Ảnh · Card">
<h2>Ảnh (thumbnail) và Card — dùng cho ProductCard/CartItem</h2>
<div class="ds-row">
<div class="ds-thumb">ảnh</div>
<div class="ds-card" style="max-width:360px">
<div class="ds-row ds-row--between"><b>Shop mẫu</b></div>
<div class="ds-row">
<div class="ds-thumb"></div>
<div>
<div>Tên sản phẩm</div>
<div class="ds-small ds-muted">Biến thể (VD Size M, Đỏ)</div>
<div class="ds-small ds-muted">100.000 ₫</div>
</div>
</div>
</div>
</div>
<p class="ds-small ds-muted">Ảnh lỗi tải hoặc chưa có: ô xám <code>.ds-thumb</code> (theo UICONV §1 "ô xám mặc định").</p>
</section>
<section class="ds-card" data-component="Bảng / Card" data-uiconv="Bảng · Card · Badge trạng thái">
<h2>Bảng (desktop) → Card (mobile)</h2>
<div class="ds-table-wrap"><table class="ds-table">
<thead><tr><th aria-sort="ascending">Mã ▲</th><th>Tên</th><th>Trạng thái</th><th class="ds-num">Số tiền</th><th></th></tr></thead>
<tbody>
<tr><td>STR-001</td><td>Cửa hàng A</td><td><span class="ds-badge">Nháp</span></td><td class="ds-num ds-price">1.234.567 ₫</td><td><a class="ds-link">Chi tiết</a></td></tr>
<tr aria-selected="true"><td>STR-002</td><td>Cửa hàng B</td><td><span class="ds-badge ds-badge--success">Đã duyệt</span></td><td class="ds-num ds-price">890.000 ₫</td><td><a class="ds-link">Chi tiết</a></td></tr>
<tr><td>STR-003</td><td>Cửa hàng C</td><td><span class="ds-badge ds-badge--danger">Từ chối</span></td><td class="ds-num ds-price">—</td><td><a class="ds-link">Chi tiết</a></td></tr>
</tbody></table></div>
<div class="ds-cards" style="display:grid"><div class="ds-card"><div class="ds-row ds-row--between"><b>STR-001</b><span class="ds-badge">Nháp</span></div><div>Cửa hàng A</div><div class="ds-small ds-muted">30/08/2026</div></div></div>
<div class="ds-row"><span class="ds-badge">Mặc định</span><span class="ds-badge ds-badge--info">Thông tin</span><span class="ds-badge ds-badge--success">Thành công</span><span class="ds-badge ds-badge--warning">Cảnh báo</span><span class="ds-badge ds-badge--danger">Lỗi</span></div>
<p class="ds-small ds-muted">Ánh xạ trạng thái nghiệp vụ (CART/ORDER_SELLER/PAYMENT) → biến thể badge: xem DS_e-commerce_v1.0.md §3.</p>
<div class="ds-row ds-row--between"><div class="ds-tabs"><span class="ds-tab" aria-selected="true">Tất cả</span><span class="ds-tab">Nháp</span><span class="ds-tab">Đã duyệt</span></div><div class="ds-pagination"><button class="ds-btn ds-btn--secondary ds-btn--icon" aria-label="Trang trước">‹</button><span>1 / 7</span><button class="ds-btn ds-btn--secondary ds-btn--icon" aria-label="Trang sau">›</button></div></div>
</section>
<section class="ds-card" data-component="Trạng thái chuẩn" data-uiconv="Skeleton · Toast · Banner · Trang trạng thái">
<h2>Trạng thái chuẩn (UICONV §6) — hai trạng thái rỗng phải trông khác nhau</h2>
<div class="ds-cols" style="grid-template-columns:1fr 1fr">
<div class="ds-card"><b class="ds-small">Đang tải</b><div class="ds-skeleton"></div><div class="ds-skeleton" style="width:85%"></div><div class="ds-skeleton" style="width:60%"></div></div>
<div class="ds-card"><b class="ds-small">Lỗi tải (banner trong vùng)</b><div class="ds-banner ds-banner--error"><svg class="ds-icon" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 8v5m0 3h.01"/></svg><span>Không tải được dữ liệu, thử lại.</span><button class="ds-btn ds-btn--secondary" style="margin-left:auto;height:32px">Thử lại</button></div></div>
<div class="ds-card"><b class="ds-small">Rỗng — chưa có dữ liệu (minh hoạ + nút chính)</b><div class="ds-empty"><svg class="ds-empty__illus" viewBox="0 0 120 88" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="16" y="20" width="88" height="56" rx="6"/><path d="M16 36h88M32 52h28M32 62h44"/></svg><span>Giỏ hàng trống</span><button class="ds-btn ds-btn--primary">Tiếp tục mua sắm</button></div></div>
<div class="ds-card"><b class="ds-small">Rỗng — bộ lọc không khớp (không minh hoạ + nút phụ)</b><div class="ds-empty ds-empty--filter"><span>Không tìm thấy kết quả phù hợp với bộ lọc.</span><button class="ds-btn ds-btn--secondary">Xoá lọc</button></div></div>
</div>
<div class="ds-row">
<div class="ds-toast" style="position:static"><svg class="ds-icon" viewBox="0 0 24 24"><path d="m5 12 4 4L19 6"/></svg><span>Đã lưu cửa hàng STR-004.</span><button class="ds-btn ds-btn--ghost ds-btn--icon" aria-label="Đóng" style="color:inherit;height:24px;width:24px;margin-left:auto">✕</button></div>
<div class="ds-toast ds-toast--error" style="position:static"><svg class="ds-icon" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 8v5m0 3h.01"/></svg><span>Không lưu được. Thử lại sau.</span></div>
</div>
</section>
<section class="ds-card" data-component="Modal" data-uiconv="Modal · Drawer · Bottom-sheet">
<h2>Modal xác nhận (UICONV §5) — nút chính bên phải, hành động nguy hiểm dùng nút nguy hiểm</h2>
<div class="ds-modal" style="box-shadow:var(--ds-shadow-sm)">
<div class="ds-modal__head"><h3>Xoá sản phẩm khỏi giỏ hàng?</h3><button class="ds-btn ds-btn--ghost ds-btn--icon" aria-label="Đóng"><svg class="ds-icon" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg></button></div>
<div class="ds-modal__body"><p style="margin:0">Sản phẩm sẽ được xoá khỏi giỏ hàng của bạn.</p></div>
<div class="ds-modal__foot"><button class="ds-btn ds-btn--secondary">Huỷ</button><button class="ds-btn ds-btn--danger">Xoá</button></div>
</div>
</section>
<section class="ds-card" data-component="Icon" data-uiconv="—">
<h2>Icon — inline SVG 24 grid, stroke 1.75, không emoji</h2>
<div class="ds-row" style="gap:var(--ds-space-4)">
<svg class="ds-icon" viewBox="0 0 24 24" aria-label="search"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
<svg class="ds-icon" viewBox="0 0 24 24" aria-label="plus"><path d="M12 5v14M5 12h14"/></svg>
<svg class="ds-icon" viewBox="0 0 24 24" aria-label="close"><path d="M6 6l12 12M18 6 6 18"/></svg>
<svg class="ds-icon" viewBox="0 0 24 24" aria-label="chevron-down"><path d="m6 9 6 6 6-6"/></svg>
<svg class="ds-icon" viewBox="0 0 24 24" aria-label="back"><path d="M19 12H5m7-7-7 7 7 7"/></svg>
<svg class="ds-icon" viewBox="0 0 24 24" aria-label="check"><path d="m5 12 4 4L19 6"/></svg>
<svg class="ds-icon" viewBox="0 0 24 24" aria-label="alert"><circle cx="12" cy="12" r="9"/><path d="M12 8v5m0 3h.01"/></svg>
<svg class="ds-icon" viewBox="0 0 24 24" aria-label="trash"><path d="M4 7h16M9 7V4h6v3m-8 0 1 13h8l1-13"/></svg>
<svg class="ds-icon" viewBox="0 0 24 24" aria-label="filter"><path d="M3 5h18l-7 8v6l-4-2v-4z"/></svg>
<svg class="ds-icon" viewBox="0 0 24 24" aria-label="cart"><path d="M3 4h2l2.4 11h11.2L21 7H6"/><circle cx="9" cy="19" r="1.5"/><circle cx="17" cy="19" r="1.5"/></svg>
<svg class="ds-icon" viewBox="0 0 24 24" aria-label="user"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg>
<svg class="ds-icon" viewBox="0 0 24 24" aria-label="menu"><path d="M4 7h16M4 12h16M4 17h16"/></svg>
</div>
</section>
</main>
</body>
</html>

View File

@@ -0,0 +1,56 @@
/* tokens.css — SINH TỪ tokens.json, không sửa tay.
Quy tắc tên: --ds-<đường dẫn token nối bằng '-'>. Kiểm khớp: node scripts/design-check.mjs --tokens tokens.json --css tokens.css
Khối :root này được chép nguyên văn vào <style> của mọi HIFI_*.html và components.html (file tự chứa).
Dự án e-commerce: giá trị vẫn là placeholder trung tính của template ba-design (chưa có brand guideline —
SAD §7.0; chưa chọn design system — OQ-022; breakpoint/content-max chưa chốt — OQ-018). Xem DS_e-commerce_v1.0.md §0. */
:root{
--ds-color-bg-canvas:#F6F7F9;
--ds-color-bg-surface:#FFFFFF;
--ds-color-bg-subtle:#EEF1F5;
--ds-color-border-default:#D5DAE1;
--ds-color-border-strong:#767F8C;
--ds-color-text-primary:#1B1F24;
--ds-color-text-secondary:#5B6472;
--ds-color-text-disabled:#9AA3AF;
--ds-color-text-inverse:#FFFFFF;
--ds-color-brand-primary:#2456A6;
--ds-color-brand-primary-hover:#1C468A;
--ds-color-brand-primary-subtle:#E7EEF9;
--ds-color-status-danger:#B42318;
--ds-color-status-danger-subtle:#FDECEA;
--ds-color-status-success:#1E7B45;
--ds-color-status-success-subtle:#E6F4EC;
--ds-color-status-warning:#B7791F;
--ds-color-status-warning-subtle:#FFF4DE;
--ds-color-focus-ring:#2456A6;
--ds-font-family-base:"Segoe UI","Roboto","Helvetica Neue","Arial",sans-serif;
--ds-font-family-mono:"SFMono-Regular","Consolas","Menlo",monospace;
--ds-font-size-xs:12px;
--ds-font-size-sm:14px;
--ds-font-size-md:16px;
--ds-font-size-lg:20px;
--ds-font-size-xl:24px;
--ds-font-size-2xl:32px;
--ds-font-weight-regular:400;
--ds-font-weight-semibold:600;
--ds-font-line-height-tight:1.25;
--ds-font-line-height-normal:1.5;
--ds-space-1:4px;
--ds-space-2:8px;
--ds-space-3:12px;
--ds-space-4:16px;
--ds-space-6:24px;
--ds-space-8:32px;
--ds-space-12:48px;
--ds-radius-sm:4px;
--ds-radius-md:8px;
--ds-radius-pill:999px;
--ds-shadow-sm:0px 1px 2px 0px #1B1F2414;
--ds-shadow-md:0px 4px 12px 0px #1B1F2426;
--ds-size-control-desktop:36px;
--ds-size-control-touch:44px;
--ds-size-icon:20px;
--ds-size-content-max:1200px;
--ds-breakpoint-tablet:768px;
--ds-breakpoint-desktop:1024px;
}

View File

@@ -0,0 +1,106 @@
{
"$schema": "https://tr.designtokens.org/format/",
"$description": "DS_e-commerce — design token theo chuẩn W3C DTCG. Nguồn sự thật duy nhất về màu/chữ/khoảng cách; tokens.css và khối :root trong HIFI_*.html phải sinh từ file này (kiểm bằng scripts/design-check.mjs). Giá trị bên dưới là PLACEHOLDER trung tính của template ba-design — dự án chưa có brand guideline (SAD §7.0: 'không có brand guideline cố định') và chưa chọn design system tham chiếu (OQ-022). KHÔNG coi đây là đề xuất nhận diện đã duyệt — xem DS_e-commerce_v1.0.md §0.",
"color": {
"bg": {
"canvas": { "$type": "color", "$value": "#F6F7F9", "$description": "Nền trang" },
"surface": { "$type": "color", "$value": "#FFFFFF", "$description": "Nền card/bảng/modal" },
"subtle": { "$type": "color", "$value": "#EEF1F5", "$description": "Header bảng, vùng phụ" }
},
"border": {
"default": { "$type": "color", "$value": "#D5DAE1" },
"strong": { "$type": "color", "$value": "#767F8C" }
},
"text": {
"primary": { "$type": "color", "$value": "#1B1F24" },
"secondary": { "$type": "color", "$value": "#5B6472" },
"disabled": { "$type": "color", "$value": "#9AA3AF", "$description": "Miễn kiểm tương phản (WCAG 1.4.3 loại trừ thành phần vô hiệu)" },
"inverse": { "$type": "color", "$value": "#FFFFFF" }
},
"brand": {
"primary": { "$type": "color", "$value": "#2456A6", "$description": "Nút chính, liên kết, focus ring — placeholder trung tính, chưa có brand guideline (OQ-022)" },
"primary-hover": { "$type": "color", "$value": "#1C468A" },
"primary-subtle": { "$type": "color", "$value": "#E7EEF9", "$description": "Nền badge/hàng được chọn" }
},
"status": {
"danger": { "$type": "color", "$value": "#B42318" },
"danger-subtle": { "$type": "color", "$value": "#FDECEA" },
"success": { "$type": "color", "$value": "#1E7B45" },
"success-subtle": { "$type": "color", "$value": "#E6F4EC" },
"warning": { "$type": "color", "$value": "#B7791F" },
"warning-subtle": { "$type": "color", "$value": "#FFF4DE" }
},
"focus": { "ring": { "$type": "color", "$value": "#2456A6" } }
},
"font": {
"family": {
"base": { "$type": "fontFamily", "$value": ["Segoe UI", "Roboto", "Helvetica Neue", "Arial", "sans-serif"], "$description": "PLACEHOLDER — system stack giữ nguyên theo chỉ đạo (không có font brand); ZH/KO/JA cần rà lại fallback font khi có bản dịch (OQ-023)" },
"mono": { "$type": "fontFamily", "$value": ["SFMono-Regular", "Consolas", "Menlo", "monospace"] }
},
"size": {
"xs": { "$type": "dimension", "$value": "12px" },
"sm": { "$type": "dimension", "$value": "14px" },
"md": { "$type": "dimension", "$value": "16px" },
"lg": { "$type": "dimension", "$value": "20px" },
"xl": { "$type": "dimension", "$value": "24px" },
"2xl": { "$type": "dimension", "$value": "32px" }
},
"weight": {
"regular": { "$type": "fontWeight", "$value": 400 },
"semibold": { "$type": "fontWeight", "$value": 600 }
},
"line-height": {
"tight": { "$type": "number", "$value": 1.25 },
"normal": { "$type": "number", "$value": 1.5 }
}
},
"space": {
"1": { "$type": "dimension", "$value": "4px" },
"2": { "$type": "dimension", "$value": "8px" },
"3": { "$type": "dimension", "$value": "12px" },
"4": { "$type": "dimension", "$value": "16px" },
"6": { "$type": "dimension", "$value": "24px" },
"8": { "$type": "dimension", "$value": "32px" },
"12": { "$type": "dimension", "$value": "48px" }
},
"radius": {
"sm": { "$type": "dimension", "$value": "4px" },
"md": { "$type": "dimension", "$value": "8px" },
"pill": { "$type": "dimension", "$value": "999px" }
},
"shadow": {
"sm": { "$type": "shadow", "$value": { "color": "#1B1F2414", "offsetX": "0px", "offsetY": "1px", "blur": "2px", "spread": "0px" } },
"md": { "$type": "shadow", "$value": { "color": "#1B1F2426", "offsetX": "0px", "offsetY": "4px", "blur": "12px", "spread": "0px" } }
},
"size": {
"control": {
"desktop": { "$type": "dimension", "$value": "36px", "$description": "Chiều cao nút/ô nhập desktop" },
"touch": { "$type": "dimension", "$value": "44px", "$description": "Chiều cao tối thiểu trên mobile (hit target ≥ 44px)" }
},
"icon": { "$type": "dimension", "$value": "20px" },
"content-max": { "$type": "dimension", "$value": "1200px", "$description": "PLACEHOLDER — UICONV §1 chưa chốt max-width thật (OQ-018), giữ giá trị template" }
},
"breakpoint": {
"tablet": { "$type": "dimension", "$value": "768px", "$description": "PLACEHOLDER — UICONV §1 chưa chốt ngưỡng thật (OQ-018), giữ giá trị template" },
"desktop": { "$type": "dimension", "$value": "1024px", "$description": "PLACEHOLDER — UICONV §1 chưa chốt ngưỡng thật (OQ-018), giữ giá trị template" }
},
"$extensions": {
"ba": {
"project": "e-commerce",
"version": "1.0",
"source": "UICONV_e-commerce_v1.0.md v1.1 §0/§1/§8/§11 (chưa chốt design system — OQ-022; chưa chốt breakpoint/grid — OQ-018) · brand guideline: chưa có (SAD §7.0) — placeholder trung tính của templates/tokens.json",
"contrastPairs": [
{ "fg": "color.text.primary", "bg": "color.bg.surface", "min": 4.5, "use": "Chữ thường trên nền card (VD tên sản phẩm C04, biến thể C05)" },
{ "fg": "color.text.primary", "bg": "color.bg.canvas", "min": 4.5, "use": "Chữ thường trên nền trang" },
{ "fg": "color.text.secondary", "bg": "color.bg.surface", "min": 4.5, "use": "Hint, tên gian hàng phụ" },
{ "fg": "color.text.inverse", "bg": "color.brand.primary", "min": 4.5, "use": "Nhãn nút chính (C11 Tiến hành Checkout)" },
{ "fg": "color.text.inverse", "bg": "color.status.danger", "min": 4.5, "use": "Nhãn nút nguy hiểm (C07 Xoá)" },
{ "fg": "color.brand.primary", "bg": "color.bg.surface", "min": 4.5, "use": "Liên kết" },
{ "fg": "color.status.danger", "bg": "color.bg.surface", "min": 4.5, "use": "Message lỗi dưới field (E-CART-0002)" },
{ "fg": "color.status.danger", "bg": "color.status.danger-subtle", "min": 4.5, "use": "Banner lỗi tải/cập nhật (C12, E-CART-0003/0006)" },
{ "fg": "color.text.primary", "bg": "color.brand.primary-subtle", "min": 4.5, "use": "Chữ trên badge/hàng chọn" },
{ "fg": "color.border.strong", "bg": "color.bg.surface", "min": 3.0, "use": "Viền ô nhập / bộ đếm F01 (WCAG 1.4.11 non-text)" }
]
}
}
}