@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.
- package/README.md +14 -22
- package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
- package/bin/__tests__/codex-hooks.test.js +0 -36
- package/bin/__tests__/codex-native.test.js +46 -88
- package/bin/__tests__/develop-contract.test.js +26 -57
- 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 +159 -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 +100 -541
- 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 +0 -27
- package/bin/__tests__/specs-v2-execution-closeout.test.js +4 -69
- 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 +9 -28
- package/bin/phases/codex-runtime.js +24 -0
- package/bin/phases/omp-runtime.js +22 -0
- package/package.json +1 -1
- package/src/claude/CLAUDE.md +3 -10
- package/src/claude/agents/brainstormer.md +7 -3
- package/src/claude/agents/code-auditor.md +35 -50
- package/src/claude/agents/debugger.md +5 -79
- package/src/claude/agents/deployer.md +3 -5
- package/src/claude/agents/docs-keeper.md +2 -7
- package/src/claude/agents/implementer.md +6 -9
- package/src/claude/agents/inspector.md +2 -3
- package/src/claude/agents/project-manager.md +7 -15
- package/src/claude/agents/researcher.md +9 -3
- package/src/claude/agents/spec-maker.md +11 -11
- package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
- package/src/claude/hooks/__tests__/spec-gate.test.js +74 -3
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +134 -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/context.cjs +0 -89
- 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 -28
- package/src/claude/hooks/spec-state.cjs +95 -1
- package/src/claude/hooks/state.cjs +21 -2
- package/src/claude/migration-manifest.json +30 -15
- package/src/claude/rules/ai-dev-rules.md +1 -1
- 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 +10 -18
- 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 -7
- package/src/claude/scripts/spec-resolver.cjs +1 -2
- package/src/claude/scripts/validate-spec-output.cjs +5 -32
- package/src/claude/settings/settings.json +1 -32
- package/src/claude/skills/{question → ask}/SKILL.md +9 -75
- package/src/claude/skills/brainstorm/SKILL.md +43 -131
- package/src/claude/skills/code-review/SKILL.md +48 -17
- package/src/claude/skills/code-review/references/spec-compliance-review.md +2 -3
- package/src/claude/skills/code-review/references/verification-gate.md +22 -27
- package/src/claude/skills/debug/SKILL.md +29 -149
- package/src/claude/skills/develop/SKILL.md +27 -36
- 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/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 +2 -0
- 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 +8 -15
- 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 +22 -3
- package/src/claude/skills/test/references/execution-strategy.md +83 -0
- 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 +1 -1
- 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 +2 -2
- package/src/codex/hooks/lib/spec-utils.cjs +0 -7
- package/src/codex/hooks/rules.cjs +2 -1
- package/src/codex/hooks/session.cjs +0 -2
- package/src/codex/hooks/spec-gate.cjs +7 -18
- package/src/codex/hooks.json +0 -20
- package/src/codex/rules/state-sync.md +3 -12
- package/src/common/AGENTS.md +2 -2
- package/src/omp/extensions/cafekit-bridge.mjs +6 -6
- 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/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/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/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,650 @@
|
|
|
1
|
+
# Thiết kế từ đầu như một designer — luật U
|
|
2
|
+
|
|
3
|
+
**Nhánh mặc định** (chủ dự án chốt): mọi đề dựng hay làm lại một màn trở lên đều
|
|
4
|
+
vào đây, tiếng Việt hay tiếng Anh, sản phẩm mới hay màn đã có. Chỉ không vào khi đề nói rõ lối
|
|
5
|
+
khác (bảng câu 1 của `SKILL.md`): soi, giữ brand, dựng lại theo gu skill, refactor, dựng
|
|
6
|
+
luôn, hoặc việc nhỏ hơn một màn.
|
|
7
|
+
|
|
8
|
+
Khác nhánh `V` (`review.md`): `V` giữ khung trang, sửa lỗi và làm gọn. Kết quả là bản hi-fi
|
|
9
|
+
sạch hơn của **đúng wireframe cũ**. Nhánh `U` bắt đầu từ câu *"người dùng đến màn này để
|
|
10
|
+
làm gì"*, nên được đổi cả khung: cái gì đứng đầu, lọc nằm đâu, card nói gì, có chế độ xem
|
|
11
|
+
nào. Bản dựng lại theo `V` sạch hết lỗi đo được mà người xem vẫn nói
|
|
12
|
+
"nhìn không khác gì bản cũ, vẫn cần người làm UX".
|
|
13
|
+
|
|
14
|
+
Hai cổng chờ của nhánh này là hai chỗ duy nhất skill dừng hỏi. Đề ghi sẵn đủ cho cổng nào
|
|
15
|
+
(brief đã rõ, "chọn A luôn") thì qua cổng đó không dừng.
|
|
16
|
+
|
|
17
|
+
**"Dựng luôn" thì không vẽ wireframe** (vẽ wireframe tốn nhiều token, chủ dự án chốt).
|
|
18
|
+
Đề có "dựng luôn", "just build it", hoặc người dùng trả lời `dựng luôn` ở cổng 1: vẫn làm `U1`,
|
|
19
|
+
`U2` và chọn phương án `U3` sẽ khuyên dùng, **trong đầu, không gửi, không dừng**, rồi dựng thẳng
|
|
20
|
+
theo phương án đó (`U4`), màu theo nấc sẽ khuyên. Lúc giao ghi một dòng: *"Bố cục: [phương án]
|
|
21
|
+
vì [việc chính]. Muốn xem các hướng khác thì nhắn `vẽ wireframe`."* Lúc gửi brief ở cổng 1, thêm một dòng cuối: *"Muốn bỏ wireframe, dựng luôn thì
|
|
22
|
+
trả lời `dựng luôn`."* (tiếng Anh: *"Reply `just build it` to skip the wireframes."*)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Bốn bước, hai cổng
|
|
27
|
+
|
|
28
|
+
| Bước | Ra cái gì | Cổng |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `U1` Brief | Một khối ngắn: sản phẩm, người dùng, việc chính, nền tảng | Gộp với `U2`, **cổng 1** |
|
|
31
|
+
| `U2` Việc chính của từng màn | Bảng: đến để làm gì, so sánh bằng gì, hành động cuối, quy ước loại sản phẩm | **Cổng 1**: người dùng sửa hoặc trả lời `ok` |
|
|
32
|
+
| `U3` Wireframe | 2–3 phương án bố cục khác nhau thật, nội dung thật, có ảnh; biến thể nội dung D, E; một thanh công cụ: phương án, màu, desktop / mobile, trạng thái, khung lý do | **Cổng 2**: người dùng chọn |
|
|
33
|
+
| `U4` Dựng thật | Code theo phương án đã chọn, probe tới khi danh sách `P` trống | Như cổng 3 của `checklist.md` |
|
|
34
|
+
|
|
35
|
+
Chưa qua cổng 2 thì **không đụng file nào của dự án**. Wireframe và ảnh để ở
|
|
36
|
+
`$TMPDIR/evon-design/`.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## U1. Brief: đọc trước, hỏi sau ⚑
|
|
41
|
+
|
|
42
|
+
- **Chạy audit câu 2 của `SKILL.md` trước khi viết brief**, cho mọi dự án, sản phẩm mới hay
|
|
43
|
+
đã có UI: stack, component có sẵn, phong cách. Dòng `Audit:` đứng đầu tin cổng 1. Bỏ audit
|
|
44
|
+
thì wireframe vẽ control mà dự án đã có kiểu khác, `U4` dựng ra app thứ hai.
|
|
45
|
+
- Đọc README, file route, kiểu dữ liệu (type, mock), chữ trên các màn đang có. Từ đó ghi
|
|
46
|
+
một khối năm dòng: **sản phẩm gì**, **cho ai**, **một đến ba việc chính**, **nền tảng
|
|
47
|
+
dùng nhiều** (điện thoại hay máy tính), **điểm khác biệt** (thứ sản phẩm bán mà nơi khác
|
|
48
|
+
không có).
|
|
49
|
+
- Dòng nào không suy ra được thì hỏi, **tối đa năm câu, gửi một lần**, mỗi câu kèm câu trả
|
|
50
|
+
lời đoán sẵn để người dùng chỉ cần gõ `ok`.
|
|
51
|
+
- **Không viết persona, không vẽ hành trình người dùng, không bịa số liệu nghiên cứu.**
|
|
52
|
+
Model không phỏng vấn được ai. Brief chỉ ghi điều đọc được từ code hoặc người dùng đã nói,
|
|
53
|
+
mỗi dòng ghi nguồn: *đọc code*, *người dùng nói*, *đoán*.
|
|
54
|
+
|
|
55
|
+
## U2. Việc chính của từng màn ⚑
|
|
56
|
+
|
|
57
|
+
Mỗi màn trong phạm vi một dòng:
|
|
58
|
+
|
|
59
|
+
| Màn | Đến để làm gì | So sánh, quyết định bằng gì | Hành động cuối | Loại sản phẩm này thường làm |
|
|
60
|
+
| --- | --- | --- | --- | --- |
|
|
61
|
+
| Danh sách khoá học | Tìm khoá hợp trình độ, trong ngân sách | Giá, thời lượng, trình độ, đánh giá | Mở chi tiết, lưu | Lọc dính đầu trang, card nói giá và trình độ trước, có sắp xếp cạnh số kết quả |
|
|
62
|
+
|
|
63
|
+
- Cột "so sánh bằng gì" quyết định card và bảng: thứ người dùng dùng để chọn giữa các mục
|
|
64
|
+
phải **nổi nhất và đứng đầu**. Thứ không giúp chọn thì lùi xuống hoặc để trang chi tiết.
|
|
65
|
+
- Cột cuối: **tra thật** cách vài sản phẩm cùng loại đang làm (tìm web nếu có công cụ), ghi
|
|
66
|
+
thành quy ước, không ghi tên sản phẩm vào code hay vào file của dự án. Không tra được thì
|
|
67
|
+
ghi *"theo trí nhớ, cần kiểm"*. Quy ước số đông thắng gu riêng.
|
|
68
|
+
- Điểm khác biệt ở `U1` phải **hiện trên màn chính**, không chỉ nằm trong bộ lọc.
|
|
69
|
+
|
|
70
|
+
Gửi `U1` và `U2` trong **một** tin, kết bằng *"Đúng thì trả lời `ok`, sai dòng nào thì sửa
|
|
71
|
+
dòng đó."* Dừng chờ.
|
|
72
|
+
|
|
73
|
+
## U3. Wireframe: 2–3 phương án khác nhau thật ⚑
|
|
74
|
+
|
|
75
|
+
- **Khác ở chiến lược bố cục, không khác ở trang trí.** Ví dụ cho một trang danh sách:
|
|
76
|
+
A giữ lưới card với thanh lọc gọn dính đầu trang; B chia đôi danh sách và panel chi tiết;
|
|
77
|
+
C đặt ô tìm lên trước, lọc sau. Ba phương án chỉ khác bo góc hay màu là **một** phương án.
|
|
78
|
+
- **Mặc định xám.** Ảnh là khối xám có tỉ lệ thật. Mở ra người dùng nhìn bố cục trước, không
|
|
79
|
+
sa vào màu; màu nhấn chỉ bật bằng nút Màu trên thanh công cụ (dưới). Xám chỉ là tắt màu nhấn,
|
|
80
|
+
còn lại vẫn là token và component thật (dòng "Wireframe dựng bằng chính token…" dưới).
|
|
81
|
+
- **Icon lucide thật ở đúng chỗ các app đều đặt**, không vẽ ô vuông giữ chỗ (ô vuông cạnh mục
|
|
82
|
+
sidebar đọc ra checkbox). Nạp `lucide` từ `cdn.jsdelivr.net`, gọi
|
|
83
|
+
`lucide.createIcons()`. **Mỗi lần ghi lại `innerHTML` là phải vẽ lại icon**: `createIcons()`
|
|
84
|
+
chỉ thay các `<i data-lucide>` có lúc gọi, thẻ mới ghi sau đó nằm trống. Gắn một lần ở đầu
|
|
85
|
+
script `new MutationObserver(() => { if (document.querySelector("i[data-lucide]")) lucide.createIcons(); }).observe(document.body, { childList: true, subtree: true })`
|
|
86
|
+
thay vì gọi tay sau từng lần render. **Phải có điều kiện `i[data-lucide]`**: svg vẽ xong vẫn mang
|
|
87
|
+
`data-lucide`, gọi thẳng `createIcons()` trong observer thì nó thay svg mãi, trang treo. Không gắn
|
|
88
|
+
observer thì chọn xong một mục là ô select và ô ngày mất icon (svg thành `<i data-lucide>` trống),
|
|
89
|
+
phải mở lại mới thấy. Ba chỗ: **mỗi mục sidebar**; **ô icon 32px nền nhạt ở góc mỗi card số
|
|
90
|
+
liệu**; **ô icon hay avatar đầu dòng** khi dòng thuộc một loại (bộ phận, nhà cung cấp, loại
|
|
91
|
+
giao dịch). Tiêu đề khối, dòng meta thì không icon (`V1c`). Nấc Xám thì icon xám; ba icon
|
|
92
|
+
giống hệt nhau cho ba mục là thà bỏ (`F17`). Dashboard không icon trông "chán", người xem
|
|
93
|
+
nói ngay. **Xám vẫn có mức nhấn**: nút chính tô xám đậm chữ
|
|
94
|
+
trắng, nút phụ viền. Hai nút cùng một kiểu trong wireframe là chưa quyết thứ bậc, lúc dựng
|
|
95
|
+
thật sẽ lại thành hai nút tranh nhau (`V1b`).
|
|
96
|
+
- **Wireframe dựng bằng chính token, font, component mà bản dựng sẽ dùng** ⚑, vì `U4` chép
|
|
97
|
+
nguyên. Nấc Màu của wireframe phải **là** bản dựng, chỉ khác là file HTML: người dùng chọn thứ
|
|
98
|
+
họ thấy, dựng ra y hệt thì họ hài lòng; dựng ra khác là chọn nhầm.
|
|
99
|
+
- **Khung file như `layouts/app-kanban.html`**: nạp font, Tailwind v4 bản trình duyệt
|
|
100
|
+
(`@tailwindcss/browser@4`), rồi dán **nguyên** khối token vào `<style type="text/tailwindcss">`
|
|
101
|
+
(`:root` và `@theme inline`). Sản phẩm mới: `references/tokens.css`, màu nhấn theo nhóm Nhấn
|
|
102
|
+
(dưới). Dự án đã có UI: **file token của dự án** (`globals.css`, `index.css`, config theme),
|
|
103
|
+
màu xếp theo vai như `U4` sẽ làm (`review.md`, bảng vai màu), dáng theo bảng "Dáng lấy từ
|
|
104
|
+
skill". Không tự đặt mã màu, bo góc, bóng nào ngoài khối token.
|
|
105
|
+
**Dự án đã qua `D9`** (có trang `/design-system` hay story design system, file token đã sửa)
|
|
106
|
+
tính là **dự án đã có UI** dù chưa có màn nào: dán file token của dự án, không dán
|
|
107
|
+
`tokens.css` của skill, không thì mất font và màu nhấn vừa duyệt.
|
|
108
|
+
- **Component viết đúng class bản dựng sẽ dùng**: dự án có thư viện component (shadcn, bộ nội
|
|
109
|
+
bộ) thì mở source `Button`, `Input`, `Badge`, `Card`… của dự án, chép chuỗi class của biến
|
|
110
|
+
thể sẽ dùng; chưa có thì chép công thức trong `components/*.md`. Không vẽ lại "cho giống".
|
|
111
|
+
- **Khoảng cách, cỡ viết thẳng bằng class** ở từng khối (padding khung trang, `gap`, chiều cao
|
|
112
|
+
control, cỡ chữ, cỡ icon), theo thang của dự án hay của skill, không để trình duyệt tự canh.
|
|
113
|
+
Người dùng chọn wireframe là chọn luôn độ thoáng.
|
|
114
|
+
- **Nấc Xám chỉ đổi màu nhấn và màu trạng thái sang xám** (ghi đè `--primary` và các token
|
|
115
|
+
trạng thái trên `:root[data-mau="xam"]`); nền, viền, chữ, bo góc, bóng, font giữ nguyên token.
|
|
116
|
+
Bật Màu lên là thấy đúng bản dựng.
|
|
117
|
+
- **Đổi màu nhấn trên `:root`, không trên `body`, và kéo theo mọi token suy từ nó.** Biến
|
|
118
|
+
`--border-focus: var(--primary)` khai ở `:root` được tính xong ngay tại `:root` rồi mới truyền
|
|
119
|
+
xuống: đổi `--primary` ở `body` thì nút chính đổi màu mà viền focus vẫn màu cũ. Token viết cứng
|
|
120
|
+
theo màu nhấn (`--ring-focus`, `--primary-hover`, `--primary-light`) cũng phải suy lại ở từng nấc,
|
|
121
|
+
như `brand-tokens.md` dặn khi đổi `--primary`. Không suy lại thì chọn
|
|
122
|
+
xanh ngọc, nút xanh ngọc, nhưng ô tìm và select focus vẫn viền và ring màu chàm ở mọi nấc,
|
|
123
|
+
cả nấc Xám. Probe báo "Token focus không theo màu nhấn".
|
|
124
|
+
- **Nội dung thật**: chữ lấy từ dữ liệu của dự án, cả ca dài nhất và ca trống. Wireframe chữ
|
|
125
|
+
"Lorem" thì không thấy được card quá tải.
|
|
126
|
+
- **Dữ liệu mẫu có đủ mọi trạng thái, nhất là trạng thái suy từ giờ.** Màn có mốc "bây giờ"
|
|
127
|
+
(lịch hẹn, hạn chót, đơn đang giao) thì thứ người dùng cần thấy nhất thường là thứ **đã quá
|
|
128
|
+
mốc mà chưa xong**: khách trễ giờ hẹn chưa đến, việc quá hạn, đơn giao chậm. Nó không nằm
|
|
129
|
+
trong danh sách trạng thái lưu trong dữ liệu (chỉ có "Đã xác nhận"), mà tính từ giờ, nên
|
|
130
|
+
hay bị quên. Đặt giờ "bây giờ" của wireframe sao cho có ít nhất một mục như vậy, vẽ nó thành
|
|
131
|
+
trạng thái riêng (`M4`: hổ phách hay đỏ ở nấc Màu), và ghi vào `U2` là thứ phải nổi. Ví dụ lịch
|
|
132
|
+
hẹn lúc 10:40 mà mọi lịch trước đó đều đã đến, đang khám hoặc không đến, không có ai "trễ": việc
|
|
133
|
+
chính của lễ tân lúc đó (gọi người trễ) không có trên wireframe.
|
|
134
|
+
- **Đúng hình dạng dữ liệu**: mỗi mục có mấy ảnh, trường nào hay trống, danh sách dài bao
|
|
135
|
+
nhiêu. Dữ liệu chỉ có một ảnh mỗi tin mà wireframe vẽ lưới ba ảnh là hứa thứ dữ liệu
|
|
136
|
+
không có: người dùng chọn vì lưới ảnh, bản dựng ra một ảnh to quá khổ. Muốn phương án cần thêm dữ liệu thì vẽ đúng cái đang có và ghi *"đẹp hơn khi
|
|
137
|
+
có X"* ở dòng đánh đổi.
|
|
138
|
+
- Mỗi phương án ghi **ba dòng**: việc chính giờ thấy ở đâu, đổi gì so với bản cũ, đánh đổi.
|
|
139
|
+
Ba dòng đó cũng là nội dung khung lý do trên trang wireframe (dưới), hai chỗ cùng một chữ.
|
|
140
|
+
Phương án cần dữ liệu hay logic chưa có (khoảng cách, chế độ xem mới) thì ghi rõ
|
|
141
|
+
*"cần dữ liệu X, logic do bạn nối"* (`N10`).
|
|
142
|
+
- **Tính cả khung app vào bề ngang.** App đã có sidebar điều hướng mà phương án thêm một cột
|
|
143
|
+
lọc bên trái thì ghi rõ ở dòng đánh đổi: hai cột trái, nội dung còn lại bao nhiêu px ở 1280.
|
|
144
|
+
- **Đánh dấu một phương án khuyên dùng**, kèm một câu vì sao (bám `U2`).
|
|
145
|
+
- Một file HTML, mọi lựa chọn nằm trên tham số (`?v=a&mau=xam&kho=desktop&tt=du-lieu`) để probe
|
|
146
|
+
mở được từng cái, và người dùng chép link gửi đi thì mở ra đúng cái đang xem.
|
|
147
|
+
- **Đánh số từng khối chính** (header, hàng lọc, danh sách, panel, chân trang…): `data-wf-block="1"`,
|
|
148
|
+
số hiện nhỏ ở góc trên trái khối. Người dùng góp ý bằng số (*"bỏ khối 3"*, *"đưa khối 2 lên
|
|
149
|
+
đầu"*), không phải tả "cái thanh chữ nhỏ ở trên bảng". Cùng một khối ở các phương án giữ cùng
|
|
150
|
+
số; khối chỉ phương án đó có thì số mới.
|
|
151
|
+
**Số không được đè chữ hay icon của khối.** Khối không có padding (hàng công cụ, hàng chữ đếm
|
|
152
|
+
trạng thái) có chữ ngay góc trên trái: số đè thành "ThBa". Sau mỗi lần vẽ, `placeBlockNumbers()`
|
|
153
|
+
(mẫu dưới) đo chữ và icon dưới số, đè thì gắn `data-wf-block-out` để số lên ngay trên mép khối.
|
|
154
|
+
**`data-wf-block` đặt trên khối bọc không cuộn**: khối `overflow-x-auto` (hàng chip) cắt mất số
|
|
155
|
+
nằm ngoài mép, bọc thêm một `div` rồi đánh số lên đó.
|
|
156
|
+
- **Khối nào skill đã có mẫu thì wireframe vẽ đúng hình mẫu đó**, vì `U4` dựng đúng wireframe:
|
|
157
|
+
vẽ sai là bản dựng chép sai theo. Trước khi vẽ, liệt kê các khối của phương án rồi mở mẫu
|
|
158
|
+
tương ứng ở bảng mục 2 của `SKILL.md` (`components/`, `layouts/`): nút, ô nhập, select, ô
|
|
159
|
+
chọn ngày, checkbox, công tắc, tab, chip, phân trang, badge, avatar, dòng danh sách, card, card
|
|
160
|
+
số liệu, biểu đồ, khối rỗng, đường dẫn, header, sidebar, bảng. Chép **hình và class**: cỡ, số phần tử,
|
|
161
|
+
cách xếp, chữ nằm đâu, chuỗi class của mẫu; không cần chép code React. Control là thẻ thật (`<input>`, `<button>`,
|
|
162
|
+
`<select>` nếu mẫu dùng), không `div` giả. Khối chưa có mẫu mới tự vẽ. Hai
|
|
163
|
+
lỗi hay gặp: phân trang vẽ hai nút chữ "Trước / Sau" rộng khác nhau thay cho
|
|
164
|
+
`‹ 1 2 3 … ›`; ô tìm là `div` nên placeholder dài rớt xuống dòng hai.
|
|
165
|
+
**App đang có mà dùng control gốc của trình duyệt thì wireframe không chép theo**, kể cả khi
|
|
166
|
+
app đã tô viền, bo góc: `<select>`, ô ngày / giờ bấm vào vẫn bung menu và lịch của hệ điều hành;
|
|
167
|
+
checkbox, radio, thanh trượt, ô chọn tệp gốc lạc dáng giữa app. Vẽ theo mẫu tương ứng của skill
|
|
168
|
+
(`components/choice-controls.md`, `range-slider.md`, `file-upload.md`), tô bằng token của app. Chỉ giữ control gốc khi nó chỉ hiện trên mobile (luật `<select>` gốc cho màn
|
|
169
|
+
cảm ứng ở đó); ô nằm trong dialog dùng cho cả hai khổ thì dựng. Khối trong lớp nổi (dialog,
|
|
170
|
+
sheet, popover) cũng đối chiếu mẫu, mở ra rồi xem, không chỉ phần trang đang hiện.
|
|
171
|
+
- **Tự đối chiếu trước khi probe**: mỗi khối có mẫu, so wireframe với mẫu một dòng ("phân trang:
|
|
172
|
+
khớp", "ô tìm: `<input>`, placeholder vừa"). Probe bắt được một phần (placeholder dài hơn ô,
|
|
173
|
+
khối trông như ô nhập mà chữ xuống dòng, phân trang chỉ có nút chữ, hàng control lệch), phần
|
|
174
|
+
còn lại là mắt.
|
|
175
|
+
- **Đủ bốn trạng thái của `I19`**: có dữ liệu, đang tải, rỗng, lỗi, chuyển bằng nút Trạng thái.
|
|
176
|
+
Màn rỗng có câu và nút của `components/empty-state.md`, đang tải là khung chờ đúng hình dòng
|
|
177
|
+
thật. Người dùng góp ý màn rỗng từ lúc wireframe, không đợi dựng xong mới thấy.
|
|
178
|
+
- **Wireframe có đủ trạng thái như bản thật**: một mục đang chọn đánh dấu `aria-current` (hay
|
|
179
|
+
`aria-selected`), có nền rê. Wireframe tĩnh không có rê thì không ai thấy
|
|
180
|
+
"rê trùng nền đang chọn" cho tới khi đã dựng xong.
|
|
181
|
+
- **Probe từng phương án trước khi gửi**, ở 1280 và 375, sửa tới khi sạch các mục: danh sách
|
|
182
|
+
`P`, rê ra đúng màu mục đang chọn, vạch trái bị bo góc cắt, mục lặp dày chữ, cột dính cuộn
|
|
183
|
+
riêng, nội dung trôi giữa màn rộng, hàng nút rớt một nút lẻ (`R3`), select và ô ngày gốc (kể cả
|
|
184
|
+
trong dialog đang đóng), "khung wireframe làm hỏng
|
|
185
|
+
bản thiết kế" (số đè chữ, `sticky` mất, thanh tràn). Rồi chạy luật Cấu trúc (`V1b` trong `review.md`) bằng
|
|
186
|
+
mắt. Người dùng không tự thấy "card chữ quá trời" hay "vạch bị cắt" trên wireframe xám, họ
|
|
187
|
+
chọn theo bố cục rồi vấp lỗi ở bản dựng (năm dòng mỗi mục, vạch trái bị bo cắt, đang chọn và rê
|
|
188
|
+
cùng một xám; probe đo ra cả hai lỗi đầu trên chính file wireframe). Ghi một dòng khi gửi: *"Probe wireframe: A sạch, B sạch, C sạch"*.
|
|
189
|
+
- **Hai biến thể nội dung, D và E, trên phương án khuyên dùng.** Cùng bố cục, chỉ khác nội
|
|
190
|
+
dung, để người dùng thấy cạnh nhau cái họ không tự nghĩ ra:
|
|
191
|
+
- **D, gọn chữ:** mỗi mục chỉ giữ thứ dùng để chọn ở cột "so sánh bằng gì" của `U2`, tối
|
|
192
|
+
đa ba dòng. Phần còn lại để trang hay panel chi tiết.
|
|
193
|
+
- **E, bỏ lặp:** mỗi thông tin một chỗ trên màn: không lặp giữa mục và panel chi tiết, giữa
|
|
194
|
+
header và sidebar, giữa tên trang và mục đang chọn (`V1b`, "Hai chỗ một việc").
|
|
195
|
+
|
|
196
|
+
D và E cũng qua probe như các phương án bố cục.
|
|
197
|
+
|
|
198
|
+
- **Công tắc Màu** (tắt là Xám, bật là Màu), áp cho mọi phương án, không phải bản riêng:
|
|
199
|
+
- **Tắt, Xám:** mặc định lúc mở, chỉ xem bố cục.
|
|
200
|
+
- **Bật, Màu:** như bản dựng sẽ ra theo mặc định (`P6`): màu nhấn ở nút chính, mục đang chọn, link,
|
|
201
|
+
biểu đồ; **và màu trạng thái của `M4` trên mọi dữ liệu có trạng thái**: vượt ngân sách,
|
|
202
|
+
quá hạn đỏ hay hổ phách, đã xong xanh, thanh tiến độ tô theo ngưỡng. Nấc Màu mà thanh 103%
|
|
203
|
+
vẫn đen thì người xem hỏi *"chọn màu mà sao vẫn trắng đen"*. Dự án đã
|
|
204
|
+
có phong cách khác flat (`P4`) thì Màu là phong cách đó.
|
|
205
|
+
|
|
206
|
+
⚠️ **Nấc thứ ba "Có màu" đã bỏ khỏi wireframe (chủ dự án: thêm vào cũng không
|
|
207
|
+
khác gì mấy).** Màu brand người dùng cần thấy nằm ở chỗ tương tác (control bấm được, dưới),
|
|
208
|
+
không ở dải màu trang trí. Người dùng tự xin "có màu" trong đề thì theo `P12` ở `styles.md`
|
|
209
|
+
lúc dựng, không vẽ thành nấc.
|
|
210
|
+
|
|
211
|
+
**Dự án chưa có màu brand** (màu nhấn là gần đen mặc định, `brand-tokens.md`; dự án đã qua
|
|
212
|
+
`D9` thì màu nhấn đã chốt ở cổng đó, kể cả khi chốt gần đen, không tính là chưa có) thì thêm nhóm
|
|
213
|
+
**Nhấn: ● ● ●** gồm ba màu gợi ý (chàm `#4f46e5`, xanh ngọc `#0d9488`, cam `#ea580c`), đổi
|
|
214
|
+
`--primary` tại chỗ. Không có nhóm này thì nấc Màu của dự án mới vẫn đen trắng. Màu người dùng
|
|
215
|
+
chọn thành màu nhấn lúc dựng (`brand-tokens.md`); không chọn thì dựng màu đầu, báo một dòng.
|
|
216
|
+
Đổi màu bằng biến CSS trên `:root[data-mau]`, `:root[data-nhan]` (không trên `body`, xem trên), không vẽ lại. Probe cả hai
|
|
217
|
+
nấc (tương phản chữ trắng trên nút chính, trên mục đang chọn).
|
|
218
|
+
|
|
219
|
+
**Dự án chưa có logo** thì thêm nhóm **Logo: 1 · 2 · 3** (`?logo=`): ba dấu theo ba hướng của
|
|
220
|
+
`components/logo.md`, đã kiểm ở 16px, đổi ở mọi chỗ có logo trên trang. Khung lý do có một dòng
|
|
221
|
+
vì sao cho từng dấu. Không có nhóm này thì đầu sidebar là ô chữ cái, người dùng duyệt xong vẫn
|
|
222
|
+
chưa thấy sản phẩm mình trông ra sao. Logo người dùng chọn thành logo lúc dựng; không chọn thì
|
|
223
|
+
dựng hướng khuyên dùng, báo một dòng.
|
|
224
|
+
|
|
225
|
+
- **Control trong wireframe bấm được và hiện trạng thái như bản thật** ⚑: ô nhập, ô tìm focus
|
|
226
|
+
thì viền và ring màu nhấn (`I13`: `--border-focus`, `--ring-focus`); select, dropdown, nút lọc
|
|
227
|
+
bấm là xổ ra danh sách mục thật theo `layouts/overlay.md` (khung, chuyển động), bấm ngoài hay
|
|
228
|
+
Esc thì đóng. Mục đang chọn có badge thì badge đảo màu như bản dựng (`layouts/app.md`,
|
|
229
|
+
Sidebar). Bật Màu lên là người dùng thấy màu brand đúng ở chỗ họ sẽ bấm (chủ dự án chốt:
|
|
230
|
+
"cho user thấy còn hay hơn" nấc Có màu).
|
|
231
|
+
|
|
232
|
+
- **Nút Khổ: Desktop · Mobile.** Mobile hiện chính trang đó trong một khung 375 × 812 giữa màn
|
|
233
|
+
(iframe cùng link, thêm `frame=1` để trong khung không có thanh công cụ), nên media query chạy
|
|
234
|
+
thật. Người dùng hầu như không tự thu cửa sổ, nên không thấy bảng thành danh sách, lọc thành
|
|
235
|
+
nút ra sao ở điện thoại.
|
|
236
|
+
- **Nút ☰ trong khung mobile bấm được**: mở panel trượt từ trái theo `layouts/app.md` (lớp
|
|
237
|
+
phủ, bấm ngoài hay Esc thì đóng, không nút ✕), để người dùng thấy menu có bao nhiêu mục, mục nào đang
|
|
238
|
+
chọn. ☰ không bấm được thì mobile chỉ là ảnh chụp.
|
|
239
|
+
- **App có từ 5 mục điều hướng chính trở xuống** thì thêm nhóm **Nav: ☰ · Thanh dưới** (chỉ
|
|
240
|
+
hiện khi Khổ là Mobile), vẽ thêm thanh điều hướng dưới theo `layouts/app.md`, và ghi trong
|
|
241
|
+
khung lý do nên dùng cái nào: app dùng hằng ngày, chuyển mục liên tục thì thanh dưới; app
|
|
242
|
+
quản trị ít mở trên điện thoại thì ☰. Từ 6 mục thì chỉ ☰.
|
|
243
|
+
|
|
244
|
+
- **Thanh công cụ ở đỉnh trang, bắt buộc, một dòng**, nằm ngoài bản thiết kế: dải **sáng** cao
|
|
245
|
+
56px, nền trắng, viền dưới xám nhạt, chữ 14px, **không dính đỉnh**: thanh dính đè lên sidebar,
|
|
246
|
+
header, panel `sticky top-0` của chính bản thiết kế, người xem thấy sidebar mất logo khi cuộn và
|
|
247
|
+
tưởng bản dựng sẽ vậy. Các nhóm xếp liền từ trái, cách nhau
|
|
248
|
+
24px, theo thứ tự: Màn (đề nhiều màn) · **Phương án** · **Màu** (công tắc) · Nhấn (dự án chưa có brand) · Logo (dự án chưa có logo) · Khổ · Nav (mobile, ít mục)
|
|
249
|
+
· **Trạng thái**.
|
|
250
|
+
- **Đề nhiều màn** (lịch và hồ sơ, danh sách và chi tiết) thì một file, nhóm **Màn** đứng đầu
|
|
251
|
+
(`?man=`), nhãn một hai chữ ("Lịch", "Hồ sơ"); mỗi màn có A, B, C riêng. **Ở 1280 thanh phải
|
|
252
|
+
vừa một dòng không cuộn** (probe đo): nhãn dài như "Lịch trong ngày", "Hồ sơ bệnh
|
|
253
|
+
nhân" đẩy thanh tràn, "Trạng thái" bị cắt mất.
|
|
254
|
+
- **Mỗi nhóm là một segmented control**: rãnh xám nhạt bo 10px, nút trong rãnh không nền, nút
|
|
255
|
+
đang bật (`aria-current="page"`) nền trắng, bóng mảnh, chữ đậm đen; nút khác chữ xám. Không
|
|
256
|
+
dải tối, không nút chữ trắng rời rạc: dải tối nặng hơn chính bản thiết kế, kéo mắt khỏi thứ
|
|
257
|
+
cần xem, và mười mấy nút cùng hình đọc không ra nhóm nào.
|
|
258
|
+
- **Phương án chỉ ghi chữ cái** `A B C D E`, có nhãn "Phương án" xám đứng trước; tên đầy đủ ở
|
|
259
|
+
`title` và ở đầu khung lý do. Phương án khuyên dùng có chấm nhỏ màu nhấn cạnh chữ cái. Tên
|
|
260
|
+
dài trên thanh ("A · Báo cáo một trang (khuyên dùng)") đẩy cả thanh phải cuộn ngang ở 1280.
|
|
261
|
+
- **Khổ có icon**: màn hình trước Desktop, điện thoại trước Mobile (icon 16px, nét 2).
|
|
262
|
+
- **Trạng thái là menu thả**, nhãn xám "Trạng thái:" kèm giá trị đang xem đậm và mũi tên nhỏ;
|
|
263
|
+
bấm thì ra bốn link. Bốn trạng thái ít đổi, không đáng chiếm bốn nút trên thanh.
|
|
264
|
+
- **Màu là công tắc** có nhãn "Màu" đứng trước, không phải segmented: chỉ còn hai nấc.
|
|
265
|
+
Nhóm Nhấn, Nav không cần nhãn: chữ trong nút đã tự nói.
|
|
266
|
+
- **Chữ trên thanh và khung lý do theo tiếng người dùng đang viết (`T27`)**, không theo
|
|
267
|
+
`T24`: đó là lời skill nói với người dùng, không phải chữ của sản phẩm. Mẫu HTML dưới viết
|
|
268
|
+
tiếng Việt; đề tiếng Anh thì đổi hết: Phương án → Option, Màu → Color, Nhấn (`aria-label`
|
|
269
|
+
Chàm, Xanh ngọc, Cam) → Indigo, Teal, Orange, Thanh dưới → Bottom bar, Trạng thái → State
|
|
270
|
+
(Có dữ liệu, Đang tải, Rỗng, Lỗi → Data, Loading, Empty, Error), Khuyên dùng → Recommended,
|
|
271
|
+
Ưu, nhược → Pros, cons, Ưu / Nhược / Hợp khi → Pros / Cons / Best when, Gợi ý góp ý →
|
|
272
|
+
Feedback ideas, tên phương án, câu lý do, câu gợi ý cũng tiếng Anh. Tham số URL (`v`, `mau`,
|
|
273
|
+
`kho`, `tt`…) giữ nguyên. Chữ **trong** bản thiết kế vẫn theo `T24`: dự án đang có nhãn tiếng
|
|
274
|
+
Việt thì wireframe tiếng Việt dù đề tiếng Anh. Thanh tiếng Việt trên đề tiếng Anh thì người
|
|
275
|
+
dùng không đọc được nút nào, mà thanh là chỗ duy nhất để chọn.
|
|
276
|
+
|
|
277
|
+
Mỗi nút là link giữ nguyên các lựa chọn khác, chỉ đổi đúng tham số của nó. Màn hẹp thì thanh
|
|
278
|
+
cuộn ngang, không xuống dòng. Mở không tham số thì: phương án khuyên dùng, Màu tắt, Desktop, Có
|
|
279
|
+
dữ liệu. Thiếu thanh thì người dùng phải tự gõ `?v=`.
|
|
280
|
+
|
|
281
|
+
- **Khung lý do ngay dưới thanh**, không modal (modal che mất bản thiết kế đúng lúc cần nhìn),
|
|
282
|
+
nền xám rất nhạt. **Dòng đóng**: tên phương án đậm đen, nhãn "Khuyên dùng" (chỉ phương án khuyên
|
|
283
|
+
dùng), một câu lý do chữ `#525252` cắt một dòng, nút "Ưu, nhược ⌄" dạt phải: *"**A · Lưới card**
|
|
284
|
+
[Khuyên dùng] Người dùng đến để so lương, nên lương đứng đầu mỗi dòng"* (bám việc chính ở `U2`,
|
|
285
|
+
không viết "gọn gàng, hiện đại"). Dưới 768px câu lý do xuống dòng riêng, nút chỉ còn mũi tên để
|
|
286
|
+
tên và nhãn giữ một dòng. Bấm mở ra đủ:
|
|
287
|
+
- **Ba cột** Ưu (chấm xanh), Nhược (chấm hổ phách), Hợp khi (chấm xám): tiêu đề 12px đậm đen,
|
|
288
|
+
Ưu 2–3 gạch đầu dòng, Nhược 1–2, Hợp khi một câu, chữ `#404040`. Màn hẹp xếp chồng. Đổi theo
|
|
289
|
+
phương án đang xem.
|
|
290
|
+
- **Gợi ý góp ý**: hàng riêng dưới đường kẻ, 3–4 câu ngắn người dùng chép gửi lại cho AI, **mỗi
|
|
291
|
+
câu là một nút** (chữ cả câu kèm icon chép, không xuống dòng, bấm xong icon thành dấu ✓ xanh
|
|
292
|
+
1,5 giây). Chọn
|
|
293
|
+
theo chính trang này, bằng ngôn ngữ của đề: trang đang Xám nhạt thì *"Thêm màu brand ở header
|
|
294
|
+
và hàng lọc"*; tiêu đề mảnh thì *"Tiêu đề đậm hơn"*; khối sát nhau thì *"Thoáng hơn, tăng
|
|
295
|
+
khoảng cách giữa các khối"*; *"Font khác hợp sản phẩm hơn"*; *"Bỏ khối 3"*. Không gợi ý
|
|
296
|
+
thứ trang đã có (đã nhiều màu thì không "thêm màu").
|
|
297
|
+
|
|
298
|
+
Đừng viết mọi thứ thành chữ 13px xám `#737373` liền một khối (Ưu, Nhược, Hợp khi nối đuôi, nút
|
|
299
|
+
Chép chen giữa câu, một câu gợi ý gãy làm hai dòng): người xem thấy "màu chìm, cấu trúc loạn xạ".
|
|
300
|
+
|
|
301
|
+
```html
|
|
302
|
+
<nav class="wf-bar" aria-label="Wireframe">
|
|
303
|
+
<div class="wf-group">
|
|
304
|
+
<span class="wf-label">Phương án</span>
|
|
305
|
+
<span class="wf-set" data-wf-param="v">
|
|
306
|
+
<a data-value="a" title="A · Lưới card (khuyên dùng)" data-recommended>A</a><a data-value="b" title="B · Danh sách + chi tiết">B</a><a data-value="d" title="D · A gọn chữ">D</a>
|
|
307
|
+
</span>
|
|
308
|
+
</div>
|
|
309
|
+
<a class="wf-switch" data-wf-toggle="mau" data-on="mau" data-off="xam" role="switch">Màu <i></i></a>
|
|
310
|
+
<span class="wf-set" data-wf-param="nhan"><a data-value="cham" aria-label="Chàm"><i></i></a><a data-value="ngoc" aria-label="Xanh ngọc"><i></i></a><a data-value="cam" aria-label="Cam"><i></i></a></span>
|
|
311
|
+
<span class="wf-set" data-wf-param="kho">
|
|
312
|
+
<a data-value="desktop"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="20" height="14" x="2" y="3" rx="2"/><path d="M8 21h8M12 17v4"/></svg>Desktop</a>
|
|
313
|
+
<a data-value="mobile"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="20" x="5" y="2" rx="2"/><path d="M12 18h.01"/></svg>Mobile</a>
|
|
314
|
+
</span>
|
|
315
|
+
<span class="wf-set" data-wf-param="nav"><a data-value="menu">☰ Menu</a><a data-value="duoi">Thanh dưới</a></span>
|
|
316
|
+
<details class="wf-menu">
|
|
317
|
+
<summary><span>Trạng thái:</span><b data-wf-current="tt"></b><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></summary>
|
|
318
|
+
<div class="wf-popover" data-wf-param="tt"><a data-value="du-lieu">Có dữ liệu</a><a data-value="dang-tai">Đang tải</a><a data-value="rong">Rỗng</a><a data-value="loi">Lỗi</a></div>
|
|
319
|
+
</details>
|
|
320
|
+
</nav>
|
|
321
|
+
<details class="wf-reason" data-wf-reason>
|
|
322
|
+
<summary>
|
|
323
|
+
<span class="wf-reason-name">A · Lưới card</span>
|
|
324
|
+
<span class="wf-reason-tag">Khuyên dùng</span>
|
|
325
|
+
<span class="wf-reason-why">Người dùng đến để so lương, nên lương đứng đầu mỗi dòng.</span>
|
|
326
|
+
<span class="wf-reason-toggle"><span>Ưu, nhược</span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></span>
|
|
327
|
+
</summary>
|
|
328
|
+
<div class="wf-reason-body">
|
|
329
|
+
<div class="wf-reason-cols">
|
|
330
|
+
<section><h3><i data-tone="uu"></i>Ưu</h3><ul><li>…</li><li>…</li></ul></section>
|
|
331
|
+
<section><h3><i data-tone="nhuoc"></i>Nhược</h3><ul><li>…</li></ul></section>
|
|
332
|
+
<section><h3><i></i>Hợp khi</h3><p>…</p></section>
|
|
333
|
+
</div>
|
|
334
|
+
<div class="wf-reason-tips">
|
|
335
|
+
<h3>Gợi ý góp ý</h3>
|
|
336
|
+
<button type="button" data-copy="Tiêu đề đậm hơn">Tiêu đề đậm hơn<svg data-icon="chep" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg><svg data-icon="da-chep" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg></button>
|
|
337
|
+
…mỗi câu gợi ý một nút như trên…
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
</details>
|
|
341
|
+
<main id="wf-design">…khối có data-wf-block="1", "2"…</main>
|
|
342
|
+
<style>
|
|
343
|
+
.wf-bar { position: relative; z-index: 50; display: flex; align-items: center; gap: 24px; height: 56px;
|
|
344
|
+
padding: 0 16px; overflow-x: auto; white-space: nowrap; background: #fff; border-bottom: 1px solid #e5e5e5;
|
|
345
|
+
color: #737373; font: 14px/1 system-ui, -apple-system, sans-serif; }
|
|
346
|
+
.wf-bar *, .wf-reason * { box-sizing: border-box; }
|
|
347
|
+
.wf-group { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
|
|
348
|
+
.wf-set { display: flex; align-items: center; gap: 2px; flex-shrink: 0; padding: 3px; border-radius: 10px; background: #f4f4f5; }
|
|
349
|
+
.wf-set a { position: relative; display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 12px;
|
|
350
|
+
border-radius: 7px; color: #737373; text-decoration: none; }
|
|
351
|
+
.wf-set a:hover { color: #171717; }
|
|
352
|
+
.wf-switch { display: inline-flex; align-items: center; gap: 8px; flex-shrink: 0; color: #737373; text-decoration: none; }
|
|
353
|
+
.wf-switch i { position: relative; width: 36px; height: 20px; border-radius: 10px; background: #e4e4e7; transition: background .15s; }
|
|
354
|
+
.wf-switch i::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%;
|
|
355
|
+
background: #fff; box-shadow: 0 1px 2px rgb(0 0 0 / .2); transition: translate .15s; }
|
|
356
|
+
.wf-switch[aria-checked="true"] i { background: #171717; }
|
|
357
|
+
.wf-switch[aria-checked="true"] i::after { translate: 16px 0; }
|
|
358
|
+
.wf-set a[aria-current="page"] { background: #fff; color: #171717; font-weight: 500;
|
|
359
|
+
box-shadow: 0 1px 2px rgb(0 0 0 / .08), 0 0 0 1px rgb(0 0 0 / .04); }
|
|
360
|
+
.wf-bar svg { width: 16px; height: 16px; flex-shrink: 0; }
|
|
361
|
+
[data-wf-param="v"] a { justify-content: center; min-width: 32px; padding: 0 10px; }
|
|
362
|
+
[data-wf-param="v"] a[data-recommended]::after { content: ""; position: absolute; top: 5px; right: 5px;
|
|
363
|
+
width: 5px; height: 5px; border-radius: 50%; background: #4f46e5; }
|
|
364
|
+
[data-wf-param="nhan"] a { padding: 0 8px; }
|
|
365
|
+
[data-wf-param="nhan"] i { width: 14px; height: 14px; border-radius: 50%; background: currentColor; }
|
|
366
|
+
[data-wf-param="nhan"] a[data-value="cham"] { color: #4f46e5; } [data-wf-param="nhan"] a[data-value="ngoc"] { color: #0d9488; }
|
|
367
|
+
[data-wf-param="nhan"] a[data-value="cam"] { color: #ea580c; }
|
|
368
|
+
.wf-menu { flex-shrink: 0; }
|
|
369
|
+
.wf-menu summary { display: flex; align-items: center; gap: 6px; height: 36px; padding: 0 10px; border-radius: 8px;
|
|
370
|
+
cursor: pointer; list-style: none; }
|
|
371
|
+
.wf-menu summary::-webkit-details-marker { display: none; }
|
|
372
|
+
.wf-menu summary:hover, .wf-menu[open] summary { background: #f4f4f5; }
|
|
373
|
+
.wf-menu b { color: #171717; font-weight: 500; }
|
|
374
|
+
.wf-popover { position: fixed; z-index: 60; display: grid; min-width: 168px; padding: 4px; background: #fff;
|
|
375
|
+
border: 1px solid #e5e5e5; border-radius: 10px; box-shadow: 0 8px 24px rgb(0 0 0 / .08); }
|
|
376
|
+
.wf-popover a { display: flex; align-items: center; height: 34px; padding: 0 10px; border-radius: 6px; color: #404040; text-decoration: none; }
|
|
377
|
+
.wf-popover a:hover, .wf-popover a[aria-current="page"] { background: #f4f4f5; color: #171717; }
|
|
378
|
+
.wf-popover a[aria-current="page"] { font-weight: 500; }
|
|
379
|
+
.wf-reason { border-bottom: 1px solid #e5e5e5; background: #fafafa; color: #404040; font: 13px/1.5 system-ui, -apple-system, sans-serif; }
|
|
380
|
+
.wf-reason summary { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; min-height: 44px; padding: 8px 16px;
|
|
381
|
+
cursor: pointer; list-style: none; }
|
|
382
|
+
.wf-reason summary::-webkit-details-marker { display: none; }
|
|
383
|
+
.wf-reason-name { min-width: 0; overflow: hidden; color: #171717; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
|
|
384
|
+
.wf-reason-tag { flex-shrink: 0; height: 20px; padding: 0 8px; border-radius: 10px; background: #eef2ff; color: #4338ca;
|
|
385
|
+
font-size: 12px; font-weight: 500; line-height: 20px; }
|
|
386
|
+
.wf-reason-why { flex: 1; min-width: 0; overflow: hidden; color: #525252; text-overflow: ellipsis; white-space: nowrap; }
|
|
387
|
+
.wf-reason[open] .wf-reason-why { white-space: normal; }
|
|
388
|
+
.wf-reason-toggle { display: inline-flex; flex-shrink: 0; align-items: center; gap: 4px; height: 28px; margin-left: auto;
|
|
389
|
+
padding: 0 8px; border-radius: 6px; color: #171717; font-weight: 500; }
|
|
390
|
+
.wf-reason summary:hover .wf-reason-toggle { background: #f0f0f0; }
|
|
391
|
+
.wf-reason-toggle svg { width: 16px; height: 16px; transition: rotate .15s; }
|
|
392
|
+
.wf-reason[open] .wf-reason-toggle svg { rotate: 180deg; }
|
|
393
|
+
/* minmax(0, 1fr) và min(240px, 100%): thiếu thì ở 375 lưới cột tính theo max-width, tràn ngang. */
|
|
394
|
+
.wf-reason-body { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; padding: 4px 16px 16px; }
|
|
395
|
+
.wf-reason-cols { display: grid; max-width: 1120px; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); gap: 16px 32px; }
|
|
396
|
+
.wf-reason h3 { display: flex; align-items: center; gap: 6px; margin: 0 0 4px; color: #171717; font-size: 12px; font-weight: 600; }
|
|
397
|
+
.wf-reason h3 i { width: 6px; height: 6px; border-radius: 50%; background: #a3a3a3; }
|
|
398
|
+
.wf-reason h3 i[data-tone="uu"] { background: #16a34a; }
|
|
399
|
+
.wf-reason h3 i[data-tone="nhuoc"] { background: #d97706; }
|
|
400
|
+
.wf-reason ul { display: grid; gap: 2px; margin: 0; padding-left: 16px; }
|
|
401
|
+
.wf-reason li::marker { color: #a3a3a3; }
|
|
402
|
+
.wf-reason p { margin: 0; }
|
|
403
|
+
.wf-reason-tips { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding-top: 12px; border-top: 1px solid #ebebeb; }
|
|
404
|
+
.wf-reason-tips h3 { margin: 0 4px 0 0; }
|
|
405
|
+
.wf-reason-tips button { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px; border: 1px solid #e5e5e5;
|
|
406
|
+
border-radius: 8px; background: #fff; color: #262626; font: inherit; white-space: nowrap; cursor: pointer; }
|
|
407
|
+
.wf-reason-tips button:hover { border-color: #d4d4d4; background: #f5f5f5; }
|
|
408
|
+
.wf-reason-tips button svg { width: 14px; height: 14px; color: #737373; }
|
|
409
|
+
.wf-reason-tips button [data-icon="da-chep"], .wf-reason-tips button[data-copied] [data-icon="chep"] { display: none; }
|
|
410
|
+
.wf-reason-tips button[data-copied] [data-icon="da-chep"] { display: block; color: #16a34a; }
|
|
411
|
+
.wf-reason :focus-visible { outline: 2px solid #4f46e5; outline-offset: 2px; }
|
|
412
|
+
@media (max-width: 767px) {
|
|
413
|
+
.wf-reason-why { order: 1; flex-basis: 100%; }
|
|
414
|
+
/* Chỉ còn mũi tên để tên và nhãn giữ một dòng; chữ vẫn còn cho trình đọc màn hình. */
|
|
415
|
+
.wf-reason-toggle span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
416
|
+
.wf-reason-tips h3 { flex-basis: 100%; }
|
|
417
|
+
}
|
|
418
|
+
/* Trong layer: rule ngoài layer thắng mọi utility Tailwind, `sticky` của sidebar thành `relative`, sidebar
|
|
419
|
+
trôi khi cuộn. Không Tailwind thì class `sticky` của dự án vẫn thắng rule trong layer. */
|
|
420
|
+
@layer base { [data-wf-block] { position: relative; } }
|
|
421
|
+
[data-wf-block]::before { content: attr(data-wf-block); position: absolute; top: 4px; left: 4px; z-index: 5;
|
|
422
|
+
display: grid; place-items: center; width: 18px; height: 18px; border-radius: 9px; background: #1f1f1f; color: #fff; font-size: 11px; }
|
|
423
|
+
[data-wf-block][data-wf-block-out]::before { top: auto; bottom: 100%; } /* số đè chữ thì lên trên mép khối */
|
|
424
|
+
body[data-frame] .wf-bar, body[data-frame] .wf-reason { display: none; }
|
|
425
|
+
body:not([data-kho="mobile"]) [data-wf-param="nav"] { display: none; }
|
|
426
|
+
/* Màu nhấn đổi trên :root, cùng chỗ khai token: var(--primary) khai ở :root đã tính xong tại :root, đổi ở
|
|
427
|
+
body thì viền focus, ring, hover vẫn màu cũ. */
|
|
428
|
+
:root[data-mau="xam"] { --primary: #2c2c2c; } /* nấc Xám: bỏ màu nhấn, cả màu trạng thái */
|
|
429
|
+
:root:not([data-mau="xam"])[data-nhan="cham"] { --primary: #4f46e5; } /* …ngoc, cam tương tự */
|
|
430
|
+
/* Token suy từ màu nhấn, tính lại theo nấc (brand-tokens.md). Chỉ ở nấc Xám hoặc khi có nhóm Nhấn: dự án đã có
|
|
431
|
+
brand thì ở nấc Màu giữ nguyên token của dự án. */
|
|
432
|
+
:root[data-mau="xam"], :root:has([data-wf-param="nhan"]) {
|
|
433
|
+
--primary-hover: color-mix(in oklab, var(--primary) 85%, var(--background)); /* lệch về phía nền */
|
|
434
|
+
--primary-light: color-mix(in srgb, var(--primary) 5%, transparent);
|
|
435
|
+
--ring-focus: color-mix(in srgb, var(--primary) 10%, transparent);
|
|
436
|
+
--border-focus: var(--primary);
|
|
437
|
+
--chart-fill: var(--primary);
|
|
438
|
+
}
|
|
439
|
+
.wf-drawer { position: fixed; inset: 0 auto 0 0; width: 280px; translate: -100% 0; transition: translate .35s; }
|
|
440
|
+
body[data-menu-open] .wf-drawer { translate: 0 0; }
|
|
441
|
+
</style>
|
|
442
|
+
<script>
|
|
443
|
+
const params = new URLSearchParams(location.search);
|
|
444
|
+
// Mặc định: phương án khuyên dùng, Xám, màu nhấn gợi ý đầu, Desktop, ☰, Có dữ liệu.
|
|
445
|
+
const state = { v: "a", mau: "xam", nhan: "cham", kho: "desktop", nav: "menu", tt: "du-lieu" };
|
|
446
|
+
for (const key of Object.keys(state)) state[key] = params.get(key) || state[key];
|
|
447
|
+
Object.assign(document.body.dataset, state);
|
|
448
|
+
Object.assign(document.documentElement.dataset, { mau: state.mau, nhan: state.nhan }); // màu nhấn đổi trên :root
|
|
449
|
+
if (params.has("frame")) document.body.dataset.frame = "1";
|
|
450
|
+
for (const set of document.querySelectorAll("[data-wf-param]")) {
|
|
451
|
+
for (const link of set.querySelectorAll("a")) {
|
|
452
|
+
link.href = `?${new URLSearchParams({ ...state, [set.dataset.wfParam]: link.dataset.value })}`;
|
|
453
|
+
if (state[set.dataset.wfParam] !== link.dataset.value) continue;
|
|
454
|
+
link.setAttribute("aria-current", "page");
|
|
455
|
+
const currentLabel = document.querySelector(`[data-wf-current="${set.dataset.wfParam}"]`);
|
|
456
|
+
if (currentLabel) currentLabel.textContent = link.textContent;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// Công tắc Màu: một link, bấm là sang nấc kia.
|
|
460
|
+
for (const toggle of document.querySelectorAll("[data-wf-toggle]")) {
|
|
461
|
+
const param = toggle.dataset.wfToggle;
|
|
462
|
+
const isOn = state[param] === toggle.dataset.on;
|
|
463
|
+
toggle.setAttribute("aria-checked", String(isOn));
|
|
464
|
+
toggle.href = `?${new URLSearchParams({ ...state, [param]: isOn ? toggle.dataset.off : toggle.dataset.on })}`;
|
|
465
|
+
}
|
|
466
|
+
// Menu Trạng thái: thanh cuộn ngang cắt mất khối absolute, nên menu là fixed, đặt ngay dưới nút.
|
|
467
|
+
const statusMenu = document.querySelector(".wf-menu");
|
|
468
|
+
statusMenu?.addEventListener("toggle", () => {
|
|
469
|
+
const summaryRect = statusMenu.querySelector("summary").getBoundingClientRect();
|
|
470
|
+
Object.assign(statusMenu.querySelector(".wf-popover").style, { top: `${summaryRect.bottom + 6}px`, left: `${summaryRect.left}px` });
|
|
471
|
+
});
|
|
472
|
+
document.addEventListener("click", (event) => { if (statusMenu && !statusMenu.contains(event.target)) statusMenu.open = false; });
|
|
473
|
+
if (state.kho === "mobile" && !params.has("frame")) {
|
|
474
|
+
const frameSource = `?${new URLSearchParams({ ...state, kho: "desktop", frame: "1" })}`;
|
|
475
|
+
document.getElementById("wf-design").innerHTML =
|
|
476
|
+
`<div style="display:grid;place-items:center;padding:24px"><iframe src="${frameSource}" title="Mobile" style="width:375px;height:812px;border:1px solid #ddd;border-radius:24px;background:#fff"></iframe></div>`;
|
|
477
|
+
}
|
|
478
|
+
// ☰ trong khung mobile mở panel trượt; bấm lớp phủ hay Esc thì đóng.
|
|
479
|
+
for (const toggle of document.querySelectorAll("[data-wf-menu]")) {
|
|
480
|
+
toggle.addEventListener("click", () => document.body.toggleAttribute("data-menu-open"));
|
|
481
|
+
}
|
|
482
|
+
document.addEventListener("keydown", (event) => {
|
|
483
|
+
if (event.key !== "Escape") return;
|
|
484
|
+
document.body.removeAttribute("data-menu-open");
|
|
485
|
+
if (statusMenu) statusMenu.open = false;
|
|
486
|
+
});
|
|
487
|
+
for (const button of document.querySelectorAll("[data-copy]")) {
|
|
488
|
+
button.addEventListener("click", async () => {
|
|
489
|
+
await navigator.clipboard.writeText(button.dataset.copy);
|
|
490
|
+
button.setAttribute("data-copied", "");
|
|
491
|
+
setTimeout(() => button.removeAttribute("data-copied"), 1500);
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
// Số khối đè chữ hay icon thì lên trên mép khối. Gọi sau mỗi lần vẽ lại, sau hai khung hình: Tailwind bản
|
|
495
|
+
// trình duyệt dựng CSS sau khi HTML vào trang, đo ngay thì mọi khối chưa có layout, số nào cũng "đè".
|
|
496
|
+
function placeBlockNumbers() {
|
|
497
|
+
const isOverlap = (first, second) => first.left < second.right && first.right > second.left && first.top < second.bottom && first.bottom > second.top;
|
|
498
|
+
for (const block of document.querySelectorAll("[data-wf-block]")) {
|
|
499
|
+
const blockRect = block.getBoundingClientRect();
|
|
500
|
+
const numberRect = { left: blockRect.left + 4, top: blockRect.top + 4, right: blockRect.left + 22, bottom: blockRect.top + 22 };
|
|
501
|
+
const contentRects = [...block.querySelectorAll("svg, img")].map((node) => node.getBoundingClientRect());
|
|
502
|
+
const walker = document.createTreeWalker(block, NodeFilter.SHOW_TEXT);
|
|
503
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
|
504
|
+
if (!node.textContent.trim()) continue;
|
|
505
|
+
const range = document.createRange();
|
|
506
|
+
range.selectNodeContents(node);
|
|
507
|
+
contentRects.push(...range.getClientRects());
|
|
508
|
+
}
|
|
509
|
+
block.toggleAttribute("data-wf-block-out", contentRects.some((rect) => rect.width > 0 && isOverlap(rect, numberRect)));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
requestAnimationFrame(() => requestAnimationFrame(placeBlockNumbers));
|
|
513
|
+
addEventListener("resize", placeBlockNumbers);
|
|
514
|
+
</script>
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
Dự án đã có màu brand thì bỏ nhóm Nhấn; đã có logo thì bỏ nhóm Logo; app từ 6 mục chính thì bỏ nhóm Nav. Thanh dưới vẽ
|
|
518
|
+
sẵn trong trang, chỉ hiện khi `body[data-kho="mobile"][data-nav="duoi"]` (trong khung là
|
|
519
|
+
`frame=1` kèm `nav=duoi`).
|
|
520
|
+
|
|
521
|
+
- **Gửi link bấm được cho từng phương án**, không chỉ đường dẫn ảnh. Chạy một server tĩnh nền
|
|
522
|
+
trên thư mục wireframe (`python3 -m http.server <cổng> -d "$TMPDIR/evon-design"`, chạy nền),
|
|
523
|
+
rồi liệt kê mỗi phương án một dòng dạng link đầy đủ, người dùng bấm hoặc chép vào trình
|
|
524
|
+
duyệt được ngay:
|
|
525
|
+
|
|
526
|
+
```
|
|
527
|
+
- A · Lưới card + hàng lọc gọn (khuyên dùng): http://localhost:<cổng>/wireframe.html?v=a
|
|
528
|
+
- B · Danh sách + bản đồ: http://localhost:<cổng>/wireframe.html?v=b
|
|
529
|
+
- D · A gọn chữ: http://localhost:<cổng>/wireframe.html?v=d
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Kèm một dòng: *"Mỗi trang có thanh trên cùng: bật Màu, xem Mobile, xem Rỗng / Lỗi, và khung lý
|
|
533
|
+
do có sẵn câu góp ý để chép."*
|
|
534
|
+
|
|
535
|
+
Mở thử từng link (probe đã mở là được) trước khi gửi. Không chạy được server thì ghi đường
|
|
536
|
+
dẫn tệp `file://…/wireframe.html` và nói tham số `?v=` chọn phương án.
|
|
537
|
+
|
|
538
|
+
Kết bằng *"Chọn A, B hay C, kèm D, E nếu muốn (ví dụ `C + D`, `B + E`). Góp ý theo số khối
|
|
539
|
+
cũng được."* Bản dựng theo nấc Màu. Dừng chờ.
|
|
540
|
+
|
|
541
|
+
Người dùng trả lời `ok`, `dựng luôn` mà không ghi chữ cái nào thì dựng **phương án khuyên dùng**,
|
|
542
|
+
màu và nhấn theo mức đã khuyên, không hỏi lại.
|
|
543
|
+
|
|
544
|
+
## U4. Dựng thật ⚑
|
|
545
|
+
|
|
546
|
+
- **Dự án đã có UI:** giữ brand theo bảng vai màu (`review.md`, chế độ dựng lại giữ brand),
|
|
547
|
+
dáng theo gu skill: đi hết bảng **"Dáng lấy từ skill, không từ CSS cũ"** trong `review.md`
|
|
548
|
+
(dropdown, checkbox, viền, scrollbar, nút header…), lúc giao có dòng `Dáng:`. Giữ brand
|
|
549
|
+
chỉ là giữ màu theo vai, logo, font. Khung trang theo phương án đã chọn. Logic, handler, dữ liệu không đụng;
|
|
550
|
+
thứ cần dữ liệu mới thì để prop và handler rỗng, lúc giao liệt kê.
|
|
551
|
+
- **Sản phẩm mới:** audit câu 2 đã chạy ở `U1`; đi tiếp câu 3 của mục 0 trong `SKILL.md`,
|
|
552
|
+
rồi dựng theo phương án đã chọn thay cho bố cục mặc định của câu 4. Chưa có logo thì dựng
|
|
553
|
+
`ProductBrand` với dấu đã chọn ở nhóm Logo, kèm favicon từ cùng dấu (`components/logo.md`).
|
|
554
|
+
- **Đề nhiều hơn một màn:** chốt hợp đồng nguyên tố `D1` (`system.md`) ở đây, trước khi dựng
|
|
555
|
+
màn đầu tiên. Wireframe đã chọn nói khung, bảng `D1` nói control nào dùng kiểu nào cho cả bộ.
|
|
556
|
+
- Ráp bằng mẫu của skill (`SKILL.md` mục 2). Chạy probe `--sweep --wireframe "<link phương án
|
|
557
|
+
đã chọn>&mau=mau"` tới khi danh sách `P` trống, tối đa ba vòng. **Mục probe về dáng cũng sửa**, dù không nằm trong `P`: hàng nút
|
|
558
|
+
header không đồng cỡ, vòng focus, control gốc, lớp nổi không chuyển động, viền trang trí
|
|
559
|
+
đậm, thanh cuộn. Ở `U4` dáng là của skill, nên đó không phải "Lệch hệ để tuỳ" như lúc soi.
|
|
560
|
+
**Danh sách `P` tính cả khung app trên route đó** (header, sidebar,
|
|
561
|
+
thanh dưới, menu thông báo): người dùng nhìn cả màn, không chỉ phần mới dựng. Khung app lỗi
|
|
562
|
+
thì sửa luôn, sửa ở component dùng chung và nói nó đổi cả các màn khác. Trang
|
|
563
|
+
dựng lại đúng bố cục mà header vẫn bị bóp thì người xem vẫn chấm "xấu".
|
|
564
|
+
- **Wireframe vẽ khung app (header, sidebar) thì khung app cũng là phương án**: dựng lại
|
|
565
|
+
component dùng chung theo wireframe (số mục, mục nào nút đặc, mục nào chỉ icon), dáng theo
|
|
566
|
+
gu, màu theo vai màu. Không để nguyên header cũ rồi chỉ vá cho khỏi rớt dòng.
|
|
567
|
+
- **Dựng đúng wireframe đã chọn, không bịa.** Wireframe ở nấc Màu là bản đặc tả, đã dùng token
|
|
568
|
+
và component của bản dựng (`U3`): bản dựng là nó viết lại bằng code dự án. **Không thêm** mục, dòng chữ, badge, nút, khối mà wireframe không có;
|
|
569
|
+
**không bỏ** thứ wireframe có; không đổi thứ tự. Thấy wireframe thiếu gì thì hỏi hoặc ghi
|
|
570
|
+
một dòng lúc giao, không tự chêm vào.
|
|
571
|
+
- **Khoảng cách, cỡ, màu, chữ chép nguyên từ wireframe, tới từng px** ⚑. Padding của khung trang
|
|
572
|
+
và từng khối, `gap` giữa các khối và trong hàng, chiều cao header, ô tìm, nút, chip, bề rộng
|
|
573
|
+
sidebar, cỡ ảnh card, cỡ, độ đậm, dòng cao của chữ, cỡ icon; màu chữ, nền, viền theo token
|
|
574
|
+
wireframe đã dùng. Chữ cũng chép nguyên: tiêu đề, placeholder, câu đếm kết quả, dòng cuối danh
|
|
575
|
+
sách, nhãn nút. Cách làm: mở file wireframe, **với từng khối chép chuỗi class sang bản dựng**
|
|
576
|
+
(wireframe đã viết bằng class và token của dự án, `U3`), không viết lại theo trí nhớ hay theo
|
|
577
|
+
thói quen của dự án.
|
|
578
|
+
- **Khung trang có sẵn của dự án (container, layout, `PageShell`) padding khác wireframe thì
|
|
579
|
+
wireframe thắng**: đổi padding ở khung đó (dùng chung thì nói nó đổi các màn khác, như khung
|
|
580
|
+
app ở dòng trên), không để khung cũ đẩy cả trang lệch. Đây là chỗ lệch hay gặp nhất.
|
|
581
|
+
- Giá trị wireframe không có trong thang spacing của dự án thì dùng đúng px đó (`pt-[18px]`),
|
|
582
|
+
không làm tròn sang bậc gần nhất: làm tròn là lệch vài px ở mỗi khối, cộng dồn xuống cuối trang.
|
|
583
|
+
- **Chạy probe kèm `--wireframe "<link phương án đã chọn>&mau=mau"`** (link `U3`, server đã tắt
|
|
584
|
+
thì `file://$TMPDIR/evon-design/wireframe.html?v=<chữ cái>&mau=mau`). Probe mở cả hai ở 1440 và
|
|
585
|
+
375, neo theo chữ, placeholder, icon, báo **đúng khoảng lệch** (*"«Phòng trọ» → ô tìm: bản dựng
|
|
586
|
+
33px, wireframe 16px"*), cỡ chữ, độ đậm, cỡ icon khác, màu chữ, màu icon, nền khác (gom theo
|
|
587
|
+
cặp màu, một token sai là một dòng), chữ và icon thiếu hay thêm. Thiếu `mau=mau` thì probe
|
|
588
|
+
không so màu, vì nấc Xám cố ý tắt màu nhấn. Các mục đó vào danh sách
|
|
589
|
+
`P`, sửa tới khi trống như mọi mục `P`. Chỉ được lệch khi người dùng dặn hoặc dữ liệu thật khác
|
|
590
|
+
wireframe (chữ dài hơn nên thêm dòng); lúc giao ghi từng chỗ.
|
|
591
|
+
|
|
592
|
+
Lệch vài px mỗi khối, đổi chữ đếm, đổi placeholder, mất dòng cuối danh sách thì kéo thanh so
|
|
593
|
+
sánh wireframe với bản dựng qua lại là mọi khối nhảy.
|
|
594
|
+
- **Mục điều hướng trỏ tới màn ngoài đề** (sidebar có "Bệnh nhân" mà đề chỉ xin hồ sơ một người):
|
|
595
|
+
màn đó chưa qua `U2`, chưa có wireframe, nên không tự nghĩ bố cục. Dựng tối giản theo khuôn mặc
|
|
596
|
+
định của skill cho loại màn đó (danh sách thì "Danh sách có bộ lọc" trong `layouts/app.md`: ô tìm,
|
|
597
|
+
dòng theo `components/list-row.md` có giá trị so sánh bên phải), rồi ghi một dòng lúc giao:
|
|
598
|
+
*"Màn [X] ngoài đề, dựng tạm để menu không dẫn vào trang trống; muốn làm thật thì nhắn."* Đừng tự
|
|
599
|
+
nghĩ bố cục: ra cột tên + mã rộng 1500px, nửa phải trống, không ô tìm, không giá trị so sánh.
|
|
600
|
+
- **Đối chiếu wireframe từng khối trước khi giao.** Mở ảnh wireframe đã chọn cạnh ảnh 1440
|
|
601
|
+
của bản dựng, đi từng khối (header, sidebar, hàng lọc, danh sách, panel): số mục, thứ tự,
|
|
602
|
+
mục nào nút đặc, mục nào chỉ icon, thứ gì wireframe đã bỏ. Khoảng cách và chữ thì đã có
|
|
603
|
+
phần so của probe `--wireframe` (trên), mắt lo phần probe không neo được: khối không chữ, ảnh. Khác chỗ nào thì sửa, hoặc ghi
|
|
604
|
+
một dòng vì sao lệch (thiếu dữ liệu, người dùng dặn). Màu thì theo "Mỗi vai đúng một mã
|
|
605
|
+
màu" trong `review.md`: wireframe xám không nói màu, nhưng bản dựng phải ăn nhập từ viền
|
|
606
|
+
tới brand. Tin giao có bảng *"Đối chiếu wireframe"*: khối, wireframe có gì, bản dựng có
|
|
607
|
+
gì, khớp hay lý do lệch. Kèm số dòng chữ mỗi mục ở hai bên (probe "mục lặp dày chữ").
|
|
608
|
+
- **Dựng xong chạy một lượt làm gọn** trên các khối mới **và khung app của route**: `V1b` và `V1c` trong `review.md`
|
|
609
|
+
(card cao thấp theo dòng có dòng không, link trông như chữ thường, nửa khối trống ở màn
|
|
610
|
+
rộng). Sửa luôn, không đưa bảng: người dùng đã chọn phương án rồi.
|
|
611
|
+
- **Thứ không được tự sửa thì nêu ra, đừng giữ im lặng** ⚑ (dự án đã có UI). Lượt làm gọn chỉ
|
|
612
|
+
sửa dáng và sắp lại. Các dòng `V1b` loại bỏ, ẩn, gộp thông tin, gom màu trang trí, chỗ
|
|
613
|
+
vai màu tranh nhau (badge nhiều màu nặng hơn giá), và mọi chỗ **nhận diện** trông xấu theo
|
|
614
|
+
`V1` trong `review.md` (màu vai, font, logo, khối màu đậm) là quyết định sản phẩm và nhận diện: bản
|
|
615
|
+
dựng giữ nguyên, nhưng tin giao có mục **"Còn thấy"**, tối đa năm dòng đánh số. Mỗi dòng
|
|
616
|
+
một vấn đề người dùng cuối vấp kèm hướng sửa, nói bằng thứ bậc chứ không bằng màu
|
|
617
|
+
(`V1b`, "Không phải lối vòng để đổi màu"): *"1. Card nào cũng có badge, năm màu đè trên
|
|
618
|
+
ảnh, mắt dừng ở badge trước giá. Chỉ giữ badge ở phòng khác đi (giảm giá, đã xác thực)."*
|
|
619
|
+
Kết bằng *"Muốn sửa dòng nào thì trả lời số, ví dụ `sửa 1, 3`."* Không thấy gì thì ghi
|
|
620
|
+
"Còn thấy: không". Đây không phải cổng: bản dựng đã giao xong, người dùng trả lời hay không
|
|
621
|
+
tuỳ họ. **Lỗi dáng mà skill đã có luật thì sửa trước khi giao, không đẩy vào "Còn thấy"**:
|
|
622
|
+
mục chỉ dành cho thứ bản dựng không được tự quyết (ví dụ hàng đếm 375px cắt mục cuối không mép
|
|
623
|
+
mờ theo `R10`, email xuống dòng ở gạch nối tên miền theo `description-list.md`: sửa, không đưa
|
|
624
|
+
vào "Còn thấy").
|
|
625
|
+
- **Tự soi bằng mắt trước khi giao, ghi ra.** Mở ảnh 375, 1440 và 1920 của probe, trả lời
|
|
626
|
+
từng câu thành một dòng trong tin giao (câu nào có lỗi thì sửa trước, rồi mới ghi "không"):
|
|
627
|
+
1. Card, dòng cùng loại có cao thấp khác nhau vì có dòng thiếu một mẩu không?
|
|
628
|
+
2. Thứ bấm được (link "Xem thêm", nút chữ) có trông như chữ thường không?
|
|
629
|
+
3. Trong một màn có bao nhiêu khung viền đứng cạnh hay lồng nhau? Gộp được khung nào?
|
|
630
|
+
4. Ở 1920, chỗ nào trống mà không có lý do (nửa card, hai bên nội dung)?
|
|
631
|
+
5. Thứ nặng nhất màn (đậm nhất, màu nhất) có đúng là việc chính ở `U2` không?
|
|
632
|
+
|
|
633
|
+
Không ghi mấy dòng này thì coi như chưa soi. Người dùng tự phát hiện ra lỗi nằm trong năm
|
|
634
|
+
câu này là skill chưa làm xong việc.
|
|
635
|
+
- **Lúc giao** nói bằng ngôn ngữ trải nghiệm, không bằng class: việc chính giờ làm trong mấy
|
|
636
|
+
bước, thấy ngay ở khổ nào; ảnh trước và sau ở 1280 và 375, là link bấm được và một trang
|
|
637
|
+
`so-sanh.html` như `V5` trong `review.md` (cả ảnh của mục "Còn thấy"); danh sách thứ cần bạn nối logic
|
|
638
|
+
hay thêm dữ liệu. Cuối tin một dòng **Muốn chỉnh thì nhắn** với 3–4 câu ngắn chọn theo bản vừa
|
|
639
|
+
dựng, như khung lý do của `U3` (*"Thêm màu ở header"*, *"Tiêu đề đậm hơn"*, *"Thoáng hơn"*,
|
|
640
|
+
*"Đổi font"*). Người dùng thường chỉ thấy "chưa đã" mà không gọi được tên.
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
## U5. Không làm
|
|
645
|
+
|
|
646
|
+
- Không moodboard, không hi-fi mock riêng rồi dựng lại: với skill này code chính là hi-fi.
|
|
647
|
+
- Không tự thêm tính năng ngoài `U2` (chat, thông báo, đánh giá) cho "đủ bộ".
|
|
648
|
+
- Không đổi vai màu của dự án đã có, trừ khi người dùng nói bỏ style cũ (`review.md`, chế độ
|
|
649
|
+
dựng lại theo gu skill).
|
|
650
|
+
- Không quay về nhánh `V` giữa chừng để "vá cho nhanh": người dùng đã xin nghĩ lại khung.
|