agemon 0.1.0 → 6.0.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 +732 -28
- package/assets/skills/architecture/agemon-architecture/LICENSE +21 -0
- package/assets/skills/architecture/agemon-architecture/NOTICE.md +9 -0
- package/assets/skills/architecture/agemon-architecture/SKILL.md +275 -0
- package/assets/skills/code-quality/agemon-clean-code/LICENSE +21 -0
- package/assets/skills/code-quality/agemon-clean-code/NOTICE.md +9 -0
- package/assets/skills/code-quality/agemon-clean-code/SKILL.md +78 -0
- package/assets/skills/code-quality/agemon-house-rules/SKILL.md +103 -0
- package/assets/skills/commit/agemon-commit/SKILL.md +87 -0
- package/assets/skills/design/agemon-design/LICENSE +21 -0
- package/assets/skills/design/agemon-design/NOTICE.md +9 -0
- package/assets/skills/design/agemon-design/SKILL.md +391 -0
- package/assets/skills/design/agemon-design/references/accessibility.md +182 -0
- package/assets/skills/design/agemon-design/references/agents.md +63 -0
- package/assets/skills/design/agemon-design/references/ai-chat.md +158 -0
- package/assets/skills/design/agemon-design/references/brief.md +186 -0
- package/assets/skills/design/agemon-design/references/color.md +216 -0
- package/assets/skills/design/agemon-design/references/components.md +91 -0
- package/assets/skills/design/agemon-design/references/copy.md +300 -0
- package/assets/skills/design/agemon-design/references/craft-intent.md +160 -0
- package/assets/skills/design/agemon-design/references/dashboard.md +131 -0
- package/assets/skills/design/agemon-design/references/dataviz.md +197 -0
- package/assets/skills/design/agemon-design/references/finish-bar.md +243 -0
- package/assets/skills/design/agemon-design/references/forms.md +192 -0
- package/assets/skills/design/agemon-design/references/heuristics.md +403 -0
- package/assets/skills/design/agemon-design/references/inspiration.md +287 -0
- package/assets/skills/design/agemon-design/references/layout.md +236 -0
- package/assets/skills/design/agemon-design/references/loops.md +155 -0
- package/assets/skills/design/agemon-design/references/metadata.md +98 -0
- package/assets/skills/design/agemon-design/references/modern-css.md +448 -0
- package/assets/skills/design/agemon-design/references/motion.md +434 -0
- package/assets/skills/design/agemon-design/references/personas.md +206 -0
- package/assets/skills/design/agemon-design/references/principles-catalog.md +299 -0
- package/assets/skills/design/agemon-design/references/recipe-auth.md +88 -0
- package/assets/skills/design/agemon-design/references/recipe-dashboard.md +130 -0
- package/assets/skills/design/agemon-design/references/recipe-landing.md +122 -0
- package/assets/skills/design/agemon-design/references/responsive.md +168 -0
- package/assets/skills/design/agemon-design/references/review.md +340 -0
- package/assets/skills/design/agemon-design/references/sound.md +150 -0
- package/assets/skills/design/agemon-design/references/spec.md +123 -0
- package/assets/skills/design/agemon-design/references/stack.md +522 -0
- package/assets/skills/design/agemon-design/references/state-design.md +188 -0
- package/assets/skills/design/agemon-design/references/themes.md +229 -0
- package/assets/skills/design/agemon-design/references/tokens.md +350 -0
- package/assets/skills/design/agemon-design/references/typography.md +246 -0
- package/assets/skills/design/agemon-design-dashboard/LICENSE +21 -0
- package/assets/skills/design/agemon-design-dashboard/NOTICE.md +9 -0
- package/assets/skills/design/agemon-design-dashboard/SKILL.md +113 -0
- package/assets/skills/design/agemon-design-editorial/LICENSE +21 -0
- package/assets/skills/design/agemon-design-editorial/NOTICE.md +9 -0
- package/assets/skills/design/agemon-design-editorial/SKILL.md +98 -0
- package/assets/skills/design/agemon-design-minimal/LICENSE +21 -0
- package/assets/skills/design/agemon-design-minimal/NOTICE.md +9 -0
- package/assets/skills/design/agemon-design-minimal/SKILL.md +97 -0
- package/assets/skills/geo-seo/geo/LICENSE +21 -0
- package/assets/skills/geo-seo/geo/NOTICE.md +18 -0
- package/assets/skills/geo-seo/geo/SKILL.md +145 -0
- package/assets/skills/geo-seo/geo-audit/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-audit/NOTICE.md +17 -0
- package/assets/skills/geo-seo/geo-audit/SKILL.md +371 -0
- package/assets/skills/geo-seo/geo-audit/references/ai-visibility.md +233 -0
- package/assets/skills/geo-seo/geo-audit/references/content.md +329 -0
- package/assets/skills/geo-seo/geo-audit/references/platform-analysis.md +294 -0
- package/assets/skills/geo-seo/geo-audit/references/schema.md +383 -0
- package/assets/skills/geo-seo/geo-audit/references/technical.md +368 -0
- package/assets/skills/geo-seo/geo-brand-mentions/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-brand-mentions/NOTICE.md +15 -0
- package/assets/skills/geo-seo/geo-brand-mentions/SKILL.md +459 -0
- package/assets/skills/geo-seo/geo-citability/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-citability/NOTICE.md +14 -0
- package/assets/skills/geo-seo/geo-citability/SKILL.md +320 -0
- package/assets/skills/geo-seo/geo-content/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-content/NOTICE.md +14 -0
- package/assets/skills/geo-seo/geo-content/SKILL.md +352 -0
- package/assets/skills/geo-seo/geo-crawlers/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-crawlers/NOTICE.md +15 -0
- package/assets/skills/geo-seo/geo-crawlers/SKILL.md +388 -0
- package/assets/skills/geo-seo/geo-llmstxt/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-llmstxt/NOTICE.md +14 -0
- package/assets/skills/geo-seo/geo-llmstxt/SKILL.md +433 -0
- package/assets/skills/geo-seo/geo-platform-optimizer/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-platform-optimizer/NOTICE.md +14 -0
- package/assets/skills/geo-seo/geo-platform-optimizer/SKILL.md +282 -0
- package/assets/skills/geo-seo/geo-report/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-report/NOTICE.md +14 -0
- package/assets/skills/geo-seo/geo-report/SKILL.md +406 -0
- package/assets/skills/geo-seo/geo-report-pdf/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-report-pdf/NOTICE.md +20 -0
- package/assets/skills/geo-seo/geo-report-pdf/SKILL.md +153 -0
- package/assets/skills/geo-seo/geo-report-pdf/templates/geo-report-template.html +548 -0
- package/assets/skills/geo-seo/geo-schema/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-schema/NOTICE.md +16 -0
- package/assets/skills/geo-seo/geo-schema/SKILL.md +396 -0
- package/assets/skills/geo-seo/geo-technical/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-technical/NOTICE.md +15 -0
- package/assets/skills/geo-seo/geo-technical/SKILL.md +566 -0
- package/assets/skills/performance/agemon-performance/NOTICE.md +1 -0
- package/assets/skills/performance/agemon-performance/SKILL.md +76 -0
- package/assets/skills/security/agemon-security/LICENSE +21 -0
- package/assets/skills/security/agemon-security/NOTICE.md +8 -0
- package/assets/skills/security/agemon-security/SKILL.md +356 -0
- package/assets/skills/self-review/agemon-review-intake/LICENSE +21 -0
- package/assets/skills/self-review/agemon-review-intake/NOTICE.md +9 -0
- package/assets/skills/self-review/agemon-review-intake/SKILL.md +205 -0
- package/assets/skills/self-review/agemon-review-request/LICENSE +21 -0
- package/assets/skills/self-review/agemon-review-request/NOTICE.md +9 -0
- package/assets/skills/self-review/agemon-review-request/SKILL.md +104 -0
- package/assets/skills/self-review/agemon-review-request/code-reviewer.md +188 -0
- package/assets/skills/self-review/agemon-root-cause/LICENSE +21 -0
- package/assets/skills/self-review/agemon-root-cause/NOTICE.md +10 -0
- package/assets/skills/self-review/agemon-root-cause/SKILL.md +283 -0
- package/assets/skills/self-review/agemon-root-cause/condition-based-waiting-example.ts +158 -0
- package/assets/skills/self-review/agemon-root-cause/condition-based-waiting.md +115 -0
- package/assets/skills/self-review/agemon-root-cause/defense-in-depth.md +122 -0
- package/assets/skills/self-review/agemon-root-cause/find-polluter.sh +72 -0
- package/assets/skills/self-review/agemon-root-cause/root-cause-tracing.md +169 -0
- package/assets/skills/self-review/agemon-root-cause/test-academic.md +14 -0
- package/assets/skills/self-review/agemon-root-cause/test-pressure-1.md +58 -0
- package/assets/skills/self-review/agemon-root-cause/test-pressure-2.md +68 -0
- package/assets/skills/self-review/agemon-root-cause/test-pressure-3.md +69 -0
- package/assets/skills/self-review/agemon-verify-before-done/LICENSE +21 -0
- package/assets/skills/self-review/agemon-verify-before-done/NOTICE.md +9 -0
- package/assets/skills/self-review/agemon-verify-before-done/SKILL.md +120 -0
- package/assets/skills/team-lead/agemon-plan-rfc/SKILL.md +317 -0
- package/assets/skills/team-lead/prd-generator/SKILL.md +437 -0
- package/assets/skills/team-lead/skill-headofit-audit/SKILL.md +72 -0
- package/assets/skills/team-lead/skill-incident-rca/SKILL.md +66 -0
- package/assets/skills/team-lead/skill-meeting-lifecycle/SKILL.md +63 -0
- package/assets/skills/team-lead/skill-obsidian-vault-sync/SKILL.md +69 -0
- package/assets/skills/team-lead/skill-prd-decomposer/SKILL.md +71 -0
- package/assets/skills/team-lead/skill-sec-check/SKILL.md +70 -0
- package/assets/skills/team-lead/skill-task-extractor/SKILL.md +79 -0
- package/assets/skills/team-lead/skill-tech-spike/SKILL.md +57 -0
- package/assets/skills/team-lead/skill-techlead-reviewer/SKILL.md +81 -0
- package/assets/tools/code-review-graph-constraints.txt +1418 -0
- package/assets/tools/package-lock.json +111 -0
- package/assets/tools/package.json +6 -0
- package/dist/chunk-5LATHJYU.js +4026 -0
- package/dist/chunk-5LATHJYU.js.map +1 -0
- package/dist/fake-subprocess-2BETWZVZ.js +666 -0
- package/dist/fake-subprocess-2BETWZVZ.js.map +1 -0
- package/dist/index.js +21101 -1956
- package/dist/index.js.map +1 -1
- package/package.json +30 -17
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
# Motion
|
|
2
|
+
|
|
3
|
+
Unified motion reference — decision ladder, token scales, interaction rules, choreography, motion budget, reduced-motion contract, anti-patterns.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to Read This
|
|
8
|
+
|
|
9
|
+
Any motion work: hover, enter/exit, modals, drawers, page transitions, scroll reveals, stagger choreography, spring vs tween decisions, reduced-motion, or bespoke durations showing up in a review.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Decision Ladder
|
|
14
|
+
|
|
15
|
+
1. **Justify every animation** — motion must communicate something (hierarchy, state, spatial relationship). Decorative motion is noise.
|
|
16
|
+
2. **Frequency determines budget** — 100+ times/day gets zero animation. Occasional (modals, drawers) get standard. First-time experiences can delight.
|
|
17
|
+
|
|
18
|
+
| Frequency | Animation |
|
|
19
|
+
|-----------|-----------|
|
|
20
|
+
| 100+ times/day (keyboard shortcuts, command palette) | **None. Ever.** |
|
|
21
|
+
| Tens of times/day (hover, list nav, typing) | Remove or drastically reduce — under 150ms or none |
|
|
22
|
+
| Occasional (modals, drawers, toasts) | Standard animation, 200-300ms, clear purpose |
|
|
23
|
+
| Rare / first-time (onboarding, celebrations) | Can add delight — tell a story |
|
|
24
|
+
|
|
25
|
+
3. **Speed communicates confidence** — under 200ms feels instant. 300ms+ starts feeling sluggish.
|
|
26
|
+
4. **Respect the user's system** — `prefers-reduced-motion` is not optional. Provide meaningful fallbacks, not just `animation: none`.
|
|
27
|
+
5. **GPU-only properties** — stick to `transform` and `opacity`. Animating `width`, `height`, `top`, `left` causes layout thrashing.
|
|
28
|
+
6. **List properties explicitly** — `transition: all` animates things you didn't intend.
|
|
29
|
+
7. **Degrade before removing.** When an animation is causing jank, the fix ladder is: shrink duration, shrink distance, swap to a cheaper property, reduce the number of animated elements — in that order. Cutting the animation entirely is the last rung, not the first response to a performance complaint.
|
|
30
|
+
|
|
31
|
+
**Asymmetric enter/exit.** Pressing can be slow when deliberate (hold-to-delete: 2s linear), but release is always snappy (200ms ease-out). Slow where user decides, fast where system responds.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Duration Scale
|
|
36
|
+
|
|
37
|
+
Perception bands that govern every token below:
|
|
38
|
+
|
|
39
|
+
- **Under 100ms** — registers as instant. Use for acknowledgment-only feedback (button press ripple, toggle snap). Anything longer in this slot feels laggy.
|
|
40
|
+
- **100–250ms** — registers as a transition but doesn't slow the user. Correct for UI state changes, hover effects, and small reveals.
|
|
41
|
+
- **250–400ms** — noticeable. The user expects a moment of change. Reserve for screen-level events: modal open, drawer slide, page transition.
|
|
42
|
+
- **400ms+** — deliberate and slow. Productive UI has no business here. Reserve for storytelling moments (onboarding, hero animations) used at most once per session.
|
|
43
|
+
|
|
44
|
+
Five tokens cover ~95% of UI. Pick from this scale; do not invent.
|
|
45
|
+
|
|
46
|
+
```css
|
|
47
|
+
:root {
|
|
48
|
+
--motion-fast: 120ms; /* color / opacity, tooltip, hover */
|
|
49
|
+
--motion-base: 200ms; /* small UI — dropdown, toggle, tab, select */
|
|
50
|
+
--motion-medium: 280ms; /* medium UI — modal open, drawer, popover */
|
|
51
|
+
--motion-slow: 400ms; /* large UI — page transition, full sheet */
|
|
52
|
+
--motion-slower: 600ms; /* decorative / onboarding — sparingly */
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
| Token | Range | Use for |
|
|
57
|
+
|-------|-------|---------|
|
|
58
|
+
| `--motion-fast` | 120ms | Opacity, color, hover, focus ring, tooltip show/hide |
|
|
59
|
+
| `--motion-base` | 200ms | Dropdowns, toggles, tabs, selects, chips, accordion |
|
|
60
|
+
| `--motion-medium` | 280ms | Modals, popovers, drawers (desktop), snackbars |
|
|
61
|
+
| `--motion-slow` | 400ms | Page transitions, drawers (mobile), full-screen sheets |
|
|
62
|
+
| `--motion-slower` | 600ms | Hero animations, onboarding choreography, first-run only |
|
|
63
|
+
|
|
64
|
+
Never `transition: 153ms`. Never `200ms` on one button and `220ms` on another for the same state. A bespoke duration is a finding.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Easing Scale
|
|
69
|
+
|
|
70
|
+
Four tokens cover ~95% of UI easing. System defaults — framework libraries have their own defaults (see `stack.md` for Motion/GSAP mapping).
|
|
71
|
+
|
|
72
|
+
```css
|
|
73
|
+
:root {
|
|
74
|
+
--ease-out: cubic-bezier(0.22, 1, 0.36, 1); /* entrances, most UI */
|
|
75
|
+
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* same-layer transitions */
|
|
76
|
+
--ease-emphasized: cubic-bezier(0.2, 0, 0, 1); /* hero, attention-grabbing */
|
|
77
|
+
--ease-soft: cubic-bezier(0.4, 0, 0.2, 1); /* Material-like, general purpose */
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
| Token | Perceptual basis | When to use |
|
|
82
|
+
|-------|-----------------|-------------|
|
|
83
|
+
| `--ease-out` | Decelerates to rest — matches how real objects behave under gravity. Feels natural for anything arriving. | Default for almost everything. Entrances, hover, dropdown, modal open. |
|
|
84
|
+
| `--ease-in-out` | Symmetric ramp — deliberate, reads as intentional but can feel sluggish if overused. | Elements already on screen that move or morph (layout changes, repositioning). |
|
|
85
|
+
| `--ease-emphasized` | Aggressive ease-out — arrives quickly and settles hard. Signals importance and speed of response. | One element per viewport — hero reveal, primary CTA emphasis. |
|
|
86
|
+
| `--ease-soft` | Gentle ease-out with softer deceleration tail — less snappy, more comfortable for dense UI. | Gentler alternative for general UI; Material-style products. |
|
|
87
|
+
|
|
88
|
+
**Never:**
|
|
89
|
+
- `ease-in` for UI — acceleration without deceleration means the element speeds up as it arrives, which reads as a collision, not a landing. A dropdown with `ease-in` at 300ms *feels* slower than `ease-out` at the same duration.
|
|
90
|
+
- `linear` for spatial motion — machine-like and robotic; use only for opacity-only fades, loading bars, marquees, scroll-linked progress, and continuous rotation.
|
|
91
|
+
- Bespoke easings named "smooth" with `cubic-bezier(0.5, 0.5, 0.5, 0.5)` — that's a straight line.
|
|
92
|
+
- Bounce / elastic easing on functional UI — reads dated and draws attention to the animation itself.
|
|
93
|
+
- Different easings on entrance vs exit of the same element.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Interaction Rules
|
|
98
|
+
|
|
99
|
+
- **Interaction feedback ≤ 100ms** — if it takes longer, it stops feeling like a reaction.
|
|
100
|
+
- **Hover contract** — transform + color + shadow move together, never just one property.
|
|
101
|
+
- **Focus contract** — a focus ring still needs a perceptible transition (fast, no easing drama) so keyboard users see the state change.
|
|
102
|
+
- **Never change metrics on interaction** — `font-weight`, `font-size`, `letter-spacing`, `padding`, `border-width`. **Why:** these are layout properties, so the text re-flows and every glyph shifts under the cursor; the element also can't be animated on the compositor, so the change lands as a jump instead of a transition. Signal hover with color, background, shadow, or `transform` — all of which leave the box untouched. When a bolder hover is the design, render the bold weight from the start in a hidden layer and cross-fade opacity, or pair a `border` with a same-width transparent `border` in the resting state so the box never resizes.
|
|
103
|
+
- **Disable interactions on exiting elements** — visually present, logically gone.
|
|
104
|
+
- **Never block interaction while a stagger plays** — decorative motion must not gate input.
|
|
105
|
+
- **Transitions vs. keyframes — pick by interruptibility.** A CSS transition retargets smoothly toward whatever the newest end state is, even mid-flight — interrupt a hover-out with a hover-in and it reverses cleanly from its current position. A keyframe animation restarts from its `0%` frame every time it's re-triggered, discarding wherever it currently was. Interactive state changes (hover, press, toggle, open/close) are transitions for exactly this reason; keyframes are for one-shot choreography that never needs to be interrupted mid-play (a one-time celebration, a loading spinner).
|
|
106
|
+
|
|
107
|
+
General interaction rules (keyboard timing, touch targets, `overscroll-behavior`, `touch-action`, optimistic UI, destructive confirmations) live in `accessibility.md` — honor them on every animated control.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Choreography Rules
|
|
112
|
+
|
|
113
|
+
**Exit runs at ~75% of entrance duration.** This is the one rule that matters most. If entrance is `--motion-medium` (280ms), exit is ~200ms with `--ease-out` (or a flatter tail like `cubic-bezier(0.4, 0, 1, 1)`).
|
|
114
|
+
|
|
115
|
+
1. **Hierarchy first.** Parent/container animates before child/content. Context arrives, then detail. Modal backdrop fades in (`--motion-fast`), then the modal panel slides (`--motion-medium`), then form fields enter (staggered).
|
|
116
|
+
2. **Stagger is 30-80ms between siblings.** Never uniform at zero (feels pasted-on), never more than 80ms per item (list feels slow beyond the 6th item). Marketing pages can push to 100-150ms for dramatic entrances.
|
|
117
|
+
3. **Co-located properties share timing.** Multiple properties on one element (transform + opacity + color on hover) use ONE duration + easing, not three competing. `transition: transform 200ms var(--ease-out), opacity 200ms var(--ease-out);` — not three different durations.
|
|
118
|
+
4. **Shared elements use layout animations.** When the "same" element appears in two places (list → detail), use `layoutId` (Motion), View Transitions API, or FLIP — not discrete enter/exit pairs that visually unmount and remount.
|
|
119
|
+
5. **Exit mirrors initial** for symmetry: if entering from `opacity: 0, y: 20`, exit to the same.
|
|
120
|
+
6. **Unique keys** (not index) for dynamic lists — enables smooth add/remove.
|
|
121
|
+
7. **List reordering** needs layout animation mode, not sequential mode.
|
|
122
|
+
8. **Wait modes nearly double perceived duration** — use shorter durations when sequencing enter/exit.
|
|
123
|
+
9. **Suppress enter animation on first paint.** Elements already in their default state on page load must not animate in — only on later state changes. With Motion, set `initial={false}` on `AnimatePresence` (icon swaps, tabs, toggles, segmented controls). Exception: a deliberate first-time entrance (staggered hero, loading sequence) relies on `initial` — don't kill it there. Verify on a hard refresh. This is the fix for the "entrance on every page load" anti-pattern below.
|
|
124
|
+
|
|
125
|
+
Stagger example:
|
|
126
|
+
|
|
127
|
+
```css
|
|
128
|
+
.item:nth-child(1) { animation-delay: 0ms; }
|
|
129
|
+
.item:nth-child(2) { animation-delay: 50ms; }
|
|
130
|
+
.item:nth-child(3) { animation-delay: 100ms; }
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
For multi-stage storyboards with named timing + config objects + stage-driven sequencing, see `../examples/animation-storyboard.md`.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Motion Budget per Surface
|
|
138
|
+
|
|
139
|
+
A ceiling on simultaneous entrance animations. This is the tool against AI's "animate everything on mount" slop.
|
|
140
|
+
|
|
141
|
+
| Surface | Budget |
|
|
142
|
+
|---------|--------|
|
|
143
|
+
| Landing hero | Up to 3 staggered entrances (headline / subhead / CTA). One scroll-linked element max. |
|
|
144
|
+
| Feature section | 1 reveal-on-scroll per card, stagger 40ms, triggers once. |
|
|
145
|
+
| Dashboard | **Micro-interactions only.** No entrance animations on metric cards, charts, tables. |
|
|
146
|
+
| Forms | 1 focus ring transition per field. No field-by-field staggered entrance. |
|
|
147
|
+
| Modals | Backdrop fade + panel transform. Nothing inside the modal animates on open. |
|
|
148
|
+
| Settings / admin | Zero entrance animations. High-frequency tool — motion wastes time. |
|
|
149
|
+
| Onboarding (first-run) | Larger budget — the one moment it pays off. |
|
|
150
|
+
|
|
151
|
+
If a viewport has more than its budget, cut. Every "let's add one more" compounds.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Spring vs Tween
|
|
156
|
+
|
|
157
|
+
> **Pick spring OR tween globally, not per-component.** Mixing in the same app reads as inconsistent. Document the choice in the project's design doc.
|
|
158
|
+
|
|
159
|
+
Springs feel natural because they simulate real physics — no fixed duration, they settle based on physical parameters. Stiffness controls resistance to displacement: high stiffness means the spring snaps back hard and fast. Damping controls resistance to velocity: low damping lets the element overshoot and oscillate; high damping absorbs the energy and settles immediately. The practical result: high stiffness + high damping = quick, settled, confident. High stiffness + low damping = bouncy and playful. Low stiffness + high damping = slow and heavy. Never tune by feel alone — fix a target feel, then dial parameters to match it.
|
|
160
|
+
|
|
161
|
+
**Use springs when:**
|
|
162
|
+
- Drag interactions with momentum
|
|
163
|
+
- Elements that should feel "alive" (Dynamic Island)
|
|
164
|
+
- Gestures that can be interrupted mid-animation (springs maintain velocity; CSS animations restart from zero)
|
|
165
|
+
- Mouse-tracking decorative interactions (`useSpring` to interpolate)
|
|
166
|
+
|
|
167
|
+
**Use tween when:**
|
|
168
|
+
- The project has strict duration budgets (200-300ms UI rule)
|
|
169
|
+
- Deterministic motion matches the brand
|
|
170
|
+
- Motion is purely functional (no "alive" personality)
|
|
171
|
+
|
|
172
|
+
**Spring configuration (Motion / Framer):**
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
// Apple-style (recommended — easier to reason about)
|
|
176
|
+
{ type: "spring", duration: 0.5, bounce: 0.2 }
|
|
177
|
+
|
|
178
|
+
// Traditional physics (more control)
|
|
179
|
+
{ type: "spring", mass: 1, stiffness: 100, damping: 10 }
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Spring rules:**
|
|
183
|
+
- Bounce in functional UI is an anti-pattern. The only acceptable use is single-element micro-feedback (heart toggle, badge appearance) where bounce amplitude stays at 0.1-0.3 of base.
|
|
184
|
+
- Balanced parameters: `stiffness: 500, damping: 30` settles quickly; `stiffness: 1000, damping: 5` is too bouncy.
|
|
185
|
+
- Drag release: `{ type: "spring", velocity: info.velocity.x }` preserves input energy.
|
|
186
|
+
- Springs have no fixed duration → constrain via `bounce ≤ 0.25` and `visualDuration`.
|
|
187
|
+
|
|
188
|
+
**Spring presets:**
|
|
189
|
+
|
|
190
|
+
| Use case | Config | Feel |
|
|
191
|
+
|----------|--------|------|
|
|
192
|
+
| Cards / containers | `stiffness: 300, damping: 30` | Smooth settle — no overshoot, reads as composed |
|
|
193
|
+
| Pop-ins / badges | `stiffness: 500, damping: 25` | Snappy with a very slight bounce — draws the eye without distracting |
|
|
194
|
+
| Slides / entrances | `stiffness: 350, damping: 28` | Balanced — lands confidently, slight energy without playfulness |
|
|
195
|
+
| Drag release | `stiffness: 500, damping: 30` + velocity | Fast return that preserves the momentum the user imparted |
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Swapping One Icon for Another In Place
|
|
200
|
+
|
|
201
|
+
A sun that becomes a moon on theme toggle, a checkmark that replaces a copy icon after a click — same slot, different glyph, no layout shift. Done wrong it either reads as a flicker (plain crossfade, nothing else) or as the icon resizing itself rather than being replaced (entering scale starts too large).
|
|
202
|
+
|
|
203
|
+
- **Both icons cross-fade opacity together** — outgoing down, incoming up, one shared duration.
|
|
204
|
+
- **The incoming icon grows in from roughly 0.25-0.35 of its full size, not from a near-full scale.** The point isn't the exact number — it's that entering should read as *arriving*, not as *resizing*. Starting closer to 1 makes the icon look like it's stretching into place; starting small enough makes it unambiguous that a new element appeared.
|
|
205
|
+
- **A few pixels of blur clearing to zero** as the incoming icon settles takes the edge off the transition further, so the swap feels like one continuous event rather than a hard cut.
|
|
206
|
+
- **No overshoot.** Springs here want to land and stop — a wobbling checkmark after a save reads as a glitch, not a flourish, because nothing about "your click registered" calls for bounce.
|
|
207
|
+
|
|
208
|
+
DOM-only fallback when a motion library isn't in play — stack both icons in the same grid cell and toggle a class from parent state:
|
|
209
|
+
|
|
210
|
+
```css
|
|
211
|
+
.swap-slot {
|
|
212
|
+
position: relative;
|
|
213
|
+
display: inline-grid;
|
|
214
|
+
}
|
|
215
|
+
.swap-slot > svg {
|
|
216
|
+
grid-area: 1 / 1;
|
|
217
|
+
opacity: 0;
|
|
218
|
+
transform: scale(0.3);
|
|
219
|
+
filter: blur(2px);
|
|
220
|
+
transition: opacity var(--motion-fast) var(--ease-out),
|
|
221
|
+
transform var(--motion-fast) var(--ease-out),
|
|
222
|
+
filter var(--motion-fast) var(--ease-out);
|
|
223
|
+
}
|
|
224
|
+
.swap-slot > svg[data-visible="true"] {
|
|
225
|
+
opacity: 1;
|
|
226
|
+
transform: scale(1);
|
|
227
|
+
filter: blur(0);
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Reduced-Motion Contract
|
|
234
|
+
|
|
235
|
+
`prefers-reduced-motion` is not optional. The system must degrade to zero-motion gracefully — not break, not disappear, not skip states.
|
|
236
|
+
|
|
237
|
+
```css
|
|
238
|
+
@media (prefers-reduced-motion: reduce) {
|
|
239
|
+
*,
|
|
240
|
+
*::before,
|
|
241
|
+
*::after {
|
|
242
|
+
animation-duration: 0.01ms !important;
|
|
243
|
+
animation-iteration-count: 1 !important;
|
|
244
|
+
transition-duration: 0.01ms !important;
|
|
245
|
+
scroll-behavior: auto !important;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Two exceptions to "disable everything":**
|
|
251
|
+
- **Loading indicators keep animating.** Reduced motion removes gratuitous motion, not signal. A frozen spinner reads as "broken."
|
|
252
|
+
- **Essential feedback stays.** A focus ring still needs a perceptible transition (fast, no easing drama) so keyboard users see the state change.
|
|
253
|
+
|
|
254
|
+
Test: macOS → System Preferences → Accessibility → Display → "Reduce motion." Windows → Settings → Accessibility → Visual effects → "Animation effects" off. Every entrance and exit should still deliver the user to the right visual state, instantly.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Rendering Performance
|
|
259
|
+
|
|
260
|
+
### Pipeline
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
Layout (expensive) → Paint (moderate) → Composite (cheap)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
- **Composite only** (`transform`, `opacity`) — GPU-accelerated, off main thread. Default choice.
|
|
267
|
+
- **Paint** (`color`, `borders`, `gradients`, `filters`) — repaints the layer, moderate cost.
|
|
268
|
+
- **Layout** (`width`, `height`, `margin`, `padding`, `flex`, `grid`) — forces full reflow. Never animate continuously.
|
|
269
|
+
|
|
270
|
+
Prefer CSS transitions/animations → WAAPI → JS libraries, in that order. Some JS libraries use shorthand props (`x`, `y`, `scale`) that run on the main thread via `requestAnimationFrame` — verify compositor promotion. When in doubt, use explicit `transform` strings.
|
|
271
|
+
|
|
272
|
+
```js
|
|
273
|
+
// May drop frames (shorthand — main thread)
|
|
274
|
+
animate({ x: 100 })
|
|
275
|
+
// Hardware-accelerated (explicit)
|
|
276
|
+
animate({ transform: "translateX(100px)" })
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### FLIP Pattern
|
|
280
|
+
|
|
281
|
+
Measure once, animate via transform — never animate layout properties directly:
|
|
282
|
+
|
|
283
|
+
```js
|
|
284
|
+
const first = el.getBoundingClientRect(); // 1. First: measure
|
|
285
|
+
el.classList.add('moved');
|
|
286
|
+
const last = el.getBoundingClientRect(); // 2. Last: apply final state
|
|
287
|
+
el.style.transform = `translateX(${first.left - last.left}px)`; // 3. Invert
|
|
288
|
+
requestAnimationFrame(() => { // 4. Play
|
|
289
|
+
el.style.transition = 'transform 0.3s';
|
|
290
|
+
el.style.transform = '';
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Scroll-Linked Motion
|
|
295
|
+
|
|
296
|
+
Prefer CSS Scroll Timelines — runs off main thread:
|
|
297
|
+
|
|
298
|
+
```css
|
|
299
|
+
.reveal {
|
|
300
|
+
animation: fade-in linear;
|
|
301
|
+
animation-timeline: view();
|
|
302
|
+
}
|
|
303
|
+
@keyframes fade-in {
|
|
304
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
305
|
+
to { opacity: 1; transform: translateY(0); }
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Prefer `IntersectionObserver` for visibility triggers. Never poll `scrollY` or use `scroll` event listeners for continuous animation — causes layout/paint on every frame.
|
|
310
|
+
|
|
311
|
+
**A `requestAnimationFrame` loop needs an owner.** Something in the code must be responsible for calling `cancelAnimationFrame` once the element unmounts or the animation reaches its resting state — otherwise the loop just keeps scheduling itself against a target that no longer needs it, spending frame budget for no visible benefit.
|
|
312
|
+
|
|
313
|
+
### Layers & Promotion
|
|
314
|
+
|
|
315
|
+
`will-change` is surgical and temporary — add it before the animation starts, remove it after:
|
|
316
|
+
|
|
317
|
+
```css
|
|
318
|
+
.animating { will-change: transform; }
|
|
319
|
+
.done { will-change: auto; }
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Avoid many or large promoted layers — each consumes GPU memory. Validate with DevTools Layers panel when performance matters.
|
|
323
|
+
|
|
324
|
+
### Blur & Filters
|
|
325
|
+
|
|
326
|
+
Keep blur ≤ 8px when animating (≤ 20px static). Never on large surfaces — extremely expensive, especially Safari. `backdrop-filter` is costly; never animate it. Reach for `opacity` + `translate` before blur.
|
|
327
|
+
|
|
328
|
+
### CSS Variables Gotcha
|
|
329
|
+
|
|
330
|
+
Changing a CSS variable on a parent recalculates all children. Update `transform` directly on the element:
|
|
331
|
+
|
|
332
|
+
```js
|
|
333
|
+
// Bad: triggers recalc on all children
|
|
334
|
+
element.style.setProperty('--swipe-amount', `${distance}px`);
|
|
335
|
+
// Good: only affects this element
|
|
336
|
+
element.style.transform = `translateY(${distance}px)`;
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Never animate inherited CSS variables. Scope animated variables locally.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Anti-Patterns
|
|
344
|
+
|
|
345
|
+
- [ ] Bespoke durations (`transition-[153ms]`, `duration: 0.23`). Pick a token.
|
|
346
|
+
- [ ] `transition: all` — list specific properties.
|
|
347
|
+
- [ ] Easings named "smooth" with symmetric `cubic-bezier(0.5, 0.5, 0.5, 0.5)`. That's linear.
|
|
348
|
+
- [ ] Bounce / elastic easing on functional UI. Bounce is for celebration, not buttons.
|
|
349
|
+
- [ ] `ease-in` on UI — slow start reads as sluggish.
|
|
350
|
+
- [ ] Parallax on body content (kills scroll performance; hostile to reduced-motion).
|
|
351
|
+
- [ ] Scroll-jacking (hijacking the wheel). Users have a mental model for scroll. Don't break it.
|
|
352
|
+
- [ ] Motion on every hover (death by a thousand animations). Hover affordance is enough; not every row needs to move.
|
|
353
|
+
- [ ] Entrance animations on every page load. First visit is special; the 47th is not.
|
|
354
|
+
- [ ] Different easings on entrance vs exit of the same element. Asymmetry feels broken.
|
|
355
|
+
- [ ] Exaggerated scale on hover (`1.08+` on CTAs). Keep ≤ 1.02 for functional UI; 1.02-1.05 for cards.
|
|
356
|
+
- [ ] Animating `width` / `height` / `top` / `left` — layout thrash. Use `transform` / `opacity`.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Motion Gap Audit
|
|
361
|
+
|
|
362
|
+
The most common motion failure is not bad easing — it is missing motion entirely: UI state changes that snap with no transition, modals that appear at full opacity in one frame, lists that teleport items in on mount.
|
|
363
|
+
|
|
364
|
+
**Audit pattern.** Search the codebase for conditional renders without enter/exit, modal mounts without entry, and list items appearing without stagger. These are the sites most likely to have a motion gap.
|
|
365
|
+
|
|
366
|
+
Grep patterns for React:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
# Conditional renders — element may appear/disappear with no transition
|
|
370
|
+
grep -rn '&&\s*(<' src/
|
|
371
|
+
|
|
372
|
+
# Modals / dialogs — check whether AnimatePresence or CSSTransition wraps the mount
|
|
373
|
+
grep -rn 'useState.*open\|isOpen.*useState' src/
|
|
374
|
+
|
|
375
|
+
# Lists — check whether items have stagger or enter animation
|
|
376
|
+
grep -rn '\.map(' src/ | grep -v 'test\|spec\|story'
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Not every state change deserves motion. The audit surfaces candidates; the **Decision Ladder** (frequency × justification) filters them. A settings panel that opens 50 times a day should snap — that gap is intentional. A first-run modal that appears once deserves an entrance. Apply the ladder before adding motion to every hit.
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Figma JSON Export
|
|
384
|
+
|
|
385
|
+
Shareable tokens for Figma variables, Style Dictionary, or tokens.studio:
|
|
386
|
+
|
|
387
|
+
```json
|
|
388
|
+
{
|
|
389
|
+
"motion": {
|
|
390
|
+
"duration": {
|
|
391
|
+
"fast": { "value": 120, "type": "duration", "unit": "ms" },
|
|
392
|
+
"base": { "value": 200, "type": "duration", "unit": "ms" },
|
|
393
|
+
"medium": { "value": 280, "type": "duration", "unit": "ms" },
|
|
394
|
+
"slow": { "value": 400, "type": "duration", "unit": "ms" },
|
|
395
|
+
"slower": { "value": 600, "type": "duration", "unit": "ms" }
|
|
396
|
+
},
|
|
397
|
+
"ease": {
|
|
398
|
+
"out": { "value": [0.22, 1, 0.36, 1], "type": "cubicBezier" },
|
|
399
|
+
"inOut": { "value": [0.65, 0, 0.35, 1], "type": "cubicBezier" },
|
|
400
|
+
"emphasized": { "value": [0.2, 0, 0, 1], "type": "cubicBezier" },
|
|
401
|
+
"soft": { "value": [0.4, 0, 0.2, 1], "type": "cubicBezier" }
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Figma handles `cubicBezier` via plugins; Style Dictionary compiles to CSS / JS / iOS / Android.
|
|
408
|
+
|
|
409
|
+
**Framework mapping:**
|
|
410
|
+
|
|
411
|
+
| Framework | Duration | Easing |
|
|
412
|
+
|-----------|----------|--------|
|
|
413
|
+
| Vanilla CSS | `var(--motion-base)` | `var(--ease-out)` |
|
|
414
|
+
| Tailwind (extend) | `theme.transitionDuration: { base: '200ms' }` | `theme.transitionTimingFunction: { out: 'cubic-bezier(0.22, 1, 0.36, 1)' }` |
|
|
415
|
+
| Motion (framer-motion) | `transition={{ duration: 0.2 }}` | `transition={{ ease: [0.22, 1, 0.36, 1] }}` |
|
|
416
|
+
| GSAP | `gsap.to(el, { duration: 0.2 })` | `ease: 'power2.out'` (≈ ease-out) |
|
|
417
|
+
|
|
418
|
+
Map the project's animation library to the token names, not raw values — when the scale changes, one file updates.
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## Cross-Refs
|
|
423
|
+
|
|
424
|
+
- `stack.md` — Motion / GSAP / Three.js library specifics (opt-in during Discovery).
|
|
425
|
+
- `modern-css.md` — `@starting-style` for enter-on-mount, View Transitions API.
|
|
426
|
+
- `accessibility.md` — reduced-motion + full interaction rules (keyboard, focus, touch).
|
|
427
|
+
- `../examples/animation-storyboard.md` — multi-stage sequenced animations pattern.
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## Sources
|
|
432
|
+
|
|
433
|
+
- W3C — CSS Media Queries Level 5: `prefers-reduced-motion`.
|
|
434
|
+
- WCAG 2.3.3 (AAA) — Animation from Interactions.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Personas — Walkthrough Checklists
|
|
2
|
+
|
|
3
|
+
Five personas, five journeys. A heuristic score says what's wrong in the abstract. A persona walkthrough says *who* it breaks for. Use these as overlays on top of `heuristics.md`: same UI, five different lenses.
|
|
4
|
+
|
|
5
|
+
Each persona has a checklist. Every un-ticked box is a finding. Rank findings using the same impact tags from `heuristics.md` (`blocks-conversion`, `adds-friction`, `reduces-trust`, `minor-polish`).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. First-timer Priya
|
|
10
|
+
|
|
11
|
+
**Tagline:** Never used this product category. Doesn't know what's possible.
|
|
12
|
+
|
|
13
|
+
**Profile:** 28, mobile-only, moderate English literacy, small-business owner evaluating the product for the first time. Comes in cold from a social ad. Has 90 seconds before she bounces. Doesn't know industry jargon and won't search for it.
|
|
14
|
+
|
|
15
|
+
**Journey:** Land → understand what the product does → decide whether to sign up → complete signup → reach the "first value" moment (whatever that is for this app).
|
|
16
|
+
|
|
17
|
+
### Walkthrough checklist
|
|
18
|
+
|
|
19
|
+
- [ ] Above the fold, I understand what this product does in 5 seconds
|
|
20
|
+
- [ ] No jargon on the landing page ("synergy", "orchestrate", "leverage")
|
|
21
|
+
- [ ] One primary CTA is obvious — I don't have to hunt for "sign up"
|
|
22
|
+
- [ ] Signup asks for the minimum; I'm not giving 8 fields before I see the product
|
|
23
|
+
- [ ] Email / Google / Apple auth all work on mobile (no desktop-only flow)
|
|
24
|
+
- [ ] After signup, there's a clear next action — not a blank dashboard
|
|
25
|
+
- [ ] Empty states tell me what to do: "Create your first X"
|
|
26
|
+
- [ ] Terms I don't know have tooltips or inline explanations
|
|
27
|
+
- [ ] Error messages explain the problem in plain language
|
|
28
|
+
- [ ] If I tap something and nothing happens, I know whether it's loading or broken
|
|
29
|
+
- [ ] I can undo my first destructive action
|
|
30
|
+
- [ ] The primary value moment is reachable in ≤ 3 steps from signup
|
|
31
|
+
- [ ] Help/contact is reachable without leaving the app
|
|
32
|
+
- [ ] Keyboard autocorrect doesn't fight the form (autocomplete attrs set)
|
|
33
|
+
- [ ] I can pinch-zoom if text is too small
|
|
34
|
+
|
|
35
|
+
### Red flags
|
|
36
|
+
|
|
37
|
+
- Landing page uses phrases like "AI-powered orchestration platform" without saying what it does
|
|
38
|
+
- Signup form asks for company size / industry / role before account creation
|
|
39
|
+
- Dashboard is empty with no "Create your first" CTA
|
|
40
|
+
- Tooltips missing on icon-only buttons — she doesn't know what the cog vs wrench means
|
|
41
|
+
- Email verification required before she sees any product value
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 2. Power User Jordan
|
|
46
|
+
|
|
47
|
+
**Tagline:** Lives in this tool 4+ hours a day. Expects to fly.
|
|
48
|
+
|
|
49
|
+
**Profile:** 34, senior ops at a scaleup. Keyboard-first, multiple-monitor, deep domain expertise. Uses the product as a core part of their job. Metrics their own speed — any friction compounds. Will build a userscript or quit the product if workflows feel slow.
|
|
50
|
+
|
|
51
|
+
**Journey:** Log in → jump straight to a specific record via shortcut or search → perform bulk operations → export/report → jump to the next task.
|
|
52
|
+
|
|
53
|
+
### Walkthrough checklist
|
|
54
|
+
|
|
55
|
+
- [ ] Cmd+K (or /) command palette exists and is fast (< 100ms to open)
|
|
56
|
+
- [ ] Every common action has a keyboard shortcut, listed in tooltips
|
|
57
|
+
- [ ] Tab order is sensible; Shift+Tab reverses; Escape closes
|
|
58
|
+
- [ ] Bulk select works on lists of 100+ with Shift-click and Cmd+A
|
|
59
|
+
- [ ] Common bulk actions (delete, archive, assign) execute in < 400ms
|
|
60
|
+
- [ ] URL state reflects filters / sort / selection so I can bookmark and share
|
|
61
|
+
- [ ] I can paginate 1000+ items without virtualized list jank
|
|
62
|
+
- [ ] Saved views / segments / filters persist across sessions
|
|
63
|
+
- [ ] The app never traps me in a modal I can't Escape out of
|
|
64
|
+
- [ ] Destructive actions are undoable, not confirm-blocking (confirms slow me down)
|
|
65
|
+
- [ ] Forms submit on Cmd+Enter
|
|
66
|
+
- [ ] I can customize columns, hotkeys, or density
|
|
67
|
+
- [ ] Paste-to-action works (paste a URL, paste a list of IDs)
|
|
68
|
+
- [ ] Export works for the current view, not "everything"
|
|
69
|
+
- [ ] No scroll hijacking, no unasked page transitions
|
|
70
|
+
|
|
71
|
+
### Red flags
|
|
72
|
+
|
|
73
|
+
- No command palette on a tool users will spend hours in
|
|
74
|
+
- Modal-confirm on every delete instead of undo toast
|
|
75
|
+
- Lists of 200+ items without bulk select
|
|
76
|
+
- Filters reset on navigation — state lives in local React, not URL
|
|
77
|
+
- Shortcuts exist but are only discoverable in a hidden help page
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 3. Low-bandwidth Adaeze
|
|
82
|
+
|
|
83
|
+
**Tagline:** 2G cellular, older Android, often offline.
|
|
84
|
+
|
|
85
|
+
**Profile:** 31, field worker in a region with intermittent connectivity. Budget Android device (4GB RAM, Chrome). Tethers off a phone. Comes in and out of signal throughout the day. Data costs real money per MB.
|
|
86
|
+
|
|
87
|
+
**Journey:** Open the app on a flaky connection → read or edit a record → queue work while offline → have it sync when reconnected.
|
|
88
|
+
|
|
89
|
+
### Walkthrough checklist
|
|
90
|
+
|
|
91
|
+
- [ ] First paint < 3s on simulated 2G (check Lighthouse mobile)
|
|
92
|
+
- [ ] Critical CSS inlined; no blocking font load
|
|
93
|
+
- [ ] Images have explicit dimensions to prevent layout shift
|
|
94
|
+
- [ ] Images are lazy below the fold and use modern formats (WebP/AVIF)
|
|
95
|
+
- [ ] Bundle doesn't ship megabytes of unused code
|
|
96
|
+
- [ ] Skeletons appear within 200ms so the screen isn't blank
|
|
97
|
+
- [ ] Offline state is detected and communicated ("You're offline — changes will sync")
|
|
98
|
+
- [ ] Writes are queued locally and reconciled on reconnect
|
|
99
|
+
- [ ] Optimistic UI so she doesn't wait for round-trips
|
|
100
|
+
- [ ] Error message distinguishes network failure from server failure
|
|
101
|
+
- [ ] Retry is one tap, not "refresh the page"
|
|
102
|
+
- [ ] Text remains readable without images loaded
|
|
103
|
+
- [ ] The app doesn't poll aggressively on cellular
|
|
104
|
+
- [ ] Large uploads resumeable or chunked
|
|
105
|
+
- [ ] Service worker caches the shell so return visits are instant
|
|
106
|
+
|
|
107
|
+
### Red flags
|
|
108
|
+
|
|
109
|
+
- Blank screen for 6+ seconds on 2G
|
|
110
|
+
- "Network error" toast that disappears; no retry, no queue
|
|
111
|
+
- Data lost on tab close because it wasn't persisted locally
|
|
112
|
+
- Every interaction requires a server round-trip before the UI updates
|
|
113
|
+
- App auto-plays hero video over cellular
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 4. Screen-reader user Kwame
|
|
118
|
+
|
|
119
|
+
**Tagline:** Fully blind. NVDA + Chrome. Keyboard-only. Needs semantic HTML.
|
|
120
|
+
|
|
121
|
+
**Profile:** 40, accessibility consultant, experienced screen-reader user. Runs NVDA on Windows + Chrome. Never uses a mouse. Relies on heading structure, ARIA landmarks, and predictable focus order.
|
|
122
|
+
|
|
123
|
+
**Journey:** Land on the page → use headings / landmarks to skim structure → tab through interactive elements → complete the same task any sighted user can.
|
|
124
|
+
|
|
125
|
+
### Walkthrough checklist
|
|
126
|
+
|
|
127
|
+
- [ ] Page has exactly one `<h1>` and a sensible heading outline
|
|
128
|
+
- [ ] Landmarks present: `<main>`, `<nav>`, `<header>`, `<footer>`, `<aside>`
|
|
129
|
+
- [ ] Every interactive element is reachable via Tab in logical order
|
|
130
|
+
- [ ] Focus is always visible (`:focus-visible` ring on every control)
|
|
131
|
+
- [ ] Buttons are `<button>`; links are `<a>`; no `<div onClick>`
|
|
132
|
+
- [ ] Icon-only buttons have `aria-label` or visually-hidden text
|
|
133
|
+
- [ ] Form fields have associated `<label>` (not just placeholder)
|
|
134
|
+
- [ ] Errors use `aria-invalid` and `aria-describedby` to link the message
|
|
135
|
+
- [ ] Modals trap focus, restore on close, and have `aria-labelledby`
|
|
136
|
+
- [ ] Live regions (`aria-live`) announce loading / success / error states
|
|
137
|
+
- [ ] Dynamic content changes are announced — not silent
|
|
138
|
+
- [ ] Images have `alt`; decorative images have `alt=""`
|
|
139
|
+
- [ ] Tables have `<th scope>` and captions where relevant
|
|
140
|
+
- [ ] Custom components (combobox, tabs, menu) follow WAI-ARIA APG
|
|
141
|
+
- [ ] Skip-to-content link exists and works
|
|
142
|
+
- [ ] No positive `tabindex` values; no focus traps outside modals
|
|
143
|
+
- [ ] Color is not the only signal for error / success / state
|
|
144
|
+
|
|
145
|
+
### Red flags
|
|
146
|
+
|
|
147
|
+
- `<div>` with onClick used as a button — not keyboard-reachable
|
|
148
|
+
- Custom select built on `<div>` with no ARIA combobox pattern
|
|
149
|
+
- Focus ring removed globally (`*:focus { outline: none }`)
|
|
150
|
+
- Error toast shown with no `aria-live` — screen reader never hears it
|
|
151
|
+
- Modal opens but focus stays on the triggering button
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 5. One-thumb Margo
|
|
156
|
+
|
|
157
|
+
**Tagline:** On the subway. Holding coffee. 30 seconds of attention.
|
|
158
|
+
|
|
159
|
+
**Profile:** 29, commuter, Mobile Safari, iPhone 14. Holding a coffee in one hand, phone in the other. Checking the app for a quick status glance. If she can't get what she needs in 30 seconds, she closes the tab.
|
|
160
|
+
|
|
161
|
+
**Journey:** Open link from a notification → get the single answer she came for → maybe take one quick action → leave.
|
|
162
|
+
|
|
163
|
+
### Walkthrough checklist
|
|
164
|
+
|
|
165
|
+
- [ ] Primary info is above the fold on a 390px-wide screen
|
|
166
|
+
- [ ] Everything she needs to tap sits in the thumb zone (bottom 2/3 of screen)
|
|
167
|
+
- [ ] Touch targets ≥ 44×44px
|
|
168
|
+
- [ ] No reliance on hover — every affordance works on tap
|
|
169
|
+
- [ ] Sticky header/footer doesn't eat 40% of the viewport
|
|
170
|
+
- [ ] Safe areas respected (notch, home indicator, keyboard)
|
|
171
|
+
- [ ] Forms use correct `inputmode` and `autocomplete` so iOS keyboard cooperates
|
|
172
|
+
- [ ] Submit buttons are large and reachable with one thumb
|
|
173
|
+
- [ ] Confirmations are visible without scrolling
|
|
174
|
+
- [ ] No horizontal scroll unless explicitly intended
|
|
175
|
+
- [ ] Back-swipe gesture works; browser back doesn't lose state
|
|
176
|
+
- [ ] Modals are dismissible by swipe-down or tap-outside
|
|
177
|
+
- [ ] Copy doesn't assume desktop ("click the button on the right")
|
|
178
|
+
- [ ] Text is readable without zoom (min 16px body)
|
|
179
|
+
- [ ] Sharing respects the system share sheet
|
|
180
|
+
|
|
181
|
+
### Red flags
|
|
182
|
+
|
|
183
|
+
- Primary CTA at the top of the viewport — she can't reach it
|
|
184
|
+
- Iframe / captcha that doesn't scale to mobile
|
|
185
|
+
- Pinch-zoom disabled
|
|
186
|
+
- Tooltip-only explanations (no hover on touch)
|
|
187
|
+
- Cancel / submit placed 8px apart — fat-finger tapping wrong one
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## How to Use
|
|
192
|
+
|
|
193
|
+
Invoked by `/ui-craft:heuristic <target> --persona=<name>`:
|
|
194
|
+
|
|
195
|
+
| Arg | Persona | Loads |
|
|
196
|
+
|-----|---------|-------|
|
|
197
|
+
| `--persona=priya` | First-timer Priya | Section 1 |
|
|
198
|
+
| `--persona=jordan` | Power user Jordan | Section 2 |
|
|
199
|
+
| `--persona=adaeze` | Low-bandwidth Adaeze | Section 3 |
|
|
200
|
+
| `--persona=kwame` | Screen-reader user Kwame | Section 4 |
|
|
201
|
+
| `--persona=margo` | One-thumb Margo | Section 5 |
|
|
202
|
+
| `--persona=all` | All five | All sections |
|
|
203
|
+
|
|
204
|
+
Output format: same scorecard shape as `heuristics.md` (rows of `| Checklist item | Pass/Fail | Finding | Impact |`). End with top 3-5 findings ranked by impact tag.
|
|
205
|
+
|
|
206
|
+
Persona walkthroughs do not replace the Nielsen + Design Laws pass — they supplement it. A UI can score well on Nielsen and still fail Kwame.
|