@haposoft/cafekit 0.16.7 → 0.18.1
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.
- package/README.md +14 -22
- package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
- package/bin/__tests__/codex-hooks.test.js +169 -272
- package/bin/__tests__/codex-native.test.js +100 -202
- package/bin/__tests__/develop-contract.test.js +122 -1413
- package/bin/__tests__/docs-sync-on-demand.test.js +93 -0
- package/bin/__tests__/grok-envelope.test.js +0 -56
- package/bin/__tests__/hook-payload.test.js +4 -6
- package/bin/__tests__/legacy-authority-decoupled.test.js +154 -0
- package/bin/__tests__/legacy-hooks-retired.test.js +221 -0
- package/bin/__tests__/omp-bridge.test.js +20 -17
- package/bin/__tests__/omp-hooks.test.js +0 -19
- package/bin/__tests__/optional-skill-inventory.test.js +112 -4
- package/bin/__tests__/orca-session.test.js +0 -2
- package/bin/__tests__/package-inventory.test.js +90 -925
- package/bin/__tests__/runtime-dir.test.js +0 -3
- package/bin/__tests__/runtime-schema.test.js +3 -2
- package/bin/__tests__/skill-routing-source.test.js +27 -15
- package/bin/__tests__/spec-narrowing.test.js +30 -161
- package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -437
- package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
- package/bin/__tests__/ui-ux-skill.test.js +101 -0
- package/bin/__tests__/usage-hook-retired.test.js +114 -0
- package/bin/__tests__/validator-grounding.test.js +18 -2949
- package/bin/lib/managed-writer.js +2 -3
- package/bin/phases/codex-runtime.js +27 -0
- package/bin/phases/copy-payload.js +13 -28
- package/bin/phases/omp-runtime.js +25 -0
- package/package.json +2 -3
- package/src/claude/CLAUDE.md +3 -10
- package/src/claude/agents/brainstormer.md +7 -3
- package/src/claude/agents/code-auditor.md +44 -56
- package/src/claude/agents/debugger.md +8 -88
- package/src/claude/agents/deployer.md +3 -5
- package/src/claude/agents/docs-keeper.md +6 -14
- package/src/claude/agents/git-ops.md +2 -4
- package/src/claude/agents/implementer.md +12 -16
- package/src/claude/agents/inspector.md +4 -3
- package/src/claude/agents/project-manager.md +9 -17
- package/src/claude/agents/researcher.md +9 -3
- package/src/claude/agents/spec-maker.md +15 -15
- package/src/claude/agents/test-runner.md +8 -11
- package/src/claude/agents/ui-ux-designer.md +0 -1
- package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
- package/src/claude/hooks/__tests__/spec-gate.test.js +244 -333
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +160 -0
- package/src/claude/hooks/__tests__/state.test.js +68 -0
- package/src/claude/hooks/__tests__/statusline.test.js +140 -11
- package/src/claude/hooks/agent.cjs +2 -1
- package/src/claude/hooks/lib/hook-payload.cjs +2 -2
- package/src/claude/hooks/rules.cjs +2 -1
- package/src/claude/hooks/session.cjs +0 -1
- package/src/claude/hooks/spec-gate.cjs +17 -83
- package/src/claude/hooks/spec-state.cjs +142 -39
- package/src/claude/hooks/state.cjs +21 -2
- package/src/claude/migration-manifest.json +42 -22
- package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
- package/src/claude/output-styles/coding-level-1-junior.md +3 -3
- package/src/claude/output-styles/coding-level-2-mid.md +3 -3
- package/src/claude/output-styles/coding-level-3-senior.md +2 -2
- package/src/claude/output-styles/coding-level-4-lead.md +5 -5
- package/src/claude/output-styles/coding-level-5-god.md +6 -6
- package/src/claude/rules/ai-dev-rules.md +2 -3
- package/src/claude/rules/hook-protocols.md +8 -15
- package/src/claude/rules/manage-docs.md +9 -23
- package/src/claude/rules/orchestrator.md +3 -4
- package/src/claude/rules/review-audit-self-decision.md +3 -3
- package/src/claude/rules/skill-domain-routing.md +1 -3
- package/src/claude/rules/skill-workflow-routing.md +4 -5
- package/src/claude/rules/state-sync.md +3 -12
- package/src/claude/rules/workflow.md +11 -19
- package/src/claude/runtime.schema.json +5 -3
- package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
- package/src/claude/scripts/provenance.cjs +23 -5
- package/src/claude/scripts/spec-receipt.cjs +24 -121
- package/src/claude/scripts/spec-resolver.cjs +41 -458
- package/src/claude/scripts/workflow-policy.cjs +3 -1801
- package/src/claude/settings/settings.json +1 -32
- package/src/claude/skills/agent-browser/SKILL.md +1 -1
- package/src/claude/skills/{question → ask}/SKILL.md +9 -75
- package/src/claude/skills/brainstorm/SKILL.md +43 -131
- package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
- package/src/claude/skills/code-review/SKILL.md +29 -81
- package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -8
- package/src/claude/skills/debug/SKILL.md +29 -149
- package/src/claude/skills/develop/SKILL.md +26 -83
- package/src/claude/skills/develop/references/parallel-waves.md +0 -8
- package/src/claude/skills/develop/references/quality-gate.md +15 -13
- package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
- package/src/claude/skills/docs/SKILL.md +2 -2
- package/src/claude/skills/docs/references/update-workflow.md +1 -1
- package/src/claude/skills/fix/SKILL.md +153 -0
- package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
- package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
- package/src/claude/skills/git/SKILL.md +48 -9
- package/src/claude/skills/git/references/commit-protocols.md +35 -16
- package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
- package/src/claude/skills/orca/SKILL.md +9 -8
- package/src/claude/skills/research/SKILL.md +17 -46
- package/src/claude/skills/scout/SKILL.md +61 -0
- package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
- package/src/claude/skills/specs/SKILL.md +22 -29
- package/src/claude/skills/specs/references/review.md +8 -6
- package/src/claude/skills/specs/references/templates.md +34 -41
- package/src/claude/skills/sync/SKILL.md +16 -45
- package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
- package/src/claude/skills/sync/references/sync-protocols.md +4 -13
- package/src/claude/skills/test/SKILL.md +39 -104
- package/src/claude/skills/test/references/execution-strategy.md +93 -21
- package/src/claude/skills/test/references/failure-triage.md +1 -1
- package/src/claude/skills/ui-ux/LICENSE +21 -0
- package/src/claude/skills/ui-ux/SKILL.md +497 -0
- package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
- package/src/claude/skills/ui-ux/references/budgets.md +113 -0
- package/src/claude/skills/ui-ux/references/checklist.md +271 -0
- package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
- package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
- package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
- package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
- package/src/claude/skills/ui-ux/references/components/button.md +142 -0
- package/src/claude/skills/ui-ux/references/components/card.md +113 -0
- package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
- package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
- package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
- package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
- package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
- package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
- package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
- package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
- package/src/claude/skills/ui-ux/references/components/input.md +161 -0
- package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
- package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
- package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
- package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
- package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
- package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
- package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
- package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
- package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
- package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
- package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
- package/src/claude/skills/ui-ux/references/design-process.md +650 -0
- package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
- package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
- package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
- package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
- package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
- package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
- package/src/claude/skills/ui-ux/references/principles.md +408 -0
- package/src/claude/skills/ui-ux/references/refactor.md +245 -0
- package/src/claude/skills/ui-ux/references/responsive.md +195 -0
- package/src/claude/skills/ui-ux/references/review.md +528 -0
- package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
- package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
- package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
- package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
- package/src/claude/skills/ui-ux/references/styles.md +311 -0
- package/src/claude/skills/ui-ux/references/system.md +219 -0
- package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
- package/src/claude/skills/ui-ux/references/tokens.css +362 -0
- package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
- package/src/claude/skills/ui-ux/scripts/package.json +9 -0
- package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +25 -80
- package/src/claude/skills/web-testing/SKILL.md +2 -20
- package/src/claude/status.cjs +149 -144
- package/src/codex/AGENTS.md +8 -13
- package/src/codex/hooks/agent.cjs +2 -1
- package/src/codex/hooks/lib/spec-receipt.cjs +4 -32
- package/src/codex/hooks/lib/spec-utils.cjs +6 -52
- package/src/codex/hooks/rules.cjs +2 -1
- package/src/codex/hooks/session.cjs +0 -2
- package/src/codex/hooks/spec-gate.cjs +13 -81
- package/src/codex/hooks/spec-state.cjs +37 -41
- package/src/codex/hooks.json +0 -20
- package/src/codex/rules/state-sync.md +3 -12
- package/src/common/AGENTS.md +6 -12
- package/src/omp/extensions/cafekit-bridge.mjs +6 -6
- package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
- package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
- package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
- package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
- package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
- package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
- package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
- package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
- package/src/claude/hooks/completion-authority-check.cjs +0 -56
- package/src/claude/hooks/completion-authority-state.cjs +0 -527
- package/src/claude/hooks/completion-authority.cjs +0 -163
- package/src/claude/hooks/lib/context.cjs +0 -616
- package/src/claude/hooks/lib/detect.cjs +0 -474
- package/src/claude/hooks/semantic-review-authority.cjs +0 -154
- package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
- package/src/claude/hooks/usage.cjs +0 -197
- package/src/claude/scripts/change-firewall.cjs +0 -1748
- package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
- package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
- package/src/claude/scripts/spec-final-state.cjs +0 -290
- package/src/claude/scripts/spec-ground.cjs +0 -650
- package/src/claude/scripts/spec-readiness.cjs +0 -425
- package/src/claude/scripts/spec-scaffold.cjs +0 -2317
- package/src/claude/scripts/spec-semantic-model.cjs +0 -396
- package/src/claude/scripts/validate-spec-output.cjs +0 -3941
- package/src/claude/skills/code-review/references/verification-gate.md +0 -46
- package/src/claude/skills/delegate/SKILL.md +0 -124
- package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
- package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
- package/src/claude/skills/hotfix/SKILL.md +0 -289
- package/src/claude/skills/inspect/SKILL.md +0 -142
- package/src/claude/skills/loop/SKILL.md +0 -114
- package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
- package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
- package/src/claude/skills/route/SKILL.md +0 -94
- package/src/claude/skills/route/references/agent-timing.md +0 -46
- package/src/claude/skills/route/references/chaining-patterns.md +0 -53
- package/src/claude/skills/route/references/task-taxonomy.md +0 -48
- package/src/claude/skills/specs/templates/design.md +0 -82
- package/src/claude/skills/specs/templates/requirements-init.md +0 -8
- package/src/claude/skills/specs/templates/requirements.md +0 -42
- package/src/claude/skills/specs/templates/spec-state.json +0 -56
- package/src/claude/skills/specs/templates/task.md +0 -47
- package/src/codex/hooks/completion-authority-check.cjs +0 -67
- package/src/codex/hooks/completion-authority-state.cjs +0 -527
- package/src/codex/hooks/completion-authority.cjs +0 -109
- package/src/codex/hooks/semantic-review-authority.cjs +0 -130
- package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
- /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
- /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
- /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
|
+
}
|