@haposoft/cafekit 0.16.6 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/README.md +14 -22
  2. package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
  3. package/bin/__tests__/codex-hooks.test.js +0 -36
  4. package/bin/__tests__/codex-native.test.js +46 -88
  5. package/bin/__tests__/develop-contract.test.js +26 -57
  6. package/bin/__tests__/docs-sync-on-demand.test.js +93 -0
  7. package/bin/__tests__/grok-envelope.test.js +0 -56
  8. package/bin/__tests__/hook-payload.test.js +4 -6
  9. package/bin/__tests__/legacy-authority-decoupled.test.js +159 -0
  10. package/bin/__tests__/legacy-hooks-retired.test.js +221 -0
  11. package/bin/__tests__/omp-bridge.test.js +20 -17
  12. package/bin/__tests__/omp-hooks.test.js +0 -19
  13. package/bin/__tests__/optional-skill-inventory.test.js +112 -4
  14. package/bin/__tests__/orca-session.test.js +0 -2
  15. package/bin/__tests__/package-inventory.test.js +100 -541
  16. package/bin/__tests__/runtime-dir.test.js +0 -3
  17. package/bin/__tests__/runtime-schema.test.js +3 -2
  18. package/bin/__tests__/skill-routing-source.test.js +27 -15
  19. package/bin/__tests__/spec-narrowing.test.js +0 -27
  20. package/bin/__tests__/specs-v2-execution-closeout.test.js +4 -69
  21. package/bin/__tests__/ui-ux-skill.test.js +101 -0
  22. package/bin/__tests__/usage-hook-retired.test.js +114 -0
  23. package/bin/__tests__/validator-grounding.test.js +9 -28
  24. package/bin/phases/codex-runtime.js +24 -0
  25. package/bin/phases/omp-runtime.js +22 -0
  26. package/package.json +1 -1
  27. package/src/claude/CLAUDE.md +3 -10
  28. package/src/claude/agents/brainstormer.md +7 -3
  29. package/src/claude/agents/code-auditor.md +35 -50
  30. package/src/claude/agents/debugger.md +5 -79
  31. package/src/claude/agents/deployer.md +3 -5
  32. package/src/claude/agents/docs-keeper.md +2 -7
  33. package/src/claude/agents/implementer.md +6 -9
  34. package/src/claude/agents/inspector.md +2 -3
  35. package/src/claude/agents/project-manager.md +7 -15
  36. package/src/claude/agents/researcher.md +9 -3
  37. package/src/claude/agents/spec-maker.md +11 -11
  38. package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
  39. package/src/claude/hooks/__tests__/spec-gate.test.js +112 -3
  40. package/src/claude/hooks/__tests__/spec-state-touched.test.js +134 -0
  41. package/src/claude/hooks/__tests__/state.test.js +68 -0
  42. package/src/claude/hooks/__tests__/statusline.test.js +140 -11
  43. package/src/claude/hooks/agent.cjs +2 -1
  44. package/src/claude/hooks/lib/context.cjs +0 -89
  45. package/src/claude/hooks/lib/hook-payload.cjs +2 -2
  46. package/src/claude/hooks/rules.cjs +2 -1
  47. package/src/claude/hooks/session.cjs +0 -1
  48. package/src/claude/hooks/spec-gate.cjs +18 -28
  49. package/src/claude/hooks/spec-state.cjs +95 -1
  50. package/src/claude/hooks/state.cjs +21 -2
  51. package/src/claude/migration-manifest.json +30 -15
  52. package/src/claude/rules/ai-dev-rules.md +1 -1
  53. package/src/claude/rules/hook-protocols.md +8 -15
  54. package/src/claude/rules/manage-docs.md +9 -23
  55. package/src/claude/rules/orchestrator.md +3 -4
  56. package/src/claude/rules/review-audit-self-decision.md +3 -3
  57. package/src/claude/rules/skill-domain-routing.md +1 -3
  58. package/src/claude/rules/skill-workflow-routing.md +4 -5
  59. package/src/claude/rules/state-sync.md +3 -12
  60. package/src/claude/rules/workflow.md +10 -18
  61. package/src/claude/runtime.schema.json +5 -3
  62. package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
  63. package/src/claude/scripts/provenance.cjs +23 -5
  64. package/src/claude/scripts/spec-receipt.cjs +52 -6
  65. package/src/claude/scripts/spec-resolver.cjs +1 -2
  66. package/src/claude/scripts/validate-spec-output.cjs +5 -32
  67. package/src/claude/scripts/workflow-policy.cjs +22 -10
  68. package/src/claude/settings/settings.json +1 -32
  69. package/src/claude/skills/{question → ask}/SKILL.md +9 -75
  70. package/src/claude/skills/brainstorm/SKILL.md +43 -131
  71. package/src/claude/skills/code-review/SKILL.md +48 -17
  72. package/src/claude/skills/code-review/references/spec-compliance-review.md +2 -3
  73. package/src/claude/skills/code-review/references/verification-gate.md +22 -27
  74. package/src/claude/skills/debug/SKILL.md +29 -149
  75. package/src/claude/skills/develop/SKILL.md +27 -36
  76. package/src/claude/skills/develop/references/parallel-waves.md +0 -8
  77. package/src/claude/skills/develop/references/quality-gate.md +15 -13
  78. package/src/claude/skills/docs/SKILL.md +2 -2
  79. package/src/claude/skills/docs/references/update-workflow.md +1 -1
  80. package/src/claude/skills/fix/SKILL.md +153 -0
  81. package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
  82. package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
  83. package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
  84. package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
  85. package/src/claude/skills/git/SKILL.md +48 -9
  86. package/src/claude/skills/git/references/commit-protocols.md +35 -16
  87. package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
  88. package/src/claude/skills/orca/SKILL.md +9 -8
  89. package/src/claude/skills/research/SKILL.md +2 -0
  90. package/src/claude/skills/scout/SKILL.md +61 -0
  91. package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
  92. package/src/claude/skills/specs/SKILL.md +22 -29
  93. package/src/claude/skills/specs/references/review.md +8 -6
  94. package/src/claude/skills/specs/references/templates.md +38 -44
  95. package/src/claude/skills/sync/SKILL.md +8 -15
  96. package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
  97. package/src/claude/skills/sync/references/sync-protocols.md +4 -13
  98. package/src/claude/skills/test/SKILL.md +22 -3
  99. package/src/claude/skills/test/references/execution-strategy.md +83 -0
  100. package/src/claude/skills/test/references/failure-triage.md +1 -1
  101. package/src/claude/skills/ui-ux/LICENSE +21 -0
  102. package/src/claude/skills/ui-ux/SKILL.md +497 -0
  103. package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
  104. package/src/claude/skills/ui-ux/references/budgets.md +113 -0
  105. package/src/claude/skills/ui-ux/references/checklist.md +271 -0
  106. package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
  107. package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
  108. package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
  109. package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
  110. package/src/claude/skills/ui-ux/references/components/button.md +142 -0
  111. package/src/claude/skills/ui-ux/references/components/card.md +113 -0
  112. package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
  113. package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
  114. package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
  115. package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
  116. package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
  117. package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
  118. package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
  119. package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
  120. package/src/claude/skills/ui-ux/references/components/input.md +161 -0
  121. package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
  122. package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
  123. package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
  124. package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
  125. package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
  126. package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
  127. package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
  128. package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
  129. package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
  130. package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
  131. package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
  132. package/src/claude/skills/ui-ux/references/design-process.md +650 -0
  133. package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
  134. package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
  135. package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
  136. package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
  137. package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
  138. package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
  139. package/src/claude/skills/ui-ux/references/principles.md +408 -0
  140. package/src/claude/skills/ui-ux/references/refactor.md +245 -0
  141. package/src/claude/skills/ui-ux/references/responsive.md +195 -0
  142. package/src/claude/skills/ui-ux/references/review.md +528 -0
  143. package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
  144. package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
  145. package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
  146. package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
  147. package/src/claude/skills/ui-ux/references/styles.md +311 -0
  148. package/src/claude/skills/ui-ux/references/system.md +219 -0
  149. package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
  150. package/src/claude/skills/ui-ux/references/tokens.css +362 -0
  151. package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
  152. package/src/claude/skills/ui-ux/scripts/package.json +9 -0
  153. package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
  154. package/src/claude/skills/ui-ux-pro-max/SKILL.md +1 -1
  155. package/src/claude/status.cjs +149 -144
  156. package/src/codex/AGENTS.md +8 -13
  157. package/src/codex/hooks/agent.cjs +2 -1
  158. package/src/codex/hooks/lib/spec-receipt.cjs +5 -0
  159. package/src/codex/hooks/lib/spec-utils.cjs +0 -7
  160. package/src/codex/hooks/rules.cjs +2 -1
  161. package/src/codex/hooks/session.cjs +0 -2
  162. package/src/codex/hooks/spec-gate.cjs +8 -18
  163. package/src/codex/hooks.json +0 -20
  164. package/src/codex/rules/state-sync.md +3 -12
  165. package/src/common/AGENTS.md +2 -2
  166. package/src/omp/extensions/cafekit-bridge.mjs +6 -6
  167. package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
  168. package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
  169. package/src/claude/hooks/completion-authority-check.cjs +0 -56
  170. package/src/claude/hooks/completion-authority-state.cjs +0 -527
  171. package/src/claude/hooks/completion-authority.cjs +0 -163
  172. package/src/claude/hooks/semantic-review-authority.cjs +0 -154
  173. package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
  174. package/src/claude/hooks/usage.cjs +0 -197
  175. package/src/claude/skills/delegate/SKILL.md +0 -124
  176. package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
  177. package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
  178. package/src/claude/skills/hotfix/SKILL.md +0 -289
  179. package/src/claude/skills/inspect/SKILL.md +0 -142
  180. package/src/claude/skills/loop/SKILL.md +0 -114
  181. package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
  182. package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
  183. package/src/claude/skills/route/SKILL.md +0 -94
  184. package/src/claude/skills/route/references/agent-timing.md +0 -46
  185. package/src/claude/skills/route/references/chaining-patterns.md +0 -53
  186. package/src/claude/skills/route/references/task-taxonomy.md +0 -48
  187. package/src/codex/hooks/completion-authority-check.cjs +0 -67
  188. package/src/codex/hooks/completion-authority-state.cjs +0 -527
  189. package/src/codex/hooks/completion-authority.cjs +0 -109
  190. package/src/codex/hooks/semantic-review-authority.cjs +0 -130
  191. package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
  192. /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
  193. /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
  194. /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
