luckiest-co 1.0.13 → 1.0.15

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 (132) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/skills/luckiest-ab-testing/SKILL.md +1 -1
  4. package/skills/luckiest-ad-creative/SKILL.md +1 -1
  5. package/skills/luckiest-ads/SKILL.md +1 -1
  6. package/skills/luckiest-advisors/SKILL.md +1 -1
  7. package/skills/luckiest-aeo-grader/SKILL.md +1 -1
  8. package/skills/luckiest-ai-seo/SKILL.md +1 -1
  9. package/skills/luckiest-analytics/SKILL.md +1 -1
  10. package/skills/luckiest-aso/SKILL.md +1 -1
  11. package/skills/luckiest-churn-prevention/SKILL.md +1 -1
  12. package/skills/luckiest-co-marketing/SKILL.md +1 -1
  13. package/skills/luckiest-coder/SKILL.md +1 -1
  14. package/skills/luckiest-coder-brainstorming/SKILL.md +1 -1
  15. package/skills/luckiest-coder-consistency/SKILL.md +1 -1
  16. package/skills/luckiest-coder-constitution/SKILL.md +1 -1
  17. package/skills/luckiest-coder-debugging/SKILL.md +1 -1
  18. package/skills/luckiest-coder-dispatching-parallel-agents/SKILL.md +1 -1
  19. package/skills/luckiest-coder-executing-plans/SKILL.md +1 -1
  20. package/skills/luckiest-coder-finishing-a-branch/SKILL.md +1 -1
  21. package/skills/luckiest-coder-git-worktrees/SKILL.md +1 -1
  22. package/skills/luckiest-coder-guard/SKILL.md +1 -1
  23. package/skills/luckiest-coder-qa/SKILL.md +1 -1
  24. package/skills/luckiest-coder-receiving-code-review/SKILL.md +1 -1
  25. package/skills/luckiest-coder-requesting-code-review/SKILL.md +1 -1
  26. package/skills/luckiest-coder-shipping/SKILL.md +1 -1
  27. package/skills/luckiest-coder-subagent-driven-development/SKILL.md +1 -1
  28. package/skills/luckiest-coder-tdd/SKILL.md +1 -1
  29. package/skills/luckiest-coder-verification/SKILL.md +1 -1
  30. package/skills/luckiest-coder-writing-plans/SKILL.md +1 -1
  31. package/skills/luckiest-coder-writing-skills/SKILL.md +1 -1
  32. package/skills/luckiest-cold-email/SKILL.md +1 -1
  33. package/skills/luckiest-community-marketing/SKILL.md +1 -1
  34. package/skills/luckiest-competitor-profiling/SKILL.md +1 -1
  35. package/skills/luckiest-competitors/SKILL.md +1 -1
  36. package/skills/luckiest-content-strategy/SKILL.md +1 -1
  37. package/skills/luckiest-copy-editing/SKILL.md +1 -1
  38. package/skills/luckiest-copywriting/SKILL.md +1 -1
  39. package/skills/luckiest-cro/SKILL.md +1 -1
  40. package/skills/luckiest-customer-research/SKILL.md +1 -1
  41. package/skills/luckiest-directory-submissions/SKILL.md +1 -1
  42. package/skills/luckiest-emails/SKILL.md +1 -1
  43. package/skills/luckiest-extract-design-system/SKILL.md +1 -1
  44. package/skills/luckiest-free-tools/SKILL.md +1 -1
  45. package/skills/luckiest-image/SKILL.md +1 -1
  46. package/skills/luckiest-launch/SKILL.md +1 -1
  47. package/skills/luckiest-lead-magnets/SKILL.md +1 -1
  48. package/skills/luckiest-marketing-ideas/SKILL.md +1 -1
  49. package/skills/luckiest-marketing-plan/SKILL.md +1 -1
  50. package/skills/luckiest-marketing-psychology/SKILL.md +1 -1
  51. package/skills/luckiest-model-router/SKILL.md +1 -1
  52. package/skills/luckiest-offers/SKILL.md +1 -1
  53. package/skills/luckiest-onboarding/SKILL.md +1 -1
  54. package/skills/luckiest-paywalls/SKILL.md +1 -1
  55. package/skills/luckiest-popups/SKILL.md +1 -1
  56. package/skills/luckiest-pricing/SKILL.md +1 -1
  57. package/skills/luckiest-product-marketing/SKILL.md +1 -1
  58. package/skills/luckiest-programmatic-seo/SKILL.md +1 -1
  59. package/skills/luckiest-prompt-rewrite/SKILL.md +1 -1
  60. package/skills/luckiest-prospecting/SKILL.md +1 -1
  61. package/skills/luckiest-public-relations/SKILL.md +1 -1
  62. package/skills/luckiest-referrals/SKILL.md +1 -1
  63. package/skills/luckiest-revops/SKILL.md +1 -1
  64. package/skills/luckiest-sales-enablement/SKILL.md +1 -1
  65. package/skills/luckiest-schema/SKILL.md +1 -1
  66. package/skills/luckiest-seo-audit/SKILL.md +1 -1
  67. package/skills/luckiest-session-handoff/SKILL.md +1 -1
  68. package/skills/luckiest-signup/SKILL.md +1 -1
  69. package/skills/luckiest-site-architecture/SKILL.md +1 -1
  70. package/skills/luckiest-sms/SKILL.md +1 -1
  71. package/skills/luckiest-social/SKILL.md +1 -1
  72. package/skills/luckiest-thinker/SKILL.md +1 -1
  73. package/skills/luckiest-trends/SKILL.md +1 -1
  74. package/skills/luckiest-video/SKILL.md +1 -1
  75. package/skills/luckiest-website-cloner/ATTRIBUTION.md +29 -0
  76. package/skills/luckiest-website-cloner/CHANGELOG.md +8 -0
  77. package/skills/luckiest-website-cloner/LICENSE +21 -0
  78. package/skills/luckiest-website-cloner/SKILL.md +289 -0
  79. package/skills/luckiest-website-cloner/references/builtin-browser-recipes.md +74 -0
  80. package/skills/luckiest-website-cloner/references/compositing.md +65 -0
  81. package/skills/luckiest-website-cloner/references/fidelity-fast-path.md +92 -0
  82. package/skills/luckiest-website-cloner/references/playwright-cli-recipes.md +68 -0
  83. package/skills/luckiest-website-cloner/references/teardown.md +142 -0
  84. package/skills/luckiest-website-cloner/references/visual-qa.md +102 -0
  85. package/skills/luckiest-website-cloner/scripts/build-bundle.cjs +12 -0
  86. package/skills/luckiest-website-cloner/scripts/motion-probe.js +423 -0
  87. package/skills/luckiest-website-cloner/scripts/surface-map.js +392 -0
  88. package/skills/luckiest-website-cloner/scripts/tokens-probe.js +178 -0
  89. package/skills/luckiest-website-cloner-dom/ATTRIBUTION.md +4 -0
  90. package/skills/luckiest-website-cloner-dom/CHANGELOG.md +8 -0
  91. package/skills/luckiest-website-cloner-dom/LICENSE +21 -0
  92. package/skills/luckiest-website-cloner-dom/SKILL.md +170 -0
  93. package/skills/luckiest-website-cloner-dom/references/asset-resolution.md +93 -0
  94. package/skills/luckiest-website-cloner-dom/references/interaction-fingerprints.md +85 -0
  95. package/skills/luckiest-website-cloner-remix/ATTRIBUTION.md +4 -0
  96. package/skills/luckiest-website-cloner-remix/CHANGELOG.md +8 -0
  97. package/skills/luckiest-website-cloner-remix/LICENSE +21 -0
  98. package/skills/luckiest-website-cloner-remix/SKILL.md +202 -0
  99. package/skills/luckiest-website-cloner-remix/references/directions.md +82 -0
  100. package/skills/luckiest-website-cloner-remix/references/reskin.md +84 -0
  101. package/skills/luckiest-website-cloner-remix/references/tweak-panel.md +72 -0
  102. package/skills/luckiest-website-cloner-remix/scripts/apply-overrides.cjs +105 -0
  103. package/skills/luckiest-website-cloner-remix/scripts/gallery.cjs +71 -0
  104. package/skills/luckiest-website-cloner-remix/scripts/tokenize-css.cjs +127 -0
  105. package/skills/luckiest-website-cloner-remix/scripts/tweak-panel.js +169 -0
  106. package/skills/luckiest-website-cloner-shaders/ATTRIBUTION.md +11 -0
  107. package/skills/luckiest-website-cloner-shaders/CHANGELOG.md +8 -0
  108. package/skills/luckiest-website-cloner-shaders/LICENSE +21 -0
  109. package/skills/luckiest-website-cloner-shaders/SKILL.md +129 -0
  110. package/skills/luckiest-website-cloner-shaders/references/capture-backends.md +89 -0
  111. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/SKILL.md +123 -0
  112. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/references/capture-backends.md +201 -0
  113. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/references/evidence-policy.md +93 -0
  114. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/references/operating-contract.md +82 -0
  115. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/references/qa-failure-policy.md +100 -0
  116. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/references/recon-kernel.md +206 -0
  117. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/references/replay-policy.md +145 -0
  118. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/references/shaders-com.md +212 -0
  119. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/references/source-analysis.md +112 -0
  120. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/references/surface-discovery.md +113 -0
  121. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/references/target-lock.md +205 -0
  122. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/references/three-shader-reconstruction.md +155 -0
  123. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/references/tool-capability-matrix.md +57 -0
  124. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/references/unicorn-studio.md +387 -0
  125. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/scripts/fetch-rendered-dom.mjs +178 -0
  126. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/scripts/scan-bundle.sh +80 -0
  127. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/templates/extraction-report.md +41 -0
  128. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/templates/known-gaps.md +14 -0
  129. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/templates/qa-report.md +74 -0
  130. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/templates/replay-manifest.json +147 -0
  131. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/templates/run-state.json +75 -0
  132. package/skills/luckiest-website-cloner-shaders/vendor/web-shader-extractor/templates/scout-card.json +106 -0
