oh-my-design-cli 1.8.2 → 1.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.ja.md +5 -5
  2. package/README.ko.md +7 -7
  3. package/README.md +7 -7
  4. package/README.zh-TW.md +5 -5
  5. package/agents/omd-master.md +1 -1
  6. package/data/reference-fingerprints.json +1696 -17
  7. package/package.json +3 -1
  8. package/skills/omd-feel/SKILL.md +152 -0
  9. package/skills/omd-feel/provenance.md +233 -0
  10. package/skills/omd-feel/reference.md +254 -0
  11. package/skills/omd-harness/SKILL.md +1 -1
  12. package/skills/omd-init/SKILL.md +1 -1
  13. package/skills/omd-reference-capture/SKILL.md +1 -1
  14. package/web/references/591/DESIGN.md +460 -0
  15. package/web/references/airbridge/DESIGN.md +451 -0
  16. package/web/references/asana/DESIGN.md +485 -0
  17. package/web/references/asos/DESIGN.md +475 -0
  18. package/web/references/bahamut/DESIGN.md +416 -0
  19. package/web/references/bbc/DESIGN.md +439 -0
  20. package/web/references/bigin/DESIGN.md +454 -0
  21. package/web/references/buzzvil/DESIGN.md +457 -0
  22. package/web/references/cafe24/DESIGN.md +472 -0
  23. package/web/references/chunghwa/DESIGN.md +419 -0
  24. package/web/references/codeit/DESIGN.md +470 -0
  25. package/web/references/databricks/DESIGN.md +467 -0
  26. package/web/references/datarize/DESIGN.md +451 -0
  27. package/web/references/deliveroo/DESIGN.md +458 -0
  28. package/web/references/doordash/DESIGN.md +429 -0
  29. package/web/references/easywallet/DESIGN.md +449 -0
  30. package/web/references/elice/DESIGN.md +445 -0
  31. package/web/references/esunbank/DESIGN.md +445 -0
  32. package/web/references/farfetch/DESIGN.md +436 -0
  33. package/web/references/fubon/DESIGN.md +438 -0
  34. package/web/references/furiosaai/DESIGN.md +450 -0
  35. package/web/references/goorm/DESIGN.md +470 -0
  36. package/web/references/govuk/DESIGN.md +450 -0
  37. package/web/references/greencar/DESIGN.md +420 -0
  38. package/web/references/hackle/DESIGN.md +472 -0
  39. package/web/references/hana/DESIGN.md +439 -0
  40. package/web/references/hubspot/DESIGN.md +485 -0
  41. package/web/references/hwahae/DESIGN.md +453 -0
  42. package/web/references/hyundai/DESIGN.md +468 -0
  43. package/web/references/icook/DESIGN.md +432 -0
  44. package/web/references/instacart/DESIGN.md +439 -0
  45. package/web/references/ipassmoney/DESIGN.md +407 -0
  46. package/web/references/kakaopage/DESIGN.md +439 -0
  47. package/web/references/kbpay/DESIGN.md +442 -0
  48. package/web/references/kcd/DESIGN.md +432 -0
  49. package/web/references/kia/DESIGN.md +411 -0
  50. package/web/references/kyobobook/DESIGN.md +445 -0
  51. package/web/references/lablup/DESIGN.md +474 -0
  52. package/web/references/lemonbase/DESIGN.md +452 -0
  53. package/web/references/liner/DESIGN.md +465 -0
  54. package/web/references/makinarocks/DESIGN.md +442 -0
  55. package/web/references/monzo/DESIGN.md +461 -0
  56. package/web/references/moreh/DESIGN.md +437 -0
  57. package/web/references/naverpay/DESIGN.md +478 -0
  58. package/web/references/neosapience/DESIGN.md +441 -0
  59. package/web/references/nota/DESIGN.md +451 -0
  60. package/web/references/octopusenergy/DESIGN.md +436 -0
  61. package/web/references/openpoint/DESIGN.md +431 -0
  62. package/web/references/paypal/DESIGN.md +459 -0
  63. package/web/references/portone/DESIGN.md +446 -0
  64. package/web/references/queenit/DESIGN.md +432 -0
  65. package/web/references/rebellions/DESIGN.md +449 -0
  66. package/web/references/reddit/DESIGN.md +537 -0
  67. package/web/references/returnzero/DESIGN.md +460 -0
  68. package/web/references/samsung/DESIGN.md +465 -0
  69. package/web/references/saramin/DESIGN.md +465 -0
  70. package/web/references/shiftee/DESIGN.md +468 -0
  71. package/web/references/shinhanbank/DESIGN.md +429 -0
  72. package/web/references/skyscanner/DESIGN.md +563 -0
  73. package/web/references/snapchat/DESIGN.md +460 -0
  74. package/web/references/solapi/DESIGN.md +483 -0
  75. package/web/references/squarespace/DESIGN.md +454 -0
  76. package/web/references/ssg/DESIGN.md +439 -0
  77. package/web/references/starling/DESIGN.md +404 -0
  78. package/web/references/supertone/DESIGN.md +413 -0
  79. package/web/references/taiwanmobile/DESIGN.md +445 -0
  80. package/web/references/trainline/DESIGN.md +454 -0
  81. package/web/references/vuno/DESIGN.md +413 -0
  82. package/web/references/weverse/DESIGN.md +437 -0
  83. package/web/references/zoom/DESIGN.md +457 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-design-cli",
3
- "version": "1.8.2",
3
+ "version": "1.8.7",
4
4
  "description": "Bootstrap oh-my-design skills + agents into your project. After install, talk to your AI coding agent in natural language — no other CLI commands.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,6 +24,7 @@
24
24
  "skills/omd-locale-adapter",
25
25
  "skills/omd-designer-review",
26
26
  "skills/omd-final-qa",
27
+ "skills/omd-feel",
27
28
  "skills/omd-codex-image",
28
29
  "skills/claude-design",
29
30
  "agents",
@@ -51,6 +52,7 @@
51
52
  "prepare": "npm run build",
52
53
  "prepublishOnly": "npm run build && node scripts/gen-llms-full.cjs && bash scripts/check-release-hygiene.sh pack",
53
54
  "gen:llms-full": "node scripts/gen-llms-full.cjs",
55
+ "check-counts": "node scripts/check-counts.mjs",
54
56
  "postinstall": "node scripts/postinstall.cjs"
55
57
  },
