skillui 1.1.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 (59) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.js +202 -0
  3. package/dist/extractors/components.d.ts +11 -0
  4. package/dist/extractors/components.js +455 -0
  5. package/dist/extractors/framework.d.ts +4 -0
  6. package/dist/extractors/framework.js +126 -0
  7. package/dist/extractors/tokens/computed.d.ts +7 -0
  8. package/dist/extractors/tokens/computed.js +249 -0
  9. package/dist/extractors/tokens/css.d.ts +3 -0
  10. package/dist/extractors/tokens/css.js +510 -0
  11. package/dist/extractors/tokens/http-css.d.ts +14 -0
  12. package/dist/extractors/tokens/http-css.js +1689 -0
  13. package/dist/extractors/tokens/tailwind.d.ts +3 -0
  14. package/dist/extractors/tokens/tailwind.js +353 -0
  15. package/dist/extractors/tokens/tokens-file.d.ts +3 -0
  16. package/dist/extractors/tokens/tokens-file.js +229 -0
  17. package/dist/extractors/ultra/animations.d.ts +21 -0
  18. package/dist/extractors/ultra/animations.js +530 -0
  19. package/dist/extractors/ultra/components-dom.d.ts +13 -0
  20. package/dist/extractors/ultra/components-dom.js +152 -0
  21. package/dist/extractors/ultra/interactions.d.ts +14 -0
  22. package/dist/extractors/ultra/interactions.js +225 -0
  23. package/dist/extractors/ultra/layout.d.ts +14 -0
  24. package/dist/extractors/ultra/layout.js +126 -0
  25. package/dist/extractors/ultra/pages.d.ts +16 -0
  26. package/dist/extractors/ultra/pages.js +231 -0
  27. package/dist/font-resolver.d.ts +10 -0
  28. package/dist/font-resolver.js +280 -0
  29. package/dist/modes/dir.d.ts +6 -0
  30. package/dist/modes/dir.js +213 -0
  31. package/dist/modes/repo.d.ts +6 -0
  32. package/dist/modes/repo.js +76 -0
  33. package/dist/modes/ultra.d.ts +22 -0
  34. package/dist/modes/ultra.js +285 -0
  35. package/dist/modes/url.d.ts +14 -0
  36. package/dist/modes/url.js +161 -0
  37. package/dist/normalizer.d.ts +11 -0
  38. package/dist/normalizer.js +867 -0
  39. package/dist/screenshot.d.ts +9 -0
  40. package/dist/screenshot.js +94 -0
  41. package/dist/types-ultra.d.ts +157 -0
  42. package/dist/types-ultra.js +4 -0
  43. package/dist/types.d.ts +182 -0
  44. package/dist/types.js +4 -0
  45. package/dist/writers/animations-md.d.ts +17 -0
  46. package/dist/writers/animations-md.js +313 -0
  47. package/dist/writers/components-md.d.ts +8 -0
  48. package/dist/writers/components-md.js +151 -0
  49. package/dist/writers/design-md.d.ts +7 -0
  50. package/dist/writers/design-md.js +704 -0
  51. package/dist/writers/interactions-md.d.ts +8 -0
  52. package/dist/writers/interactions-md.js +146 -0
  53. package/dist/writers/layout-md.d.ts +8 -0
  54. package/dist/writers/layout-md.js +120 -0
  55. package/dist/writers/skill.d.ts +12 -0
  56. package/dist/writers/skill.js +1006 -0
  57. package/dist/writers/tokens-json.d.ts +11 -0
  58. package/dist/writers/tokens-json.js +164 -0
  59. package/package.json +78 -0