@@ -0,0 +1,423 @@
1
+ /*
2
+ * motion-probe.js — runtime animation/interaction extractor for /luckiest-website-cloner.
3
+ *
4
+ * Purpose: recover the EXACT parameters of every animation on a loaded page by
5
+ * asking the running libraries, instead of paraphrasing a minified bundle.
6
+ * A minified GSAP config is unreadable as text; `ScrollTrigger.getAll()` on the
7
+ * live page hands you start/end/scrub/pin/targets verbatim. Same for WAAPI
8
+ * (`document.getAnimations()`), Lenis options, Swiper/Splide params, and the
9
+ * stylesheet's @keyframes. This is the difference between "uses scroll
10
+ * animations" and "ScrollTrigger pinned to .hero, scrub:true, scale 1→0.3".
11
+ *
12
+ * Two ways to run it, best first:
13
+ *
14
+ * 1. INSTRUMENTED. Inject instrumentMotion() as an init script BEFORE the
15
+ * page's scripts run (Playwright page.addInitScript / playwright-cli).
16
+ * It records IntersectionObserver registrations (otherwise un-enumerable),
17
+ * requestAnimationFrame usage, and the real event listeners added to
18
+ * window/document (scroll/mousemove/wheel). Then run motionProbe() after
19
+ * the page settles.
20
+ *
21
+ * 2. POST-HOC (any browser eval tool — no preload). Just run
22
+ * motionProbe(). You still get GSAP/ScrollTrigger/WAAPI/Lenis/keyframes/
23
+ * sliders — everything that lives on a global or in the CSSOM. You lose
24
+ * IO/rAF/listener inventory; the probe says so in `instrumented:false`.
25
+ *
26
+ * Output is JSON. Save it to output/<hostname>/motion.json — it can be large.
27
+ * Nothing here mutates the page.
28
+ */
29
+
30
+ /* ---- Mode 1 helper: inject BEFORE page scripts run ---- */
31
+ function instrumentMotion() {
32
+ const W = window;
33
+ W.__motion = { io: [], raf: 0, listeners: [], mutationObs: 0 };
34
+
35
+ // IntersectionObserver: record options + observed targets (reveal-on-scroll,
36
+ // sticky-sidebar active states, lazy sections all live here).
37
+ try {
38
+ const OIO = W.IntersectionObserver;
39
+ if (OIO) {
40
+ W.IntersectionObserver = function (cb, opts) {
41
+ const rec = { options: opts || {}, targets: [] };
42
+ W.__motion.io.push(rec);
43
+ const inst = new OIO(cb, opts);
44
+ const oobs = inst.observe.bind(inst);
45
+ inst.observe = function (el) {
46
+ try {
47
+ rec.targets.push(
48
+ (el.id ? '#' + el.id : '') +
49
+ (el.className && typeof el.className === 'string'
50
+ ? '.' + el.className.trim().split(/\s+/).slice(0, 3).join('.')
51
+ : '') || el.tagName.toLowerCase()
52
+ );
53
+ } catch (_) {}
54
+ return oobs(el);
55
+ };
56
+ return inst;
57
+ };
58
+ W.IntersectionObserver.prototype = OIO.prototype;
59
+ }
60
+ } catch (_) {}
61
+
62
+ // rAF: count loops (a steady count after settle = custom animation loop).
63
+ try {
64
+ const oraf = W.requestAnimationFrame.bind(W);
65
+ W.requestAnimationFrame = function (cb) {
66
+ W.__motion.raf++;
67
+ return oraf(cb);
68
+ };
69
+ } catch (_) {}
70
+
71
+ // Global listeners: which input drives the page? (scroll/wheel/mousemove/
72
+ // pointermove/resize/touchmove). Only window + document; element-level
73
+ // listeners are too noisy to be useful here.
74
+ try {
75
+ const patch = (target, name) => {
76
+ const oadd = target.addEventListener.bind(target);
77
+ target.addEventListener = function (type, fn, opts) {
78
+ if (/^(scroll|wheel|mousemove|pointermove|touchmove|resize|mouseenter|mouseleave|keydown)$/.test(type)) {
79
+ W.__motion.listeners.push({ on: name, type, passive: !!(opts && opts.passive) });
80
+ }
81
+ return oadd(type, fn, opts);
82
+ };
83
+ };
84
+ patch(W, 'window');
85
+ patch(document, 'document');
86
+ } catch (_) {}
87
+
88
+ try {
89
+ const OMO = W.MutationObserver;
90
+ if (OMO) {
91
+ W.MutationObserver = function (cb) {
92
+ W.__motion.mutationObs++;
93
+ return new OMO(cb);
94
+ };
95
+ W.MutationObserver.prototype = OMO.prototype;
96
+ }
97
+ } catch (_) {}
98
+ }
99
+
100
+ /* ---- The probe: run AFTER the page has settled (and after one full scroll) ---- */
101
+ function motionProbe() {
102
+ const W = window, D = document;
103
+ const out = {
104
+ url: location.href,
105
+ viewport: { w: W.innerWidth, h: W.innerHeight, dpr: W.devicePixelRatio },
106
+ instrumented: !!W.__motion,
107
+ libraries: {},
108
+ gsap: null,
109
+ scrollTrigger: [],
110
+ waapi: [],
111
+ keyframes: [],
112
+ scrollTimelines: [],
113
+ transitions: [],
114
+ lenis: null,
115
+ sliders: [],
116
+ splitText: [],
117
+ intersectionObservers: W.__motion ? W.__motion.io : undefined,
118
+ listeners: W.__motion ? W.__motion.listeners : undefined,
119
+ rafCalls: W.__motion ? W.__motion.raf : undefined,
120
+ customProps: {},
121
+ notes: [],
122
+ };
123
+
124
+ const sel = (el) => {
125
+ if (!el || !el.tagName) return String(el);
126
+ if (el.id) return '#' + el.id;
127
+ const cls =
128
+ typeof el.className === 'string' && el.className.trim()
129
+ ? '.' + el.className.trim().split(/\s+/).slice(0, 3).join('.')
130
+ : '';
131
+ return el.tagName.toLowerCase() + cls;
132
+ };
133
+ const safe = (v, depth = 0) => {
134
+ // Serialize tween vars / options without choking on DOM nodes or functions.
135
+ if (v == null) return v;
136
+ if (typeof v === 'function') return '[fn]';
137
+ // Host objects first — Lenis stores `wrapper: window`, which would otherwise
138
+ // serialize the entire global scope.
139
+ if (v === W) return '[window]';
140
+ if (v === D) return '[document]';
141
+ if (v instanceof Element) return sel(v);
142
+ if (typeof Node !== 'undefined' && v instanceof Node) return '[' + v.nodeName + ']';
143
+ if (typeof EventTarget !== 'undefined' && v instanceof EventTarget) return '[' + (v.constructor && v.constructor.name || 'EventTarget') + ']';
144
+ if (Array.isArray(v)) return depth > 3 ? '[…]' : v.slice(0, 20).map((x) => safe(x, depth + 1));
145
+ if (typeof v === 'object') {
146
+ if (depth > 3) return '[obj]';
147
+ const o = {};
148
+ let n = 0;
149
+ for (const k of Object.keys(v)) {
150
+ if (k.startsWith('_') || k === 'parent' || k === 'vars' && depth > 0) continue;
151
+ if (++n > 40) { o['…'] = 'truncated'; break; }
152
+ try { o[k] = safe(v[k], depth + 1); } catch (_) {}
153
+ }
154
+ return o;
155
+ }
156
+ return v;
157
+ };
158
+
159
+ /* ---- Library globals (hypotheses about HOW things move) ---- */
160
+ const libs = {
161
+ gsap: 'gsap', ScrollTrigger: 'ScrollTrigger', SplitText: 'SplitText', Flip: 'Flip',
162
+ Draggable: 'Draggable', Observer: 'Observer', ScrollSmoother: 'ScrollSmoother',
163
+ Lenis: 'Lenis', lenis: 'lenis', LocomotiveScroll: 'LocomotiveScroll',
164
+ Swiper: 'Swiper', Splide: 'Splide', Flickity: 'Flickity', Embla: 'EmblaCarousel',
165
+ barba: 'barba', Swup: 'Swup', anime: 'anime', Motion: 'Motion', motion: 'motion',
166
+ THREE: 'THREE', PIXI: 'PIXI', lottie: 'lottie', bodymovin: 'bodymovin', Rive: 'rive',
167
+ AOS: 'AOS', ScrollReveal: 'ScrollReveal', Alpine: 'Alpine', jQuery: 'jQuery',
168
+ __NEXT_DATA__: '__NEXT_DATA__', __NUXT__: '__NUXT__', Webflow: 'Webflow',
169
+ Shopify: 'Shopify', wp: 'wp', ___gatsby: '___gatsby', __remixContext: '__remixContext',
170
+ __sveltekit: '__sveltekit', __astro: '__astro',
171
+ };
172
+ for (const [k, g] of Object.entries(libs)) {
173
+ try {
174
+ const v = W[g];
175
+ if (v !== undefined) {
176
+ out.libraries[k] = (v && (v.version || v.VERSION || (v.core && v.core.version))) || true;
177
+ }
178
+ } catch (_) {}
179
+ }
180
+ // Framework fingerprints that don't expose globals
181
+ try {
182
+ if (D.querySelector('[data-reactroot], #__next, #root [data-reactid]') || Object.keys(D.body).some((k) => k.startsWith('__react')))
183
+ out.libraries.react = true;
184
+ if (D.querySelector('[data-v-app], #__nuxt, [data-server-rendered]')) out.libraries.vue = true;
185
+ if (D.querySelector('.w-nav, .w-slider, [data-wf-page]')) out.libraries.webflow = true;
186
+ if (D.querySelector('[data-framer-name], #__framer-badge-container, [data-framer-component-type]')) out.libraries.framer = true;
187
+ if (D.querySelector('link[href*="wp-content"], script[src*="wp-includes"]')) out.libraries.wordpress = true;
188
+ if (D.querySelector('script[src*="unicorn.studio"], [data-us-project]')) out.libraries.unicornStudio = true;
189
+ if (D.querySelector('spline-viewer, canvas[data-spline]')) out.libraries.spline = true;
190
+ } catch (_) {}
191
+
192
+ /* ---- GSAP: every live tween/timeline + all ScrollTriggers ---- */
193
+ try {
194
+ const g = W.gsap;
195
+ // Bundled GSAP (Next/Vite builds) registers no `window.gsap`, only
196
+ // `window.gsapVersions`. Then getAll() is unreachable — but GSAP stamps every
197
+ // tweened element with `_gsap`, so we can still list WHAT it animates and
198
+ // tell the builder to read params via the instrumented/source path.
199
+ if (!g && Array.isArray(W.gsapVersions)) {
200
+ out.libraries.gsap = W.gsapVersions.join(',') + ' (bundled, no global)';
201
+ const stamped = Array.from(D.querySelectorAll('*')).filter((el) => el._gsap).slice(0, 200);
202
+ out.gsap = {
203
+ version: W.gsapVersions[0], bundled: true, tweens: [],
204
+ stampedTargets: stamped.map((el) => ({ target: sel(el), x: el._gsap.x, y: el._gsap.y, scaleX: el._gsap.scaleX, rotation: el._gsap.rotation })),
205
+ };
206
+ if (D.querySelector('.pin-spacer')) out.gsap.pinSpacers = Array.from(D.querySelectorAll('.pin-spacer')).slice(0, 20).map((p) => ({ spacer: sel(p), pinned: p.firstElementChild ? sel(p.firstElementChild) : null, height: Math.round(p.getBoundingClientRect().height) }));
207
+ out.notes.push('gsap is bundled (no window.gsap): ScrollTrigger params not readable live. Use instrumentMotion() preload (listeners/IO) + grep the bundle for "scrollTrigger" near the target selectors; .pin-spacer count = pinned sections.');
208
+ }
209
+ if (g) {
210
+ out.gsap = { version: g.version, tweens: [], defaults: safe(g.defaults && g.defaults()) };
211
+ const root = g.globalTimeline;
212
+ const children = root && root.getChildren ? root.getChildren(true, true, true) : [];
213
+ for (const t of children.slice(0, 400)) {
214
+ try {
215
+ out.gsap.tweens.push({
216
+ kind: t.getChildren ? 'timeline' : 'tween',
217
+ targets: t.targets ? t.targets().slice(0, 5).map(sel) : undefined,
218
+ duration: t.duration && t.duration(),
219
+ delay: t.delay && t.delay(),
220
+ repeat: t.repeat && t.repeat(),
221
+ yoyo: t.yoyo && t.yoyo(),
222
+ paused: t.paused && t.paused(),
223
+ vars: safe(t.vars),
224
+ hasScrollTrigger: !!(t.scrollTrigger || (t.vars && t.vars.scrollTrigger)),
225
+ });
226
+ } catch (_) {}
227
+ }
228
+ if (children.length > 400) out.notes.push(`gsap: ${children.length} tweens, truncated to 400`);
229
+ }
230
+ const ST = W.ScrollTrigger || (g && g.plugins && g.plugins.ScrollTrigger);
231
+ if (ST && ST.getAll) {
232
+ for (const s of ST.getAll()) {
233
+ try {
234
+ out.scrollTrigger.push({
235
+ trigger: sel(s.trigger),
236
+ pin: s.pin ? sel(s.pin) : false,
237
+ start: s.start,
238
+ end: s.end,
239
+ scrub: s.vars.scrub,
240
+ markers: !!s.vars.markers,
241
+ snap: safe(s.vars.snap),
242
+ toggleActions: s.vars.toggleActions,
243
+ toggleClass: safe(s.vars.toggleClass),
244
+ once: !!s.vars.once,
245
+ pinSpacing: s.vars.pinSpacing,
246
+ anticipatePin: s.vars.anticipatePin,
247
+ animationTargets: s.animation && s.animation.targets ? s.animation.targets().slice(0, 5).map(sel) : undefined,
248
+ animationVars: s.animation ? safe(s.animation.vars) : undefined,
249
+ progress: s.progress,
250
+ });
251
+ } catch (_) {}
252
+ }
253
+ }
254
+ } catch (e) { out.notes.push('gsap probe error: ' + e.message); }
255
+
256
+ /* ---- WAAPI: CSS animations/transitions + JS element.animate() ---- */
257
+ try {
258
+ const anims = D.getAnimations ? D.getAnimations() : [];
259
+ for (const a of anims.slice(0, 300)) {
260
+ try {
261
+ const eff = a.effect;
262
+ const timing = eff && eff.getTiming ? eff.getTiming() : {};
263
+ const kf = eff && eff.getKeyframes ? eff.getKeyframes().slice(0, 12) : undefined;
264
+ out.waapi.push({
265
+ type: a.constructor.name, // CSSAnimation | CSSTransition | Animation
266
+ name: a.animationName || a.transitionProperty || a.id || undefined,
267
+ target: eff && eff.target ? sel(eff.target) : undefined,
268
+ pseudo: eff && eff.pseudoElement || undefined,
269
+ playState: a.playState,
270
+ timeline: a.timeline ? a.timeline.constructor.name : undefined, // ScrollTimeline/ViewTimeline = scroll-driven
271
+ duration: timing.duration,
272
+ delay: timing.delay,
273
+ iterations: timing.iterations,
274
+ direction: timing.direction,
275
+ easing: timing.easing,
276
+ fill: timing.fill,
277
+ keyframes: kf,
278
+ });
279
+ } catch (_) {}
280
+ }
281
+ if (anims.length > 300) out.notes.push(`waapi: ${anims.length} animations, truncated to 300`);
282
+ } catch (e) { out.notes.push('waapi probe error: ' + e.message); }
283
+
284
+ /* ---- CSSOM: @keyframes, scroll-timelines, transitions on real elements ---- */
285
+ try {
286
+ const seenKf = new Set();
287
+ for (const ss of Array.from(D.styleSheets)) {
288
+ let rules;
289
+ try { rules = ss.cssRules; } catch (_) { out.notes.push('cross-origin stylesheet (no CSSOM): ' + ss.href); continue; }
290
+ const walk = (list) => {
291
+ for (const r of Array.from(list)) {
292
+ if (r.type === CSSRule.KEYFRAMES_RULE) {
293
+ if (!seenKf.has(r.name)) { seenKf.add(r.name); out.keyframes.push({ name: r.name, css: r.cssText.slice(0, 2000) }); }
294
+ } else if (r.type === CSSRule.MEDIA_RULE || r.type === CSSRule.SUPPORTS_RULE) {
295
+ walk(r.cssRules);
296
+ } else if (r.style) {
297
+ const st = r.style;
298
+ if (st.animationTimeline && st.animationTimeline !== 'auto' || st.scrollTimelineName || st.viewTimelineName) {
299
+ out.scrollTimelines.push({ selector: r.selectorText, css: r.cssText.slice(0, 600) });
300
+ }
301
+ }
302
+ }
303
+ };
304
+ walk(rules);
305
+ }
306
+ } catch (e) { out.notes.push('cssom probe error: ' + e.message); }
307
+
308
+ // Transitions: which elements have non-trivial transitions (hover/state feel)
309
+ try {
310
+ const seenT = new Map();
311
+ for (const el of Array.from(D.querySelectorAll('a,button,[class*=card],[class*=btn],[class*=link],[class*=item],[class*=nav],img,li,h1,h2,h3,p,span,div')).slice(0, 3000)) {
312
+ const cs = getComputedStyle(el);
313
+ const dur = cs.transitionDuration;
314
+ if (dur && dur !== '0s' && cs.transitionProperty !== 'all' || (dur && dur !== '0s' && cs.transitionProperty === 'all' && parseFloat(dur) >= 0.15)) {
315
+ const key = cs.transitionProperty + '|' + dur + '|' + cs.transitionTimingFunction;
316
+ if (!seenT.has(key)) seenT.set(key, { property: cs.transitionProperty, duration: dur, easing: cs.transitionTimingFunction, delay: cs.transitionDelay, example: sel(el), count: 0 });
317
+ seenT.get(key).count++;
318
+ }
319
+ }
320
+ out.transitions = Array.from(seenT.values()).sort((a, b) => b.count - a.count).slice(0, 40);
321
+ } catch (_) {}
322
+
323
+ /* ---- Smooth scroll: Lenis / Locomotive with real options ---- */
324
+ try {
325
+ const l = W.lenis || (W.Lenis && W.Lenis.instance) || W.__lenis || (W.locomotive && W.locomotive.lenisInstance);
326
+ if (l) {
327
+ out.lenis = {
328
+ options: safe(l.options),
329
+ isSmooth: l.isSmooth, isStopped: l.isStopped,
330
+ htmlClasses: Array.from(D.documentElement.classList).filter((c) => /lenis|locomotive|smooth/i.test(c)),
331
+ };
332
+ } else if (D.documentElement.classList.contains('lenis') || D.querySelector('[data-lenis-prevent],[data-scroll-container]')) {
333
+ out.lenis = { detected: 'by-markup', options: 'not exposed on window — fall back to default lerp 0.1 / duration 1.2', htmlClasses: Array.from(D.documentElement.classList) };
334
+ }
335
+ if (W.ScrollSmoother && W.ScrollSmoother.get) { const s = W.ScrollSmoother.get(); if (s) out.lenis = Object.assign(out.lenis || {}, { scrollSmoother: safe(s.vars) }); }
336
+ } catch (_) {}
337
+
338
+ /* ---- Sliders / carousels with live params ---- */
339
+ try {
340
+ for (const el of Array.from(D.querySelectorAll('.swiper, .splide, .flickity-enabled, .embla, .slick-slider, [data-slider], [class*=carousel], [class*=marquee]')).slice(0, 30)) {
341
+ const rec = { selector: sel(el), lib: null, params: null };
342
+ if (el.swiper) { rec.lib = 'swiper'; rec.params = safe(el.swiper.params); }
343
+ else if (el.splide) { rec.lib = 'splide'; rec.params = safe(el.splide.options); }
344
+ else if (W.Flickity && W.Flickity.data && W.Flickity.data(el)) { rec.lib = 'flickity'; rec.params = safe(W.Flickity.data(el).options); }
345
+ else {
346
+ // CSS marquee? check child animation
347
+ const a = (D.getAnimations ? D.getAnimations({ subtree: true }) : []).find((x) => el.contains(x.effect && x.effect.target));
348
+ rec.lib = a ? 'css-animation:' + (a.animationName || 'waapi') : 'unknown';
349
+ }
350
+ out.sliders.push(rec);
351
+ }
352
+ } catch (_) {}
353
+
354
+ /* ---- SplitText-style char/word/line wrappers (text reveal pattern) ---- */
355
+ try {
356
+ const hits = D.querySelectorAll('.char, .word, .line, [class*=split-char], [class*=split-word], [class*=split-line], [data-split]');
357
+ if (hits.length) {
358
+ const parents = new Set();
359
+ hits.forEach((h) => { const p = h.closest('h1,h2,h3,h4,p,a,span,div'); if (p && p !== h) parents.add(sel(p.parentElement && p.parentElement.matches('h1,h2,h3,h4,p') ? p.parentElement : p)); });
360
+ out.splitText = { wrapperCount: hits.length, containers: Array.from(parents).slice(0, 20) };
361
+ }
362
+ } catch (_) {}
363
+
364
+ /* ---- Root custom properties that look animation-related ---- */
365
+ try {
366
+ const cs = getComputedStyle(D.documentElement);
367
+ for (const ss of Array.from(D.styleSheets)) {
368
+ let rules; try { rules = ss.cssRules; } catch (_) { continue; }
369
+ for (const r of Array.from(rules)) {
370
+ if (r.selectorText === ':root' || r.selectorText === 'html') {
371
+ for (const p of Array.from(r.style)) if (p.startsWith('--')) out.customProps[p] = cs.getPropertyValue(p).trim();
372
+ }
373
+ }
374
+ }
375
+ } catch (_) {}
376
+
377
+ /* ---- Summary: what drives this page? ---- */
378
+ out.summary = {
379
+ animationStack: [
380
+ out.gsap && 'gsap@' + out.gsap.version + (out.gsap.bundled ? '(bundled)' : ''),
381
+ out.gsap && out.gsap.pinSpacers && out.gsap.pinSpacers.length && `pin-spacers(${out.gsap.pinSpacers.length})`,
382
+ out.scrollTrigger.length && `ScrollTrigger(${out.scrollTrigger.length})`,
383
+ out.scrollTrigger.some((s) => s.pin) && 'pinned-sections',
384
+ out.scrollTrigger.some((s) => s.scrub) && 'scrub',
385
+ out.lenis && 'lenis',
386
+ out.scrollTimelines.length && 'css-scroll-timeline',
387
+ out.waapi.some((a) => a.type === 'CSSAnimation') && 'css-keyframes',
388
+ out.splitText.wrapperCount && 'split-text-reveal',
389
+ out.sliders.length && `sliders(${out.sliders.length})`,
390
+ out.libraries.THREE && 'three.js',
391
+ out.libraries.lottie && 'lottie',
392
+ ].filter(Boolean),
393
+ inputDrivers: out.listeners ? Array.from(new Set(out.listeners.map((l) => l.type))) : 'unknown (not instrumented)',
394
+ ioCount: out.intersectionObservers ? out.intersectionObservers.length : 'unknown (not instrumented)',
395
+ platform: Object.keys(out.libraries).filter((k) => /next|nuxt|webflow|framer|wordpress|gatsby|remix|svelte|astro|shopify|react|vue/i.test(k)),
396
+ };
397
+
398
+ return out;
399
+ }
400
+
401
+ /* Small, console-safe view for a first look (< ~4KB). Save the full
402
+ * motionProbe() to disk; print this one. */
403
+ function motionSummary() {
404
+ const m = motionProbe();
405
+ return {
406
+ summary: m.summary,
407
+ libraries: m.libraries,
408
+ lenis: m.lenis && { options: m.lenis.options, detected: m.lenis.detected },
409
+ scrollTriggers: m.scrollTrigger.length,
410
+ pinned: m.scrollTrigger.filter((s) => s.pin).map((s) => s.trigger).slice(0, 10),
411
+ tweens: m.gsap ? (m.gsap.tweens.length || (m.gsap.stampedTargets || []).length) : 0,
412
+ waapi: { total: m.waapi.length, css: m.waapi.filter((a) => a.type === 'CSSAnimation').length, scrollDriven: m.waapi.filter((a) => /Scroll|View/.test(a.timeline || '')).length },
413
+ keyframes: m.keyframes.map((k) => k.name).slice(0, 20),
414
+ sliders: m.sliders.map((s) => s.lib),
415
+ splitText: m.splitText.wrapperCount || 0,
416
+ topTransitions: m.transitions.slice(0, 5),
417
+ notes: m.notes,
418
+ };
419
+ }
420
+
421
+ if (typeof module !== 'undefined' && module.exports) {
422
+ module.exports = { instrumentMotion, motionProbe, motionSummary };
423
+ }