@tamsensedev/runner 0.10.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +5 -5
  2. package/README.md +61 -50
  3. package/dist/cli.js +1188 -324
  4. package/dist/cli.js.map +1 -1
  5. package/dist/recorder.cjs +42 -6
  6. package/dist/recorder.cjs.map +1 -1
  7. package/library/.claude-plugin/plugin.json +5 -5
  8. package/library/THIRD_PARTY_NOTICES.md +123 -123
  9. package/library/agents/code-architect.md +67 -67
  10. package/library/agents/code-explorer.md +61 -61
  11. package/library/agents/code-reviewer.md +73 -73
  12. package/library/agents/code-simplifier.md +62 -62
  13. package/library/agents/design-critic.md +52 -52
  14. package/library/agents/second-opinion.md +64 -64
  15. package/library/agents/security-auditor.md +86 -86
  16. package/library/agents/silent-failure-hunter.md +70 -70
  17. package/library/agents/test-coverage-analyzer.md +78 -78
  18. package/library/agents/test-runner.md +48 -48
  19. package/library/agents/ui-tester.md +50 -50
  20. package/library/licenses/Apache-2.0.txt +202 -202
  21. package/library/skills/accessibility/SKILL.md +144 -144
  22. package/library/skills/accessibility/references/wcag-checks.md +168 -168
  23. package/library/skills/addressing-review-feedback/SKILL.md +109 -109
  24. package/library/skills/app-screens/SKILL.md +96 -96
  25. package/library/skills/app-screens/references/shell-kpis-charts.md +108 -108
  26. package/library/skills/app-screens/references/states-density-phone.md +54 -54
  27. package/library/skills/app-screens/references/tables-forms-settings.md +104 -104
  28. package/library/skills/avoiding-generic-ai-design/SKILL.md +92 -92
  29. package/library/skills/avoiding-generic-ai-design/references/tells.md +112 -112
  30. package/library/skills/clean-commits/SKILL.md +85 -85
  31. package/library/skills/design-review/SKILL.md +107 -107
  32. package/library/skills/design-review/references/critique-passes.md +101 -101
  33. package/library/skills/design-review/references/screenshot-procedure.md +121 -121
  34. package/library/skills/differential-security-review/LICENSE +427 -427
  35. package/library/skills/differential-security-review/SKILL.md +105 -105
  36. package/library/skills/differential-security-review/references/adversarial.md +115 -115
  37. package/library/skills/differential-security-review/references/methodology.md +145 -145
  38. package/library/skills/differential-security-review/references/patterns.md +108 -108
  39. package/library/skills/false-positive-check/LICENSE +427 -427
  40. package/library/skills/false-positive-check/SKILL.md +95 -95
  41. package/library/skills/false-positive-check/references/bug-class-verification.md +128 -128
  42. package/library/skills/false-positive-check/references/deep-verification.md +113 -113
  43. package/library/skills/false-positive-check/references/evidence-templates.md +93 -93
  44. package/library/skills/false-positive-check/references/false-positive-patterns.md +122 -122
  45. package/library/skills/false-positive-check/references/gate-reviews.md +36 -36
  46. package/library/skills/false-positive-check/references/standard-verification.md +80 -80
  47. package/library/skills/focused-implementation/SKILL.md +104 -104
  48. package/library/skills/implementation-plan/SKILL.md +66 -66
  49. package/library/skills/interface-design/SKILL.md +144 -144
  50. package/library/skills/interface-design/references/craft-defaults.md +130 -130
  51. package/library/skills/interface-design/references/new-direction.md +62 -62
  52. package/library/skills/interface-design/references/writing.md +52 -52
  53. package/library/skills/interface-polish/SKILL.md +103 -103
  54. package/library/skills/interface-polish/references/detail-rules.md +154 -154
  55. package/library/skills/interface-polish/references/worst-case-data.md +158 -158
  56. package/library/skills/motion-design/SKILL.md +106 -106
  57. package/library/skills/motion-design/references/recipes.md +243 -243
  58. package/library/skills/postgres-performance/SKILL.md +97 -97
  59. package/library/skills/postgres-performance/references/rules.md +265 -265
  60. package/library/skills/project-orientation/SKILL.md +77 -77
  61. package/library/skills/reviewing-changes/SKILL.md +183 -183
  62. package/library/skills/safe-database-changes/SKILL.md +111 -111
  63. package/library/skills/safe-database-changes/references/backfills.md +75 -75
  64. package/library/skills/safe-database-changes/references/migration-tools.md +69 -69
  65. package/library/skills/safe-database-changes/references/postgres-ddl.md +199 -199
  66. package/library/skills/security-review/SKILL.md +132 -132
  67. package/library/skills/security-review/references/checks-by-area.md +107 -107
  68. package/library/skills/security-review/references/do-not-report.md +86 -86
  69. package/library/skills/spec-writing/SKILL.md +144 -144
  70. package/library/skills/systematic-debugging/SKILL.md +130 -130
  71. package/library/skills/systematic-debugging/references/flaky-tests.md +51 -51
  72. package/library/skills/systematic-debugging/references/root-cause-tracing.md +55 -55
  73. package/library/skills/task-interview/SKILL.md +84 -84
  74. package/library/skills/test-driven-development/SKILL.md +116 -116
  75. package/library/skills/test-driven-development/references/writing-good-tests.md +85 -85
  76. package/library/skills/ui-testing/SKILL.md +102 -102
  77. package/library/skills/ui-testing/references/headless-check.md +295 -280
  78. package/library/skills/ui-testing/references/local-stand.md +51 -51
  79. package/library/skills/ui-testing/references/playwright-patterns.md +134 -134
  80. package/library/skills/ui-testing/references/recording.md +61 -58
  81. package/library/skills/verification-before-completion/SKILL.md +113 -113
  82. package/library/skills/web-interface-guidelines/SKILL.md +202 -202
  83. package/package.json +46 -46
