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.
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +202 -0
- package/dist/extractors/components.d.ts +11 -0
- package/dist/extractors/components.js +455 -0
- package/dist/extractors/framework.d.ts +4 -0
- package/dist/extractors/framework.js +126 -0
- package/dist/extractors/tokens/computed.d.ts +7 -0
- package/dist/extractors/tokens/computed.js +249 -0
- package/dist/extractors/tokens/css.d.ts +3 -0
- package/dist/extractors/tokens/css.js +510 -0
- package/dist/extractors/tokens/http-css.d.ts +14 -0
- package/dist/extractors/tokens/http-css.js +1689 -0
- package/dist/extractors/tokens/tailwind.d.ts +3 -0
- package/dist/extractors/tokens/tailwind.js +353 -0
- package/dist/extractors/tokens/tokens-file.d.ts +3 -0
- package/dist/extractors/tokens/tokens-file.js +229 -0
- package/dist/extractors/ultra/animations.d.ts +21 -0
- package/dist/extractors/ultra/animations.js +530 -0
- package/dist/extractors/ultra/components-dom.d.ts +13 -0
- package/dist/extractors/ultra/components-dom.js +152 -0
- package/dist/extractors/ultra/interactions.d.ts +14 -0
- package/dist/extractors/ultra/interactions.js +225 -0
- package/dist/extractors/ultra/layout.d.ts +14 -0
- package/dist/extractors/ultra/layout.js +126 -0
- package/dist/extractors/ultra/pages.d.ts +16 -0
- package/dist/extractors/ultra/pages.js +231 -0
- package/dist/font-resolver.d.ts +10 -0
- package/dist/font-resolver.js +280 -0
- package/dist/modes/dir.d.ts +6 -0
- package/dist/modes/dir.js +213 -0
- package/dist/modes/repo.d.ts +6 -0
- package/dist/modes/repo.js +76 -0
- package/dist/modes/ultra.d.ts +22 -0
- package/dist/modes/ultra.js +285 -0
- package/dist/modes/url.d.ts +14 -0
- package/dist/modes/url.js +161 -0
- package/dist/normalizer.d.ts +11 -0
- package/dist/normalizer.js +867 -0
- package/dist/screenshot.d.ts +9 -0
- package/dist/screenshot.js +94 -0
- package/dist/types-ultra.d.ts +157 -0
- package/dist/types-ultra.js +4 -0
- package/dist/types.d.ts +182 -0
- package/dist/types.js +4 -0
- package/dist/writers/animations-md.d.ts +17 -0
- package/dist/writers/animations-md.js +313 -0
- package/dist/writers/components-md.d.ts +8 -0
- package/dist/writers/components-md.js +151 -0
- package/dist/writers/design-md.d.ts +7 -0
- package/dist/writers/design-md.js +704 -0
- package/dist/writers/interactions-md.d.ts +8 -0
- package/dist/writers/interactions-md.js +146 -0
- package/dist/writers/layout-md.d.ts +8 -0
- package/dist/writers/layout-md.js +120 -0
- package/dist/writers/skill.d.ts +12 -0
- package/dist/writers/skill.js +1006 -0
- package/dist/writers/tokens-json.d.ts +11 -0
- package/dist/writers/tokens-json.js +164 -0
- 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
|