@haposoft/cafekit 0.16.7 → 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 (193) 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 +74 -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 +17 -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 +24 -7
  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/settings/settings.json +1 -32
  68. package/src/claude/skills/{question → ask}/SKILL.md +9 -75
  69. package/src/claude/skills/brainstorm/SKILL.md +43 -131
  70. package/src/claude/skills/code-review/SKILL.md +48 -17
  71. package/src/claude/skills/code-review/references/spec-compliance-review.md +2 -3
  72. package/src/claude/skills/code-review/references/verification-gate.md +22 -27
  73. package/src/claude/skills/debug/SKILL.md +29 -149
  74. package/src/claude/skills/develop/SKILL.md +27 -36
  75. package/src/claude/skills/develop/references/parallel-waves.md +0 -8
  76. package/src/claude/skills/develop/references/quality-gate.md +15 -13
  77. package/src/claude/skills/docs/SKILL.md +2 -2
  78. package/src/claude/skills/docs/references/update-workflow.md +1 -1
  79. package/src/claude/skills/fix/SKILL.md +153 -0
  80. package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
  81. package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
  82. package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
  83. package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
  84. package/src/claude/skills/git/SKILL.md +48 -9
  85. package/src/claude/skills/git/references/commit-protocols.md +35 -16
  86. package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
  87. package/src/claude/skills/orca/SKILL.md +9 -8
  88. package/src/claude/skills/research/SKILL.md +2 -0
  89. package/src/claude/skills/scout/SKILL.md +61 -0
  90. package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
  91. package/src/claude/skills/specs/SKILL.md +22 -29
  92. package/src/claude/skills/specs/references/review.md +8 -6
  93. package/src/claude/skills/specs/references/templates.md +34 -41
  94. package/src/claude/skills/sync/SKILL.md +8 -15
  95. package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
  96. package/src/claude/skills/sync/references/sync-protocols.md +4 -13
  97. package/src/claude/skills/test/SKILL.md +22 -3
  98. package/src/claude/skills/test/references/execution-strategy.md +83 -0
  99. package/src/claude/skills/test/references/failure-triage.md +1 -1
  100. package/src/claude/skills/ui-ux/LICENSE +21 -0
  101. package/src/claude/skills/ui-ux/SKILL.md +497 -0
  102. package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
  103. package/src/claude/skills/ui-ux/references/budgets.md +113 -0
  104. package/src/claude/skills/ui-ux/references/checklist.md +271 -0
  105. package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
  106. package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
  107. package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
  108. package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
  109. package/src/claude/skills/ui-ux/references/components/button.md +142 -0
  110. package/src/claude/skills/ui-ux/references/components/card.md +113 -0
  111. package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
  112. package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
  113. package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
  114. package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
  115. package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
  116. package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
  117. package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
  118. package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
  119. package/src/claude/skills/ui-ux/references/components/input.md +161 -0
  120. package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
  121. package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
  122. package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
  123. package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
  124. package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
  125. package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
  126. package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
  127. package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
  128. package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
  129. package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
  130. package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
  131. package/src/claude/skills/ui-ux/references/design-process.md +650 -0
  132. package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
  133. package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
  134. package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
  135. package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
  136. package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
  137. package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
  138. package/src/claude/skills/ui-ux/references/principles.md +408 -0
  139. package/src/claude/skills/ui-ux/references/refactor.md +245 -0
  140. package/src/claude/skills/ui-ux/references/responsive.md +195 -0
  141. package/src/claude/skills/ui-ux/references/review.md +528 -0
  142. package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
  143. package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
  144. package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
  145. package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
  146. package/src/claude/skills/ui-ux/references/styles.md +311 -0
  147. package/src/claude/skills/ui-ux/references/system.md +219 -0
  148. package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
  149. package/src/claude/skills/ui-ux/references/tokens.css +362 -0
  150. package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
  151. package/src/claude/skills/ui-ux/scripts/package.json +9 -0
  152. package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
  153. package/src/claude/skills/ui-ux-pro-max/SKILL.md +1 -1
  154. package/src/claude/status.cjs +149 -144
  155. package/src/codex/AGENTS.md +8 -13
  156. package/src/codex/hooks/agent.cjs +2 -1
  157. package/src/codex/hooks/lib/spec-receipt.cjs +2 -2
  158. package/src/codex/hooks/lib/spec-utils.cjs +0 -7
  159. package/src/codex/hooks/rules.cjs +2 -1
  160. package/src/codex/hooks/session.cjs +0 -2
  161. package/src/codex/hooks/spec-gate.cjs +7 -18
  162. package/src/codex/hooks.json +0 -20
  163. package/src/codex/rules/state-sync.md +3 -12
  164. package/src/common/AGENTS.md +2 -2
  165. package/src/omp/extensions/cafekit-bridge.mjs +6 -6
  166. package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
  167. package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
  168. package/src/claude/hooks/completion-authority-check.cjs +0 -56
  169. package/src/claude/hooks/completion-authority-state.cjs +0 -527
  170. package/src/claude/hooks/completion-authority.cjs +0 -163
  171. package/src/claude/hooks/semantic-review-authority.cjs +0 -154
  172. package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
  173. package/src/claude/hooks/usage.cjs +0 -197
  174. package/src/claude/skills/delegate/SKILL.md +0 -124
  175. package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
  176. package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
  177. package/src/claude/skills/hotfix/SKILL.md +0 -289
  178. package/src/claude/skills/inspect/SKILL.md +0 -142
  179. package/src/claude/skills/loop/SKILL.md +0 -114
  180. package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
  181. package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
  182. package/src/claude/skills/route/SKILL.md +0 -94
  183. package/src/claude/skills/route/references/agent-timing.md +0 -46
  184. package/src/claude/skills/route/references/chaining-patterns.md +0 -53
  185. package/src/claude/skills/route/references/task-taxonomy.md +0 -48
  186. package/src/codex/hooks/completion-authority-check.cjs +0 -67
  187. package/src/codex/hooks/completion-authority-state.cjs +0 -527
  188. package/src/codex/hooks/completion-authority.cjs +0 -109
  189. package/src/codex/hooks/semantic-review-authority.cjs +0 -130
  190. package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
  191. /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
  192. /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
  193. /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