@@ -1,243 +1,243 @@
1
- # Motion recipes
2
-
3
- Read this when implementing a specific transition. Use the project's existing motion tokens and library if it has them; these recipes are CSS first, then examples in common libraries. Do not add a library to use a recipe.
4
-
5
- ## Tokens
6
-
7
- ```css
8
- :root {
9
- --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
10
- --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
11
- --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
12
- --duration-press: 150ms;
13
- --duration-popover: 180ms;
14
- --duration-dialog: 240ms;
15
- }
16
- ```
17
-
18
- ## Reduced motion pattern
19
-
20
- Define movement only for people who have not asked to reduce it; keep the opacity change for everyone.
21
-
22
- ```css
23
- .toast { transition: opacity 200ms ease; }
24
-
25
- @media (prefers-reduced-motion: no-preference) {
26
- .toast {
27
- transition:
28
- opacity 200ms var(--ease-out),
29
- transform 200ms var(--ease-out);
30
- }
31
- }
32
- ```
33
-
34
- ## Press feedback
35
-
36
- ```css
37
- .button {
38
- transition: transform var(--duration-press) var(--ease-out);
39
- }
40
- .button:active:not(:disabled) { transform: scale(0.97); }
41
- ```
42
-
43
- ## Popover or menu (native popover, no JavaScript)
44
-
45
- ```css
46
- .menu[popover] {
47
- transform-origin: top left;
48
- opacity: 0;
49
- transform: scale(0.97);
50
- transition:
51
- opacity var(--duration-popover) var(--ease-out),
52
- transform var(--duration-popover) var(--ease-out),
53
- overlay var(--duration-popover) allow-discrete,
54
- display var(--duration-popover) allow-discrete;
55
- }
56
- .menu[popover]:popover-open { opacity: 1; transform: scale(1); }
57
-
58
- @starting-style {
59
- .menu[popover]:popover-open { opacity: 0; transform: scale(0.97); }
60
- }
61
- ```
62
-
63
- Browsers without `@starting-style` simply show the menu without animation. Set `transform-origin` to the side of the trigger. With a headless library, use the origin variable it exposes (for example `var(--radix-popover-content-transform-origin)` or `var(--transform-origin)`).
64
-
65
- ## Dialog
66
-
67
- Same pattern on `dialog[open]`, centred origin, `--duration-dialog`, plus a backdrop fade:
68
-
69
- ```css
70
- dialog::backdrop { background: rgb(0 0 0 / 0.4); transition: opacity var(--duration-dialog) ease; }
71
- ```
72
-
73
- Exit about 75% of the enter duration.
74
-
75
- ## Drawer or bottom sheet
76
-
77
- ```css
78
- .sheet {
79
- transform: translateY(100%);
80
- transition: transform 300ms var(--ease-drawer);
81
- }
82
- .sheet[data-open="true"] { transform: translateY(0); }
83
- ```
84
-
85
- Percent translations move by the element's own size, so they work at any height. Add `overscroll-behavior: contain` on the sheet's scroll area.
86
-
87
- ## Expand and collapse without animating height
88
-
89
- ```css
90
- .collapsible {
91
- display: grid;
92
- grid-template-rows: 0fr;
93
- transition: grid-template-rows 250ms var(--ease-out);
94
- }
95
- .collapsible[data-open="true"] { grid-template-rows: 1fr; }
96
- .collapsible > .inner { overflow: hidden; }
97
- ```
98
-
99
- ## Toast
100
-
101
- Enter from the edge it lives on, leave the same way, a little faster:
102
-
103
- ```css
104
- .toast { transform: translateY(0); opacity: 1; }
105
- .toast[data-state="entering"], .toast[data-state="leaving"] { transform: translateY(100%); opacity: 0; }
106
- .toast[data-state="leaving"] { transition-duration: 150ms; }
107
- ```
108
-
109
- Pause auto-dismiss timers while the toast is hovered or the tab is hidden.
110
-
111
- ## Icon swap (copy to copied, play to pause)
112
-
113
- Keep both icons in the DOM, one positioned over the other, and cross-fade:
114
-
115
- ```css
116
- .icon-swap { position: relative; display: inline-grid; }
117
- .icon-swap > * {
118
- grid-area: 1 / 1;
119
- transition:
120
- opacity 250ms cubic-bezier(0.2, 0, 0, 1),
121
- transform 250ms cubic-bezier(0.2, 0, 0, 1),
122
- filter 250ms cubic-bezier(0.2, 0, 0, 1);
123
- }
124
- .icon-swap > [data-hidden="true"] { opacity: 0; transform: scale(0.25); filter: blur(4px); }
125
- ```
126
-
127
- ## Skeleton
128
-
129
- A flat muted block at the final size. If the project pulses skeletons, use a slow opacity pulse (1.5 to 2s) and stop it under reduced motion. No shimmer gradient sweeping across the screen.
130
-
131
- ## Web Animations API
132
-
133
- ```js
134
- const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches
135
- element.animate(
136
- reduce
137
- ? [{ opacity: 0 }, { opacity: 1 }]
138
- : [{ opacity: 0, transform: 'translateY(8px)' }, { opacity: 1, transform: 'none' }],
139
- { duration: 200, easing: 'cubic-bezier(0.23, 1, 0.32, 1)', fill: 'backwards' },
140
- )
141
- ```
142
-
143
- ## View Transitions
144
-
145
- ```js
146
- function update(change) {
147
- if (!document.startViewTransition) return change()
148
- document.startViewTransition(change)
149
- }
150
- ```
151
-
152
- ```css
153
- ::view-transition-old(root),
154
- ::view-transition-new(root) { animation-duration: 200ms; }
155
-
156
- @media (prefers-reduced-motion: reduce) {
157
- ::view-transition-group(*),
158
- ::view-transition-old(*),
159
- ::view-transition-new(*) { animation: none; }
160
- }
161
- ```
162
-
163
- Use them when continuity between two states is the point (a card expanding into its detail), not on every route change. Many routers (Astro, Nuxt, SvelteKit, Next.js) have their own switch for this; use it.
164
-
165
- ## Vue
166
-
167
- ```vue
168
- <template>
169
- <Transition name="pop">
170
- <div v-if="open" class="menu">...</div>
171
- </Transition>
172
- </template>
173
-
174
- <style>
175
- .pop-enter-active { transition: opacity 180ms var(--ease-out), transform 180ms var(--ease-out); }
176
- .pop-leave-active { transition: opacity 120ms var(--ease-out), transform 120ms var(--ease-out); }
177
- .pop-enter-from,
178
- .pop-leave-to { opacity: 0; transform: scale(0.97); }
179
- </style>
180
- ```
181
-
182
- For lists, `<TransitionGroup>` with a `.list-move { transition: transform 200ms var(--ease-out); }` class animates reordering.
183
-
184
- ## Svelte
185
-
186
- ```svelte
187
- <script>
188
- import { fly } from 'svelte/transition'
189
- import { cubicOut } from 'svelte/easing'
190
- </script>
191
-
192
- {#if open}
193
- <div class="menu" transition:fly={{ y: -4, duration: 180, easing: cubicOut }}>...</div>
194
- {/if}
195
- ```
196
-
197
- ## Motion for React
198
-
199
- Only if `motion` or `framer-motion` is already installed; import from whichever the project uses.
200
-
201
- ```jsx
202
- import { AnimatePresence, motion } from 'motion/react'
203
-
204
- <AnimatePresence initial={false}>
205
- {open && (
206
- <motion.div
207
- key="panel"
208
- initial={{ opacity: 0, transform: 'scale(0.97)' }}
209
- animate={{ opacity: 1, transform: 'scale(1)' }}
210
- exit={{ opacity: 0, transform: 'scale(0.97)', transition: { duration: 0.12 } }}
211
- transition={{ duration: 0.18, ease: [0.23, 1, 0.32, 1] }}
212
- />
213
- )}
214
- </AnimatePresence>
215
- ```
216
-
217
- `initial={false}` stops elements already present on first render from animating in. Full `transform` strings stay on the compositor when the main thread is busy; the `x` and `y` shorthands do not. For gestures and drags, springs (`{ type: 'spring', duration: 0.3, bounce: 0 }`) keep their velocity when interrupted; keep bounce at 0 in product UI. `useReducedMotion()` returns the user's preference.
218
-
219
- ## GSAP
220
-
221
- Only if GSAP is already installed.
222
-
223
- ```js
224
- import { gsap } from 'gsap'
225
-
226
- const mm = gsap.matchMedia()
227
- mm.add('(prefers-reduced-motion: no-preference)', () => {
228
- gsap.from('.step', { opacity: 0, y: 12, duration: 0.4, ease: 'power3.out', stagger: 0.06 })
229
- })
230
- ```
231
-
232
- `gsap.matchMedia()` reverts the animation when the media query stops matching. Keep scroll-triggered work for marketing pages, and give it a version that leaves content visible.
233
-
234
- ## Drag and gestures
235
-
236
- - Capture the pointer once dragging starts (`setPointerCapture`) so the drag continues outside the element.
237
- - Ignore extra touch points after the drag begins.
238
- - Dismiss on velocity as well as distance (a quick flick is enough).
239
- - Past a boundary, apply friction instead of a hard stop.
240
- - On `pointercancel`, `lostpointercapture` and window `blur`, clear the dragging state so the next drag works.
241
- - Offer a click or keyboard alternative for every gesture.
242
-
243
- Sources: adapted from emilkowalski/skills emil-design-eng (MIT, changed: examples made framework-neutral, Vue, Svelte and GSAP examples added), jakubkrehel/make-interfaces-feel-better animations reference (MIT, changed: icon swap duration aligned to 250ms, CSS grid variant) and pbakaus/impeccable animate and harden references (Apache-2.0, changed: rewritten).
1
+ # Motion recipes
2
+
3
+ Read this when implementing a specific transition. Use the project's existing motion tokens and library if it has them; these recipes are CSS first, then examples in common libraries. Do not add a library to use a recipe.
4
+
5
+ ## Tokens
6
+
7
+ ```css
8
+ :root {
9
+ --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
10
+ --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
11
+ --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
12
+ --duration-press: 150ms;
13
+ --duration-popover: 180ms;
14
+ --duration-dialog: 240ms;
15
+ }
16
+ ```
17
+
18
+ ## Reduced motion pattern
19
+
20
+ Define movement only for people who have not asked to reduce it; keep the opacity change for everyone.
21
+
22
+ ```css
23
+ .toast { transition: opacity 200ms ease; }
24
+
25
+ @media (prefers-reduced-motion: no-preference) {
26
+ .toast {
27
+ transition:
28
+ opacity 200ms var(--ease-out),
29
+ transform 200ms var(--ease-out);
30
+ }
31
+ }
32
+ ```
33
+
34
+ ## Press feedback
35
+
36
+ ```css
37
+ .button {
38
+ transition: transform var(--duration-press) var(--ease-out);
39
+ }
40
+ .button:active:not(:disabled) { transform: scale(0.97); }
41
+ ```
42
+
43
+ ## Popover or menu (native popover, no JavaScript)
44
+
45
+ ```css
46
+ .menu[popover] {
47
+ transform-origin: top left;
48
+ opacity: 0;
49
+ transform: scale(0.97);
50
+ transition:
51
+ opacity var(--duration-popover) var(--ease-out),
52
+ transform var(--duration-popover) var(--ease-out),
53
+ overlay var(--duration-popover) allow-discrete,
54
+ display var(--duration-popover) allow-discrete;
55
+ }
56
+ .menu[popover]:popover-open { opacity: 1; transform: scale(1); }
57
+
58
+ @starting-style {
59
+ .menu[popover]:popover-open { opacity: 0; transform: scale(0.97); }
60
+ }
61
+ ```
62
+
63
+ Browsers without `@starting-style` simply show the menu without animation. Set `transform-origin` to the side of the trigger. With a headless library, use the origin variable it exposes (for example `var(--radix-popover-content-transform-origin)` or `var(--transform-origin)`).
64
+
65
+ ## Dialog
66
+
67
+ Same pattern on `dialog[open]`, centred origin, `--duration-dialog`, plus a backdrop fade:
68
+
69
+ ```css
70
+ dialog::backdrop { background: rgb(0 0 0 / 0.4); transition: opacity var(--duration-dialog) ease; }
71
+ ```
72
+
73
+ Exit about 75% of the enter duration.
74
+
75
+ ## Drawer or bottom sheet
76
+
77
+ ```css
78
+ .sheet {
79
+ transform: translateY(100%);
80
+ transition: transform 300ms var(--ease-drawer);
81
+ }
82
+ .sheet[data-open="true"] { transform: translateY(0); }
83
+ ```
84
+
85
+ Percent translations move by the element's own size, so they work at any height. Add `overscroll-behavior: contain` on the sheet's scroll area.
86
+
87
+ ## Expand and collapse without animating height
88
+
89
+ ```css
90
+ .collapsible {
91
+ display: grid;
92
+ grid-template-rows: 0fr;
93
+ transition: grid-template-rows 250ms var(--ease-out);
94
+ }
95
+ .collapsible[data-open="true"] { grid-template-rows: 1fr; }
96
+ .collapsible > .inner { overflow: hidden; }
97
+ ```
98
+
99
+ ## Toast
100
+
101
+ Enter from the edge it lives on, leave the same way, a little faster:
102
+
103
+ ```css
104
+ .toast { transform: translateY(0); opacity: 1; }
105
+ .toast[data-state="entering"], .toast[data-state="leaving"] { transform: translateY(100%); opacity: 0; }
106
+ .toast[data-state="leaving"] { transition-duration: 150ms; }
107
+ ```
108
+
109
+ Pause auto-dismiss timers while the toast is hovered or the tab is hidden.
110
+
111
+ ## Icon swap (copy to copied, play to pause)
112
+
113
+ Keep both icons in the DOM, one positioned over the other, and cross-fade:
114
+
115
+ ```css
116
+ .icon-swap { position: relative; display: inline-grid; }
117
+ .icon-swap > * {
118
+ grid-area: 1 / 1;
119
+ transition:
120
+ opacity 250ms cubic-bezier(0.2, 0, 0, 1),
121
+ transform 250ms cubic-bezier(0.2, 0, 0, 1),
122
+ filter 250ms cubic-bezier(0.2, 0, 0, 1);
123
+ }
124
+ .icon-swap > [data-hidden="true"] { opacity: 0; transform: scale(0.25); filter: blur(4px); }
125
+ ```
126
+
127
+ ## Skeleton
128
+
129
+ A flat muted block at the final size. If the project pulses skeletons, use a slow opacity pulse (1.5 to 2s) and stop it under reduced motion. No shimmer gradient sweeping across the screen.
130
+
131
+ ## Web Animations API
132
+
133
+ ```js
134
+ const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches
135
+ element.animate(
136
+ reduce
137
+ ? [{ opacity: 0 }, { opacity: 1 }]
138
+ : [{ opacity: 0, transform: 'translateY(8px)' }, { opacity: 1, transform: 'none' }],
139
+ { duration: 200, easing: 'cubic-bezier(0.23, 1, 0.32, 1)', fill: 'backwards' },
140
+ )
141
+ ```
142
+
143
+ ## View Transitions
144
+
145
+ ```js
146
+ function update(change) {
147
+ if (!document.startViewTransition) return change()
148
+ document.startViewTransition(change)
149
+ }
150
+ ```
151
+
152
+ ```css
153
+ ::view-transition-old(root),
154
+ ::view-transition-new(root) { animation-duration: 200ms; }
155
+
156
+ @media (prefers-reduced-motion: reduce) {
157
+ ::view-transition-group(*),
158
+ ::view-transition-old(*),
159
+ ::view-transition-new(*) { animation: none; }
160
+ }
161
+ ```
162
+
163
+ Use them when continuity between two states is the point (a card expanding into its detail), not on every route change. Many routers (Astro, Nuxt, SvelteKit, Next.js) have their own switch for this; use it.
164
+
165
+ ## Vue
166
+
167
+ ```vue
168
+ <template>
169
+ <Transition name="pop">
170
+ <div v-if="open" class="menu">...</div>
171
+ </Transition>
172
+ </template>
173
+
174
+ <style>
175
+ .pop-enter-active { transition: opacity 180ms var(--ease-out), transform 180ms var(--ease-out); }
176
+ .pop-leave-active { transition: opacity 120ms var(--ease-out), transform 120ms var(--ease-out); }
177
+ .pop-enter-from,
178
+ .pop-leave-to { opacity: 0; transform: scale(0.97); }
179
+ </style>
180
+ ```
181
+
182
+ For lists, `<TransitionGroup>` with a `.list-move { transition: transform 200ms var(--ease-out); }` class animates reordering.
183
+
184
+ ## Svelte
185
+
186
+ ```svelte
187
+ <script>
188
+ import { fly } from 'svelte/transition'
189
+ import { cubicOut } from 'svelte/easing'
190
+ </script>
191
+
192
+ {#if open}
193
+ <div class="menu" transition:fly={{ y: -4, duration: 180, easing: cubicOut }}>...</div>
194
+ {/if}
195
+ ```
196
+
197
+ ## Motion for React
198
+
199
+ Only if `motion` or `framer-motion` is already installed; import from whichever the project uses.
200
+
201
+ ```jsx
202
+ import { AnimatePresence, motion } from 'motion/react'
203
+
204
+ <AnimatePresence initial={false}>
205
+ {open && (
206
+ <motion.div
207
+ key="panel"
208
+ initial={{ opacity: 0, transform: 'scale(0.97)' }}
209
+ animate={{ opacity: 1, transform: 'scale(1)' }}
210
+ exit={{ opacity: 0, transform: 'scale(0.97)', transition: { duration: 0.12 } }}
211
+ transition={{ duration: 0.18, ease: [0.23, 1, 0.32, 1] }}
212
+ />
213
+ )}
214
+ </AnimatePresence>
215
+ ```
216
+
217
+ `initial={false}` stops elements already present on first render from animating in. Full `transform` strings stay on the compositor when the main thread is busy; the `x` and `y` shorthands do not. For gestures and drags, springs (`{ type: 'spring', duration: 0.3, bounce: 0 }`) keep their velocity when interrupted; keep bounce at 0 in product UI. `useReducedMotion()` returns the user's preference.
218
+
219
+ ## GSAP
220
+
221
+ Only if GSAP is already installed.
222
+
223
+ ```js
224
+ import { gsap } from 'gsap'
225
+
226
+ const mm = gsap.matchMedia()
227
+ mm.add('(prefers-reduced-motion: no-preference)', () => {
228
+ gsap.from('.step', { opacity: 0, y: 12, duration: 0.4, ease: 'power3.out', stagger: 0.06 })
229
+ })
230
+ ```
231
+
232
+ `gsap.matchMedia()` reverts the animation when the media query stops matching. Keep scroll-triggered work for marketing pages, and give it a version that leaves content visible.
233
+
234
+ ## Drag and gestures
235
+
236
+ - Capture the pointer once dragging starts (`setPointerCapture`) so the drag continues outside the element.
237
+ - Ignore extra touch points after the drag begins.
238
+ - Dismiss on velocity as well as distance (a quick flick is enough).
239
+ - Past a boundary, apply friction instead of a hard stop.
240
+ - On `pointercancel`, `lostpointercapture` and window `blur`, clear the dragging state so the next drag works.
241
+ - Offer a click or keyboard alternative for every gesture.
242
+
243
+ Sources: adapted from emilkowalski/skills emil-design-eng (MIT, changed: examples made framework-neutral, Vue, Svelte and GSAP examples added), jakubkrehel/make-interfaces-feel-better animations reference (MIT, changed: icon swap duration aligned to 250ms, CSS grid variant) and pbakaus/impeccable animate and harden references (Apache-2.0, changed: rewritten).