56
58
  "keywords": [
@@ -0,0 +1,152 @@
1
+ ---
2
+ name: omd:feel
3
+ description: "디자인·프론트 업계가 '감'으로 쓰던 인터페이스 디테일을 수치화한 규칙으로 적용(APPLY)하거나 감사(AUDIT)한다. Jakub Krehel의 make-interfaces-feel-better 철학을 계승 + HIG/Material/WCAG/DS 토큰/실무자 리서치로 확장한 17축·113규칙(provenance 등급별). 모션 타이밍·이징·동심원 radius·tabular-nums·44px 타깃·focus ring·prefers-reduced-motion 등. 'feel 좋게 다듬어줘', '인터페이스 디테일 적용', 'feel 점검', '마이크로 인터랙션 손봐줘', 'make this feel better', 'polish the interactions', 「インターフェースの細部を詰めて」, 「介面細節打磨」 류에 트리거. 브랜드 토큰은 DESIGN.md(omd:apply)가 우선."
4
+ user-invocable: true
5
+ ---
6
+
7
+ # omd:feel — Interface Feel, Quantified
8
+
9
+ 업계가 **감(感)으로 쓰던** 인터페이스 디테일 — "이 정도 속도", "이 정도 그림자", "이 정도 여백" — 을 **출처 등급이 매겨진 체크 가능한 숫자**로 바꿔 적용하고 감사하는 스킬. Jakub Krehel의 *"great interfaces are a collection of small things that compound"* 명제를 계승하되, **어떤 숫자를 맞춰야 하는지 / 언제 틀렸는지**까지 수치로 답한다.
10
+
11
+ 핵심 통찰: **품질은 수십 개 작은 제약을 각자의 band 안에 가둔 것의 적분**이다. 600ms짜리 체크박스 틱, 1px focus ring, 본문 2.85:1 대비, `@media (hover:hover)` 게이트 없는 hover lift — 하나하나가 "feel"에서 측정 가능한 차감이다. 이 스킬은 그 차감을 막는다.
12
+
13
+ 전체 규칙(17축·113개)은 [`reference.md`](./reference.md)에, 각 숫자의 출처·tier·방법론은 [`provenance.md`](./provenance.md)에 있다. 아래 **cheat-sheet**는 항상 로드되는 핵심이고, 깊은 감사·엣지 축은 reference를 읽는다.
14
+
15
+ ## 언제 쓰나
16
+
17
+ - **APPLY** — UI를 새로 만들거나 고칠 때 정량 feel 규칙을 주입. ("feel 좋게", "인터랙션 손봐줘", "디테일 적용")
18
+ - **AUDIT** — 기존 HTML/JSX/TSX/CSS를 규칙표 대비 점검해 feel-score + BLOCK/WARN/FYI 리포트. ("feel 점검", "이 화면 감사해줘")
19
+
20
+ UI 작업이 아니면 트리거하지 않는다. 카피·정보구조만 다루는 작업은 대상 아님.
21
+
22
+ ## 0. 출처 등급 → 강제 강도 (이 스킬의 심장)
23
+
24
+ 모든 규칙은 tier를 달고 있고, **tier가 강제 강도를 정한다.** 이게 "감을 정직하게 수치화"하는 방법 — spec 숫자는 게이트, 취향 숫자는 제안.
25
+
26
+ | Badge | Tier | APPLY | AUDIT severity |
27
+ |---|---|---|---|
28
+ | 🟢 **SPEC** | spec-authoritative (W3C/WCAG·HIG·normative) | 강제 | **BLOCK** |
29
+ | 🟢 **DS** | ds-token (커밋된 디자인시스템 토큰) | 강제 (브랜드가 덮지 않으면) | **BLOCK** |
30
+ | 🟡 **CONV** | industry-convention (독립 실무자 ≥2 수렴) | 기본값으로 적용 | **WARN** |
31
+ | ⚪ **OP** | single-opinion (신뢰할 만한 1인) | 제안만 | **FYI** |
32
+ | ⚪ **FOLK** | folklore-unverified (근거 추적 불가) | 제안 + tier 명시 | **FYI** |
33
+
34
+ 분포: SPEC 61 · DS 24 · CONV 19 · OP 7 · FOLK 2 → **85/113이 spec·token 근거(강제 가능)**.
35
+
36
+ > **DESIGN.md가 항상 이긴다.** 브랜드 `DESIGN.md` 토큰이 규칙과 충돌하면 브랜드 토큰이 권위. 이 규칙들은 브랜드가 **침묵할 때의 기본값**이지 override가 아니다. 프로젝트 루트에 DESIGN.md가 있으면 `omd:apply`로 먼저 토큰을 읽고, 빈 축만 feel 규칙으로 채운다.
37
+
38
+ ## 1. Cheat-sheet — 항상 적용하는 핵심 숫자
39
+
40
+ > tier 표기: 🟢=강제, 🟡=기본값, ⚪=제안. 충돌 시 DESIGN.md > 🟢 > 🟡 > ⚪.
41
+
42
+ ### Motion & Timing
43
+ - **일상 UI 전환 ~200ms** (hover ≤150 · popover ~200 · overlay/modal ~300). 범위 150–300ms, **>400ms = 굼떠 보임**. 🟡
44
+ - duration은 **50ms 그리드**에 양자화 (220/333ms 금지). 🟢
45
+ - **exit ≈ 0.8 × enter** (열 때보다 닫을 때 빠르게). 🟢
46
+ - 한 개의 전역 duration 금지 — 이동 면적/거리에 비례. 🟢
47
+
48
+ ### Easing & Springs
49
+ - **enter/사용자 트리거 = ease-out** `cubic-bezier(0,0,0.2,1)` · **exit = ease-in/accelerate** · 이미 보이는 morph = ease-in-out · 스피너/progress = linear. **enter에 ease-in 금지.** 🟡
50
+ - Apple = spring 기본 `(response 0.55, damping 0.825, bounce 0)`, 기능적 UI는 bounce <0.4. 🟢
51
+ - **interruptible**: 인터랙티브 상태는 transition/spring(재타깃 가능), one-shot 연출만 `@keyframes`. 🟢
52
+
53
+ ### Spacing · Radius · Rhythm
54
+ - **8px base, 4px half-step** (4·8·12·16·24·32·40·48·64). 🟢
55
+ - **내부 padding ≤ 형제 간 외부 gap.** 🟡
56
+ - 동심원 radius: **outer = inner + padding.** 🟢
57
+ - UI 타입 스케일 ~**1.25**(major third). 🟡
58
+
59
+ ### Shadow & Depth
60
+ - **레이어드 그림자 3–5겹**, 광원은 위 하나. 저고도=tight+sharp, 고고도=diffuse+spread (Tailwind md→lg 램프). 🟡/DS
61
+ - **다크모드는 그림자보다 밝은 표면**으로 깊이를 낸다. 🟡
62
+
63
+ ### Typography
64
+ - 본문 **line-height 1.5**, **measure ~66ch** (45–75). 🟢
65
+ - display tracking **−0.02~−0.03em**, all-caps **+0.05~0.1em**, 본문 ~0. 🟡
66
+ - 변하는 숫자엔 **tabular-nums**; 제목 **text-wrap: balance**, 본문 **pretty**; 레이아웃에 **antialiased**. (seed/jakub) 🟡
67
+
68
+ ### Color & Contrast
69
+ - 텍스트 **4.5:1**(large 3:1) AA · 비텍스트/UI 경계 **3:1** · focus ring **3:1 + 2px + ~2px offset**. 🟢
70
+ - M3 state layer: **hover 8% · focus 10% · pressed 10% · dragged 16%.** 🟢
71
+ - disabled ~**38% opacity**. 🟡
72
+
73
+ ### States & Feedback
74
+ - 데이터 컴포넌트는 **5 상태**(empty/loading/error/content/skeleton) 전부 정의. 🟡
75
+ - pressed: **scale 0.96–0.97, 150ms ease-out** (바닥 0.95). DS/⚪
76
+ - 아이콘 상태 스왑: scale+opacity+blur 4px→0, spring ~0.3. 🟡
77
+
78
+ ### Targets & Pointer
79
+ - 탭 타깃 **44pt(iOS/WCAG AAA) / 48dp(Material) / 24px(WCAG AA 최소)**, 인접 타깃 간 **≥8pt**. 🟢
80
+ - **cursor**: 인터랙티브=pointer · 비활성=not-allowed · 드래그=grab→grabbing · 편집=text. 🟡
81
+
82
+ ### Scroll & Gesture
83
+ - 오버레이/중첩 스크롤러에 **`overscroll-behavior: contain`** (스크롤 체이닝·PTR 차단). 🟢
84
+ - 스와이프 해제 임계 **~30% 거리 또는 velocity flick**. DS
85
+ - scroll-snap: 롱폼=proximity · 캐러셀=mandatory. 🟢
86
+
87
+ ### Response Time & Perceived Perf
88
+ - **100ms = 즉각 · 1s = 사고 흐름 한계 · 10s = 주의 한계(% progress 표시).** Doherty **400ms** ceiling. 🟢
89
+ - **INP ≤200ms p75, 프레임 예산 16.6ms.** 🟢
90
+ - 스피너는 예상 대기 **>1s일 때만**; 띄웠으면 **최소 500ms 유지**(깜빡임 금지). 레이아웃을 알면 skeleton. 🟡/DS
91
+
92
+ ### Layout Stability (CLS)
93
+ - **CLS ≤0.1 p75.** 미디어는 width/height 또는 **aspect-ratio**로 자리 예약. **`scrollbar-gutter: stable`**. `font-display: optional`(또는 swap)+metric override. 입력 500ms 창 밖에서 콘텐츠 위쪽 삽입 금지. 🟢
94
+
95
+ ### Modals · Overlays · Z-index
96
+ - `role=dialog` + **`aria-modal=true`**, 열 때 다이얼로그로 focus, **Tab trap, Esc 닫기, 닫을 때 트리거로 focus 복원, 배경 inert**. scrim `#000 ~32%`. 🟢
97
+ - light-dismiss: 임시/정보 다이얼로그는 ON, **데이터 입력·파괴적 액션은 OFF.** 🟢
98
+ - z 사다리: dropdown 1000 / sticky 1100 / fixed 1200 / backdrop 1300 / modal 1400 / popover 1500 / toast 1600 / tooltip 1700. 오버레이는 **portal**. (gap-fill)
99
+
100
+ ### Performance · Glass · A11y
101
+ - 애니메이션은 **transform/opacity(+filter)만**, **`transition: all` 금지**; `will-change`는 transient(쉴 때 제거). 🟢
102
+ - glass: `backdrop-filter: blur(16px) saturate(180%)` + 반투명 fill ~0.6 + 1px hairline + **불투명 fallback**, 텍스트 ≥4.5:1. 🟡/🟢
103
+ - **`prefers-reduced-motion: reduce` → cross-fade/즉시 전환**, 큰 이동·parallax 제거(작은 opacity는 유지). ≤3 flashes/s. **focus outline은 대체 없이 제거 금지**(`:focus-visible`). 🟢
104
+
105
+ ### Form Validation
106
+ - 1차 검증은 **blur에서**, 한 번 에러난 필드는 **on-change(라이브)**로 전환(고치는 즉시 에러 해제). async 검증 debounce **300–500ms**. 🟡
107
+
108
+ ## 2. APPLY 워크플로
109
+
110
+ 1. **컨텍스트 감지** — 프로젝트 루트/대상 폴더에 `DESIGN.md`가 있나? 있으면 `omd:apply` 절차로 토큰(색·radius·spacing·motion·type) 먼저 read. 브랜드 토큰 = 권위.
111
+ 2. **빈 축 채우기** — DESIGN.md가 침묵하는 축만 cheat-sheet 기본값으로. 깊은 축(스크롤 물리·glass·z 사다리·CLS)은 `reference.md`의 해당 섹션 참조.
112
+ 3. **tier 존중** — 🟢는 그대로 적용, 🟡는 기본값(브랜드/요청이 덮으면 양보), ⚪/FOLK는 **"이건 취향 휴리스틱이에요"라고 밝히고** 제안만.
113
+ 4. **출력에 근거 남기기** — 비자명한 숫자(예: exit 0.8×, focus offset 2px)는 한 줄로 왜인지 + tier를 코드 주석이나 설명에 남긴다. 숫자를 **발명하지 말 것** — cheat-sheet/reference에 없으면 reference를 읽거나 모른다고 말한다.
114
+ 5. **prefers-reduced-motion은 항상** 동반 — 모션을 넣으면 reduce 분기도 같이 넣는다 (A11y는 옵션이 아님).
115
+
116
+ ## 3. AUDIT 워크플로
117
+
118
+ `omd:designer-review`와 같은 advisory(읽기 전용) 패턴 + feel 축. 매 호출 DESIGN.md 재read(캐싱 금지).
119
+
120
+ 1. **입력**: `artifact_path`(HTML/JSX/TSX/CSS) + (있으면) `design_md_path` + viewport.
121
+ 2. **machine_check 스캔** — `reference.md`의 각 규칙 `🔍 machine_check`를 artifact에 적용. 예:
122
+ - `transition: all` / >400ms duration / off-grid duration → 검출
123
+ - 탭 타깃 <44px, focus outline 제거, 텍스트 대비 <4.5:1
124
+ - aspect-ratio 없는 `<img>`, `aria-modal` 없는 modal, `overscroll-behavior` 없는 오버레이
125
+ 3. **severity = tier 매핑** — SPEC/DS 위반 = **BLOCK**, CONV = **WARN**, OP/FOLK = **FYI**. (severity inflation/deflation 금지.)
126
+ 4. **feel-score** — 적용 가능한 축 중 in-band 비율: `🟢 in-band / 🟢 applicable`을 핵심 숫자로, WARN/FYI는 부차. "feel-score 18/22 SPEC in-band, BLOCK 2, WARN 5".
127
+ 5. **출력**: `<work_dir>/.reviews/feel-audit-round-<N>.md` — issue마다 **line ref + tier + machine_check 근거 + fix(actionable)**. line ref 없는 issue·"looks good" rubber-stamp 금지.
128
+
129
+ ```markdown
130
+ ### [BLOCK] Tap target below minimum — icon button
131
+ - **Location:** `components/Toolbar.tsx:31`
132
+ - **Rule:** §8 Targets — 44pt/48dp/24px min (🟢 SPEC, WCAG 2.5.8)
133
+ - **Evidence:** `<button className="w-6 h-6">` → 24px, hit-slop 없음
134
+ - **Fix:** 패딩으로 44×44 hit region 확보 (`p-2.5` + 시각 24px 아이콘 유지)
135
+ ```
136
+
137
+ ## 4. 가드레일
138
+
139
+ - ❌ **folklore를 spec처럼 제시 금지.** ⚪/FOLK는 항상 tier를 밝힌다. (corpus Appendix B의 demote 목록 — Hick-Hyman 0.155s/bit, Fitts ms/bit, rubberband 0.7, pressed 0.95 바닥, exit 0.8×, stagger 80/100ms, optical 2px 등 — 은 **숫자 게이트로 강제하지 말 것**, 방향만 차용.)
140
+ - ❌ **숫자 발명 금지.** cheat-sheet/reference에 없으면 reference를 읽거나 "모름"이라고 말한다.
141
+ - ❌ **DESIGN.md 무시하고 일반 best-practice로 덮기 금지.** 브랜드 토큰이 먼저.
142
+ - ❌ **모션을 넣고 `prefers-reduced-motion` 분기 누락 금지.**
143
+ - ❌ AUDIT에서 severity inflation(다 BLOCK)·deflation(BLOCK을 FYI로)·line ref 없는 issue 금지.
144
+ - ❌ 숫자 변경이 필요하면 `reference.md`를 손으로 고치지 말고 **corpus → 제너레이터로 재생성**.
145
+
146
+ ## 5. omd 생태계 관계
147
+
148
+ - **`omd:apply`** — 브랜드 DESIGN.md 토큰 적용. omd:feel은 그 위에서 토큰이 침묵하는 *느낌* 층을 채운다.
149
+ - **`omd:designer-review`** — 브랜드 일관성(typo/color budget/radius scale) 감사. omd:feel AUDIT은 그 **feel 축 짝꿍**(모션 타이밍·타깃·CLS·a11y).
150
+ - **`omd:taste`** — 사용자 *취향* 대시보드(별개 개념). 반복되는 feel 결정이 취향으로 굳으면 `omd:remember`로 흘려보낸다.
151
+
152
+ > **수동 검증**: `transition: all 0.6s` + `<button class="w-5 h-5">` + `prefers-reduced-motion` 분기 없는 컴포넌트를 AUDIT하면 — BLOCK 최소 3건(transition all=GPU SPEC, 20px 타깃=WCAG SPEC, reduced-motion 누락=a11y SPEC)이 line ref와 함께 뜨고 feel-score가 그만큼 깎여야 한다.
@@ -0,0 +1,233 @@
1
+ # omd:feel — Provenance & Methodology
2
+
3
+ **Date:** 2026-06-23
4
+ **What this is:** the evidence layer behind [`reference.md`](./reference.md). Every quantified rule traces to a source and a provenance tier, so an AI (or a reviewer) can tell a *spec gate* from a *taste heuristic*. The full rule bodies (default/range/anti-pattern/machine_check) live in `reference.md`; this file adds **where each number comes from**.
5
+ **Seed:** Jakub Krehel, *Details That Make Interfaces Feel Better* / `jakubkrehel/make-interfaces-feel-better` (https://jakub.kr/writing/details-that-make-interfaces-feel-better).
6
+
7
+ ## Methodology
8
+
9
+ Multi-agent research workflow (44 agents): Seed → 14-category Extract → adversarial provenance Verify → dedup Synthesize → completeness Critic + Gap-fill.
10
+
11
+ **Result:** 113 synthesized rules across 17 dimensions, from 224 verified findings + 21 seed rules.
12
+
13
+ **Tier distribution:** SPEC 61, DS 24, CONV 19, OP 7, FOLK 2 — 85 of 113 spec/token-backed (enforceable).
14
+
15
+ ## Extended philosophy
16
+
17
+ Jakub Krehel's thesis — "great interfaces are a collection of small things that compound" — is correct but, as stated, unfalsifiable: it tells you to care about details without telling you which numbers a detail should hit, or how to know when you've gotten one wrong. This corpus closes that gap. It converts the gut-feel of senior designers into a numbered, provenance-graded, machine-checkable system: every rule carries a default value, a defensible range, the curve or unit it lives in, an anti-pattern that names the failure mode, and a `machine_check` that an auditor (or an AI) can run against real DOM/CSS/native code. The compounding claim becomes operational — quality is the integral of dozens of small constraints each held inside its band, and a single out-of-band value (a 600ms checkbox tick, a 1px focus ring, body text at 2.85:1, a hover lift with no `@media (hover: hover)` gate) is a measurable subtraction from "feel," not a vibe.
18
+
19
+ The extension that makes this AI-applicable is honest provenance tiering. Not all numbers are equal: WCAG normative criteria, Apple HIG verbatim values, and committed design-system tokens (Carbon, Fluent, Polaris, Primer, M3) are load-bearing and can be enforced as gates; convergent multi-practitioner conventions (the ~150-300ms UI band, exit ≈ 0.8× enter, ease-out for enters) are strong defaults; and single-opinion craft tricks (Comeau's 2:1 shadow ratio, transient blur on crossfades) or outright folklore ("skeletons feel 30% faster," "1px @ 10% image hairline") are kept but *labeled* so downstream systems treat them as heuristics, never specs. An AI generating UI should therefore (1) hard-enforce spec-authoritative and ds-token rules, (2) apply industry-convention defaults unless overridden by a named design system, and (3) offer single-opinion/folklore items only as suggestions with their tier disclosed. The system also resolves conflicts by provenance: when M3 says state-layer focus is 10% and mdui/M2 say 12%, the M3 spec value wins and the alternative is noted — so the corpus converges on one answer per question while preserving the dissent. This is the quantified, checkable continuation of "small things compound": a machine can now audit the small things one by one.
20
+
21
+ ---
22
+
23
+ ## Per-rule provenance
24
+
25
+ ### 1. Motion & Timing (durations)
26
+
27
+ - 🟢 SPEC · **Scale transition duration to the traversed area/distance: tiny state changes get short tokens, full-screen transitions get long tokens — never one global duration.** — material-components-android Motion.md ('duration should increase as the area/traversal of an animation increases'); m3.material.io/styles/motion/overview; vercel.com/design Geist (150/200/300ms tiering)
28
+ - 🟢 SPEC · **Quantize every duration to the M3 token scale (50ms steps 50-600, then 700/800/900/1000) — never hand-pick arbitrary ms.** — m3.material.io/styles/motion/easing-and-duration/tokens-specs; material-components-android Motion.md; mdui design-tokens
29
+ - 🟡 CONV · **Keep functional UI animations under ~300ms; aim ~150-200ms for most enter/exit transitions.** — Emil Kowalski 'Great Animations' / '7 Practical Tips' (animations.dev); Rauno Freiberg raunofreiberg/interfaces (≤200ms 'feel immediate'); nngroup.com/articles/animation-duration (100-500ms)
30
+ - 🟢 DS · **Use the Carbon duration ladder for productive/enterprise UI: 70ms button/toggle, 110ms fade, 150ms small expand, 240ms toast/expansion, 400ms large expansion, 700ms backdrop dim.** — @carbon/motion src/index.ts (committed tokens); carbondesignsystem.com/elements/motion/overview
31
+ - 🟢 DS · **Use the Fluent 2 duration ramp (50-500ms in 8 steps): hover ≤100ms, standard controls 150-200ms, dialogs/panels ≥300ms.** — fluentui packages/tokens/src/global/durations.ts (committed)
32
+ - 🟢 DS · **Quantize Polaris durations to a 50ms grid 0-500ms (plus a 5000ms loop slot); never use in-between values.** — Shopify/polaris polaris-tokens/src/themes/base/motion.ts (committed MotionDurationScale)
33
+ - 🟢 DS · **Quantize Primer durations to a 100ms grid (with a 50ms half-step) 0-1000ms; keep UI transitions 100-400ms. Primer skips 150/250/350.** — primer/primitives src/tokens/base/motion/timing.json5 (committed DTCG tokens)
34
+ - 🟢 DS · **Collapse Ant Design motion into fast/mid/slow ≈100/200/300ms (computed from motionBase=0 + motionUnit=0.1).** — ant-design seed.ts + genCommonMapToken.ts (computed tokens)
35
+ - 🟢 DS · **Use 300ms entering / 250ms returning for container-transform transitions; 150ms incoming / 75ms outgoing (linear) for cross-fades.** — material-components-android Motion.md (container transform 300/250; fade 150/75 linear)
36
+ - 🟢 DS · **Make exit/dismiss/collapse animations shorter than their enter — roughly 0.8× (75-85%).** — m2.material.io/design/motion/speed (nav drawer 250/200, card 300/250); MUI enteringScreen 225 / leavingScreen 195; NN/g (popup 300/200-250)
37
+
38
+ ### 2. Easing & Springs
39
+
40
+ - 🟡 CONV · **Default to ease-out/decelerate for elements entering and any user-initiated transition; the fast start reads as instant responsiveness. Never use ease-in for enters.** — Emil Kowalski 'The Easing Blueprint' / 'Great Animations'; m1.material.io/motion/duration-easing (decelerate 0,0,0.2,1); Material decelerate token
41
+ - 🟢 SPEC · **Use the M3 standard easing cubic-bezier(0.2,0,0,1) for on-screen moves; standard-decelerate (0,0,0,1) for enters, standard-accelerate (0.3,0,1,1) for exits; emphasized variants for hero motion.** — material-components-android Motion.md; mdui design-tokens; m3.material.io/styles/motion/easing-and-duration/tokens-specs
42
+ - 🟢 SPEC · **Treat M3 'emphasized' as a 3-stop spring-like PATH curve, not a single cubic-bezier; cubic-bezier(0.2,0,0,1) is only its approximation (and is actually the 'standard' token). Use CSS linear() or an Android PathInterpolator for true emphasized motion.** — material-components-android Motion.md (emphasized = Path curve); developer.chrome.com/docs/css-ui/css-linear-easing-function
43
+ - 🟢 DS · **Use ease-in-out only for elements already on-screen that change position or shape (morphing) — not for enter/exit.** — Emil Kowalski 'The Easing Blueprint'; m1.material.io (standard 0.4,0,0.2,1); MUI easeInOut
44
+ - 🟢 DS · **Pick easing by intent and lifecycle from your DS token set — productive/practical for dense task UI, expressive/bold for hero moments; entrance curves start at (0,0), exit curves end at x=1.** — @carbon/motion src/index.ts; fluentui packages/tokens/src/global/curves.ts; Shopify/polaris base/motion.ts; primer/primitives easing.json5; uber/baseweb animation.ts; ant-design seed.ts; atlassian.design/foundations/motion
45
+ - 🟢 DS · **Reserve overshoot 'back' easings (control points outside 0-1) for playful emphasis only; never on dense/productive UI.** — ant-design seed.ts (motionEase* committed curves)
46
+ - 🟢 SPEC · **On Apple platforms, default UI motion to a spring (response 0.55, dampingFraction 0.825, blendDuration 0); think response+damping, not fixed duration. Pick smooth/snappy/bouncy by intent.** — Apple SwiftUI Animation.spring docs (defaults 0.55/0.825/0); WWDC23 'Animate with springs'
47
+ - 🟢 SPEC · **Default spring bounce to 0 (smooth); use up to ~0.15 for liveliness, ~0.3 for noticeable bounce, but stay under ~0.4 for functional UI. In Motion/Framer, set bounce + visualDuration, not raw stiffness/damping/mass.** — WWDC23 'Animate with springs' ('when not sure use bounce 0', caution above ~0.4); motion.dev/docs/spring (defaults bounce 0.25)
48
+ - ⚪ OP · **Use Apple-talk practitioner spring tuning by interaction context (single-source reconstruction, not Apple-official): light tap ~damping 0.4/response 0.2, momentum drawers ~0.8/0.3, draggable PiP ~1.0/0.4.** — Nathan Gitter 'Building Fluid Interfaces' (Medium reconstruction of WWDC18 803)
49
+
50
+ ### 3. Spacing & Rhythm
51
+
52
+ - 🟢 SPEC · **Set every margin, padding, gap and dimension to a multiple of 8px (4px allowed as a half-step for icons/small text).** — designsystems.com/space-grids-and-layouts; m2.material.io ('components align to 8dp baseline grid; icons/type to 4dp')
53
+ - 🟢 SPEC · **Round every line-height (and text-block height) to a multiple of 4px so baselines land on a 4px vertical grid.** — designsystems.com/space-grids-and-layouts; Material (type aligns to 4dp baseline grid)
54
+ - 🟡 CONV · **Generate font sizes by multiplying a base by a fixed modular ratio rather than picking sizes ad hoc.** — A List Apart 'More Meaningful Typography' (Tim Brown / modularscale.com); Cieden type-scale guide
55
+ - 🟡 CONV · **Keep space INSIDE a component ≤ the space AROUND it, so grouping reads via Gestalt proximity.** — Cieden spacing best-practices; nngroup.com/articles/gestalt-proximity
56
+ - 🟢 SPEC · **Use responsive Material body margins (16dp phone → 32dp at small breakpoint, cap 200dp) and gutters (16dp ≥360dp / 24dp ≥600dp); align icon-leading content to the 72dp keyline (icon at 16dp).** — m2.material.io/design/layout/responsive-layout-grid; m1.material.io/layout/metrics-keylines (verbatim 16dp/72dp)
57
+
58
+ ### 4. Radius & Shape
59
+
60
+ - 🟢 SPEC · **Set a nested element's radius to the parent's radius minus the gap between them: outer = inner + padding (e.g. 20 = 12 + 8).** — ondrejkonecny.com/blog/nested-rounded-corners; frontendmasters.com/blog (Lily Konings formula); SwiftUI ConcentricRectangle WWDC25 / iOS 26; jakub.kr 'Details That Make Interfaces Feel Better'
61
+ - ⚪ OP · **Optionally clip the parent's overflow at the padding box so children inherit a correct concentric corner automatically — but keep a calc()-based fallback (weak Safari support).** — frontendmasters.com/blog/the-classic-border-radius-advice-plus-an-unusual-trick
62
+
63
+ ### 5. Shadow & Depth
64
+
65
+ - 🟡 CONV · **Stack 3-6 box-shadow layers (blur/offset roughly doubling 1/2/4/8/16px) instead of one shadow; lower each layer's alpha as you add layers so total darkness stays ~constant.** — Tobias Ahlin 'Layered smooth box-shadows' (5-layer @0.12, 6-layer @0.11); joshwcomeau.com/css/designing-shadows (doubling 1/2/4/8/16)
66
+ - ⚪ OP · **Keep vertical (Y) shadow offset ~2× the horizontal (X) offset and keep that ratio/direction identical site-wide to imply one fixed light source.** — joshwcomeau.com/css/designing-shadows ('vertical offset is always 2x the horizontal one')
67
+ - 🟢 DS · **As elevation increases, increase BOTH blur radius and Y-offset and DECREASE per-layer opacity (the three move together); pull large layers in with a negative spread so they don't balloon into a halo.** — joshwcomeau.com/css/designing-shadows; Tailwind box-shadow docs (lg -3px, xl -5px, 2xl -12px spread); MDC _elevation-theme.scss (negative umbra/penumbra spread)
68
+ - 🟢 DS · **Use the Tailwind elevation ramp as concrete two-layer tokens (0.05-0.25 alpha): cards≈md, dropdowns/popovers≈lg, modals≈xl, hero≈2xl. (v3 names; v4 renamed sm→xs etc.)** — v3.tailwindcss.com/docs/box-shadow (exact values); tailwindcss.com/docs/box-shadow (v4 remap)
69
+ - 🟢 DS · **Build Material elevation from three stacked shadows (key-light umbra + penumbra + ambient) at fixed opacities 0.20/0.14/0.12, mapped to the dp level scale.** — MDC _elevation-theme.scss (committed 0.2/0.14/0.12); m3.material.io/styles/elevation/tokens; designfornative.com (M3 dp summary)
70
+ - 🟢 SPEC · **In dark mode convey elevation by LIGHTENING the surface with a semi-transparent white overlay (higher = brighter), not by darkening shadows. M3 expresses elevation via a tonal surface-tint overlay + shadow.** — m2.material.io/design/color/dark-theme (overlay table); Android Compose Material3 docs (tonal overlays); m3.material.io/styles/elevation/applying-elevation
71
+ - ⚪ OP · **Tint shadows with the background's hue at reduced lightness instead of pure black/grey; on colored surfaces a desaturated black reads as dull grey film.** — joshwcomeau.com/css/designing-shadows
72
+ - ⚪ FOLK · **Add a 1px inset hairline (~10% opacity, black light / white dark, outline-offset:-1px) on images/media so light-on-light media doesn't bleed into the page. NOTE: the 10% value is folklore — the technique is sound but no authoritative source pins the number.** — jakub.kr (technique, 10% value); learnui.design '37 ways' (low-opacity image borders, weaker corroboration)
73
+
74
+ ### 6. Typography
75
+
76
+ - 🟢 SPEC · **Never break layout when users override spacing to the WCAG 1.4.12 floors: line-height 1.5×, letter 0.12×, word 0.16×, paragraph 2× font-size.** — w3.org/WAI/WCAG21/Understanding/text-spacing (SC 1.4.12 normative)
77
+ - 🟢 SPEC · **Constrain body measure to ~66ch (45-75 comfort band, hard cap ~90ch) and set max-width in a font-relative unit (ch/em), not px, so measure stays correct across font-size and zoom.** — practicaltypography.com/line-length (Butterick 45-90); Bringhurst/Elements of Typographic Style (45-75, 66 ideal)
78
+ - ⚪ OP · **Set body line-height ~1.5-1.6 unitless, headings ~1.1; longer measures need more leading, mobile/narrow less.** — pimpmytype.com/line-length-line-height (Oliver Schöndorfer); m3.material.io/styles/typography/applying-type
79
+ - 🟡 CONV · **Tighten letter-spacing as size grows: −0.02 to −0.03em for display (>~60px), ~0 body; inversely, ALL-CAPS labels want POSITIVE tracking ~+0.05 to +0.1em.** — loremforge.com/learn/kerning-tracking-letter-spacing; fonts.com tracking guide; Pimp my Type / Butterick (all-caps)
80
+ - 🟢 DS · **For Inter, compute letter-spacing from size with the Dynamic Metrics curve instead of one value: tracking = −0.0223 + 0.185·e^(−0.1745·size_px).** — d.rsms.me/inter-website/v3/dynmetrics (Rasmus Andersson, font author); rsms.me/inter
81
+ - 🟢 SPEC · **Apply text-wrap:balance only to headings/short text (≤6 lines Chromium, ≤10 Firefox); use text-wrap:pretty on body to kill last-line orphans (Chromium refines last 4 lines). Don't substitute one for the other.** — developer.chrome.com/docs/css-ui/css-text-wrap-balance (6-line cap); MDN text-wrap / Firefox bug 1851756 (10-line); developer.chrome.com/blog/css-text-wrap-pretty (last 4 lines)
82
+ - 🟢 SPEC · **Keep font-optical-sizing:auto (default) on variable fonts with an opsz axis so glyphs re-shape per rendered size; only override opsz when art-directing.** — MDN font-optical-sizing; fonts.google.com/knowledge/glossary/optical_size_axis
83
+ - 🟢 SPEC · **Use font-variant-numeric:tabular-nums for numbers that change in place or align in columns (timers, counters, tables, prices, numeric inputs); leave proportional for prose.** — MDN font-variant-numeric; raunofreiberg/interfaces; jakub.kr; vercel.com/design (triple-corroborated)
84
+ - 🟢 SPEC · **Keep body and label text at least 11pt (iOS) / pass color-contrast at size; apply -webkit-font-smoothing:antialiased once at the root for macOS crispness (no-op elsewhere).** — Apple 'UI Design Dos and Don'ts' (developer.apple.com/design/tips); MDN font-smooth; dbushell.com/2024/11 (post-Mojave)
85
+
86
+ ### 7. Color & Contrast
87
+
88
+ - 🟢 SPEC · **Give body text ≥4.5:1 contrast; large text (≥24px or ≥18.67px bold) may drop to 3:1; AAA pushes to 7:1 / 4.5:1. Never round before comparing.** — w3.org/WAI/WCAG21/Understanding/contrast-minimum (1.4.3); WCAG 1.4.6 (AAA); webaim.org/articles/contrast (px conversions)
89
+ - 🟢 SPEC · **Give UI component boundaries, state borders, and meaningful icons/graphics ≥3:1 contrast against adjacent colors.** — w3.org/WAI/WCAG21/Understanding/non-text-contrast (1.4.11)
90
+ - 🟢 SPEC · **Make the keyboard focus indicator ≥2px thick covering the full perimeter, with ≥3:1 change-of-contrast between the same pixels focused vs unfocused; add ~2px outline-offset so it lifts off the edge.** — w3.org/WAI/WCAG22/Understanding/focus-appearance (SC 2.4.13); sarasoueidan.com/blog/focus-indicators
91
+ - 🟢 SPEC · **Render M3 interaction feedback as a translucent state-layer overlay (hover 8%, focus 10%, pressed 10%, dragged 16%) tinted with the content's 'on-' color, not a swapped background. (Legacy mdui/M2 use focus/pressed 12% — prefer M3 values.)** — m3.material.io/foundations/interaction/states/state-layers (8/10/10/16); mdui (M2-derived 12% — alternative)
92
+ - 🟢 DS · **Render disabled content at 38% opacity, disabled containers at 12%, and exempt disabled controls from contrast checks; never add hover/pointer feedback to them.** — m3.material.io (disabled tokens 0.38/0.12); WCAG 1.4.3/1.4.11 (inactive exemption)
93
+ - 🟢 DS · **Dim the background behind modals/dialogs/sheets with a scrim of black at ~32% opacity (Material); systems vary 30-60%.** — m3.material.io/components/dialogs/specs (scrim 32%); m2.material.io/components/dialogs
94
+ - 🟡 CONV · **Map status meaning to conventional hues (red=error/destructive, green=success, amber=warning, blue=info) and never carry meaning by hue alone — pair with icon/text.** — cross-DS convention (Material, Carbon, Polaris, Spectrum); w3.org/WAI/WCAG21/Understanding/use-of-color (1.4.1, spec for the color-independence half)
95
+ - 🟢 SPEC · **Under APCA (WCAG 3 draft, NOT yet binding) target Lc 75 body min / Lc 90 preferred / Lc 60 non-body / Lc 45 headline / Lc 30 min text / Lc 15 non-text, gated by font size+weight.** — git.apcacontrast.com/documentation/APCA_in_a_Nutshell (draft, flagged non-binding)
96
+
97
+ ### 8. States & Feedback (microinteractions)
98
+
99
+ - 🟡 CONV · **Design every data-bearing component for all five states — empty, loading (skeleton), error, content/success, and ideal/populated — with a CTA in empty and a retry in error.** — Dan Saffer 'Microinteractions'; carbondesignsystem.com/patterns/empty-states-pattern; nngroup.com/articles/empty-state-interface-design
100
+ - 🟢 DS · **On :active press, scale the control to ~0.95-0.97 over ~150ms ease-out for tactile feedback, then spring back; drive it with a CSS transition so an early release retargets smoothly.** — tailwindcss.com (active:scale-95); raunofreiberg/interfaces ('~0.96, ~0.9 or so'); animations.dev easing-blueprint (0.97/150ms); jakub.kr (scale 0.96, never below 0.95)
101
+ - ⚪ OP · **Start entrance scale from ~0.9-0.96 (not 0), fade opacity in, and set transform-origin to the anchor so popovers/dropdowns scale FROM their trigger (use --radix-*-transform-origin).** — emilkowalski skills SKILL.md (0.96); raunofreiberg/interfaces (~0.8); Emil '7 Practical Tips' (origin-aware, 0.93 demo)
102
+ - 🟡 CONV · **Animate a contextual icon swap (copy→check) with scale 0.25→1 + opacity 0→1 + blur 4px→0 on a no-bounce spring (~0.3s, bounce 0); keep both variants in the DOM (one absolutely positioned) for a cross-fade.** — jakub.kr animations.md (seed library canon)
103
+ - 🟢 SPEC · **Make animations interruptible and retargetable — a new target mid-flight redirects from the current position preserving velocity; use CSS transitions/springs (which retarget) for re-triggered UI, @keyframes only for one-shot sequences.** — Emil Kowalski 'Great Animations'; Apple WWDC18 'Designing Fluid Interfaces' (Session 803); jakub.kr animations.md; motion.dev/docs/react-use-spring
104
+ - 🟢 SPEC · **For low-risk likely-to-succeed mutations, update the UI immediately (optimistic) and reconcile/rollback on failure with an explicit message; assign request identity so only the latest commits; never for payments/deletes/irreversible actions.** — react.dev/reference/react/useOptimistic; tanstack.com/query optimistic-updates
105
+ - 🟢 DS · **Delay hover-triggered panels by a short intent window (~100ms, 6px sensitivity) and keep them open a ~300ms grace period after the cursor leaves; for mega-menus use a cursor→submenu 'safe triangle' (tolerance ~75px), not a fixed timeout.** — Brian Cherne jQuery hoverIntent (interval 100, sensitivity 6); kamens/jQuery-menu-aim (tolerance 75, DELAY 300); Emil '7 Practical Tips' (grouped tooltips 0ms)
106
+ - 🟢 SPEC · **Use the three standard haptic generators by meaning (iOS): selection for value changes, impact (light/medium/heavy/soft/rigid) for collisions/boundaries, notification (success/warning/error) for outcomes — never decoratively, always with an on-screen change.** — developer.apple.com/design/human-interface-guidelines/playing-haptics; UIKit UIFeedbackGenerator
107
+ - 🟡 CONV · **Auto-dismiss an actionless toast/snackbar after 4-10s (Material guideline); if it carries an action, keep it until the user acts or dismisses. NOTE: Android's LENGTH_LONG implementation default is ~2.75s, below the 4s guideline.** — m3.material.io/components/snackbar/guidelines (4-10s); material-components-android Snackbar.java (LENGTH_SHORT 1500 / LENGTH_LONG 2750 — impl caveat)
108
+
109
+ ### 9. Targets & Pointer
110
+
111
+ - 🟢 SPEC · **Give every interactive control a hit area ≥44×44pt (Apple/WCAG AAA), ≥48×48dp (Material/Android ≈9mm), and never below the 24×24 CSS px WCAG 2.2 AA floor — padding the touch area beyond the visible glyph.** — developer.apple.com/design/tips (44pt verbatim); support.google.com/accessibility/android/answer/7101858 (48dp ≈9mm); w3.org/WAI/WCAG22/Understanding/target-size-minimum (24px AA); w3.org/WAI/WCAG21/Understanding/target-size (44px AAA)
112
+ - 🟢 SPEC · **Make any pointer target ≥24×24 CSS px, OR if smaller keep undersized targets 24px apart (24px-diameter circles centered on each must not intersect); inline text links and UA-controlled sizes exempt.** — w3.org/WAI/WCAG22/Understanding/target-size-minimum (SC 2.5.8 verbatim)
113
+ - 🟢 SPEC · **Leave at least 8pt/8dp between the edges of adjacent tappable controls; prefer 12-16pt when layout allows.** — support.google.com/accessibility/android/answer/7101858 ('8dp or more'); developer.apple.com/design/human-interface-guidelines/buttons (~8pt for ≥24pt icons)
114
+ - 🟢 SPEC · **On visionOS give interactive elements a 60×60pt hit region (visible control may stay 44pt with padding); ≈2.5° angular / ~4.4cm at 1m.** — Apple HIG visionOS / WWDC25 'Design hover interactions for visionOS'
115
+ - 🟢 SPEC · **Cut pointing time by making targets bigger and/or closer (Fitts: MT = a + b·log2(D/W+1)); pin high-frequency global targets to a screen edge/corner where they behave as infinitely wide.** — MacKenzie 'Information-Theoretic Basis for Fitts' Law' (yorku.ca/mack/JMB89.html); Fitts 1954; ixdf.org/literature/topics/fitts-law
116
+ - 🟡 CONV · **Reduce decision time by limiting/grouping choices — reaction time grows with log2(n+1), not linearly (Hick-Hyman); chunking lowers effective n. Does NOT apply to sorted/searchable lists users already know.** — Hick 1952 / Hyman 1953; en.wikipedia.org/wiki/Hick's_law; Proctor & Schneider 2018 (scope limits)
117
+ - 🟢 DS · **Use platform gesture timing constants: long-press ~500ms (Android 400 / iOS-RN 500), don't start a drag until past ~8dp touch-slop (~4px mouse), resolve a tap at ~100ms and reserve ~300ms for double-tap.** — Apple UILongPressGestureRecognizer (0.5s default); Android ViewConfiguration.java (LONG_PRESS 400, TOUCH_SLOP 8dp, TAP 100, DOUBLE_TAP 300); React Native Gesture Handler (500ms)
118
+ - 🟢 SPEC · **Don't override reserved system edge gestures (Home indicator, screen-edge back, Control/Notification Center); keep custom edge interactions out of those regions.** — developer.apple.com/design/human-interface-guidelines/gestures
119
+ - 🟢 SPEC · **Every drag interaction must also work via a single tap/click without dragging (WCAG 2.5.7); keyboard support alone does NOT satisfy it.** — w3.org/WAI/WCAG22/Understanding/dragging-movements (SC 2.5.7 verbatim, failure F108)
120
+ - 🟢 SPEC · **Set cursor:pointer on actually-clickable controls (and nowhere else), text I-beam on editable/selectable text, grab→grabbing on drag, not-allowed on disabled, zoom-in/out, crosshair for precision; pick one button-cursor policy project-wide; pair every cursor with a non-cursor affordance (touch shows no cursor).** — w3.org/TR/css-ui-4; kizu.dev/cursor-pointer vs adamsilver.io (button-cursor camps); twbs/bootstrap#16088 (pointer-events trap); a11y-solutions.stevenwoodson.com (touch)
121
+
122
+ ### 10. Scroll & Gesture
123
+
124
+ - ⚪ OP · **At scroll/drag boundaries apply rubberband resistance (drag translation = pow(rawOffset, ~0.7)) so the surface continues with diminishing offset instead of hard-stopping. (0.7 exponent is practitioner-derived; UIScrollView ships elastic by default.)** — Nathan Gitter 'Building Fluid Interfaces' (pow(offset,0.7) reconstruction); Apple UIScrollView (elastic by default)
125
+ - 🟢 SPEC · **Model momentum/inertial scroll with exponential decay v(t)=v0·d^t using the platform deceleration constant (0.998 normal lists, 0.99 fast paging); project the fling's resting position X=x0+v0/(1000·|ln d|) to decide snap/dismiss, not raw displacement.** — Apple UIScrollView.DecelerationRate (normal 0.998, fast 0.99); Lobanov 'Scrolling mechanics of UIScrollView'; Apple WWDC 'Designing Fluid Interfaces' (projection)
126
+ - 🟢 DS · **Dismiss a sheet/card when the drag passes ~30-50% of its height OR a fast flick's projected endpoint clears it; otherwise spring back. Two paths (distance OR velocity), not one. Trigger pull-to-refresh only past a fixed threshold (~80dp / 50-100px).** — lucaszischka/BottomSheet (0.3 default, 0.1 min); Material 3 Compose pullToRefresh (80.dp); m2.material.io/components/sheets-bottom
127
+ - 🟢 SPEC · **Use scroll-snap proximity for long vertical content and mandatory + scroll-snap-stop:always for full-bleed carousels; set scroll-padding-top = fixed-header height so snapped/anchor content clears the header.** — web.dev/articles/css-scroll-snap; MDN scroll-snap-type / scroll-snap-stop
128
+ - 🟢 SPEC · **Put overscroll-behavior:contain on modals, drawers, chat panes, and nested scrollers to stop scroll-chaining to the page behind them; use overscroll-behavior-y:none only when you must also kill native pull-to-refresh and the boundary glow/rubber-band.** — developer.chrome.com/blog/overscroll-behavior; MDN overscroll-behavior
129
+ - 🟢 SPEC · **Register scroll/touchstart/touchmove/wheel listeners as {passive:true} unless you genuinely preventDefault; express directional scroll-locking with touch-action (pan-y for horizontal carousels; manipulation to drop the 300ms tap delay) instead of preventDefault; drive scroll-linked effects with IntersectionObserver / animation-timeline:scroll()/view(), not scroll-event JS.** — developer.chrome.com/blog/scrolling-intervention; MDN scroll_event (20ms throttle / IntersectionObserver); developer.chrome.com/docs/css-ui/sticky-headers; MDN Scroll-driven animations
130
+ - 🟢 SPEC · **Enable CSS scroll-behavior:smooth only inside a prefers-reduced-motion:no-preference guard (UA controls duration/easing, not author-settable in CSS).** — MDN scroll-behavior; css-tricks scroll-behavior almanac
131
+ - 🟡 CONV · **Pad fixed bottom bars with env(safe-area-inset-bottom) and offset fixed top headers by env(safe-area-inset-top) (with viewport-fit=cover) so controls clear the iOS home indicator / notch — never hardcode the px.** — css-tricks.com/almanac/functions/e/env; CSS Environment Variables spec
132
+
133
+ ### 11. Response Time & Perceived Performance
134
+
135
+ - 🟢 SPEC · **Paint a visible result within 100ms of a direct action (keep input-handler work <50ms, idle/background work in ≤50ms chunks) so it feels instantaneous — no loader needed below this.** — nngroup.com/articles/response-times-3-important-limits (0.1s); web.dev/articles/rail (process input <50ms, idle 50ms)
136
+ - 🟢 SPEC · **Keep end-to-end response under 400ms (Doherty) to hold flow and 1s (Nielsen) before the sense of direct manipulation is lost; past 10s show a determinate percent-done indicator plus a cancel control.** — Doherty & Thadani IBM Systems Journal 1982 / lawsofux.com/doherty-threshold; nngroup.com/articles/response-times-3-important-limits (1s, 10s); Apple HIG Progress indicators
137
+ - 🟢 SPEC · **Target Interaction to Next Paint ≤200ms at p75 (200-500ms needs-improvement, >500ms poor); render each animation frame in <16.6ms (60fps) — ~10ms script after browser overhead — animating only transform/opacity.** — web.dev/articles/inp (official CWV); web.dev/articles/rail (16ms/60fps); web.dev/articles/defining-core-web-vitals-thresholds
138
+ - 🟢 DS · **Don't show a loading spinner for operations under ~1s: gate the loader behind a ~200ms-1s delay timer (cleared if the response resolves first) and, once shown, hold it a minimum (~500ms-1s) to avoid a flash. NOTE: concrete framework anchor is 200ms (Vue defineAsyncComponent); the 1s headline is inferred from Nielsen, not spec.** — Vue defineAsyncComponent (delay 200ms); mitchgavan.com/delay-loading-spinners-appearance; nngroup.com (1s flow limit)
139
+ - ⚪ FOLK · **Use a layout-matching skeleton for content-shaped surfaces (feeds, dashboards, lists) and a spinner for short discrete actions (save/auth/payment); only show either if the wait exceeds ~1s. NOTE: 'skeletons feel ~30% faster' is folklore — the layout-match principle is sound, the percentage is not.** — nngroup.com/articles/skeleton-screens (1s threshold; mixed evidence on % gain); Viget 2017 study (skeletons performed worst on perceived duration — the 30% figure is uncorroborated)
140
+ - 🟢 DS · **Animate a skeleton placeholder with a calm pulse (opacity 1→~0.5 over ~1.5-2s ease-in-out) or a left-to-right shimmer sweep (~1.4-2s linear, narrow highlight band via animated background-position), gated behind prefers-reduced-motion and animating only compositor-friendly properties.** — tailwindcss.com/docs/animation (animate-pulse 2s); MUI Skeleton.js (wave 2s linear, 0.5s delay); ant-design skeleton (1.4s); carbon _skeleton.scss (reduced-motion guard)
141
+ - 🟡 CONV · **Tween a changing number over ~0.4-1.0s (~700-750ms hero, shorter for small UI) with an ease-out/decelerate curve, rolling only the digit columns that changed; use tabular-nums and snap to the final value under reduced-motion.** — NumberFlow (number-flow.barvian.me, ~750ms/bounce defaults); css-tricks.com/animating-number-counters; M3 emphasized-decelerate token; Odometer.js / CountUp.js (2s defaults flagged too long)
142
+ - 🟡 CONV · **Debounce live-search/typeahead ~200-300ms (trailing edge, sized to ~150ms inter-keystroke cadence); debounce autosave and async/remote validation ~500ms (with save-on-blur and a maxWait cap); show a pending state if a remote check exceeds ~1s.** — algolia.com/doc (200ms preferred, >300ms degrades, WPM-tuned); design.gitlab.com/patterns/saving-and-feedback (autosave 3s/500ms validation); lodash debounce maxWait; react.dev/reference/react/useDeferredValue (render vs network)
143
+ - 🟡 CONV · **Throttle continuous streams — scroll/resize/pointer-move that PAINT to one frame (16ms via requestAnimationFrame), those that only SAMPLE to ~50-250ms; act on the trailing edge for resize. Use debounce for settled intent, throttle for sampled continuous activity.** — css-tricks.com/debouncing-throttling-explained-examples (Corbacho); MDN scroll_event; lodash docs
144
+ - 🟢 SPEC · **When a real wait is unavoidable, bias the progress animation to feel faster: backward-moving, decelerating ribbing and front-loaded progress (fast start, slow end) cuts perceived duration ~11%; never let the value move backward or stall.** — Harrison, Yeo & Hudson 'Faster Progress Bars' ACM CHI 2010 (peer-reviewed, ~11%)
145
+
146
+ ### 12. Layout Stability (CLS)
147
+
148
+ - 🟢 SPEC · **Keep Cumulative Layout Shift ≤0.1 at p75 of real-user loads (segmented mobile/desktop); shifts ≥0.15 are consistently perceived as disruptive and 0.25 is the start of 'poor'. Score = impact fraction × distance fraction over the worst session window (gaps <1s, ≤5s).** — web.dev/articles/cls; web.dev/articles/defining-core-web-vitals-thresholds; wicg.github.io/layout-instability
149
+ - 🟢 SPEC · **Reserve space before content loads: width+height attributes on every <img>/<video> (→ aspect-ratio), CSS aspect-ratio for responsive media/embeds, min-height (sized to the largest creative) for ad/async slots, and never collapse a reserved slot to zero on empty fill.** — web.dev/articles/optimize-cls; developers.google.com/publisher-tag/guides/minimize-layout-shift; MDN aspect-ratio
150
+ - 🟢 SPEC · **Never inject/expand content above existing content unless it's the direct ≤500ms result of a user interaction (scroll and pointer-move do NOT qualify); otherwise reserve space up front or append below the fold; animate movement with transform, never layout properties.** — web.dev/articles/optimize-cls; wicg.github.io/layout-instability (recent-input 500ms; scroll/pointer excluded)
151
+ - 🟢 SPEC · **Eliminate font-swap CLS: use font-display:optional (≤100ms block, no swap) for zero first-visit shift, or font-display:swap WITH a fallback @font-face carrying size-adjust/ascent-override/descent-override/line-gap-override tuned to occupy the same box.** — web.dev/articles/preload-optional-fonts; web.dev/articles/optimize-cls; MDN @font-face ascent-override
152
+ - 🟢 SPEC · **Prevent scrollbar-induced shift: set scrollbar-gutter:stable on the scroll ROOT (:root/html, not body — not propagated like overflow) so locking body scroll for a modal doesn't shift content; use 'stable both-edges' for centered layouts; it's a no-op on overlay-scrollbar platforms. In JS fallbacks measure innerWidth−clientWidth BEFORE setting overflow:hidden, never a hardcoded 15px.** — w3.org/TR/css-overflow-3 (§4.2); MDN scrollbar-gutter; bram.us (root not body; both-edges); theKashey/react-remove-scroll-bar (--removed-body-scroll-bar-size)
153
+ - 🟢 DS · **Animate accordion/disclosure height via grid-template-rows 0fr→1fr (with the inner element overflow:hidden), or interpolate-size:allow-keywords / calc-size(auto) where supported — never transition straight to height:auto (a no-op); run it ~200-300ms (collapse ~0.8× expand) and gate behind reduced-motion.** — css-tricks.com/css-grid-can-do-auto-height-transitions; developer.chrome.com/docs/css-ui/animate-to-height-auto (interpolate-size/calc-size); tailwindlabs discussion #11186; MUI/Material (300ms, collapse<expand)
154
+
155
+ ### 13. Modals, Overlays & Z-Index
156
+
157
+ - 🟢 SPEC · **On modal open move keyboard focus into the dialog (autofocus the intended first action, or a tabindex=-1 container for long content); trap Tab so it wraps last↔first; close on Escape; on close restore focus to the invoking trigger.** — w3.org/WAI/ARIA/apg/patterns/dialog-modal; MDN <dialog> (showModal); radix-ui Dialog
158
+ - 🟢 SPEC · **While the modal is open make the rest of the page inert (aria-modal=true + inert / native top-layer) so nothing behind the scrim is focusable, clickable, or read by AT — but always keep a keyboard-escapable exit (Esc / focusable close); never a dead-end trap.** — w3.org/WAI/ARIA/apg/patterns/dialog-modal; w3.org/WAI/WCAG22/Understanding/no-keyboard-trap (SC 2.1.2); MDN <dialog>
159
+ - 🟢 SPEC · **Give the overlay role=dialog (or native <dialog>) with an accessible name via aria-labelledby (visible title) or aria-label; keep the focused element at least partially visible (not hidden behind a sticky header/scrim); dim the background with a ~32% scrim.** — w3.org/WAI/ARIA/apg/patterns/dialog-modal; w3.org/WAI/WCAG22/Understanding/focus-not-obscured-minimum (2.4.11); m3.material.io/components/dialogs/specs (scrim 32%)
160
+ - 🟢 SPEC · **Allow backdrop/scrim click to dismiss non-destructive dialogs (light dismiss) but disable it for forms with unsaved input or destructive flows; if closing would lose user-entered content, intercept and confirm/discard instead of silently destroying it.** — MDN <dialog> closedby; nngroup.com/articles/accidental-overlay-dismissal; developer.apple.com/design/human-interface-guidelines/modality
161
+ - 🟢 DS · **Assign every overlay to a named, role-based z-index token on a small ordered ladder (dropdown<sticky<fixed<offcanvas<modal<popover<tooltip<toast), spaced ~10-20 apart with backdrops one step below their surface, tooltip/toast ABOVE modal; cap in the low thousands.** — getbootstrap.com/docs/5.3/layout/z-index (canonical scale); Primer DESIGN_TOKENS_GUIDE (semantic layers); design.learn.microsoft.com/tokens/z-index
162
+ - 🟢 SPEC · **Portal modals/popovers/dropdowns/tooltips/toasts to document.body (or one overlay root) to escape overflow:clip and ancestor stacking contexts; know that transform/filter/opacity<1/will-change/isolation/contain/position fixed|sticky on an ancestor create a stacking context that TRAPS descendant z-index — portal out (or isolation:isolate to fence internals), don't inflate the number.** — radix-ui Portal docs; MDN Stacking context (canonical trigger list); smashingmagazine.com (parent drop-shadow filter = most common z-index bug)
163
+
164
+ ### 14. Performance & GPU Discipline
165
+
166
+ - 🟢 SPEC · **Animate only transform and opacity (composite-only, GPU); never use `transition: all` — explicitly list the properties that change. Layout properties (width/height/top/left/margin/padding) trigger layout+paint+composite and jank.** — vercel-labs/web-interface-guidelines ('Never transition: all'); emilkowal.ski/ui/great-animations (transform/opacity = composite only); browser rendering pipeline docs
167
+ - 🟡 CONV · **Use will-change only for compositor-promotable properties (transform/opacity/filter/clip-path) and only while actively animating (add then remove); never will-change:all, never on layout properties, never blanket across a grid.** — jakub.kr performance.md (compositable table); MDN will-change (warns against leaving it set); web.dev/learn/design/interaction
168
+ - 🟢 SPEC · **Treat the blur() px value as the Gaussian standard deviation — a given N spreads visibly wider than N pixels (budget perceived spread ~2-3×); keep backdrop-filter blur 8-24px (~12-16px default), low (≤10px) on mobile fixed elements to avoid scroll jank.** — w3.org/TR/filter-effects-1 ('standard deviation to the Gaussian function'); graffino.com (Safari fixed-blur jank); MDN backdrop-filter
169
+
170
+ ### 15. Material & Glass (backdrop-filter)
171
+
172
+ - 🟡 CONV · **Build frosted glass from blur 8-24px + saturate(120-200%) + a translucent fill (10-25% decorative cards, 50-80% text-bearing chrome) + a 1px translucent light hairline; ship behind an opaque @supports fallback (≥0.9 alpha base) and emit -webkit-backdrop-filter for Safari (never a CSS variable as its value).** — v3.tailwindcss.com/docs/backdrop-blur (4/8/12/16/24px); joshwcomeau.com/css/backdrop-filter (16px, saturate, @supports); css-tricks.com/backdrop-filter-effect-with-css; caniuse + MDN (Safari -webkit- prefix)
173
+ - 🟢 SPEC · **Compute text contrast against the WORST-CASE show-through background (≥4.5:1 text / ≥3:1 large/UI), raise fill opacity (frostier) as the backdrop gets busier, render foreground in vibrant/dynamic system colors (not fixed hex), and add a prefers-reduced-transparency / increased-contrast branch swapping glass to near-opaque high-contrast.** — w3.org/TR/UNDERSTANDING-WCAG20 (1.4.3 — does not account for semi-transparent overlays); Apple HIG Materials (vibrancy); Apple WWDC25 Liquid Glass (Reduced Transparency → frostier)
174
+
175
+ ### 16. Accessibility & Motion-Safety
176
+
177
+ - 🟢 SPEC · **Gate all non-essential movement behind prefers-reduced-motion: under `reduce` cut MOTION (transform/translate/scale/rotate, position, scroll, parallax, blur) and KEEP opacity/color, substituting a cross-fade rather than deleting the transition; collapse global animation/transition durations to ~0.01ms (not 0ms, which skips transitionend) and force scroll-behavior:auto.** — Emil Kowalski 'Great Animations'; w3.org/WAI/WCAG21/Understanding/animation-from-interactions; web.dev/articles/prefers-reduced-motion (fade kept/scale dropped); Andy Bell modern CSS reset (0.01ms); WCAG 2.3.3 errata (blur in scope)
178
+ - 🟢 SPEC · **Replace slide/zoom/parallax with a cross-dissolve/fade when Reduce Motion is on (iOS App Store criteria) — keep conveying meaning without large positional motion; vestibular risk scales with how much of the VIEWPORT the motion sweeps, not physical screen size.** — Apple App Store Connect 'Reduced Motion evaluation criteria' ('dissolve, highlight fade, or color shift'); Val Head 'Designing Safer Web Animation' (A List Apart); webkit.org/blog/7551 (Responsive Design for Motion)
179
+ - 🟢 SPEC · **Nothing may flash more than 3 times in any 1-second window; auto-starting moving/blinking/scrolling content running >5s alongside other content needs a pause/stop/hide control; interaction-triggered non-essential motion must be disablable.** — w3.org/WAI/WCAG22/Understanding/three-flashes-or-below-threshold (3 flashes; luminance 10%, not 25%); w3.org/WAI/WCAG22/Understanding/pause-stop-hide (5s, SC 2.2.2); w3.org/WAI/WCAG22/Understanding/animation-from-interactions (SC 2.3.3)
180
+ - 🟢 SPEC · **Never set outline:none on a focusable element without a visible replacement; scope focus rings to :focus-visible (not :focus); render rings with box-shadow/radius-aware outline (bare outline ignored border-radius historically); keep focus order = DOM source order (tabindex only in {-1,0}); hide focusable offscreen elements by positioning, not display:none.** — w3.org/WAI/WCAG (2.4.7 Focus Visible, 2.4.3 Focus Order); sarasoueidan.com/blog/focus-indicators ('Do. Not. Do. This.'); webaim.org/techniques/keyboard ('no tabindex ≥1'); raunofreiberg/interfaces (box-shadow not outline)
181
+ - 🟢 SPEC · **Gate hover styles behind @media (hover: hover) and (pointer: fine) so touch taps don't get stuck in a hover state, and set form input font-size ≥16px so iOS Safari doesn't auto-zoom on focus (or pin maximum-scale=1); never autofocus inputs on touch.** — raunofreiberg/interfaces (@media hover:hover; 16px no-zoom); vercel-labs/web-interface-guidelines; W3C Media Queries L4 (hover/pointer); WebKit iOS 16px auto-zoom behavior
182
+ - 🟢 SPEC · **When an input error is automatically detected, identify the field and describe the error in TEXT (not color/icon alone, aria-invalid + aria-describedby); never silently block submit — surface the first invalid field, move focus to it or to a top error summary (tabindex=-1, focused, 'Error:' page title), and don't shift layout when the message appears.** — w3.org/WAI/WCAG22/Understanding/error-identification (SC 3.3.1); design-system.service.gov.uk/components/error-summary (GOV.UK); nngroup.com/articles/error-message-guidelines
183
+
184
+ ### 17. Form Validation Timing
185
+
186
+ - 🟡 CONV · **Validate a clean field only on blur (and only when non-empty); the moment a field shows an error, switch it to live keystroke revalidation so the error clears as soon as the input becomes valid — 'reward early, punish late'.** — Mihael Konjević 'Inline validation in forms' (WDstack); smashingmagazine.com/2022/09/inline-validation-web-forms-ux (Friedman); baymard.com/blog/inline-form-validation ('keystroke level')
187
+ - 🟡 CONV · **For fixed-length constrained fields (ZIP, phone, card number) you may validate as soon as the input reaches the expected length; confirm correct entries with a positive inline marker (post-blur, not mid-typing). For high-assurance/accessibility-first forms a documented counter-position validates on SUBMIT only — choose per audience.** — baymard.com/blog/inline-form-validation (validate at length); design-system.service.gov.uk/patterns/validation ('Do not validate when the user moves away from a field' — counter-position)
188
+
189
+ ---
190
+
191
+ ## Appendix A — Gaps noted by synthesis
192
+
193
+ RESIDUAL GAPS (what the corpus does not yet quantify well, plus integrity flags):
194
+
195
+ 1. UNDISCLOSED SYSTEM CONSTANTS. The true iOS rubberband curve and the exact UIScrollView momentum-to-snap projection are only available as single-practitioner reconstructions (Nathan Gitter's pow(offset,0.7), Lobanov's derivation). Apple never published these as tokens, so they remain single-opinion despite being load-bearing for native feel.
196
+
197
+ 2. SPRING TUNING BY CONTEXT is single-source. The per-interaction spring triples (tap 0.4/0.2, drawer 0.8/0.3, PiP 1.0/0.4) come from one Medium reconstruction. There is no provenance-graded table mapping interaction class → spring params across platforms.
198
+
199
+ 3. PERCEIVED-PERFORMANCE NUMBERS ARE THIN. Only one quantified, peer-reviewed perceived-duration result exists (Harrison CHI 2010, ~11% from backward ribbing). The widely-cited 'skeletons feel ~30% faster' is folklore (2017 Viget testing actually found skeletons worst on perceived duration); the corpus keeps the skeleton-vs-spinner pattern but the percentage is unverified. NN/g's real threshold is 1s, not the 500ms repeated in several gap-fill findings.
200
+
201
+ 4. GAP-FILL TIER NOT INDIVIDUALLY VERIFIED. ~150 'gap-fill' findings (debounce/throttle, skeleton animation, counters, hover-elevation, cursors, CLS/scrollbar, accordions, validation, modals, z-index, glass) carry verified:false / provenance_tier:single-opinion assigned at synthesis — many cite reputable named sources (WCAG, Bootstrap, Radix, MDN, web.dev) and are effectively spec/ds-token grade, but the raw corpus did not run the adversarial verification pass on them. I upgraded tiers where the underlying source is clearly authoritative (WCAG SC, committed DS tokens, official platform docs), but a second verification pass on the gap-fill band would harden ~40% of these from 'single-opinion' to their true tier.
202
+
203
+ 5. VERSION/DATE DRIFT in several findings (scroll-snap-stop browser support, scroll-driven-animations 'Dec 2024', Tailwind v3→v4 shadow rename) — the VALUES are right but the support/version metadata is stale or fabricated and was stripped/corrected here. Any AI consuming this must re-check baseline support at generation time, not trust embedded dates.
204
+
205
+ 6. CROSS-DENSITY / PLATFORM SCALING is under-specified. Material notes wearables run ~30% shorter durations and desktop hover ramps differ from touch, but there is no quantified multiplier table for compact/comfortable/spacious density, watch/TV/desktop, or pointer-fine vs coarse beyond the touch-target split.
206
+
207
+ 7. SEMANTIC COLOR EXACT SHADES. The hue MAPPING (red/green/amber/blue) is convention-solid, but the exact per-system error/success/warning shades that simultaneously hit 4.5:1 text and 3:1 non-text on both light and dark surfaces are not enumerated — left to each DS's token file.
208
+
209
+ 8. EASING ↔ DURATION PAIRING is asserted directionally (decelerate+long for enters) but not given as a joint lookup table; an AI must currently pick duration and curve from separate rules rather than one (component-role → {duration token, curve token}) map.
210
+
211
+ 9. CONTENT/COPY, EMPTY-STATE WORDING, ICON SEMANTICS, and INFORMATION DENSITY are out of scope — this corpus quantifies the physical/temporal/contrastive layer of 'feel', not editorial or IA decisions.
212
+
213
+ 10. CONFLICTS PRESERVED, NOT ALL RESOLVED. Genuine two-source disagreements are intentionally kept with both positions: on-blur validation (Baymard/Konjević) vs submit-only (GOV.UK); state-layer 8/10/10/16 (M3) vs 12% (M2/mdui); button cursor pointer (app convention) vs default (strict spec); long-press 400ms (Android) vs 500ms (iOS/RN). These are audience/platform choices, not errors — downstream must pick per context, and the machine_checks enforce internal consistency (pick one) rather than a single global winner.
214
+
215
+ ## Appendix B — Overclaimed numbers (demote spec→taste)
216
+
217
+ The completeness critic flagged these as presented with spec-like precision but actually single-implementation / textbook-average / folklore. In `omd:feel` they are kept but **disclosed as heuristics, never enforced as gates**:
218
+
219
+ - Reaction-time RT = a + b·log2(n+1) with b ≈ 0.155 s/bit (Hick-Hyman) — the corpus already hedges 'empirical/convention not spec'; the specific 0.155 s/bit slope is a textbook average that varies widely by stimulus/practice and is routinely misapplied to menus; demote to illustrative, not a design constant
220
+ - Fitts's-law throughput b ≈ 100-150 ms/bit for a mouse — device- and study-dependent (ISO 9241-9 throughput varies ~3.7-4.9 bits/s); the ms/bit figure is a loose convention, verify before quoting as a number
221
+ - Cross-fade incoming 150ms / outgoing 75ms (linear) — the exact 75ms outgoing half is presented crisply but reads as a single-implementation convention rather than a sourced spec; re-verify the source (looks Material-motion-derived but the precise split is folklore-adjacent)
222
+ - Hover-panel open intent ~100ms (hoverIntent default) + keep-open ~300ms (menu-aim grace) + safe-triangle tolerance ~75px — the 75px tolerance and 300ms grace are library/blog-derived defaults (jQuery hoverIntent / Ben Kamens menu-aim writeups), not vendor specs; re-verify or label as practitioner convention
223
+ - Long-press Android ViewConfiguration default 400ms vs iOS 500ms — worth re-verifying: Android's getLongPressTimeout has historically been 500ms (user-adjustable), so the flat '400ms' may be wrong/outdated; confirm against current ViewConfiguration source
224
+ - Android paging slop 16dp and touch slop ~8dp — the generic 'touch slop ~8dp' is right-order but ViewConfiguration touchSlop is density/version dependent; the 16dp 'paging slop' is a specific constant that should be re-sourced before quoting
225
+ - Momentum-scroll deceleration constants d = 0.998 (normal) / 0.99 (fast) and factor ≈499.5 px per px/ms — these are reverse-engineered from a specific JS implementation (react-use-gesture / rebound style), not platform constants; iOS UIScrollView's actual decelerationRate (normal 0.998, fast 0.99) is per-frame at 60fps, so the headline math is implementation-coupled — flag the 499.5 derived figure as fragile
226
+ - Rubberband resistance exponent k ≈ 0.7 — already self-flagged as 'practitioner-derived not an Apple constant'; the well-known iOS rubberband formula uses a 0.55 constant (b·d·c/(d + c·x) form), not a pow(x,0.7), so the specific 0.7 should be demoted or reconciled
227
+ - Optimistic-UI / direct-manipulation '0ms perceived' and the '~11% perceived-duration reduction' for backward-ribbing progress — the 11% is correctly tagged as 'the one peer-reviewed result' but is from a single study (Harrison et al.) on a narrow stimulus; keep but mark as not generalizable beyond progress bars
228
+ - Pressed-scale floor 'never below 0.95' and exit animations '~0.8× (≈20% faster, or ~50ms shorter)' — both are reasonable taste heuristics but the precise 0.95 floor and the 0.8×/50ms exit ratio are single-author craft conventions presented with spec-like precision; demote to taste
229
+ - Stagger '≈80ms per word, ≈100ms per section' and reveal translateY '~8-12px' — sensible defaults but the per-word/per-section ms figures are uncited practitioner numbers; re-verify or label as convention
230
+ - Icon optical nudge 'icon-side padding = text-side padding − 2px' and 'play triangle ~2px right' — the 2px is shape/size dependent (the corpus admits this) yet states a fixed 2px; demote the literal 2px to 'a small optical nudge, not a constant'
231
+ - Toast/snackbar 4-10s with '4s typical' vs Android LENGTH_LONG ~2.75s — the corpus already notes the Android constant undercuts the guideline; flag that the '4-10s' band itself is a soft UX guideline, not a measured optimum, and the upper bound conflicts with WCAG 2.2.1 timing-adjustable concerns
232
+ - Apple default spring (response 0.55, dampingFraction 0.825, blendDuration 0) as 'the baseline for standard state changes' — these are the documented defaults of one SwiftUI spring initializer, but calling them THE baseline for standard changes overstates; many Apple system animations use other presets — re-verify the 'default/baseline' framing
233
+ - Carbon slow-02 700ms 'background dim' and the full productive ladder figures — worth spot-verifying against current Carbon motion tokens, which have been revised; some of these ms values may reflect an older version of the token set