@@ -0,0 +1,623 @@
1
+ # Màu — luật M
2
+
3
+ Đây là **nguồn duy nhất** cho mọi luật về màu, viền và bóng. `SKILL.md` chỉ chép
4
+ một dòng tóm tắt kèm số hiệu; giải thích, ngoại lệ và bằng chứng nằm ở đây.
5
+
6
+ Luật có dấu ⚑ là chưa qua vòng test nào.
7
+
8
+ ---
9
+
10
+ ## Nền và thứ bậc bề mặt
11
+
12
+ **M1. Nền trang xám nhạt, không trắng tinh. Card mới trắng.**
13
+
14
+ *Gu flat — phong cách khác đã chọn theo `P1` thì xem `P2` trong `references/styles.md`.*
15
+
16
+ **M2. 95% trung tính, 5% điểm nhấn.**
17
+
18
+ *Gu flat — phong cách khác đã chọn theo `P1` thì xem `P2` trong `references/styles.md`.*
19
+
20
+ Đây là luật quan trọng nhất trong nhóm, và là thứ quyết định một màn hình trông
21
+ có chủ ý hay trông như chưa ai quyết định gì.
22
+
23
+ Bằng chứng thật (một dự án): một trang tích dần tới **năm màu
24
+ nền** cho năm loại khối — vàng be cho dặn dò, đỏ hồng cho nội quy, xanh nhạt cho
25
+ quà và nhật ký, tím indigo cho chip quà, cộng xanh/vàng của trạng thái. Khối nào
26
+ cũng đòi được chú ý nên **không khối nào nổi**, và trang đọc ra như "rainbow UI".
27
+
28
+ Hệ quả ngược cũng đã dính: gỡ hết màu đi thì thẻ "dặn dò" trắng nằm
29
+ giữa các thẻ task trắng, bị đọc lẫn thành một task. Nên trung tính **không phải
30
+ là không có điểm neo**:
31
+
32
+ > Nổi bằng **một điểm màu nhỏ**, không tô cả khối. Giữ thẻ trắng, đặt icon trong
33
+ > một **ô vuông nhạt 28px** kèm nhãn cùng màu.
34
+
35
+ **M3. Đúng một màu nhấn cho cả app.** Nút chính, link, control đang bật (checkbox,
36
+ switch, chip lọc đã chọn) dùng chung nó. Mục **điều hướng** đang chọn (sidebar, tab,
37
+ trang hiện tại) thì dùng nền xám hoặc vạch `--foreground`, không màu nhấn (bảng
38
+ công thức phần tử bên dưới, `small-controls.md`). Màu thứ hai phải xin phép.
39
+
40
+ ---
41
+
42
+ ## Màu nói gì
43
+
44
+ **M4. Màu để báo trạng thái, không để phân loại.**
45
+
46
+ *Có màu công thức B — ô icon card số liệu, ô icon đầu dòng theo loại và chuỗi biểu đồ dashboard được tô màu phân loại, xem `P12` trong `references/styles.md`.*
47
+
48
+ > `M4` và `M5` là **gu mặc định cho dự án chưa có ngôn ngữ màu**. Dự án đã tô màu
49
+ > theo cách riêng (đếm ở `P4`) thì theo dự án; chỉ giữ các luật về nghĩa và đọc được
50
+ > liệt kê ở đầu `principles.md` (đỏ cho lỗi và phá huỷ, một bảng trạng thái, màu không
51
+ > đứng một mình, tương phản 4.5:1).
52
+
53
+ Bảng màu của MỘT màn hình, không thêm:
54
+
55
+ | Màu | Chỉ dùng cho |
56
+ | --- | --- |
57
+ | Xám trung tính | Mọi thứ còn lại: khung, chữ phụ, badge, icon, viền |
58
+ | Màu nhấn | Nút hành động chính khi thật cần nổi, mục đang chọn, link. **Không** cho badge số đếm, **không** cho trạng thái |
59
+ | Xanh lá | "Đang ổn", "đã xong": badge, thanh tiến độ xong (`M7`). Cố định, không lấy màu nhấn |
60
+ | Hổ phách | "Cần chú ý": quá hạn, nộp trễ, bỏ lỡ |
61
+ | Đỏ lỗi — `red` | Lỗi thật mà người dùng phải xử lý: bài bị từ chối, lỗi form |
62
+ | Đỏ nguy hiểm — `rose` | Hành động trả lời "có" ở một trong ba câu của `I4` (mất dữ liệu, kết thúc thứ đang chạy, cắt quyền): xoá, huỷ gói, thu hồi khoá, rời nhóm, đăng xuất. Lấy lại được không làm việc đó hết đỏ. Nút đứng riêng thì nền mờ + chữ đỏ luôn hiện; mục menu thì chỉ đỏ khi rê vào (`I4`) |
63
+
64
+ Hai sắc đỏ là cố ý, không phải gõ nhầm — xem `M30`.
65
+
66
+ Cách áp: định thêm một màu nền cho một loại khối thì **dừng lại và hỏi — màu đó
67
+ báo trạng thái gì?** Không trả lời được thì nó là trang trí, dùng xám.
68
+
69
+ Nội quy không phải lỗi → không đỏ. Quà không phải trạng thái → không màu riêng.
70
+
71
+ **M5. Phân loại khối bằng icon + chữ + viền, không bằng nền màu.**
72
+
73
+ Dặn dò, nội quy, quà, nhật ký đều là thẻ trắng viền mảnh; cái gì là gì do **icon
74
+ lucide + nhãn** nói. Thứ bậc đến từ cỡ chữ, độ đậm và khoảng cách: chữ chính đen,
75
+ chữ phụ xám; giữa các khu vực thoáng, trong từng thẻ gọn.
76
+
77
+ **M6. Một tín hiệu cho một ý.** Nhãn cộng ô màu đã nói "lưu ý" thì không thêm
78
+ badge "Lưu ý" nữa. Cùng tinh thần với `F6`: phần tử nổi bật chỉ cần một dấu hiệu.
79
+
80
+ **M7. Trạng thái đứng riêng một ô thì là badge màu. Nằm lẫn trong câu thì chữ màu.**
81
+
82
+ Cột trạng thái trong bảng, góc thẻ kanban, đầu trang chi tiết: **badge pill nền
83
+ nhạt, chấm tròn + chữ cùng tông**. Liếc dọc một cột 20 dòng thì mắt bắt màu nhanh
84
+ hơn bắt chữ; chấm xám + chữ đen thì cả cột trông như nhau ("Đang giao dịch"
85
+ với "Ngừng giao dịch" chỉ khác nhau ở độ mờ của một chấm 4px).
86
+
87
+ ```html
88
+ <span class="inline-flex items-center gap-1.5 whitespace-nowrap rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-medium text-emerald-700">
89
+ <span class="size-1.5 rounded-full bg-current"></span>Đang giao dịch
90
+ </span>
91
+ ```
92
+
93
+ Bốn tông, không thêm. Trạng thái nào vào tông nào theo **nghĩa**, không theo sở thích:
94
+
95
+ | Tông | Nền / chữ | Token (không Tailwind) | Nghĩa | Ví dụ |
96
+ | --- | --- | --- | --- | --- |
97
+ | Xám | `bg-zinc-100 text-zinc-600` | `--neutral-bg` / `--neutral` | Chờ, nháp, chưa bắt đầu, trung lập | Tiềm năng, Nháp, Cần làm |
98
+ | Xanh lá | `bg-emerald-50 text-emerald-700` | `--success-bg` / `--success` | Đang ổn, đang chạy, đã xong | Đang giao dịch, Hoạt động, Đã thanh toán, Đã giao |
99
+ | Hổ phách | `bg-amber-50 text-amber-700` | `--warning-bg` / `--warning` | Cần chú ý | Quá hạn, Sắp hết hạn, Tạm dừng |
100
+ | Đỏ | `bg-red-50 text-red-700` | `--error-bg` / `--error-strong` | Đã dừng, thất bại, bị từ chối | Ngừng giao dịch, Đã huỷ, Lỗi |
101
+
102
+ **Dự án có dark mode thì mọi class `-700` của bảng trên đi kèm bản `dark:` `-400`**
103
+ (`text-red-700 dark:text-red-400`; **riêng hổ phách là `text-amber-700 dark:text-orange-400`**, xem
104
+ dưới; không Tailwind thì token, đã lật sẵn). `-700` không tự
105
+ đổi ở nền tối: chữ trạng thái còn 2,9–3,7:1 (`red-700` 2,9, `emerald-700` 3,5, `amber-700`
106
+ 3,7). Sót một chỗ là cùng một nghĩa ra hai sắc trên một dòng ("Khẩn cấp" `text-amber-700` cam sẫm 3,74:1 cạnh "Quá hạn 3 ngày"
107
+ `dark:text-amber-400` vàng sáng). Gom mỗi tông vào một helper để
108
+ không phải nhớ từng chỗ.
109
+
110
+ **Bản tối chọn theo sắc, không theo số bậc.** Lên bậc sáng hơn thì phần lớn thang Tailwind giữ sắc
111
+ (đỏ, xanh lá, xanh dương, tím lệch 0–6° OKLCH), riêng **amber trôi sang vàng**: `amber-700` 49°,
112
+ `amber-400` 84°. "Quá hạn" cam ở bản sáng thành vàng ở bản tối, đọc ra hai màu, hai nghĩa. Hổ phách tối dùng **`orange-400`** (56°, lệch 7°, 7,9:1 trên card); nền
113
+ badge giữ `amber-500/15` (nền mờ không đọc ra sắc như chữ). Màu khác ngoài bảng này (avatar, chart, nhãn
114
+ tự chọn) cũng vậy: lệch quá ~20° OKLCH với bản sáng thì đổi họ màu gần sắc hơn. Probe `--dark` đo cặp này.
115
+
116
+ **Mọi badge có thêm `ring-1 ring-inset ring-black/5`** (nền tối `ring-white/10`). Nền badge
117
+ nhạt tới mức chỉ chênh vài phần trăm với nền dưới nó: `zinc-100` (#f4f4f5) đặt lên dòng
118
+ bảng đã chọn hay nền trang (#f4f4f6) là mất hẳn khung, "Tiềm năng" chỉ còn chấm với chữ.
119
+ Vòng trong 5% giữ khung ở mọi nền mà không nặng thêm.
120
+
121
+ **Một luồng có cả bước "đang" lẫn bước "xong" thì xanh dành cho "xong".** Đơn hàng:
122
+ Chờ xử lý → **hổ phách** (người bán phải làm gì đó), Đang giao → **xám** (đang chạy nhưng
123
+ không ai phải làm gì), Đã giao → **xanh**, Đã huỷ → **đỏ**. Để "Đang giao" cũng xanh thì
124
+ hai trạng thái khác nghĩa trùng màu (`N2`). "Đang giao dịch" của khách hàng vẫn xanh vì
125
+ luồng đó không có bước "xong".
126
+
127
+ **Luồng có từ hai trạng thái cùng tông thì chấm đổi thành icon, mỗi trạng thái một hình.**
128
+ Bốn tông không đủ cho luồng công việc: theo đoạn trên thì Cần làm và Đang làm cùng xám, và
129
+ bảng nhóm theo trạng thái ra ba nhóm xám một nhóm xanh, liếc không tách được nhóm nào với
130
+ nhóm nào. Các app quản lý công việc lớn tách bằng **hình**, không thêm màu:
131
+
132
+ | Trạng thái | Tông | Icon lucide | Vì sao |
133
+ | --- | --- | --- | --- |
134
+ | Cần làm | xám | `circle` | vòng rỗng: chưa bắt đầu |
135
+ | Đang làm | xám | `circle-dot` | có lõi: đang có người làm, không ai khác phải làm gì |
136
+ | Chờ duyệt | hổ phách | `circle-ellipsis` | người duyệt phải làm gì đó, cùng lý với "Chờ xử lý" |
137
+ | Xong | xanh lá | `circle-check` | |
138
+ | Đã huỷ | đỏ | `circle-x` | |
139
+
140
+ Icon `size-3.5` trong badge (thay chấm), `size-4` ở đầu nhóm và đầu cột kanban, màu
141
+ `-600` cùng tông (`text-zinc-500` cho xám). Một bảng này dùng cho mọi bề mặt (`D2`).
142
+
143
+ Nền tối: class Tailwind thì thêm `dark:` (nền `-500/15`, chữ `-400`), token thì
144
+ khối `.dark` trong `tokens.css` đã đổi sẵn.
145
+
146
+ - Xanh lá là màu **cố định**, không lấy màu nhấn. Màu nhấn mặc định gần đen, badge đen đặc giữa bảng trông như nút bấm.
147
+ - Chữ `-700`, không `-500`: chữ nhỏ trên nền nhạt cần đậm để đạt tương phản (`P3`).
148
+ - Không `border`. Vòng trong `ring-black/5` ở trên là khung giữ badge không tan vào nền, không tính là tín hiệu thứ hai (`M6`).
149
+ - Cả app một bảng ánh xạ, xem `D2` trong `system.md`.
150
+
151
+ Nhãn **nằm trong dòng chữ phụ** ("Hằng tuần · quá hạn 2 ngày") thì vẫn là chữ màu,
152
+ không nền. Pill chen giữa câu làm dòng chữ gồ lên.
153
+
154
+ ---
155
+
156
+ ## Ngoại lệ đã duyệt
157
+
158
+ **M8. Màu mã hoá dữ liệu không tính vào ngân sách một màu nhấn.**
159
+
160
+ Tag phân loại, nhãn ngành, nhãn mô hình được phép nhiều màu, vì màu ở đó **mang
161
+ thông tin**. Bốn điều kiện, thiếu một là bỏ:
162
+
163
+ - Chỉ cho phân loại thật, thứ mà người ta cần liếc là phân biệt được.
164
+ - Luôn **pastel nhạt**: nền khoảng 10%, chữ đậm cùng tông, viền một bậc đậm hơn nền.
165
+ - **Một nhãn một màu cố định** trong cả app. "Technology" xanh dương thì ở đâu cũng xanh dương.
166
+ - Không lan sang nút, nền khối, hay đường kẻ.
167
+
168
+ Đặt tên thang màu phân loại **khác tên trạng thái**. Dự án thật tách riêng
169
+ `iris` / `magenta` / `coral` thay vì dùng lại `accent` / `danger`, để badge đỏ
170
+ "B2C" không bị đọc nhầm thành lỗi.
171
+
172
+ **M9. Biểu tượng quen thuộc được giữ màu của nó, dù đó không phải trạng thái.**
173
+
174
+ Chủ dự án duyệt, sau khi trung tính hoá làm mất nghĩa:
175
+
176
+ - Huy chương top 3 tô đặc vàng / bạc / đồng — màu **trên icon**, không tô thẻ.
177
+ - Thẻ hạng nhất viền vàng, **nền vẫn trắng**. Nền vàng nhạt ra màu be, đọc như thẻ cũ hoặc thẻ đã khoá. Đã thử và đã bỏ.
178
+ - Ngọn lửa chuỗi ngày tô đặc: ruột vàng, viền cam. Lửa xám nét mảnh chìm hẳn.
179
+ - Dấu `*` của trường bắt buộc tô đỏ. Không phải lỗi, nhưng "`*` đỏ = bắt buộc" là quy ước phổ biến tới mức xám lại khó đọc.
180
+
181
+ Nguyên tắc chung: khi màu trung tính làm **một biểu tượng mất nghĩa**, chọn nghĩa,
182
+ nói một câu lý do, rồi ghi ngoại lệ vào đây.
183
+
184
+ **M10. Nội dung người dùng tự viết thì không kiểm soát màu.** SOP, ghi chú có
185
+ emoji, chữ đỏ trong markdown — chỉ làm KHUNG bao quanh trung tính, đừng đi sửa
186
+ ruột.
187
+
188
+ **M34. Thứ người dùng tạo ra thì có màu nhận diện.** Dự án, board, workspace, danh
189
+ mục, cột kanban, kênh: thứ người dùng tự đặt tên, hiện ở nhiều chỗ, và cần nhận ra
190
+ bằng một cái liếc trong danh sách dài. App một màu nhấn nhìn gọn nhưng dễ đơn điệu;
191
+ điểm màu nên nằm ở **dữ liệu**, còn **khung** (nút, sidebar, header, ô nhập) vẫn một
192
+ màu nhấn như `M2`. Khác `M8`: `M8` là nhãn phân loại gắn lên một mục, `M34` là màu
193
+ của chính thực thể. Một card có thể mang cả hai.
194
+
195
+ - **Khi nào dùng:** app có thực thể như trên, và nó xuất hiện từ hai chỗ trở lên (sidebar, bảng, breadcrumb, ô chọn) hoặc có từ năm cái trở lên. Không có thì thôi, đừng bịa ra thực thể để có chỗ tô. Có thì làm luôn, không hỏi.
196
+ - **Sáu sắc của avatar**, cùng thứ tự (`components/avatar.md`): emerald, sky, indigo, pink, amber, violet. Không thêm sắc, không `red` / `rose` (`M30`).
197
+ - **Màu nằm trên dấu nhỏ**, không trên mảng lớn: chấm tròn `size-2` sắc `-500` (nền tối `-400`) cạnh tên; ô vuông bo góc chữ cái đầu theo khuôn avatar; dải `h-1` ở đỉnh card hay đầu cột; ảnh bìa người dùng tải lên. **Không** tô nền cả card, nền dòng, nền cột, không tô chữ tên.
198
+ - **Màu là dữ liệu, không tính lúc render.** Tạo mới thì có ô chọn sáu màu, mặc định lấy theo `id` như avatar; lưu lại cùng thực thể. Ở sidebar, bảng, breadcrumb, ô chọn đều cùng một màu.
199
+ - **Tên luôn hiện cạnh màu.** Màu giúp liếc, không thay chữ; chấm và ô vuông để `aria-hidden`.
200
+ - **Trạng thái của khung không đổi:** mục sidebar đang chọn vẫn nền `bg-secondary` trung tính (`checklist.md`, mục Sidebar), chấm giữ nguyên màu, không đổi sang màu thực thể.
201
+ - Không tính vào ngân sách một màu nhấn (`budgets.md`). Dự án đã có cách tô riêng cho thực thể thì theo họ.
202
+
203
+ ```
204
+ DỰ ÁN ┌─────────────────────┐
205
+ ● Website khách A │▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔│ ← dải h-1 màu dự án
206
+ ● App nội bộ │ Sửa form đăng ký │
207
+ ● Marketing Q4 │ [Bug] [Gấp] │ ← nhãn M8
208
+ └─────────────────────┘
209
+ ```
210
+
211
+ **M11. Chữ chỉ ba sắc độ.** Chữ chính, chữ phụ, và màu nằm trên nền nhấn.
212
+
213
+ Ngoại lệ duy nhất: **mục điều hướng lúc chưa rê/chưa chọn** dùng
214
+ `foreground/70`, để hover có chỗ "sáng lên" mà lúc thường vẫn đọc rõ. Dùng
215
+ `--muted` ở đó thì tên mục mờ quá trên nền trắng.
216
+
217
+ ⚠️ Bẫy đã dính ở dự án thật: token tên `--text-muted` bị alias về `--text-normal`,
218
+ tức "chữ phụ" và "chữ chính" cùng một màu đen. Đừng tin tên token — mở giá trị
219
+ thật ra xem. Xem `refactor.md` luật L3.
220
+
221
+ **M12. Không gradient.** Ngoại lệ duy nhất: **ảnh đại diện và dấu hiệu nhận diện**
222
+ — avatar người dùng, icon workspace, logo tổ chức. Chúng là hình tròn hoặc vuông
223
+ nhỏ dưới 40px, và gradient ở đó đóng vai ảnh chứ không đóng vai nền.
224
+
225
+ *Gu flat — phong cách khác đã chọn theo `P1` thì xem `P2` trong `references/styles.md`.*
226
+
227
+ Không bao giờ cho nút, card, nền trang, hay chữ (`bg-clip-text text-transparent`).
228
+
229
+ ---
230
+
231
+ ## Viền và bóng
232
+
233
+ > **⚠️ Đảo luật.** Bản cũ của skill này cấm viền card và bắt tách khối bằng chênh
234
+ > lệch nền. **Luật đó đã bỏ.** Chủ dự án chốt phong cách "đường tóc 1px + bo góc,
235
+ > không bóng". Đừng hồi sinh luật cũ.
236
+
237
+ **M13. Tách khối bằng đường tóc 1px + bo góc, không bằng bóng.**
238
+
239
+ *Gu flat — phong cách khác đã chọn theo `P1` thì xem `P2` trong `references/styles.md`.*
240
+
241
+ Trang phẳng, sạch; thứ bậc đến từ cỡ chữ, độ đậm và màu chữ.
242
+
243
+ *Ngoại lệ: card đứng một mình giữa trang trống — xem `M29`.*
244
+
245
+ | Phần tử | Công thức |
246
+ | --- | --- |
247
+ | Thẻ / khung | trắng, viền 1px xám rất nhạt, bo 16px `rounded-2xl` (`F1`), **không bóng** |
248
+ | Danh sách nhiều mục | MỘT khung, các dòng chia bằng `divide-y`. Dòng tiêu đề và dòng hành động cuối nằm TRONG khung |
249
+ | Khối tóm tắt phụ | nền xám nhạt + viền, bo như thẻ |
250
+ | Mục sidebar, cây thư mục đang chọn | nền `--secondary` + `font-medium`, **không viền**, không màu nhấn; rê vào `--background`, mục chưa chọn không nền. Đang chọn đậm hơn nền rê một bậc (`I10`, `I15`) |
251
+ | Tab ngang trên bảng / danh sách | tab đang chọn **nền `--secondary`, viền trong suốt** (không nền trắng, không `--surface-hover`: trên card trắng liếc không thấy), mọi tab có `border`, mục chưa chọn `border-transparent`. Xem "Thanh tab" trong `components/small-controls.md` |
252
+ | Ô nhập | viền — đây là chỗ viền đúng vai nhất, người ta phải nhìn ra ranh giới vùng gõ được |
253
+
254
+ **M14. Hai token viền, chia theo vai trò. Không có cái thứ ba.**
255
+
256
+ | Token | Cho | Vì sao |
257
+ | --- | --- | --- |
258
+ | `--border` | Viền card, khung dropdown, đường chia trong danh sách và menu, **đường kẻ dưới đầu sidebar và dưới header** (cùng màu vạch trong menu, chủ dự án chốt, `layouts/app.md`) | **Trang trí**: chỉ vạch ranh giới, nhạt được bao nhiêu thì nhạt |
259
+ | `--border-strong` | **Viền ô nhập**, **viền nút outline**, select, viền card khi hover, đường kẻ nằm trên nền trang xám (dưới hàng tab khu cài đặt), kẻ dọc sidebar **chỉ khi vùng nội dung cũng trắng** (`layouts/app.md`) | **Chức năng**: ô nhập và nút outline cùng nền trắng với card, viền là thứ duy nhất báo "đây là chỗ gõ", "đây là chỗ bấm" (`I8`) |
260
+
261
+ Trong mỗi nhóm thì mọi chỗ dùng chung đúng một token, để đường tóc không chỗ
262
+ đậm chỗ nhạt. Muốn viền card nhạt đi thì hạ `--border`, ô nhập không nhạt theo.
263
+
264
+ Checkbox, radio chưa chọn cũng dùng `--border-strong`, cùng độ đậm với ô nhập.
265
+
266
+ Nền tối giữ đúng hai vai đó, cả hai là `rgba` mờ: `--border` 1.23:1 trên card, `--border-strong`
267
+ 1.33:1 (đừng dùng lại `0.2`: 1.47:1, dòng bảng thành lưới kẻ ô). Ô nhập ở nền tối
268
+ **giữ viền** `--border-strong` (`M32`).
269
+
270
+ **Dự án đã có token viền riêng** (dựng mới trong dự án có sẵn, hai chế độ dựng lại): xám
271
+ viền là **dáng, không phải vai màu** (`review.md`), nên xếp token của họ vào hai vai trên
272
+ chứ không giữ cách bản cũ dùng. Bậc nhạt nhất của dự án cho vai trang trí (viền card, khung
273
+ dropdown, đường chia). Đường kẻ khung app: **đoạn dưới đầu sidebar và đoạn dưới header
274
+ cùng một token**, vì hai đoạn nối thành một đường (probe: "đường ngăn thẳng hàng mà khác
275
+ màu"). Bậc nào đậm cỡ `#e4e4e7` trở lên (~1.25:1 trên trắng, mức đã bị chê "đường kẻ sidebar
276
+ đậm") thì không dùng cho đường kẻ khung và viền card, chỉ cho ô nhập, nút
277
+ viền. Đã dính: vạch dưới logo `border-light` `#f1f5f9` nối vào
278
+ vạch dưới header `border` `#e2e8f0`, card lọc, card danh sách, card chi tiết cũng
279
+ `#e2e8f0`: đường ở sidebar ổn, ngoài thì đậm, người xem tưởng brand bắt vậy.
280
+
281
+ > Đã thử làm đậm viền điều khiển cho đạt 3:1 (WCAG 1.4.11): ô nhập,
282
+ > select, nút outline lên `#8a8a91`; checkbox, radio lên `--muted`; track công
283
+ > tắc lên `muted/75`. Chủ dự án xem thật thấy **đậm và xấu**, trả về hết.
284
+ > Đừng đề xuất lại cho dự án thường; chỉ dự án bắt buộc AA (xem `P3`).
285
+
286
+ ⚠️ **Đừng lấy `--border` cho ô nhập hay nút outline để "cho đồng bộ".** Hạ
287
+ `--border` cho card và dropdown nhẹ đi là ô nhập và nút tan luôn vào nền. Đã xảy
288
+ ra thật: `--border` hạ từ `#f3f3f4` xuống `#f7f7f8` cho khung dropdown,
289
+ nút outline đang dùng chung token nên trông như đã bị khoá.
290
+
291
+ ⚠️ Thiếu class màu viền thì Tailwind v4 để `border-color: currentColor` — nút chữ
292
+ đen sẽ ra **viền gần đen**. Thấy viền đậm bất thường thì kiểm chỗ này trước khi
293
+ nghi mã màu.
294
+
295
+ **M15. Bóng CHỈ cho lớp nổi.**
296
+
297
+ *Gu flat — phong cách khác đã chọn theo `P1` thì xem `P2` trong `references/styles.md`.*
298
+
299
+ Modal, command palette, dropdown, popover, toast được đổ bóng vì chúng nằm **trên**
300
+ trang. Mọi thứ nằm **trong** trang thì không.
301
+
302
+ Bóng lớp nổi đi qua hai token, không viết `shadow-lg`, `shadow-xl` trần: `shadow-popover`
303
+ (dropdown, menu, popover, toast) và `shadow-modal` (modal, panel trượt, command palette).
304
+ Nền sáng chúng bằng đúng `shadow-lg` / `shadow-xl`; nền tối đậm hơn (`M23`). Khung nào
305
+ cũng `border border-border`. Bảng nền của từng khối ở đầu `layouts/overlay.md`.
306
+
307
+ Định thêm `shadow-*` cho khối nằm trong trang → thử viền trước, xem có đủ tách
308
+ khối không. Gần như luôn là đủ.
309
+
310
+ *Ngoại lệ: card đứng một mình giữa trang trống — xem `M29`. Ô đang chọn của tab
311
+ `segmented` (phím nổi trên rãnh chìm), dùng đúng hai token `--shadow-segment-*` —
312
+ xem "Thanh tab" trong `components/small-controls.md`.*
313
+
314
+ **M16. Không đẻ token viền mới từ màu nhấn.**
315
+
316
+ Viền tĩnh chỉ có token viền thường (và tối đa một bậc đậm hơn). Viền trạng thái
317
+ chỉ có `--border-focus`, và nó chỉ hiện lúc focus.
318
+
319
+ Thấy mình sắp viết `--primary-ring`, `--accent-border` là dấu hiệu đang muốn nhấn
320
+ một khối bằng viền — mà nhấn bằng viền là cách rẻ nhất. Đã xảy ra thật: một bản
321
+ dựng tự chế `--primary-ring: rgba(233,237,245,0.22)` rồi viền card nổi bật, trên
322
+ nền tối trông sáng chói.
323
+
324
+ **M17. `ring` khi không được đụng bố cục, `border` cho phần còn lại.**
325
+
326
+ `border` ăn vào hộp theo `box-sizing: border-box` nên phần tử cỡ cố định sẽ co
327
+ lại. Cần đường bao quanh avatar, quanh ô vuông cỡ chuẩn thì dùng `ring-1`.
328
+
329
+ **M18. Phần tử con trong hàng có hover không được trùng token với nền hover của hàng.**
330
+
331
+ Hàng hover đổi nền (luật `I10`: `--item-hover` hoặc `--surface-hover`). Nếu ô vuông trạng thái, checkbox hay
332
+ avatar bên trong cũng dùng đúng token đó làm nền, hoặc chỉ có viền nhạt, thì rê
333
+ chuột vào là chúng **biến mất**.
334
+
335
+ *Cách kiểm:* rê chuột lên hàng, đếm xem còn nhìn thấy đủ mọi thứ không.
336
+
337
+ ---
338
+
339
+ ## Bo góc lồng nhau
340
+
341
+ **M19. Bo lồng nhau: ngoài = trong + khoảng cách giữa hai mép.**
342
+
343
+ ```
344
+ R_ngoài = r_trong + d d = padding của khung ngoài + độ dày viền (nếu có)
345
+ ```
346
+
347
+ **Vì sao:** hai góc chỉ trông song song, khe hở đều nhau suốt đường cong, khi
348
+ chúng có **chung một tâm**. Công thức trên chính là điều kiện để hai tâm trùng
349
+ nhau. Lệch khỏi nó thì khe hở ở góc khác khe hở ở cạnh:
350
+
351
+ | Bán kính trong | Khe hở ở góc | Trông |
352
+ | --- | --- | --- |
353
+ | **Bằng** bán kính ngoài (12 trong 12) | **≈ 1.4 × d**, rộng hơn ở cạnh | Góc phình ra, như hai hình không khớp |
354
+ | **= R − d** | **= d**, bằng đúng ở cạnh | Song song, gọn |
355
+ | Nhỏ hơn nhiều, hoặc vuông | Hẹp hơn d, có thể về 0 | Góc trong bị ép sát, chọc vào đường cong ngoài |
356
+
357
+ **Các cặp hay dùng, đều nằm trên thang Tailwind:**
358
+
359
+ | Khung ngoài | Padding | Phần tử trong |
360
+ | --- | --- | --- |
361
+ | `rounded-xl` 12px | `p-1` 4px | `rounded-lg` 8px |
362
+ | `rounded-2xl` 16px | `p-1` 4px | `rounded-xl` 12px |
363
+ | `rounded-2xl` 16px | `p-2` 8px | `rounded-lg` 8px |
364
+
365
+ Cả ba cặp chỉ dùng bậc có trong thang bốn bậc của `F1`. Công thức ra một số
366
+ ngoài thang (ví dụ `p-1.5` ra 6px) thì **đổi padding cho khớp thang**, đừng đẻ
367
+ thêm bậc bo góc.
368
+
369
+ **Chỉ áp khi hai mép ở gần nhau** — khi `d` không lớn hơn `R`. Dropdown, menu,
370
+ thanh tab dạng viên thuốc, ô nhập có nút bên trong: đều là ca này, và mắt so hai
371
+ góc với nhau ngay.
372
+
373
+ **Khoảng cách lớn thì bỏ công thức.** Card `rounded-2xl` 16px với `p-5` 20px chứa
374
+ một nút: công thức ra `16 − 20 = −4`. Hai góc cách nhau quá xa để mắt so, nên dùng
375
+ bo theo vai trò (`F1`): card 16px, nút và ô nhập 12px, control nhỏ 8px. Đừng ép
376
+ công thức ra số âm hay 0.
377
+
378
+ Có viền 1px thì `d` cộng thêm 1. Lệch 1px không ai thấy, nên cứ lấy bậc gần nhất
379
+ trên thang.
380
+
381
+ Không trộn nút bo tròn hẳn với nút bo vuông trong cùng một nhóm; badge trạng thái
382
+ là ngoại lệ.
383
+
384
+ ---
385
+
386
+ ## Dark mode
387
+
388
+ **M20. Mặc định chỉ làm light mode.** Dark mode là việc gấp đôi và gấp đôi chỗ
389
+ phải kiểm tương phản. Chỉ làm khi người dùng nói cần, và hỏi một câu lúc giao.
390
+ Làm thì theo đủ `M21`–`M23`, `M31`–`M33`, và khối `.dark` của `tokens.css`.
391
+
392
+ *Gu flat — phong cách khác đã chọn theo `P1` thì xem `P2` trong `references/styles.md`.*
393
+
394
+ **M21. Tầng nổi sáng dần ở nền tối; vùng tô là lớp phủ, giữ độ chênh với nền phía sau
395
+ chứ không giữ chiều.**
396
+
397
+ Hai loại bề mặt, hai cách đổi theme:
398
+
399
+ | Loại | Gồm | Nền sáng | Nền tối |
400
+ | --- | --- | --- | --- |
401
+ | **Tầng** | nền trang → card → lớp nổi nhỏ (`--surface-overlay`) | trang xám, card và lớp nổi trắng | **sáng dần**: `#05060f` → `#0f111a` → `#171a26`, mỗi bậc ~1.07–1.09:1 |
402
+ | **Vùng tô** | nút phụ, mục / tab đang chọn (`--secondary`), nền rê (`--item-hover`, `--button-hover`), rãnh, chip | phủ **tối** hơn nền phía sau | phủ **trắng mờ**, **sáng** hơn nền phía sau |
403
+
404
+ Vùng tô ở nền sáng tối hơn card, ở nền tối sáng hơn card: đúng ở cả hai. Thứ phải giữ
405
+ là **độ chênh** với nền ngay sau nó, ngang nhau giữa hai theme (đang chọn `--secondary`
406
+ 1.22:1 cả hai bản; nền rê 1.10 / 1.12:1). Đây là cách gần như mọi bộ thiết kế lớn làm
407
+ (7/7 bộ có số liệu).
408
+
409
+ - **Vùng tô nền tối viết bằng trắng phủ mờ** (`white/4`–`white/10`), không bằng xám đặc.
410
+ Phủ mờ thì đặt trên nền trang, card hay lớp nổi đều đúng; xám đặc chỉ đúng trên một nền.
411
+ Trong code: lớp phủ `bg-foreground/5` (tự đảo theo theme) hoặc token, không `dark:bg-zinc-800`.
412
+ - **Không dùng `--background` làm nền rê hay nền chọn.** Ở nền tối nó tối hơn card, rê vào
413
+ là chìm xuống 1.07:1, gần như không thấy. Nền rê mục thụt vào là `--item-hover` (`I10`).
414
+ - **Đang chọn đậm hơn rê đúng một bậc ở cả hai theme** (`I10`, `I15`).
415
+
416
+ *Cách kiểm:* liệt kê nền trang, card, lớp nổi của mỗi theme: nền tối phải sáng dần. Rồi đo
417
+ nền rê và nền đang chọn trên card ở cả hai theme: độ chênh hai bản xấp xỉ nhau, đang chọn
418
+ đậm hơn rê.
419
+
420
+ > Đừng bắt nút phụ **chìm hơn card** ở nền tối: `--secondary` tối `#010207`, dưới cả nền
421
+ > trang, thì nút và tab đang chọn gần như không tách khỏi nền. Nút phụ `#1c2030` trên card
422
+ > `#0f111a` trông "nổi lên" là đúng chiều; thứ cần canh là độ chênh, không phải chiều.
423
+
424
+ **M22. Ở nền tối, màu nhấn chỉ dùng làm nền, không dùng làm đường mảnh.**
425
+
426
+ Màu nhấn trong dark mode thường là gần trắng. Tô nền nút thì đẹp; đem làm viền ô
427
+ nhập lúc focus, gạch chân, hay chỉ báo đang chọn thì thành sợi trắng đặc một
428
+ pixel, gắt và rẻ. Đường mảnh dùng chính màu đó **hạ độ đục xuống khoảng 42%**, vừa đủ 3:1 với nền
429
+ (WCAG 1.4.11). Xuống 35% là còn 2.9:1, trượt.
430
+
431
+ **M23. Dark mode mặc định của skill là navy rất tối. Đây là gu, không phải quy ước:
432
+ dự án đã có bảng tối riêng thì theo dự án.**
433
+
434
+ Phần lớn các bộ thiết kế lớn để nền tối trung tính hoặc gần trung tính; navy là một
435
+ trong nhiều gu. Dự án có xám kẽm, xám trung tính, hay nền tối ám màu thương hiệu thì giữ
436
+ nguyên (`P10`, `V4`). Dự án chưa có thì lấy khối `.dark` của `tokens.css`.
437
+
438
+ Dù gu nào:
439
+
440
+ - **Không đen tuyệt đối, không trắng tuyệt đối**, kể cả nền rê của nút chính. Nền gần đen,
441
+ chữ gần trắng. `--primary-hover` lệch **về phía nền** một bậc ở cả hai theme: nền sáng nhạt
442
+ đi, nền tối tối đi (`#e9edf5` → `#cfd5e0`), không sáng lên `#ffffff`.
443
+ - **Viền là `rgba` mờ**, không phải màu đặc, không thì thành lưới kẻ ô. Ở nền tối viền
444
+ **đảo vai**: nền trang và card chênh nhau quá ít (1.07:1) nên viền là thứ chính để tách khối.
445
+ - **Bóng giữ, đậm hơn, đi cặp viền 1px.** Bóng 10% trên nền gần đen gần như không thấy, nên
446
+ lớp nổi tối có ba thứ cùng lúc: nền sáng hơn một bậc (`M21`), viền `border-border`, bóng
447
+ đậm (`--elevation-*` khối `.dark`). Không bỏ bóng, không thay bằng vầng sáng.
448
+ - **Không bóng màu ở nền tối** (bóng cam, bóng hổ phách dưới nút): trên nền tối nó thành
449
+ vầng sáng bẩn. `dark:shadow-none` cho mọi bóng có sắc.
450
+
451
+ *Gu flat — phong cách khác đã chọn theo `P1` thì xem `P2` trong `references/styles.md`.*
452
+
453
+ **M31. Nút đổi theme: Sáng / Tối / Hệ thống, mặc định Hệ thống.**
454
+
455
+ - **Ba lựa chọn thấy hết một lần**, không một nút bấm xoay vòng (người dùng phải bấm thử mới
456
+ biết lần sau ra gì). Mặc định **Hệ thống** (`prefers-color-scheme`), không mặc định Sáng.
457
+ - **Chỗ đặt:** trong app là **Cài đặt → Giao diện** hoặc mục Giao diện trong **menu tài khoản**;
458
+ đề bảo đặt trên header thì là **icon button** (`Sun` / `Moon`) mở menu ba mục, mục đang chọn
459
+ có dấu ✓. Trang công khai, docs: nhóm ba icon (radiogroup) ở footer hoặc nút icon trên header.
460
+ - **Màn xác thực, trang lỗi không cần nút đổi theme**: script đầu trang áp lựa chọn đã lưu hoặc theo
461
+ hệ thống cho mọi route, người dùng đổi trong app.
462
+ - **Nhớ lựa chọn, không nháy trắng khi tải.** Script đặt class trên `<html>` trước khi vẽ;
463
+ `<html suppressHydrationWarning>` vì script sửa `<html>` trước React.
464
+ - **Lật theme thì tắt transition một nhịp**, không thì nền trang đổi tức thì còn nút, card
465
+ chuyển màu 200ms lệch nhịp.
466
+ - **Tầng trình duyệt:** `color-scheme` đi cùng theme (khối `:root` / `.dark` của `tokens.css`
467
+ đã có), để thanh cuộn gốc, ô chọn ngày, autofill, `<select>` vẽ đúng bản. `theme-color` hai thẻ
468
+ theo `media` là tuỳ chọn.
469
+ - **`dark:` phải theo class, không theo máy.** Tailwind v4 mặc định `dark:` chạy theo
470
+ `prefers-color-scheme`; thiếu dòng `@custom-variant dark` trong `tokens.css` thì bấm Tối trên
471
+ máy đang sáng ra token tối, class `dark:` sáng.
472
+
473
+ Next.js: `next-themes`.
474
+
475
+ ```tsx
476
+ // app/layout.tsx
477
+ <html lang="vi" suppressHydrationWarning>
478
+ <body>
479
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
480
+ {children}
481
+ </ThemeProvider>
482
+ </body>
483
+ </html>
484
+ ```
485
+
486
+ Icon button trên header đọc `resolvedTheme` sau khi mount (trước đó render icon cố định), không
487
+ thì lệch HTML lúc hydrate. Không Next thì script nội tuyến đầu `<head>` đọc `localStorage` +
488
+ `matchMedia("(prefers-color-scheme: dark)")` rồi gắn `.dark`, và nghe đổi của máy khi đang Hệ thống.
489
+
490
+ **M32. Ở nền tối, vài thứ đổi cách vẽ chứ không chỉ đổi màu.**
491
+
492
+ | Thứ | Nền tối | Vì sao |
493
+ | --- | --- | --- |
494
+ | Ô nhập, select, textarea | **giữ viền** `--border-strong`, nền phủ mờ `dark:bg-white/4` | bỏ viền chỉ còn nền mờ là mất ranh giới vùng gõ; mọi bộ lớn đều giữ viền |
495
+ | Nút đảo màu (nền `--foreground`, không phải `--primary`) | về variant `secondary`, hoặc nền trắng mờ + viền | đảo thẳng ra khối trắng chói, nặng hơn cả nút chính. Chỉ màu nhấn được thành khối sáng |
496
+ | Tooltip | **đảo màu**: `bg-foreground text-background` | nền tối thì tooltip sáng, nổi khỏi mọi tầng |
497
+ | Lớp phủ sau modal, panel, sidebar trượt | **`bg-black/…`** ở cả hai theme, không `bg-foreground/…` | `foreground` đảo sang gần trắng, lớp phủ thành màn sương sáng che trang |
498
+ | Mục đang trỏ, đang chọn viết bằng điều kiện JS (`isHighlighted && …`, `isActive && …`) | `bg-item-hover` (trỏ), `bg-secondary` (chọn), như class `hover:` | `bg-background` trong điều kiện JS lọt khỏi mọi phép grep `hover:`; ở nền tối thành lỗ khoét trong dropdown (ô chọn, bảng lệnh) |
499
+ | Chữ, icon màu trạng thái không nền (`text-amber-700`, `text-red-700`, `text-emerald-700`) | kèm `dark:text-*-400` cùng sắc (token `--warning`, `--error-strong`, `--success` đã lật sẵn) | `-700` trên nền tối chỉ 2,9–3,7:1, dưới ngưỡng chữ (`M7`) |
500
+ | Nền nhạt `-50`, `-100` (badge, banner, chip, ô đáp án) | màu `-500` phủ 10–20%, chữ `-300`/`-400` | nền `-50` sang tối là khối trắng hồng giữa màn đen. Màu mang nghĩa đã có sẵn trong `tokens.css` |
501
+ | Gradient, vệt màu loang trang trí | tắt (`dark:hidden`) hoặc thay bản tối | loang màu sáng trên nền tối là chói |
502
+ | Biểu đồ | cột, vùng tô dùng `--chart-fill` (màu nhấn 70% ở nền tối); thang phân loại có bản tối (`components/charts.md`) | cột màu nhấn gần trắng 100% là khối chói nhất màn; xám "Khác" `slate-300` thành nổi nhất |
503
+ | Ảnh nền trắng, logo | ảnh cần bản tối thì `<picture><source media="(prefers-color-scheme: dark)">` hoặc hai ảnh theo class; logo chuyển bản sáng | |
504
+ | Avatar, ảnh nhỏ | vòng `ring-1 ring-border` | ảnh tối tan vào nền |
505
+
506
+ **M33. Vùng khoá theme khai lại token tại chỗ; thư viện bên ngoài đọc token.**
507
+
508
+ - **Vùng luôn sáng / luôn tối** (trang chia sẻ nền kem, hero luôn tối): class `.force-light` /
509
+ `.force-dark` trên khung ngoài cùng, `tokens.css` đã cho hai class này đọc lại đúng bộ token,
510
+ mọi component con đổi theo mà không truyền gì xuống. `@custom-variant dark` của `tokens.css`
511
+ đã loại vùng `.force-light`, nên `dark:` không rò vào. Cả trang khoá theme thì `next-themes`
512
+ `forcedTheme`, và ẩn nút đổi theme ở trang đó.
513
+ - **Thư viện bên ngoài đọc token**, không để màu mặc định của nó: toast, hộp thoại, thanh tiến
514
+ trình chuyển trang, trình soạn thảo, lịch. Đè style bằng `var(--surface-overlay)`,
515
+ `var(--border)`, `var(--foreground)`, `var(--primary)` là tự lật theo theme. Để màu mặc định
516
+ của thư viện thì nền tối còn một hộp trắng giữa màn (chỉ người bật tối mới thấy, nên rất lâu
517
+ mới có người báo).
518
+
519
+ ---
520
+
521
+ ## Token
522
+
523
+ **M24. Mọi màu đi qua token đặt tên theo vai trò.** Không rải mã hex trong markup.
524
+
525
+ **M25. Một khái niệm một token.** Mọi đường kẻ và viền dùng chung một tên. Đừng
526
+ chỗ thì `divide-border` chỗ thì `border-muted/25`.
527
+
528
+ **M26. Mọi màu và font gom vào khối đánh dấu ở đầu file.** Ngoài khối đó không
529
+ được xuất hiện mã màu. Có dark mode thì màu nhấn có **hai chỗ**: `:root` và
530
+ `.dark`. Thiếu chỗ thứ hai là màu nhấn tàng hình trên nền tối.
531
+
532
+ **M27. Khối đổi thương hiệu phải chép nguyên văn từ `tokens.css`.** Mở file ra
533
+ copy, không gõ lại từ trí nhớ, không tự nghĩ mã hex. Đã có lần AI tự chế ra
534
+ `#a99cff` tím và `#fa99cff0d` sai cú pháp. Cần màu khác thì thay đúng một dòng.
535
+
536
+ **M28. Lúc giao phải chỉ rõ chỗ đổi thương hiệu.** Một dòng: "đổi màu nhấn ở dòng
537
+ 14, font ở dòng 8". Có dark mode thì nói rõ là hai chỗ.
538
+
539
+ ---
540
+
541
+ ## Card đứng một mình
542
+
543
+ **M29. Màn chỉ có đúng MỘT card giữa trang trống thì bỏ viền. Chìm quá thì dùng
544
+ bóng rất mờ, không phải viền đậm hơn.**
545
+
546
+ *Gu flat — phong cách khác đã chọn theo `P1` thì xem `P2` trong `references/styles.md`.*
547
+
548
+ Đăng nhập, đăng ký, quên mật khẩu, màn onboarding một khối. (Trang lỗi không dùng card, xem
549
+ "Trang lỗi" ở `layouts/app.md`.) Đặc điểm
550
+ chung: **không có khối thứ hai nào để mà tách khỏi.**
551
+
552
+ `M13` bắt viền vì viền là thứ phân định ranh giới giữa các khối nằm cạnh nhau.
553
+ Trên màn chỉ có một card, không còn việc đó để làm — đường viền lúc này chỉ là
554
+ một nét vẽ quanh hộp, và nó làm card trông như một cái khung chờ nội dung.
555
+
556
+ Thứ tự thử, dừng ngay khi đủ:
557
+
558
+ 1. **Không viền, không bóng.** Nền trang xám (`--background`) + card trắng (`--surface`) đã đủ chênh để đọc ra ranh giới. Đây là mặc định.
559
+ 2. **Chìm quá thì thêm bóng rất mờ.** Cỡ `shadow-sm` của Tailwind — mờ đến mức chỉ cảm thấy chứ không nhìn ra. Card lúc này đang **nổi trên** một trang trống, nên nó hợp tinh thần "lớp nổi" của `M15` hơn là khối nằm trong trang.
560
+ 3. **Không bao giờ dùng cả viền lẫn bóng.** Hai thứ cùng làm một việc. Có bóng rồi mà vẫn thấy cần viền thì bóng đang đặt sai, không phải thiếu viền.
561
+
562
+ **Đừng chữa cháy bằng viền đậm hơn.** Thấy card chìm mà tăng độ đậm của viền là
563
+ đi ngược `M14` — viền đậm lên thì cái hộp hiện ra rõ hơn nội dung bên trong nó.
564
+
565
+ Có từ hai card trở lên trên màn thì quay về `M13` như thường.
566
+
567
+ **Nền tối giữ y như vậy, không thêm viền dù `M23` nói viền gánh việc tách khối.** Card tối chênh nền
568
+ 1.07:1, ngang bản sáng 1.10:1, đủ đọc ra ranh giới. Đã thử trên màn đăng nhập: viền
569
+ `--border` biến card thành khung, bóng `--elevation-popover` gần như không thấy. `M23` nói về các khối
570
+ nằm cạnh nhau trong trang, không về một card một mình.
571
+
572
+ ---
573
+
574
+ ## Hai sắc đỏ
575
+
576
+ **M30. Đỏ có hai sắc cho hai việc. Không dùng lẫn, không thêm sắc thứ ba.**
577
+
578
+ | | Sắc | Việc | Khi nào hiện | Ở đâu |
579
+ | --- | --- | --- | --- | --- |
580
+ | **Lỗi** | `red` | *Đã có gì đó sai*, phải sửa mới đi tiếp được | Sau khi người dùng làm sai | Ô nhập, câu lỗi, banner lỗi máy chủ |
581
+ | **Nguy hiểm** | `rose` | *Bấm vào thì không lấy lại được* | Nút: luôn hiện, nền mờ. Mục menu: chỉ lúc rê vào (`I4`) | Xoá, huỷ tài khoản, rời nhóm, đăng xuất (mục menu) |
582
+
583
+ **Vì sao tách.** Hai việc khác nhau về thời điểm và mức nặng:
584
+
585
+ - **Lỗi** là chuyện **đã xảy ra**. Nó phải nhận ra ngay, không lẫn với gì — nên dùng `red`, sắc đỏ chuẩn mà ai nhìn cũng đọc ra "sai".
586
+ - **Nguy hiểm** là **lời nhắc trước khi bấm**, hiện lên chỉ vì chuột đi ngang qua. Chưa có gì sai cả. Nếu nó đỏ y như lỗi thì mỗi lần rê chuột qua menu, người dùng thấy như vừa làm hỏng gì — nên dùng `rose`, ngả hồng hơn, mềm hơn một bậc. Cùng tinh thần `I4`: nút xoá không hét vào mặt người dùng.
587
+
588
+ **Phép thử khi phân vân:** *người dùng đã làm sai gì chưa?* Rồi → `red`. Chưa,
589
+ chỉ đang sắp bấm → `rose`.
590
+
591
+ ### Bậc dùng — đừng tự chế
592
+
593
+ Mỗi ô ghi class Tailwind, token CSS trong ngoặc. Hai cách ra cùng một màu
594
+ (`tokens.css`), dự án không có Tailwind thì dùng token.
595
+
596
+ | Việc | Lỗi (`red`) | Nguy hiểm (`rose`) |
597
+ | --- | --- | --- |
598
+ | Chữ, icon | câu lỗi dưới ô: `text-red-600` (`--error-text`) — `red-500` trên nền trắng chỉ 3.8:1, trượt 4.5:1. Dấu `*` trường bắt buộc cũng sắc này (ngoại lệ `M9`) | `text-rose-700` (`--danger`) (nút luôn hiện, mục menu lúc rê vào) — `rose-500` trên nền mờ chỉ 3.2:1 |
599
+ | Viền | `border-red-500` (`--error`) | — *(không có viền đỏ)* |
600
+ | Nền mờ | `ring-red-500/10` (`--error-ring`) quanh ô nhập lỗi **đang focus** | nút: `bg-rose-500/10` (`--danger-bg`), rê vào `/15` (`--danger-bg-hover`) · mục menu: `hover:bg-rose-500/10` |
601
+ | Banner | `bg-red-50` (`--error-bg`) · `border-red-200` (`--error-border`) · tiêu đề `red-700` (`--error-strong`), mô tả `text-foreground/80` (`components/banner.md`) | — *(không có banner)* |
602
+
603
+ Ô "—" là **cố ý trống**: hành động nguy hiểm không bao giờ có viền đỏ hay banner
604
+ đỏ. Thấy mình định viết `border-rose-*` là đang biến lời nhắc thành cảnh báo.
605
+
606
+ **Không có sắc thứ ba.** Không `pink`, không `orange-red`, không đỏ tuỳ chế
607
+ `#e53e3e` để **báo trạng thái**. Cần một kiểu "nhẹ hơn lỗi nhưng vẫn cần chú ý"
608
+ thì đó là **hổ phách** (`M4`), không phải một sắc đỏ mới.
609
+
610
+ **Phạm vi: `M30` chỉ áp cho màu mang NGHĨA.** Màu nhận diện — nền avatar chữ cái
611
+ đầu, icon workspace — không báo gì cả, nên không thuộc luật này. Cùng lý do với
612
+ ngoại lệ của `M12`: ở đó màu đóng vai ảnh, không đóng vai trạng thái. Nhưng để
613
+ không ai phải phân vân, bộ màu avatar **không dùng `red` hay `rose`** — xem
614
+ `references/components/avatar.md`.
615
+
616
+ ### Đổi thương hiệu
617
+
618
+ Thương hiệu có đỏ riêng thì sửa khối `--danger*` / `--error*` trong `tokens.css`,
619
+ và đổi **cả hai nhóm cùng lúc**, giữ khoảng cách giữa chúng: lỗi đậm và chuẩn
620
+ hơn, nguy hiểm mềm hơn. Đổi một nhóm mà quên nhóm kia thì hai việc lại trông như
621
+ một. Dự án dùng class Tailwind thì phải map lại trong `@theme` hoặc thay class,
622
+ sửa token thôi không đổi được `text-rose-700` (xem `tailwind-v4-traps.md`).
623
+ Đổi xong đo lại tương phản: chữ trên nền phải từ 4.5:1.