@@ -0,0 +1,530 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.captureAnimations = captureAnimations;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ /**
40
+ * Ultra mode — Full Animation Extractor
41
+ *
42
+ * Extracts EVERYTHING animation-related from a live website:
43
+ *
44
+ * 1. CSS @keyframes — complete with property values, selectors that use them,
45
+ * duration/easing/delay/iteration metadata
46
+ * 2. Scroll journey screenshots — 7 frames at 0/17/33/50/67/83/100% scroll depth,
47
+ * showing the cinematic state at every point of the page
48
+ * 3. Animation library detection — GSAP, ScrollTrigger, Lottie, Framer Motion,
49
+ * AOS, Anime.js, Three.js, Canvas, WebGL, etc.
50
+ * 4. Video backgrounds — src, poster, autoplay/loop/muted, first-frame capture
51
+ * 5. Scroll-triggered patterns — data-aos, data-scroll, IntersectionObserver-driven,
52
+ * GSAP ScrollTrigger elements
53
+ * 6. CSS animation variables — --duration-*, --ease-*, --delay-*, etc.
54
+ *
55
+ * Requires Playwright (optional peer dependency).
56
+ */
57
+ async function captureAnimations(url, skillDir) {
58
+ const empty = {
59
+ keyframes: [],
60
+ scrollFrames: [],
61
+ libraries: [],
62
+ videos: [],
63
+ scrollPatterns: [],
64
+ animationVars: [],
65
+ globalTransitions: [],
66
+ canvasCount: 0,
67
+ webglDetected: false,
68
+ lottieCount: 0,
69
+ };
70
+ let playwright;
71
+ try {
72
+ playwright = require('playwright');
73
+ }
74
+ catch {
75
+ return empty;
76
+ }
77
+ const scrollDir = path.join(skillDir, 'screens', 'scroll');
78
+ fs.mkdirSync(scrollDir, { recursive: true });
79
+ const browser = await playwright.chromium.launch({ headless: true });
80
+ try {
81
+ const context = await browser.newContext({
82
+ viewport: { width: 1440, height: 900 },
83
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
84
+ });
85
+ const page = await context.newPage();
86
+ // Disable CSS animations for initial load (avoids capturing mid-animation state)
87
+ // We'll re-enable them before scroll capture
88
+ await page.addInitScript(() => {
89
+ // Store original to restore later
90
+ window.__claudeui_originalRAF = window.requestAnimationFrame;
91
+ });
92
+ await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
93
+ await page.waitForTimeout(2000);
94
+ // ── Phase 1: Extract CSS keyframes from document.styleSheets ──────────
95
+ const keyframesRaw = await page.evaluate(() => {
96
+ const result = [];
97
+ for (const sheet of Array.from(document.styleSheets)) {
98
+ try {
99
+ const rules = Array.from(sheet.cssRules || []);
100
+ for (const rule of rules) {
101
+ if (rule.type === CSSRule.KEYFRAMES_RULE) {
102
+ const kfRule = rule;
103
+ const stops = [];
104
+ for (const kf of Array.from(kfRule.cssRules)) {
105
+ const kfStop = kf;
106
+ const props = {};
107
+ for (let i = 0; i < kfStop.style.length; i++) {
108
+ const p = kfStop.style[i];
109
+ props[p] = kfStop.style.getPropertyValue(p);
110
+ }
111
+ stops.push({ stop: kfStop.keyText, properties: props });
112
+ }
113
+ result.push({ name: kfRule.name, stops });
114
+ }
115
+ }
116
+ }
117
+ catch {
118
+ // Cross-origin stylesheet — skip
119
+ }
120
+ }
121
+ return result;
122
+ });
123
+ // ── Phase 2: Map animation-name → selector + properties ───────────────
124
+ const animUsageRaw = await page.evaluate(() => {
125
+ const usage = {};
126
+ for (const sheet of Array.from(document.styleSheets)) {
127
+ try {
128
+ for (const rule of Array.from(sheet.cssRules || [])) {
129
+ if (rule.type === CSSRule.STYLE_RULE) {
130
+ const styleRule = rule;
131
+ const name = styleRule.style.animationName;
132
+ if (!name || name === 'none' || name === '')
133
+ continue;
134
+ // Multiple animation-names separated by commas
135
+ const names = name.split(',').map((n) => n.trim());
136
+ for (const n of names) {
137
+ if (!n || n === 'none')
138
+ continue;
139
+ if (!usage[n]) {
140
+ usage[n] = { selectors: [] };
141
+ }
142
+ usage[n].selectors.push(styleRule.selectorText?.slice(0, 80) || '');
143
+ if (!usage[n].duration)
144
+ usage[n].duration = styleRule.style.animationDuration || undefined;
145
+ if (!usage[n].easing)
146
+ usage[n].easing = styleRule.style.animationTimingFunction || undefined;
147
+ if (!usage[n].delay)
148
+ usage[n].delay = styleRule.style.animationDelay || undefined;
149
+ if (!usage[n].iteration)
150
+ usage[n].iteration = styleRule.style.animationIterationCount || undefined;
151
+ if (!usage[n].fillMode)
152
+ usage[n].fillMode = styleRule.style.animationFillMode || undefined;
153
+ if (!usage[n].direction)
154
+ usage[n].direction = styleRule.style.animationDirection || undefined;
155
+ }
156
+ }
157
+ }
158
+ }
159
+ catch { /* cross-origin */ }
160
+ }
161
+ return usage;
162
+ });
163
+ // Merge keyframes + usage
164
+ const keyframes = keyframesRaw.map((kf) => {
165
+ const usage = animUsageRaw[kf.name] || {};
166
+ return {
167
+ name: kf.name,
168
+ stops: kf.stops,
169
+ usedBy: [...new Set(usage.selectors || [])].filter(Boolean).slice(0, 8),
170
+ animDuration: usage.duration,
171
+ animEasing: usage.easing,
172
+ animDelay: usage.delay,
173
+ animIteration: usage.iteration,
174
+ animFillMode: usage.fillMode,
175
+ animDirection: usage.direction,
176
+ };
177
+ });
178
+ // ── Phase 3: CSS Animation Variables ──────────────────────────────────
179
+ const animVarsRaw = await page.evaluate(() => {
180
+ const vars = [];
181
+ for (const sheet of Array.from(document.styleSheets)) {
182
+ try {
183
+ for (const rule of Array.from(sheet.cssRules || [])) {
184
+ if (rule.type === CSSRule.STYLE_RULE) {
185
+ const sr = rule;
186
+ if (sr.selectorText !== ':root' && sr.selectorText !== 'html' && sr.selectorText !== '*')
187
+ continue;
188
+ for (let i = 0; i < sr.style.length; i++) {
189
+ const prop = sr.style[i];
190
+ if (prop.startsWith('--')) {
191
+ const val = sr.style.getPropertyValue(prop).trim();
192
+ // Only animation-related variables
193
+ if (/duration|ease|delay|timing|animation|transition|spring|bounce|motion|speed|curve/i.test(prop)) {
194
+ vars.push({ name: prop, value: val });
195
+ }
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+ catch { /* cross-origin */ }
202
+ }
203
+ // Also read computed styles on :root for CSS vars
204
+ const rootStyle = getComputedStyle(document.documentElement);
205
+ for (const prop of Array.from(rootStyle)) {
206
+ if (prop.startsWith('--') && /duration|ease|delay|timing|animation|transition|spring|bounce|motion|speed|curve/i.test(prop)) {
207
+ const val = rootStyle.getPropertyValue(prop).trim();
208
+ if (val && !vars.find(v => v.name === prop)) {
209
+ vars.push({ name: prop, value: val });
210
+ }
211
+ }
212
+ }
213
+ return vars;
214
+ });
215
+ const animationVars = animVarsRaw.map(v => ({
216
+ name: v.name,
217
+ value: v.value,
218
+ category: categorizeAnimVar(v.name),
219
+ }));
220
+ // ── Phase 4: Detect Animation Libraries ───────────────────────────────
221
+ const librariesRaw = await page.evaluate(() => {
222
+ const found = [];
223
+ const w = window;
224
+ // GSAP
225
+ if (w.gsap)
226
+ found.push({ name: 'GSAP', version: w.gsap.version, type: 'animation' });
227
+ if (w.ScrollTrigger)
228
+ found.push({ name: 'ScrollTrigger', type: 'scroll' });
229
+ if (w.ScrollSmoother)
230
+ found.push({ name: 'ScrollSmoother', type: 'scroll' });
231
+ // Lottie
232
+ if (w.lottie || w.Lottie)
233
+ found.push({ name: 'Lottie', version: (w.lottie || w.Lottie)?.version, type: 'lottie' });
234
+ if (w.bodymovin)
235
+ found.push({ name: 'Bodymovin (Lottie)', type: 'lottie' });
236
+ // Three.js / WebGL
237
+ if (w.THREE)
238
+ found.push({ name: 'Three.js', version: w.THREE.REVISION, type: '3d' });
239
+ if (w.PIXI)
240
+ found.push({ name: 'PixiJS', version: w.PIXI.VERSION, type: '3d' });
241
+ if (w.Babylon)
242
+ found.push({ name: 'BabylonJS', type: '3d' });
243
+ // Framer Motion
244
+ if (w.Motion || w.motion)
245
+ found.push({ name: 'Motion One / Framer Motion', type: 'animation' });
246
+ // AOS
247
+ if (w.AOS)
248
+ found.push({ name: 'AOS (Animate On Scroll)', version: w.AOS.version, type: 'scroll' });
249
+ // Anime.js
250
+ if (w.anime)
251
+ found.push({ name: 'Anime.js', version: w.anime.version, type: 'animation' });
252
+ // ScrollMagic
253
+ if (w.ScrollMagic)
254
+ found.push({ name: 'ScrollMagic', type: 'scroll' });
255
+ // Locomotive Scroll
256
+ if (w.LocomotiveScroll || w.locomotiveScroll)
257
+ found.push({ name: 'Locomotive Scroll', type: 'scroll' });
258
+ // Velocity.js
259
+ if (w.Velocity)
260
+ found.push({ name: 'Velocity.js', type: 'animation' });
261
+ // Popmotion
262
+ if (w.popmotion)
263
+ found.push({ name: 'Popmotion', type: 'physics' });
264
+ // Matter.js
265
+ if (w.Matter)
266
+ found.push({ name: 'Matter.js (Physics)', type: 'physics' });
267
+ // Web Animations API usage (check if any element has getAnimations)
268
+ const hasWAAPI = typeof Element.prototype.getAnimations === 'function';
269
+ const liveAnims = document.querySelectorAll('*');
270
+ let wapiCount = 0;
271
+ liveAnims.forEach(el => {
272
+ try {
273
+ const anims = el.getAnimations();
274
+ if (anims.length > 0)
275
+ wapiCount += anims.length;
276
+ }
277
+ catch { }
278
+ });
279
+ if (wapiCount > 0 && hasWAAPI) {
280
+ found.push({ name: `Web Animations API (${wapiCount} active)`, type: 'animation' });
281
+ }
282
+ // Check script src tags for CDN libraries
283
+ const scripts = Array.from(document.querySelectorAll('script[src]'));
284
+ for (const script of scripts) {
285
+ const src = script.src || '';
286
+ if (/gsap/i.test(src) && !found.find(f => f.name === 'GSAP')) {
287
+ found.push({ name: 'GSAP', type: 'animation', cdn: src.split('/').slice(0, 5).join('/') });
288
+ }
289
+ if (/lottie|bodymovin/i.test(src) && !found.find(f => f.name.includes('Lottie'))) {
290
+ found.push({ name: 'Lottie', type: 'lottie', cdn: src.split('/').slice(0, 5).join('/') });
291
+ }
292
+ if (/three\.js|three\.min/i.test(src) && !found.find(f => f.name === 'Three.js')) {
293
+ found.push({ name: 'Three.js', type: '3d', cdn: src });
294
+ }
295
+ if (/framer-motion|motion\.js/i.test(src)) {
296
+ found.push({ name: 'Framer Motion', type: 'animation', cdn: src });
297
+ }
298
+ if (/aos\.js|aos\.min/i.test(src) && !found.find(f => f.name.includes('AOS'))) {
299
+ found.push({ name: 'AOS', type: 'scroll', cdn: src });
300
+ }
301
+ }
302
+ return found;
303
+ });
304
+ const libraries = librariesRaw;
305
+ // ── Phase 5: Video detection + first-frame capture ────────────────────
306
+ const videosRaw = await page.evaluate(() => {
307
+ return Array.from(document.querySelectorAll('video')).map((v, i) => ({
308
+ index: i + 1,
309
+ src: v.src || v.querySelector('source')?.getAttribute('src') || '',
310
+ poster: v.poster || '',
311
+ autoplay: v.autoplay,
312
+ loop: v.loop,
313
+ muted: v.muted,
314
+ width: Math.round(v.offsetWidth),
315
+ height: Math.round(v.offsetHeight),
316
+ role: v.offsetWidth > 800 ? 'background' : 'content',
317
+ }));
318
+ });
319
+ const videos = [];
320
+ for (const v of videosRaw.slice(0, 6)) {
321
+ const videoEntry = { ...v, role: v.role };
322
+ // Try to capture first frame
323
+ try {
324
+ const videoEl = page.locator('video').nth(v.index - 1);
325
+ const box = await videoEl.boundingBox();
326
+ if (box && box.width > 50 && box.height > 50) {
327
+ const framePath = path.join(scrollDir, `video-${v.index}-frame.png`);
328
+ await page.evaluate((idx) => {
329
+ const vEl = document.querySelectorAll('video')[idx];
330
+ if (vEl) {
331
+ vEl.pause();
332
+ vEl.currentTime = 0;
333
+ }
334
+ }, v.index - 1);
335
+ await page.waitForTimeout(300);
336
+ await page.screenshot({
337
+ path: framePath,
338
+ clip: { x: box.x, y: box.y, width: Math.min(box.width, 1440), height: Math.min(box.height, 900) },
339
+ });
340
+ videoEntry.firstFramePath = `screens/scroll/video-${v.index}-frame.png`;
341
+ }
342
+ }
343
+ catch { /* video frame capture failed */ }
344
+ videos.push(videoEntry);
345
+ }
346
+ // ── Phase 6: Detect scroll-triggered elements ─────────────────────────
347
+ const scrollPatternsRaw = await page.evaluate(() => {
348
+ const patterns = [];
349
+ // AOS
350
+ const aosEls = document.querySelectorAll('[data-aos]');
351
+ const aosGroups = {};
352
+ aosEls.forEach(el => {
353
+ const type = el.getAttribute('data-aos') || 'unknown';
354
+ aosGroups[type] = (aosGroups[type] || 0) + 1;
355
+ });
356
+ for (const [type, count] of Object.entries(aosGroups)) {
357
+ const sample = document.querySelector(`[data-aos="${type}"]`);
358
+ patterns.push({
359
+ selector: `[data-aos="${type}"]`,
360
+ library: 'AOS',
361
+ attribute: `data-aos="${type}"`,
362
+ animationType: type,
363
+ duration: sample?.getAttribute('data-aos-duration') || undefined,
364
+ delay: sample?.getAttribute('data-aos-delay') || undefined,
365
+ easing: sample?.getAttribute('data-aos-easing') || undefined,
366
+ count,
367
+ });
368
+ }
369
+ // Locomotive Scroll
370
+ const locoEls = document.querySelectorAll('[data-scroll]');
371
+ if (locoEls.length > 0) {
372
+ patterns.push({
373
+ selector: '[data-scroll]',
374
+ library: 'Locomotive Scroll',
375
+ attribute: 'data-scroll',
376
+ animationType: 'scroll-reveal',
377
+ count: locoEls.length,
378
+ });
379
+ }
380
+ // GSAP data attributes
381
+ const gsapEls = document.querySelectorAll('[data-gsap], [data-animation], [data-parallax]');
382
+ if (gsapEls.length > 0) {
383
+ patterns.push({
384
+ selector: '[data-gsap], [data-animation]',
385
+ library: 'GSAP',
386
+ attribute: 'data-gsap',
387
+ animationType: 'scroll-trigger',
388
+ count: gsapEls.length,
389
+ });
390
+ }
391
+ // Intersection Observer — detect elements with opacity:0 / transform waiting to animate
392
+ // (common pattern: element starts invisible, IO makes it visible)
393
+ let ioCount = 0;
394
+ document.querySelectorAll('[class]').forEach(el => {
395
+ const s = window.getComputedStyle(el);
396
+ const opacity = parseFloat(s.opacity);
397
+ const transform = s.transform;
398
+ const animName = s.animationName;
399
+ const animPlayState = s.animationPlayState;
400
+ if ((opacity < 0.1 || (transform !== 'none' && transform !== 'matrix(1, 0, 0, 1, 0, 0)')) &&
401
+ animPlayState === 'paused') {
402
+ ioCount++;
403
+ }
404
+ });
405
+ if (ioCount > 0) {
406
+ patterns.push({
407
+ selector: '.animation-paused',
408
+ library: 'CSS + IntersectionObserver',
409
+ attribute: 'animation-play-state: paused',
410
+ animationType: 'scroll-reveal (paused → running)',
411
+ count: ioCount,
412
+ });
413
+ }
414
+ // Sticky + parallax elements
415
+ const stickyEls = document.querySelectorAll('[style*="sticky"], [class*="sticky"], [class*="parallax"]');
416
+ if (stickyEls.length > 0) {
417
+ patterns.push({
418
+ selector: '.sticky, .parallax',
419
+ library: 'CSS',
420
+ attribute: 'position: sticky',
421
+ animationType: 'parallax / sticky scroll',
422
+ count: stickyEls.length,
423
+ });
424
+ }
425
+ // Lottie players
426
+ const lottieEls = document.querySelectorAll('lottie-player, dotlottie-player, [data-lottie]');
427
+ if (lottieEls.length > 0) {
428
+ patterns.push({
429
+ selector: 'lottie-player',
430
+ library: 'Lottie',
431
+ attribute: 'lottie-player',
432
+ animationType: 'vector animation',
433
+ count: lottieEls.length,
434
+ });
435
+ }
436
+ return patterns;
437
+ });
438
+ const scrollPatterns = scrollPatternsRaw;
439
+ // ── Phase 7: Detect canvas + WebGL ─────────────────────────────────────
440
+ const mediaInfo = await page.evaluate(() => {
441
+ const canvases = document.querySelectorAll('canvas');
442
+ let webgl = false;
443
+ canvases.forEach(c => {
444
+ try {
445
+ if (c.getContext('webgl') || c.getContext('webgl2') || c.getContext('experimental-webgl')) {
446
+ webgl = true;
447
+ }
448
+ }
449
+ catch { }
450
+ });
451
+ const lotties = document.querySelectorAll('lottie-player, dotlottie-player, [data-lottie], svg[class*="lottie"]');
452
+ return { canvasCount: canvases.length, webgl, lottieCount: lotties.length };
453
+ });
454
+ // ── Phase 8: Global transition declarations ────────────────────────────
455
+ const transitionsRaw = await page.evaluate(() => {
456
+ const found = [];
457
+ for (const sheet of Array.from(document.styleSheets)) {
458
+ try {
459
+ for (const rule of Array.from(sheet.cssRules || [])) {
460
+ if (rule.type === CSSRule.STYLE_RULE) {
461
+ const sr = rule;
462
+ const t = sr.style.transition;
463
+ if (t && t !== 'none' && t !== 'all 0s ease 0s' && !t.startsWith('all 0s')) {
464
+ found.push(t);
465
+ }
466
+ }
467
+ }
468
+ }
469
+ catch { }
470
+ }
471
+ // Deduplicate and return top 20
472
+ return [...new Set(found)].slice(0, 20);
473
+ });
474
+ // ── Phase 9: Scroll Journey Screenshots ───────────────────────────────
475
+ const scrollFrames = [];
476
+ const scrollPercents = [0, 17, 33, 50, 67, 83, 100];
477
+ const pageHeight = await page.evaluate(() => document.documentElement.scrollHeight);
478
+ for (const pct of scrollPercents) {
479
+ const targetY = Math.round((pct / 100) * Math.max(0, pageHeight - 900));
480
+ try {
481
+ // Instant scroll to position
482
+ await page.evaluate((y) => window.scrollTo({ top: y, behavior: 'instant' }), targetY);
483
+ // Wait for scroll-triggered animations to fire
484
+ await page.waitForTimeout(700);
485
+ const fileName = `scroll-${String(pct).padStart(3, '0')}.png`;
486
+ const filePath = path.join(scrollDir, fileName);
487
+ await page.screenshot({
488
+ path: filePath,
489
+ clip: { x: 0, y: 0, width: 1440, height: 900 },
490
+ });
491
+ const actualY = await page.evaluate(() => window.scrollY);
492
+ scrollFrames.push({
493
+ scrollPercent: pct,
494
+ scrollY: actualY,
495
+ pageHeight,
496
+ filePath: `screens/scroll/${fileName}`,
497
+ });
498
+ }
499
+ catch { /* frame failed */ }
500
+ }
501
+ await page.close();
502
+ return {
503
+ keyframes,
504
+ scrollFrames,
505
+ libraries,
506
+ videos,
507
+ scrollPatterns,
508
+ animationVars: animationVars.slice(0, 40),
509
+ globalTransitions: transitionsRaw,
510
+ canvasCount: mediaInfo.canvasCount,
511
+ webglDetected: mediaInfo.webgl,
512
+ lottieCount: mediaInfo.lottieCount,
513
+ };
514
+ }
515
+ finally {
516
+ await browser.close().catch(() => { });
517
+ }
518
+ }
519
+ function categorizeAnimVar(name) {
520
+ if (/duration|speed/i.test(name))
521
+ return 'duration';
522
+ if (/ease|timing|curve|bezier/i.test(name))
523
+ return 'easing';
524
+ if (/delay/i.test(name))
525
+ return 'delay';
526
+ if (/animation|keyframe/i.test(name))
527
+ return 'animation';
528
+ return 'other';
529
+ }
530
+ //# sourceMappingURL=animations.js.map
@@ -0,0 +1,13 @@
1
+ import { DOMComponent } from '../../types-ultra';
2
+ /**
3
+ * Ultra mode — DOM Component Detector
4
+ *
5
+ * Detects repeated UI components by analyzing DOM structure:
6
+ * - Elements with the same class pattern appearing 3+ times → component
7
+ * - Groups by normalized class fingerprint
8
+ * - Extracts representative HTML snippet
9
+ *
10
+ * Requires Playwright (optional peer dependency).
11
+ */
12
+ export declare function detectDOMComponents(url: string): Promise<DOMComponent[]>;
13
+ //# sourceMappingURL=components-dom.d.ts.map