@@ -0,0 +1,237 @@
1
+ # Bẫy Tailwind v4 — luật W
2
+
3
+ Mở file này khi dự án dùng **Tailwind v4** và **có sẵn CSS cũ**. Đây là loại lỗi
4
+ không báo build, không cảnh báo, không gạch đỏ trong IDE — và không grep ra được
5
+ nếu không biết trước phải grep cái gì.
6
+
7
+ ---
8
+
9
+ ## W1. Tailwind v4 phát TOÀN BỘ theme mặc định vào `:root`
10
+
11
+ **Không phải** chỉ khi bro dùng utility. CSS build ra đã có sẵn khoảng 140 biến
12
+ `--color-*`, `--spacing`, `--blur-*`, **và `--text-*`, `--shadow-*`**.
13
+
14
+ Nếu CSS cũ của dự án dùng **trùng tên biến** — rất thường gặp, `--text-sm` là tên
15
+ ai cũng đặt — thì **hai hệ tranh nhau một không gian tên**.
16
+
17
+ Hệ quả: xoá một override trong `@theme` không chỉ đổi code mới, mà đổi luôn **mọi
18
+ chỗ CSS cũ đang đọc biến đó**. Ở một dự án thật con số là **3.419 chỗ**.
19
+
20
+ **Luật: trước khi thêm hoặc bớt bất kỳ khai báo nào trong `@theme`, đếm xem có
21
+ bao nhiêu chỗ đang đọc tên biến đó.**
22
+
23
+ ```bash
24
+ for v in text-xs text-sm text-base text-lg text-xl; do
25
+ echo "$v: $(grep -rho "var(--$v)" --include='*.css' --include='*.tsx' . | wc -l)"
26
+ done
27
+ ```
28
+
29
+ ---
30
+
31
+ ## W2. `@theme` không tự trỏ vào chính nó được
32
+
33
+ ```css
34
+ /* SAI — @theme cũng phát ra --text-sm, thành vòng lặp */
35
+ @theme { --text-sm: var(--text-sm); }
36
+
37
+ /* ĐÚNG — tên trùng thì ghi GIÁ TRỊ THẬT */
38
+ @theme { --text-sm: 15px; }
39
+
40
+ /* ĐÚNG — tên khác thì trỏ thoải mái */
41
+ @theme { --color-brand: var(--brand-green); }
42
+ ```
43
+
44
+ **Hệ quả thiết kế:** với nhóm tên trùng, `@theme` **phải** là nơi định nghĩa duy
45
+ nhất. Gỡ khối đó khỏi file token cũ, không để hai nơi — hai nguồn thì lệch nhau
46
+ lúc nào không hay.
47
+
48
+ ---
49
+
50
+ ## W3. Biến CSS cũ trùng namespace Tailwind = utility hỏng câm
51
+
52
+ Nối tiếp `W2`, nhưng nguy hiểm hơn vì **không hề báo lỗi**.
53
+
54
+ Ở một dự án thật, file token cũ có:
55
+
56
+ ```css
57
+ :root { --container-md: 720px; --container-lg: 1040px; --container-xl: 1200px; }
58
+ ```
59
+
60
+ `--container-*` đúng là namespace Tailwind v4 dùng cho thang `max-w-*`. Token cũ
61
+ nạp trong `layer(base)`, `@theme` phát vào layer `theme` — **base thắng theme**.
62
+ Kết quả: **mọi `max-w-md/lg/xl` trong cả dự án chạy sai giá trị**, không ai biết:
63
+
64
+ | Class | Đáng ra | Thực tế |
65
+ | --- | ---: | ---: |
66
+ | `max-w-md` | 448px | 720px |
67
+ | `max-w-lg` | 512px | 1040px |
68
+ | `max-w-xl` | 576px | **1200px** |
69
+
70
+ Chỉ lộ ra khi chủ dự án nhìn ảnh chụp và hỏi *"sao ô nhập không ngắn lại?"* —
71
+ code có `max-w-xl`, DOM có `max-w-xl`, mà ô vẫn full width.
72
+
73
+ Kết cục: xoá luôn 3 token + 3 class đi kèm, vì grep ra **0 chỗ dùng** trong toàn
74
+ bộ dự án. **Code chết mà vẫn kịp phá cả thang `max-w-*`.**
75
+
76
+ ### Cách phát hiện
77
+
78
+ Grep file CSS **đã build**, tìm biến khai báo hai lần:
79
+
80
+ ```bash
81
+ curl -s "<url-css-bundle>" | grep -o -- "--container-xl:[^;]*;"
82
+ # ra 2 dòng = có kẻ đè
83
+ ```
84
+
85
+ ### Danh sách namespace phải né khi đặt tên biến trong CSS cũ
86
+
87
+ ```
88
+ --color-* --font-* --text-* --spacing-* --breakpoint-* --container-*
89
+ --radius-* --shadow-* --tracking-* --leading-* --ease-* --animate-*
90
+ --blur-* --aspect-*
91
+ ```
92
+
93
+ ⚠️ Phân biệt hai ca:
94
+
95
+ - Trùng tên **đã có sẵn** của Tailwind (`--container-xl`) → **đè, hỏng câm**.
96
+ - Chỉ **thêm** tên mới trong namespace đó (`--font-heading`, `--shadow-focus`) → vô hại, nó chỉ sinh thêm utility mới.
97
+
98
+ Quét một lần và phân loại từng cái, đừng đổi tên hàng loạt.
99
+
100
+ ---
101
+
102
+ ## W4. Thiếu class màu viền thì Tailwind v4 để `border-color: currentColor`
103
+
104
+ Nút chữ đen mà quên khai màu viền sẽ ra **viền gần đen**, không phải viền nhạt.
105
+
106
+ Thấy một cái viền đậm bất thường thì kiểm chỗ này trước, đừng đi sửa mã hex của
107
+ token viền.
108
+
109
+ ---
110
+
111
+ ## W5. Radius phải nằm đúng trên bậc của thang đang chạy
112
+
113
+ Nếu không ghi đè `--radius-*` thì `rounded-*` chạy thang gốc: 4 / 6 / 8 / 12 / 16
114
+ / 24. Token bo góc của dự án **phải nằm đúng trên các bậc đó**, nếu không thì mỗi
115
+ khối refactor sang utility lại đổi hình một chút mà không ai chủ ý.
116
+
117
+ Ở một dự án thật đã phải sửa: `9px → 8px`, thêm bậc `4px`, và **xoá bậc `22px`** vì
118
+ không bậc Tailwind nào bằng.
119
+
120
+ Cùng đợt: mọi `border-radius` chôn cứng trong CSS cũ quy về token — không còn
121
+ 1 / 3 / 5 / 7 / 9 / 10 / 14 / 18 / 20 / 22px rải rác.
122
+
123
+ ---
124
+
125
+ ## W6. iOS Safari tự phóng to ô nhập có `font-size` dưới 16px
126
+
127
+ Và **không bao giờ thu lại**. Ép 16px ở màn hẹp để chặn:
128
+
129
+ ```css
130
+ @media (max-width: 768px) {
131
+ input, textarea, select { font-size: 16px !important; }
132
+ }
133
+ ```
134
+
135
+ `!important` ở đây là cần thật: nó phải thắng được cả `style={{ fontSize }}` inline
136
+ rải trong các composer và trình soạn thảo. Đây là một trong số rất ít chỗ
137
+ `!important` có lý do chính đáng — và lý do đó phải được ghi ngay trên nó.
138
+
139
+ ## W7. Tailwind v4 cho `<button>` con trỏ mũi tên, không phải bàn tay
140
+
141
+ v3 để `<button>` hiện bàn tay. **v4 đổi preflight về `cursor: default`**, theo
142
+ đúng hành vi gốc của trình duyệt. `<a href>` thì vẫn là bàn tay.
143
+
144
+ Nên trong **cùng một menu**, mục là `<a>` hiện bàn tay còn mục là `<button>`
145
+ (ví dụ "Đăng xuất") hiện mũi tên. Người dùng rê chuột dọc menu thấy con trỏ đổi
146
+ qua đổi lại, trong khi code trông không sai chỗ nào.
147
+
148
+ Hai cách, chọn một cho cả dự án:
149
+
150
+ ```css
151
+ /* Cách 1: trả lại hành vi v3 cho toàn app — một chỗ, không sót */
152
+ @layer base {
153
+ button:not(:disabled),
154
+ [role="button"]:not([aria-disabled="true"]) {
155
+ cursor: pointer;
156
+ }
157
+ }
158
+ ```
159
+
160
+ ```html
161
+ <!-- Cách 2: ghi tường minh trên từng nút -->
162
+ <button class="cursor-pointer">…</button>
163
+ ```
164
+
165
+ **Cách 1 an toàn hơn khi refactor**, vì không phải đi sót từng nút. Cách 2 hợp
166
+ khi dự án đã quen ghi `cursor-pointer` khắp nơi. Đừng trộn hai cách: nửa dự án dựa
167
+ vào base, nửa ghi tay, thì lúc bỏ một trong hai sẽ không biết chỗ nào còn phụ
168
+ thuộc.
169
+
170
+ **Cách phát hiện:** trong `package.json` có `"tailwindcss": "^4`, và grep ra
171
+ `<button` không kèm `cursor-pointer` mà trong CSS base cũng không có dòng
172
+ `cursor: pointer` nào.
173
+
174
+ ---
175
+
176
+ ## W8. `ring-*` chỉ có một lớp bóng
177
+
178
+ `ring-1`, `ring-2`, `ring-inset` cùng ghi vào **một** lớp bóng (`--tw-ring-shadow`). Skill
179
+ không vẽ vòng focus (`I13`) nên thường không đụng nhau. Dự án trả vòng focus lại (`I14`) thì
180
+ viền trạng thái (đang chọn, đang bật) vẽ bằng `inset-ring-*` (lớp bóng riêng của v4) hoặc
181
+ `border`, không `ring-*`; `ring-*` để dành cho vòng focus của `I14`. Viền chọn bằng `ring-*` thì
182
+ Tab tới là vòng focus **thay** viền chọn, không cộng vào.
183
+
184
+ ## W9. Dự án không nạp preflight thì control nào cũng giữ kiểu của trình duyệt ⚑
185
+
186
+ Preflight là phần reset của Tailwind, đi kèm `@import "tailwindcss"`. Có dự án chỉ nạp
187
+ `tailwindcss/theme.css` và `tailwindcss/utilities.css` (thường để khỏi đè CSS cũ). Khi
188
+ đó ô nhập vẫn viền 2px inset, nút viền nổi nền xám, `<select>` là ô chọn gốc của trình
189
+ duyệt, `ul` có chấm, `h1`, `p` có margin mặc định.
190
+
191
+ **Cách phát hiện** (chạy ở audit, `SKILL.md` câu 2):
192
+
193
+ ```bash
194
+ grep -rn '@import "tailwindcss' --include='*.css' . 2>/dev/null | grep -v node_modules
195
+ # chỉ ra theme.css / utilities.css, không có dòng `@import "tailwindcss";` hay preflight.css = không có reset
196
+ ```
197
+
198
+ **Làm gì:**
199
+
200
+ - Ghi vào dòng `Audit:` "không có preflight".
201
+ - **Không tự bật preflight cho cả app**: nó đổi hình mọi trang cùng lúc, và dự án tắt
202
+ nó là có lý do.
203
+ - Mỗi control mình dựng hay sửa **tự reset đủ**: ô nhập, textarea có viền token (hoặc
204
+ `border-0` nếu khung ngoài đã có viền), nền, `[font:inherit]`; nút `border-0` và nền
205
+ của nó; select `appearance-none` cộng chevron của mình. Thiếu một cái là chính nó mang
206
+ kiểu trình duyệt, giữa một trang đã có kiểu.
207
+ - Probe báo "Control còn kiểu mặc định của trình duyệt" (viền inset / outset, viền xám
208
+ `#767676`, select `appearance: auto`).
209
+
210
+ Đừng chỉ reset nút và danh sách: ô nhập bị quên (như ô của khung chat) mang nguyên viền đen
211
+ của trình duyệt.
212
+
213
+ ## W10. `scale-*`, `translate-*`, `rotate-*` không chạy theo `transition-[transform]` ⚑
214
+
215
+ Tailwind v4 ghi `scale-95`, `-translate-y-1`, `rotate-180` vào thuộc tính CSS **riêng**
216
+ `scale`, `translate`, `rotate`, không vào `transform`. Viết `transition-[opacity,transform]`
217
+ hay `transition-[transform,color]` thì ba thuộc tính đó **nhảy thẳng**, chỉ `opacity` và màu
218
+ chạy: menu co lại 95% và nhích lên 4px ngay khung đầu rồi mới mờ, nhìn như giật; mũi tên
219
+ accordion lật ngược tức thì.
220
+
221
+ - Dùng `transition-transform` (v4 gồm `transform, translate, scale, rotate`), hoặc ghi đúng tên:
222
+ `transition-[opacity,scale,translate]`, `transition-[rotate,color]`.
223
+ - Probe báo mục "Scale / translate / rotate không chạy chuyển động" (hạng Hỏng).
224
+
225
+
226
+ ## W11. `not-sr-only` trả `white-space` về `normal`, nuốt `whitespace-nowrap` cùng thẻ
227
+
228
+ `not-sr-only` không chỉ gỡ `sr-only`: nó ghi lại `position`, `width`, `height`, `padding`,
229
+ `margin`, `overflow`, `clip-path` **và `white-space: normal`**. Viết
230
+ `sr-only whitespace-nowrap @4xl:not-sr-only` thì từ `@4xl` utility có variant đứng sau trong
231
+ CSS, `normal` thắng, chữ lại xuống dòng. Đặt `nowrap` ở ô cha cũng vô ích: thẻ con đã khai
232
+ `normal` thì không kế thừa nữa (cột người phụ trách co còn 126px, "Nguyễn /
233
+ Anh / Tuấn" ba dòng, dòng bảng cao thấp lởm chởm).
234
+
235
+ - Ẩn theo chiều ngược lại: `whitespace-nowrap @max-4xl:sr-only` (bề rộng nào không ẩn thì không
236
+ cần `not-sr-only`). Cùng lý do, padding / margin khai trên thẻ đó cũng bị `not-sr-only` xoá.
237
+ - Probe báo mục "Cột chữ của bảng bị ép xuống dòng".
@@ -0,0 +1,362 @@
1
+ /* Bộ token khởi đầu. Đặt trong globals.css hoặc index.css.
2
+
3
+ ĐỔI THƯƠNG HIỆU: màu nhấn có HAI chỗ phải sửa, font chỉ có MỘT dòng.
4
+ Ngoài các khối đánh dấu bên dưới, không rải mã màu ở bất kỳ đâu.
5
+
6
+ `.force-light` / `.force-dark`: vùng khoá theme (trang chia sẻ luôn sáng, khối hero luôn tối).
7
+ Đặt class lên khung ngoài cùng của vùng, mọi component con đọc lại bộ token đó. Luật M33. */
8
+
9
+ :root,
10
+ .force-light {
11
+ /* Thanh cuộn gốc, ô chọn ngày, autofill, <select> của trình duyệt vẽ theo theme. Luật M31. */
12
+ color-scheme: light;
13
+
14
+ /* ===== FONT, KHAI MỘT LẦN, DÙNG CHUNG CHO CẢ SÁNG LẪN TỐI =====
15
+ Một font cho cả app. Phân vai bằng weight, không bằng font thứ hai:
16
+ heading 600, body 400, nhãn phụ 500. tracking-tight chỉ cho chữ có dấu từ
17
+ text-3xl trở lên, xem T2 trong rules-type.md. */
18
+ --app-font: "Inter", ui-sans-serif, system-ui, sans-serif; /* Tailwind đọc qua --font-sans, khối @theme bên dưới */
19
+ /* ============================================================== */
20
+
21
+ /* ===== MÀU NHẤN, CHỖ 1 TRONG 2: NỀN SÁNG ===== */
22
+ --primary: #181818; /* mặc định gần đen. Màu riêng thì thay vào đây */
23
+ --primary-hover: #2e2e2e; /* lệch về phía NỀN một bậc (1.31:1 với --primary): nền sáng thì nhạt đi */
24
+ --primary-foreground: #ffffff; /* màu chữ ĐỨNG TRÊN nền nhấn. Bắt buộc phải
25
+ có, vì ở nền tối màu nhấn thành gần trắng,
26
+ lúc đó viết cứng `text-white` là mất chữ. */
27
+ --primary-light: #1818180d; /* CHỈ dùng khi --primary có sắc.
28
+ Màu nhấn trung tính như mặc định thì biến này
29
+ ra xám bẩn gần như vô hình, badge phải dùng
30
+ --background làm nền và --foreground làm chữ. */
31
+ /* ============================================= */
32
+
33
+ --background: #f4f4f6; /* nền trang, xám nhạt, không bao giờ trắng */
34
+ --surface: #ffffff; /* nền card, input, header */
35
+ --foreground: #2c2c2c; /* chữ chính */
36
+ --muted: #707070; /* chữ phụ, label, placeholder, icon phụ. Xám trung tính
37
+ nhạt nhất còn qua WCAG AA 4.5 trên cả hai nền: 4.95:1
38
+ trên --surface, 4.51:1 trên --background */
39
+ /* Đường tóc trang trí: viền card, viền khung dropdown, đường chia trong danh
40
+ sách và trong menu. Nhạt đến mức chỉ đủ vạch ranh giới. Xem luật M13, M14. */
41
+ --border: #f7f7f8;
42
+ /* Đậm hơn ĐÚNG một bậc. Cho viền Ô NHẬP và NÚT OUTLINE (viền là thứ duy nhất
43
+ báo "đây là chỗ gõ / chỗ bấm", vì cả hai cùng nền trắng với card), đường kẻ
44
+ khung app và viền card khi hover. Đây là token viền thứ hai và cũng là token
45
+ cuối, xem luật M14, M16. ~1.2:1 với nền trắng: đánh đổi thẩm mỹ có chủ ý,
46
+ xem P3 trong styles.md. */
47
+ --border-strong: #eaeaea; /* #f2f2f2 (1.1:1) mờ quá, radio gần như vô hình;
48
+ #e4e4e7 (1.27:1) thì đường kẻ sidebar đậm. Chốt ở giữa. */
49
+
50
+ /* Lớp nổi nhỏ: dropdown, popover, menu, select, toast, command palette. Nền sáng bằng card;
51
+ nền tối sáng hơn card một bậc (M21). Đi cùng `border border-border` + `shadow-popover`.
52
+ Modal, panel trượt giữ --surface (+ viền + `shadow-modal`): bên trong có header dính, tab
53
+ dính, card trắng trên thân --background, đều ăn theo --surface; đổi nền khung là lệch từng mảnh. */
54
+ --surface-overlay: #ffffff;
55
+ /* Bóng lớp nổi, hai bậc: popover (dropdown, menu, toast) < modal. Bằng shadow-lg / shadow-xl
56
+ của Tailwind ở nền sáng; nền tối đậm hơn, xem khối .dark (M15, M23). Class `shadow-popover`,
57
+ `shadow-modal`. Không đặt tên --shadow-*: trùng namespace Tailwind (W3). */
58
+ --elevation-popover: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
59
+ --elevation-modal: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
60
+
61
+ /* Vùng tô (nút phụ, mục đang chọn, nền rê) là LỚP PHỦ trên nền phía sau: nền sáng tối đi,
62
+ nền tối sáng lên. Thứ giữ qua hai theme là độ chênh với nền, không phải chiều (M21). */
63
+ /* Vùng tô nhạt nhất. Nền rê của mục thụt vào: mục menu, link sidebar, nút cây thư mục, dòng
64
+ danh sách trong widget (I10); mục đang sáng trong select / bảng lệnh; dải giữa khoảng ngày, dải
65
+ chọn giờ (components/choice-controls.md). Nền sáng bằng đúng --background; nền tối là trắng phủ mờ, vì --background
66
+ tối hơn card nên rê vào sẽ chìm xuống gần như không thấy (1.07:1). */
67
+ --item-hover: #f4f4f6;
68
+
69
+ /* Mảng dữ liệu của biểu đồ (cột, vùng tô, phần donut). Nền sáng bằng --primary; nền tối hạ
70
+ còn 70%, xem khối .dark. Thang nhiều chuỗi viết `bg-chart-fill/45`… (components/charts.md).
71
+ Thanh mảnh (thanh tiến độ) vẫn `bg-primary`. */
72
+ --chart-fill: var(--primary);
73
+
74
+ /* Bề mặt của nút phụ, mục sidebar / tab đang chọn. Đậm hơn --item-hover một bậc. */
75
+ --secondary: #e7e8ec;
76
+ --secondary-hover: #dbdce2;
77
+
78
+ /* Nền của ô trạng thái nhỏ nằm trong hàng có hover: ô ngày, ô vuông chuỗi, ô
79
+ rỗng. Đừng dùng --background cho chúng, vì hàng hover cũng về --item-hover (= --background) thì
80
+ con tan vào nền. Không phải hover của nút phụ (--secondary-hover) hay của dòng
81
+ (--background, --surface-hover, luật I10). Xem luật M18. */
82
+ --background-hover: #e7e7ea;
83
+
84
+ /* Hover và dòng đang chọn của BẢNG / danh sách tràn hết bề ngang khung trắng.
85
+ Không dùng --background ở đây: dòng chạm hai mép khung mà tô đúng màu nền
86
+ trang thì trông như khung bị khoét một lỗ. Chỉ nhỉnh
87
+ hơn --surface một chút. Xem luật I10. */
88
+ --surface-hover: #f8f8fa;
89
+
90
+ /* Hover của nút viền (outline): CHỈ đổi nền, viền giữ nguyên (chủ dự án chốt).
91
+ Màu ĐẶC, và **theo nền ngay sau nút**, không một màu cho mọi chỗ:
92
+ - trên card trắng (--button-hover-surface #f1f1f3): thấy rõ, viền vẫn còn;
93
+ - thẳng trên nền trang, hoặc trong dòng đang rê (dòng chìm về #f4f4f6)
94
+ (--button-hover-page #e4e4e7): #f1f1f3 ở đây chỉ lệch nền 3 mức, nút như tan vào trang.
95
+ Biến đổi tự động bằng khối "NỀN RÊ CỦA NÚT VIỀN" bên dưới, class vẫn là
96
+ `hover:bg-button-hover`. Không dùng --surface-hover (#f8f8fa: trên card gần như không đổi),
97
+ không dùng lớp phủ foreground/5 (trong suốt, trên nền trang ra #e9e9eb trùng viền). */
98
+ --button-hover-surface: #f1f1f3;
99
+ --button-hover-page: #e4e4e7; /* đậm hơn viền #eaeaea 6 mức: #e7e7ea chỉ 3 mức, nút thành mảng không viền */
100
+ --button-hover: var(--button-hover-page);
101
+
102
+ /* Tab segmented dạng phím nổi: rãnh CHÌM (bóng trong), ô đang chọn NỔI (bóng
103
+ ngoài rất mờ + viền tóc). Ngoại lệ có tên của luật M15, chỉ dùng ở đây.
104
+ Xem "Thanh tab" trong components/small-controls.md. */
105
+ --shadow-segment-track: inset 0 0 0 1px rgb(0 0 0 / 0.05), inset 0 1px 2px rgb(0 0 0 / 0.04);
106
+ --shadow-segment-thumb: 0 0 0 1px rgb(0 0 0 / 0.04), 0 1px 2px rgb(0 0 0 / 0.06), 0 2px 6px -2px rgb(0 0 0 / 0.08);
107
+
108
+ /* Ring rất mờ theo màu nhấn, dày 2px (ring-2). Cho ô điền lúc focus (input, textarea,
109
+ select) và ô chọn dạng card đang chọn, đi cùng viền --border-focus. Nút,
110
+ menu, tab KHÔNG dùng, và không có vòng focus. Luật I13. */
111
+ --ring-focus: rgba(24, 24, 24, 0.1);
112
+
113
+ /* Viền lúc focus. Ở nền sáng thì chính là màu nhấn. Ở nền tối thì KHÔNG
114
+ dùng màu nhấn đặc, xem khối .dark bên dưới. */
115
+ --border-focus: var(--primary);
116
+
117
+ /* ===== MÀU MANG NGHĨA: nguy hiểm, lỗi, trạng thái. Luật M7, M30, I4. =====
118
+ Có Tailwind thì cứ viết class (`text-rose-700`...), mã ở đây là CHÍNH các
119
+ class đó của Tailwind v4 đổi ra hex, nên hai cách cho ra cùng một màu.
120
+ Không có Tailwind thì dùng var(--danger)... Bảng đối chiếu class ↔ token nằm
121
+ ngay trong bảng M7 và M30 của rules-color.md. Không thêm sắc thứ ba (M30). */
122
+
123
+ /* Nguy hiểm — xoá, huỷ tài khoản, đăng xuất: lời nhắc TRƯỚC khi bấm. Không viền, không banner. */
124
+ --danger: #c70036; /* rose-700: chữ + icon. 5.2:1 trên --danger-bg */
125
+ --danger-bg: rgb(255 32 86 / 0.1); /* rose-500/10 */
126
+ --danger-bg-hover: rgb(255 32 86 / 0.15);/* rose-500/15, chỉ cho nút đứng riêng */
127
+
128
+ /* Lỗi — chuyện ĐÃ xảy ra: ô nhập sai, câu lỗi, banner, badge "Đã huỷ". */
129
+ --error: #fb2c36; /* red-500: viền ô nhập. KHÔNG cho chữ, 3.8:1 */
130
+ --error-ring: rgb(251 44 54 / 0.1); /* red-500/10 quanh ô nhập */
131
+ --error-text: #e7000b; /* red-600: câu lỗi dưới ô, trên nền trắng 4.8:1 */
132
+ --error-bg: #fef2f2; /* red-50: nền banner, nền badge đỏ */
133
+ --error-border: #ffc9c9; /* red-200: viền banner */
134
+ --error-strong: #c10007; /* red-700: chữ trên --error-bg, 5.9:1 */
135
+
136
+ /* Badge trạng thái (M7): nền nhạt + chữ -700 cùng sắc. Tông đỏ lấy cặp
137
+ --error-bg / --error-strong ở trên. */
138
+ --success-bg: #ecfdf5; /* emerald-50 */
139
+ --success: #007a55; /* emerald-700, 5.1:1 */
140
+ --warning-bg: #fffbeb; /* amber-50 */
141
+ --warning: #bb4d00; /* amber-700, 4.9:1 */
142
+ --neutral-bg: #f4f4f5; /* zinc-100: chờ, nháp, trung lập */
143
+ --neutral: #52525c; /* zinc-600, 7.0:1 */
144
+ /* ======================================================================= */
145
+
146
+ /* Bo mặc định 12px = rounded-xl, cho mọi phần tử cao từ 40px. Luật F1. */
147
+ --radius: 0.75rem;
148
+ }
149
+
150
+ /* Dark mode mặc định của skill là navy rất tối: đây là GU, không phải quy ước. Dự án đã có
151
+ bảng tối riêng (xám kẽm, xám trung tính) thì theo dự án (M23).
152
+ Viền là rgba mờ chứ không phải màu đặc, nếu không sẽ thành lưới kẻ ô.
153
+ Màu tối không phải màu sáng đảo ngược: mỗi token khai giá trị riêng.
154
+
155
+ Khối này chỉ cần khi sản phẩm thật sự có dark mode. Không làm thì xoá đi. */
156
+ .dark,
157
+ .force-dark {
158
+ color-scheme: dark;
159
+
160
+ /* ===== MÀU NHẤN, CHỖ 2 TRONG 2: NỀN TỐI =====
161
+ BẮT BUỘC khai lại. Màu nhấn của nền sáng đem nguyên sang nền tối là
162
+ tàng hình, và mọi nút nhấn sẽ phải đảo thành trắng, mất sạch thứ bậc.
163
+ Màu gần đen thì bản tối là gần trắng. Màu có sắc thì lấy bản sáng hơn
164
+ khoảng hai bậc của chính nó. */
165
+ --primary: #e9edf5;
166
+ /* Lệch về phía NỀN một bậc như bản sáng, tức TỐI đi (1.26:1 với --primary). Không sáng lên
167
+ #ffffff: trắng tuyệt đối là khối chói nhất màn tối, trái M23. Chữ trên nó 13.7:1. */
168
+ --primary-hover: #cfd5e0;
169
+ --primary-foreground: #05060f; /* nền nhấn giờ gần trắng, chữ phải đảo lại */
170
+ --primary-light: rgba(233, 237, 245, 0.1);
171
+ /* ============================================ */
172
+
173
+ --background: #05060f;
174
+ /* Cột biểu đồ màu nhấn gần trắng 100% là khối chói nhất màn tối, nặng hơn tên trang và hàng
175
+ số (chủ dự án chọn 70%). M32. */
176
+ --chart-fill: color-mix(in srgb, var(--primary) 70%, transparent);
177
+
178
+ /* Tầng nổi sáng dần: nền trang < card < lớp nổi, mỗi bậc ~1.07–1.09:1 (M21). */
179
+ --surface-overlay: #171a26;
180
+ /* Bóng đậm gấp ~5 bản sáng: bóng 10% trên nền gần đen là không thấy. Thứ thật sự tách lớp
181
+ nổi khỏi trang là viền 1px (`border border-border` của lớp nổi) + nền sáng hơn (M23). */
182
+ --elevation-popover: 0 10px 15px -3px rgb(0 0 0 / 0.5), 0 4px 6px -4px rgb(0 0 0 / 0.5);
183
+ --elevation-modal: 0 20px 25px -5px rgb(0 0 0 / 0.6), 0 8px 10px -6px rgb(0 0 0 / 0.6);
184
+
185
+ /* Vùng tô là trắng phủ mờ, SÁNG hơn card (M21). Phủ mờ nên đặt trên nền trang, card hay
186
+ lớp nổi đều giữ được độ chênh, không phải khai một màu cho từng nền. Độ chênh ngang bản
187
+ sáng (đo trên card #0f111a):
188
+ - --item-hover 5% → 1.12:1 (bản sáng #f4f4f6 trên trắng 1.10:1);
189
+ - --secondary 8% → 1.22:1 (bản sáng #e7e8ec trên trắng 1.22:1), đậm hơn rê một bậc.
190
+ Đừng dùng màu đặc tối hơn nền trang (#010207): gần như không tách khỏi nền. */
191
+ --item-hover: rgb(255 255 255 / 0.05);
192
+ --secondary: rgb(255 255 255 / 0.08);
193
+ /* Rê vào đậm thêm 5%: chênh 12 mức như bản sáng (#e7e8ec → #dbdce2). 0.11 chỉ chênh 7 mức, nút
194
+ "Xem tất cả", "Huỷ" rê vào gần như không đổi. */
195
+ --secondary-hover: rgb(255 255 255 / 0.13);
196
+
197
+ --background-hover: #171a26;
198
+ --surface-hover: #141722; /* sáng hơn --surface một chút, luật I10 */
199
+ /* Nền rê nút viền: phủ mờ, nên một giá trị đúng cho cả nền trang, card lẫn lớp nổi. Bản
200
+ sáng phải đặc vì phủ đen mờ trên nền trang ra trùng màu viền; ở nền tối viền là ánh xanh
201
+ mờ, không trùng. */
202
+ --button-hover-surface: rgb(255 255 255 / 0.08);
203
+ --button-hover-page: rgb(255 255 255 / 0.08);
204
+ /* Bóng đen khó thấy trên nền tối: ô nổi nhờ viền và vệt sáng mép trên. */
205
+ --shadow-segment-track: inset 0 0 0 1px var(--border), inset 0 1px 2px rgb(0 0 0 / 0.4);
206
+ --shadow-segment-thumb: 0 0 0 1px var(--border-strong), inset 0 1px 0 rgb(255 255 255 / 0.06), 0 1px 2px rgb(0 0 0 / 0.5);
207
+ --ring-focus: rgba(233, 237, 245, 0.12);
208
+
209
+ /* Màu nhấn ở nền tối là gần trắng. Dùng làm NỀN thì đẹp, dùng làm ĐƯỜNG MẢNH
210
+ thì thành sợi trắng đặc, cắt vào mắt. Viền focus lấy chính màu đó nhưng hạ
211
+ độ đục xuống, vừa đủ 3:1 (WCAG). Xem luật M22. */
212
+ --border-focus: rgba(233, 237, 245, 0.42); /* 3.6:1, WCAG 3:1 */
213
+ --surface: #0f111a;
214
+ --foreground: #e9edf5;
215
+ --muted: #8b93a7;
216
+ /* Ở nền tối viền ĐẢO VAI: #0f111a với #05060f chênh nhau quá ít nên viền
217
+ trở thành thứ chính để tách khối; bóng vẫn giữ nhưng chỉ phụ. Xem luật M23.
218
+ Hai vai như bản sáng (M14), đo trên card: --border 1.23:1, --border-strong
219
+ 1.33:1 (bản sáng 1.2:1). Ô nhập ở nền tối GIỮ viền --border-strong, không bỏ (M32).
220
+ Đừng lên 0.2 (1.47:1): đường kẻ dòng bảng đậm hơn hẳn bản sáng, bảng thành
221
+ lưới kẻ ô (chủ dự án duyệt 0.16); ô nhập, checkbox ở 0.16 vẫn đọc ra. */
222
+ --border: rgba(160, 180, 220, 0.12);
223
+ --border-strong: rgba(160, 180, 220, 0.16);
224
+
225
+ /* Màu mang nghĩa ở nền tối: chữ SÁNG lên (-400), nền là màu -500 phủ mờ.
226
+ Nền -50 của bản sáng đem sang đây là ra khối trắng giữa màn tối.
227
+ Tương phản đo trên --surface. */
228
+ --danger: #ff637e; /* rose-400, 6.2:1 */
229
+ --danger-bg: rgb(255 32 86 / 0.1);
230
+ --danger-bg-hover: rgb(255 32 86 / 0.15);
231
+
232
+ --error: #fb2c36;
233
+ --error-ring: rgb(251 44 54 / 0.15);
234
+ --error-text: #ff6467; /* red-400, 6.5:1 */
235
+ --error-bg: rgb(251 44 54 / 0.1);
236
+ --error-border: rgb(251 44 54 / 0.3);
237
+ --error-strong: #ff6467; /* red-400, 6.0:1 trên --error-bg */
238
+
239
+ --success-bg: rgb(0 188 125 / 0.15);
240
+ --success: #00d492; /* emerald-400, 7.8:1 */
241
+ --warning-bg: rgb(254 154 0 / 0.15);
242
+ /* orange-400, KHÔNG amber-400: thang amber trôi từ cam sang vàng khi sáng lên (OKLCH: amber-700 49°,
243
+ amber-400 84°), chữ "Quá hạn" cam ở bản sáng thành vàng ở bản tối.
244
+ orange-400 56°, lệch 7°. 7.9:1 trên card, 6.2:1 trên --warning-bg. M7. */
245
+ --warning: #ff8904;
246
+ --neutral-bg: rgb(113 113 123 / 0.15);
247
+ --neutral: #9f9fa9; /* zinc-400, 6.2:1 */
248
+ }
249
+
250
+ /* ===== MAP TOKEN SANG CLASS CỦA TAILWIND V4 =====
251
+ Thiếu khối này thì `bg-surface`, `text-muted`, `border-border-strong`, `bg-primary/8`…
252
+ không sinh ra class nào, màn chép từ skill mất màu mà không báo lỗi. Đặt sau
253
+ `@import "tailwindcss"`. `inline` để class đọc thẳng biến ở :root / .dark, đổi theme
254
+ là class đổi theo. Tên bên trái KHÁC tên bên phải, không trỏ vào chính nó (W2).
255
+ Không dùng Tailwind thì xoá khối này; Tailwind v3 thì khai cùng bộ tên trong
256
+ `theme.extend.colors` của tailwind.config. Màu mang nghĩa không map: viết thẳng
257
+ class `rose-*`, `red-*`… như khối màu mang nghĩa ở trên. */
258
+ @theme inline {
259
+ --font-sans: var(--app-font);
260
+
261
+ --color-primary: var(--primary);
262
+ --color-primary-hover: var(--primary-hover);
263
+ --color-primary-foreground: var(--primary-foreground);
264
+ --color-primary-light: var(--primary-light);
265
+
266
+ --color-background: var(--background);
267
+ --color-background-hover: var(--background-hover);
268
+ --color-surface: var(--surface);
269
+ --color-surface-hover: var(--surface-hover);
270
+ --color-surface-overlay: var(--surface-overlay);
271
+ --color-item-hover: var(--item-hover);
272
+ --color-foreground: var(--foreground);
273
+ --color-muted: var(--muted);
274
+ --color-border: var(--border);
275
+ --color-border-strong: var(--border-strong);
276
+ --color-chart-fill: var(--chart-fill);
277
+ --color-secondary: var(--secondary);
278
+ --color-secondary-hover: var(--secondary-hover);
279
+ --color-button-hover: var(--button-hover);
280
+
281
+ /* `border-focus` và `ring-focus` là hai màu khác nhau (viền đặc, ring 10%). Một
282
+ --color-focus thì hai class đọc chung một màu, nên tách theo namespace riêng
283
+ của từng loại: border-* đọc --border-color-* trước, ring-* đọc --ring-color-* trước. */
284
+ --border-color-focus: var(--border-focus);
285
+ --ring-color-focus: var(--ring-focus);
286
+
287
+ --shadow-popover: var(--elevation-popover);
288
+ --shadow-modal: var(--elevation-modal);
289
+ }
290
+
291
+ /* Tailwind v4 mặc định cho `dark:` chạy theo `prefers-color-scheme` của máy, KHÔNG theo class.
292
+ Token đổi theo `.dark` mà `dark:` đổi theo máy thì bấm nút Tối trên máy đang sáng ra nửa nọ
293
+ nửa kia. Dòng này cho `dark:` theo class, và không ăn vào vùng `.force-light` (M33).
294
+ `:where` giữ độ ưu tiên bằng 0, nên `dark:` không tự thắng class khác. Không Tailwind thì xoá. */
295
+ @custom-variant dark (&:where(.dark, .dark *, .force-dark, .force-dark *):not(:where(.force-light, .force-light *)));
296
+ /* ================================================= */
297
+
298
+ /* ===== NỀN RÊ CỦA NÚT VIỀN THEO NỀN PHÍA SAU =====
299
+ Mặc định (nền trang) là --button-hover-page. Mọi thứ nằm trong khối trắng lấy
300
+ --button-hover-surface; khối nền trang lồng trong card, và dòng đang rê chìm về nền
301
+ trang, lấy lại --button-hover-page. Chỉ đặt biến, không đặt màu, nên không đè utility.
302
+ Chọn con cháu (`*`), không đặt trên chính `.bg-surface`: nút viền cũng mang `bg-surface`,
303
+ đặt trên chính nó thì nút nào cũng thành bản của card.
304
+ Dòng dùng hover khác `hover:bg-item-hover` thì thêm selector của nó vào dòng thứ hai.
305
+ Nền tối hai biến cùng một giá trị phủ mờ, khối này không đổi gì. */
306
+ .bg-surface *,
307
+ .bg-surface-overlay * { --button-hover: var(--button-hover-surface); }
308
+ .bg-surface .bg-background *,
309
+ [class~="hover:bg-item-hover"]:hover * { --button-hover: var(--button-hover-page); }
310
+ /* ================================================= */
311
+
312
+ /* ===== THANH CUỘN TỰ ẨN, ÁP CHO CẢ APP. Luật I18. =====
313
+ Đứng yên thì trong suốt; rê chuột vào vùng cuộn thì hiện mờ; đang cuộn thì
314
+ đậm hơn (class .is-scrolling do component ScrollbarAutohide gắn vào ĐÚNG phần
315
+ tử đang cuộn). Ẩn bằng MÀU TRONG SUỐT chứ không bằng `scrollbar-width: none`:
316
+ bề rộng 4px vẫn giữ chỗ, nên lúc thanh hiện ra nội dung không bị đẩy ngang.
317
+ Màu lấy từ --foreground nên nền tối tự đảo, không cần khai lại.
318
+ Nguồn: dự án thật, chủ dự án chốt.
319
+
320
+ ⚠️ Màu thumb đi qua BIẾN --scrollbar-thumb đặt trên chính khung cuộn, không
321
+ viết `*:hover::-webkit-scrollbar-thumb`. Chrome không vẽ lại thumb khi selector
322
+ :hover nằm trước pseudo thanh cuộn: rê vào không hiện, phải cuộn mới hiện. Đổi
323
+ biến trên phần tử thì Chrome vẽ lại ngay. `* { --scrollbar-thumb: transparent }` đặt lại trên TỪNG phần tử, để
324
+ khung cha đang được rê không truyền biến xuống khung cuộn con bên cạnh.
325
+ KHÔNG thêm `transition` vào rule `*` này: CSS nằm ngoài @layer thắng mọi
326
+ utility của Tailwind v4, một dòng `* { transition }` là xoá hết `transition-*`
327
+ trong app. Thanh cuộn hiện tức thì là đủ. */
328
+ * { --scrollbar-thumb: transparent; }
329
+ *:hover { --scrollbar-thumb: color-mix(in srgb, var(--foreground) 16%, transparent); }
330
+ .is-scrolling { --scrollbar-thumb: color-mix(in srgb, var(--foreground) 28%, transparent); }
331
+ ::-webkit-scrollbar { width: 4px; height: 4px; }
332
+ ::-webkit-scrollbar-track { background: transparent; }
333
+ ::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 9999px; }
334
+ ::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--foreground) 40%, transparent); }
335
+
336
+ /* Firefox. ⚠️ PHẢI nằm trong @supports: từ Chrome 121, phần tử có
337
+ scrollbar-width/scrollbar-color là Chrome BỎ QUA toàn bộ ::-webkit-scrollbar
338
+ và vẽ thanh "thin" gốc ~8-11px chiếm chỗ. Để trần thì bản 4px ở trên chỉ còn
339
+ chạy trên Safari. */
340
+ @supports not selector(::-webkit-scrollbar) {
341
+ * { scrollbar-width: thin; scrollbar-color: transparent transparent; }
342
+ *:hover { scrollbar-color: color-mix(in srgb, var(--foreground) 18%, transparent) transparent; }
343
+ .is-scrolling { scrollbar-color: color-mix(in srgb, var(--foreground) 28%, transparent) transparent; }
344
+ }
345
+ /* ===================================================== */
346
+
347
+ /* Ẩn HẲN, không bao giờ hiện: chỉ cho hàng chip / tab cuộn ngang, nơi thanh
348
+ cuộn dưới đáy hàng trông như lỗi. Máy có chuột thì hàng đó phải có nút mũi
349
+ tên hai đầu (responsive.md, sau R10). Vùng cuộn dọc (sidebar, danh sách, modal)
350
+ thì KHÔNG dùng, để thanh tự ẩn ở trên lo. */
351
+ .scrollbar-clean {
352
+ scrollbar-width: none;
353
+ }
354
+ .scrollbar-clean::-webkit-scrollbar {
355
+ display: none;
356
+ }
357
+
358
+ /* Chữ mảnh và sạch hơn. Một dòng, đặt một lần cho cả trang. Xem luật T1. */
359
+ body {
360
+ -webkit-font-smoothing: antialiased;
361
+ -moz-osx-font-smoothing: grayscale;
362
+ }