prism-design 2.13.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 (90) hide show
  1. package/CHANGELOG.md +292 -0
  2. package/LICENSE +21 -0
  3. package/README.md +203 -0
  4. package/bin/clone-architect.mjs +476 -0
  5. package/bin/prism.mjs +467 -0
  6. package/catalog/index.json +1155 -0
  7. package/extractions/airbnb.com/DESIGN.md +1068 -0
  8. package/extractions/airbnb.com/tokens.json +507 -0
  9. package/extractions/attio.com/DESIGN.md +1295 -0
  10. package/extractions/attio.com/tokens.json +438 -0
  11. package/extractions/auroxdashboard.com/DESIGN.md +724 -0
  12. package/extractions/auroxdashboard.com/tokens.json +195 -0
  13. package/extractions/careerexplorer.com/DESIGN.md +1178 -0
  14. package/extractions/careerexplorer.com/tokens.json +141 -0
  15. package/extractions/chance.co/DESIGN.md +1209 -0
  16. package/extractions/chance.co/tokens.json +160 -0
  17. package/extractions/choisis-ton-avenir.com/DESIGN.md +1265 -0
  18. package/extractions/choisis-ton-avenir.com/tokens.json +227 -0
  19. package/extractions/example.com/DESIGN.md +436 -0
  20. package/extractions/example.com/tokens.json +91 -0
  21. package/extractions/getdesign.md/DESIGN.md +1009 -0
  22. package/extractions/getdesign.md/tokens.json +219 -0
  23. package/extractions/github.com/DESIGN.md +1130 -0
  24. package/extractions/github.com/tokens.json +2092 -0
  25. package/extractions/hello-charly.com/DESIGN.md +1146 -0
  26. package/extractions/hello-charly.com/tokens.json +322 -0
  27. package/extractions/hyperliquid.xyz/DESIGN.md +779 -0
  28. package/extractions/hyperliquid.xyz/tokens.json +598 -0
  29. package/extractions/instagram.com/DESIGN.md +996 -0
  30. package/extractions/instagram.com/tokens.json +1240 -0
  31. package/extractions/jobirl.com/DESIGN.md +1160 -0
  32. package/extractions/jobirl.com/tokens.json +139 -0
  33. package/extractions/life360.com/DESIGN.md +1133 -0
  34. package/extractions/life360.com/tokens.json +491 -0
  35. package/extractions/lifesum.com/DESIGN.md +965 -0
  36. package/extractions/lifesum.com/tokens.json +170 -0
  37. package/extractions/linear.app/DESIGN.md +1301 -0
  38. package/extractions/linear.app/tokens.json +732 -0
  39. package/extractions/mavoie.org/DESIGN.md +1148 -0
  40. package/extractions/mavoie.org/tokens.json +128 -0
  41. package/extractions/miro.com/DESIGN.md +1237 -0
  42. package/extractions/miro.com/tokens.json +401 -0
  43. package/extractions/notion.so/DESIGN.md +1319 -0
  44. package/extractions/notion.so/tokens.json +906 -0
  45. package/extractions/onetonline.org/DESIGN.md +909 -0
  46. package/extractions/onetonline.org/tokens.json +280 -0
  47. package/extractions/posthog.com/DESIGN.md +1024 -0
  48. package/extractions/posthog.com/tokens.json +197 -0
  49. package/extractions/revolut.com/DESIGN.md +1080 -0
  50. package/extractions/revolut.com/tokens.json +401 -0
  51. package/extractions/stripe.com/DESIGN.md +1272 -0
  52. package/extractions/stripe.com/tokens.json +794 -0
  53. package/extractions/switchcollective.com/DESIGN.md +1040 -0
  54. package/extractions/switchcollective.com/tokens.json +98 -0
  55. package/extractions/truity.com/DESIGN.md +970 -0
  56. package/extractions/truity.com/tokens.json +166 -0
  57. package/extractions/uniquekicks.be/DESIGN.md +1171 -0
  58. package/extractions/uniquekicks.be/tokens.json +237 -0
  59. package/package.json +122 -0
  60. package/scripts/analyze.ts +281 -0
  61. package/scripts/bank-register.ts +379 -0
  62. package/scripts/bank.ts +374 -0
  63. package/scripts/browser-stealth.ts +189 -0
  64. package/scripts/clone.ts +198 -0
  65. package/scripts/compare-vs-gd-final.ts +273 -0
  66. package/scripts/compare-vs-gd.ts +269 -0
  67. package/scripts/compare.ts +405 -0
  68. package/scripts/deploy-site.ts +181 -0
  69. package/scripts/diff-snapshots.ts +340 -0
  70. package/scripts/enrich-catalog.ts +212 -0
  71. package/scripts/extract.ts +2038 -0
  72. package/scripts/extractors/advanced.ts +524 -0
  73. package/scripts/extractors/widgets.ts +711 -0
  74. package/scripts/generate-design-md.ts +5775 -0
  75. package/scripts/generate-final-pdf.ts +274 -0
  76. package/scripts/generate-og-image.ts +87 -0
  77. package/scripts/generate-showcase.ts +1588 -0
  78. package/scripts/generate-site.ts +847 -0
  79. package/scripts/mass-extract.sh +91 -0
  80. package/scripts/post-process-all.sh +55 -0
  81. package/scripts/regen-catalog.ts +203 -0
  82. package/scripts/shared/cache.ts +149 -0
  83. package/scripts/shared/css-helpers.ts +263 -0
  84. package/scripts/shared/logger.ts +57 -0
  85. package/scripts/shared/named-colors.ts +355 -0
  86. package/scripts/shared/types.ts +220 -0
  87. package/scripts/sync-catalog.ts +105 -0
  88. package/scripts/tokenize.ts +988 -0
  89. package/templates/layout-template.md +52 -0
  90. package/templates/tokens-template.json +34 -0
@@ -0,0 +1,524 @@
1
+ /**
2
+ * extractors/advanced.ts — Prism Phase 5
3
+ * Extracteurs CSS avancés (@keyframes, z-index, transform 3D, pseudo-elements,
4
+ * @container queries, grid layouts, responsive per-breakpoint).
5
+ * Tous sont best-effort avec fallback gracieux.
6
+ */
7
+
8
+ import type { Page } from 'playwright';
9
+
10
+ /**
11
+ * Scanne tous les stylesheets accessibles pour extraire les @keyframes.
12
+ * Fallback silencieux sur cross-origin.
13
+ */
14
+ export async function extractKeyframes(page: Page): Promise<Record<string, Record<string, Record<string, string>>>> {
15
+ return page.evaluate(`(function() {
16
+ var result = {};
17
+ var sheets = document.styleSheets;
18
+ for (var i = 0; i < sheets.length; i++) {
19
+ try {
20
+ var rules = sheets[i].cssRules || sheets[i].rules;
21
+ if (!rules) continue;
22
+ for (var j = 0; j < rules.length; j++) {
23
+ var rule = rules[j];
24
+ if (rule && rule.constructor && rule.constructor.name === 'CSSKeyframesRule') {
25
+ var name = rule.name;
26
+ if (result[name]) continue;
27
+ var frames = {};
28
+ var kfRules = rule.cssRules;
29
+ for (var k = 0; k < kfRules.length; k++) {
30
+ var frame = kfRules[k];
31
+ var key = frame.keyText;
32
+ var styles = {};
33
+ var style = frame.style;
34
+ for (var p = 0; p < style.length; p++) {
35
+ var prop = style[p];
36
+ var val = style.getPropertyValue(prop);
37
+ if (val) styles[prop] = val.trim();
38
+ }
39
+ frames[key] = styles;
40
+ }
41
+ result[name] = frames;
42
+ if (Object.keys(result).length >= 200) return result;
43
+ }
44
+ }
45
+ } catch (e) { /* cross-origin sheet, skip */ }
46
+ }
47
+ return result;
48
+ })()`) as any;
49
+ }
50
+
51
+ /**
52
+ * Extrait z-index non-triviaux + stacking context parent.
53
+ */
54
+ export async function extractZIndexMap(page: Page): Promise<Array<{selector: string; z: number; stackingRoot: string}>> {
55
+ return page.evaluate(`(function() {
56
+ function getPath(el) {
57
+ if (!el || el === document.body) return 'body';
58
+ if (el.id) return '#' + el.id;
59
+ var classes = (el.className || '').toString().split(' ').filter(Boolean).slice(0, 2).join('.');
60
+ var tag = el.tagName.toLowerCase();
61
+ return classes ? tag + '.' + classes : tag;
62
+ }
63
+ function isStackingContext(el) {
64
+ var cs = getComputedStyle(el);
65
+ if (cs.position !== 'static') return true;
66
+ if (parseFloat(cs.opacity) < 1) return true;
67
+ if (cs.transform !== 'none') return true;
68
+ if (cs.filter !== 'none') return true;
69
+ if (cs.mixBlendMode !== 'normal') return true;
70
+ return false;
71
+ }
72
+ var result = [];
73
+ var elements = document.querySelectorAll('*');
74
+ var count = 0;
75
+ for (var i = 0; i < elements.length && count < 100; i++) {
76
+ var el = elements[i];
77
+ var cs = getComputedStyle(el);
78
+ var z = parseInt(cs.zIndex);
79
+ if (isNaN(z) || z === 0) continue;
80
+ var parent = el.parentElement;
81
+ var stackingRoot = 'body';
82
+ while (parent) {
83
+ if (isStackingContext(parent) || parent === document.body) {
84
+ stackingRoot = getPath(parent);
85
+ break;
86
+ }
87
+ parent = parent.parentElement;
88
+ }
89
+ result.push({ selector: getPath(el), z: z, stackingRoot: stackingRoot });
90
+ count++;
91
+ }
92
+ return result;
93
+ })()`) as any;
94
+ }
95
+
96
+ /**
97
+ * Extrait les transforms 3D et parse les fonctions (rotate, translate3d, perspective, matrix3d).
98
+ */
99
+ export async function extractTransform3DMap(page: Page): Promise<Record<string, any>> {
100
+ return page.evaluate(`(function() {
101
+ function getPath(el) {
102
+ if (!el || el === document.body) return 'body';
103
+ if (el.id) return '#' + el.id;
104
+ var classes = (el.className || '').toString().split(' ').filter(Boolean).slice(0, 2).join('.');
105
+ return el.tagName.toLowerCase() + (classes ? '.' + classes : '');
106
+ }
107
+ function parseTransform(css) {
108
+ if (!css || css === 'none') return null;
109
+ if (css.indexOf('matrix') === 0) {
110
+ var m = css.match(/matrix(3d)?\\(([^)]+)\\)/);
111
+ if (!m) return null;
112
+ return { type: m[1] ? 'matrix3d' : 'matrix', values: m[2].split(',').map(function(v){return parseFloat(v.trim());}) };
113
+ }
114
+ var fns = [];
115
+ var regex = /(\\w+)\\(([^)]+)\\)/g;
116
+ var match;
117
+ while ((match = regex.exec(css)) !== null) {
118
+ fns.push({ fn: match[1], args: match[2].split(',').map(function(s){return s.trim();}) });
119
+ }
120
+ return fns.length ? { type: 'functions', fns: fns } : null;
121
+ }
122
+ var result = {};
123
+ var elements = document.querySelectorAll('*');
124
+ var count = 0;
125
+ for (var i = 0; i < elements.length && count < 150; i++) {
126
+ var el = elements[i];
127
+ var cs = getComputedStyle(el);
128
+ var transform = cs.transform;
129
+ if (!transform || transform === 'none') continue;
130
+ var parsed = parseTransform(transform);
131
+ if (!parsed) continue;
132
+ var key = getPath(el);
133
+ if (result[key]) continue;
134
+ result[key] = {
135
+ raw: transform,
136
+ parsed: parsed,
137
+ transformOrigin: cs.transformOrigin,
138
+ perspective: cs.perspective !== 'none' ? cs.perspective : undefined,
139
+ };
140
+ count++;
141
+ }
142
+ return result;
143
+ })()`) as any;
144
+ }
145
+
146
+ /**
147
+ * Container queries (@container) + container-type/name sur éléments.
148
+ */
149
+ export async function extractContainerQueries(page: Page): Promise<{queries: Array<{name?: string; condition: string; rulesCount: number}>; containers: string[]}> {
150
+ return page.evaluate(`(function() {
151
+ var result = [];
152
+ var sheets = document.styleSheets;
153
+ for (var i = 0; i < sheets.length; i++) {
154
+ try {
155
+ var rules = sheets[i].cssRules || sheets[i].rules;
156
+ if (!rules) continue;
157
+ for (var j = 0; j < rules.length; j++) {
158
+ var rule = rules[j];
159
+ if (rule && rule.constructor && rule.constructor.name === 'CSSContainerRule') {
160
+ result.push({
161
+ name: rule.containerName || undefined,
162
+ condition: rule.containerQuery || rule.conditionText || '',
163
+ rulesCount: rule.cssRules ? rule.cssRules.length : 0,
164
+ });
165
+ if (result.length >= 50) break;
166
+ }
167
+ if (rule && rule.constructor && rule.constructor.name === 'CSSMediaRule' && rule.cssRules) {
168
+ for (var k = 0; k < rule.cssRules.length; k++) {
169
+ var inner = rule.cssRules[k];
170
+ if (inner && inner.constructor && inner.constructor.name === 'CSSContainerRule') {
171
+ result.push({
172
+ name: inner.containerName || undefined,
173
+ condition: inner.containerQuery || inner.conditionText || '',
174
+ rulesCount: inner.cssRules ? inner.cssRules.length : 0,
175
+ });
176
+ }
177
+ }
178
+ }
179
+ }
180
+ } catch (e) { /* cross-origin, skip */ }
181
+ }
182
+ var elements = document.querySelectorAll('*');
183
+ var containers = new Set();
184
+ for (var m = 0; m < elements.length; m++) {
185
+ var cs = getComputedStyle(elements[m]);
186
+ var ctype = cs.containerType;
187
+ if (ctype && ctype !== 'normal') {
188
+ var cname = cs.containerName;
189
+ containers.add(ctype + ':' + (cname || 'anonymous'));
190
+ }
191
+ }
192
+ return { queries: result, containers: Array.from(containers) };
193
+ })()`) as any;
194
+ }
195
+
196
+ /**
197
+ * grid-template-areas + named lines: capture les layouts nommés.
198
+ */
199
+ export async function extractGridLayouts(page: Page): Promise<Array<{selector: string; areas?: string; cols?: string; rows?: string}>> {
200
+ return page.evaluate(`(function() {
201
+ function getPath(el) {
202
+ if (!el || el === document.body) return 'body';
203
+ if (el.id) return '#' + el.id;
204
+ var classes = (el.className || '').toString().split(' ').filter(Boolean).slice(0, 2).join('.');
205
+ return el.tagName.toLowerCase() + (classes ? '.' + classes : '');
206
+ }
207
+ var result = [];
208
+ var elements = document.querySelectorAll('*');
209
+ var count = 0;
210
+ for (var i = 0; i < elements.length && count < 50; i++) {
211
+ var el = elements[i];
212
+ var cs = getComputedStyle(el);
213
+ if (cs.display !== 'grid' && cs.display !== 'inline-grid') continue;
214
+ var areas = cs.gridTemplateAreas;
215
+ var cols = cs.gridTemplateColumns;
216
+ var rows = cs.gridTemplateRows;
217
+ if ((!areas || areas === 'none') && (!cols || cols === 'none') && (!rows || rows === 'none')) continue;
218
+ result.push({
219
+ selector: getPath(el),
220
+ areas: (areas && areas !== 'none') ? areas : undefined,
221
+ cols: (cols && cols !== 'none') ? cols : undefined,
222
+ rows: (rows && rows !== 'none') ? rows : undefined,
223
+ });
224
+ count++;
225
+ }
226
+ return result;
227
+ })()`) as any;
228
+ }
229
+
230
+ /**
231
+ * Pseudo-elements ::before / ::after — capture content + styles critiques.
232
+ */
233
+ export async function extractPseudoElements(page: Page): Promise<Record<string, { before?: Record<string, string>; after?: Record<string, string> }>> {
234
+ return page.evaluate(`(function() {
235
+ function nthPath(el) {
236
+ if (!el || el === document.body) return 'body';
237
+ var path = [];
238
+ var current = el;
239
+ var depth = 0;
240
+ while (current && current !== document.body && depth < 8) {
241
+ var parent = current.parentElement;
242
+ if (!parent) break;
243
+ var siblings = Array.from(parent.children);
244
+ var idx = siblings.indexOf(current);
245
+ var tag = current.tagName.toLowerCase();
246
+ path.unshift(tag + ':nth-child(' + (idx + 1) + ')');
247
+ current = parent;
248
+ depth++;
249
+ }
250
+ return path.length ? 'body > ' + path.join(' > ') : 'body';
251
+ }
252
+ var PSEUDO_PROPS = [
253
+ 'content', 'display', 'position', 'top', 'left', 'right', 'bottom',
254
+ 'width', 'height', 'backgroundColor', 'color', 'backgroundImage',
255
+ 'border', 'borderRadius', 'transform', 'opacity', 'fontSize',
256
+ 'fontFamily', 'fontWeight', 'padding', 'margin', 'boxShadow',
257
+ ];
258
+ var result = {};
259
+ var elements = document.querySelectorAll('*');
260
+ var count = 0;
261
+ for (var i = 0; i < elements.length && count < 100; i++) {
262
+ var el = elements[i];
263
+ var data = {};
264
+ for (var p = 0; p < 2; p++) {
265
+ var pseudo = p === 0 ? '::before' : '::after';
266
+ var cs = getComputedStyle(el, pseudo);
267
+ var content = cs.content;
268
+ if (!content || content === 'none' || content === 'normal') continue;
269
+ var styles = { content: content };
270
+ for (var k = 0; k < PSEUDO_PROPS.length; k++) {
271
+ var prop = PSEUDO_PROPS[k];
272
+ if (prop === 'content') continue;
273
+ var val = cs[prop];
274
+ if (val && val !== 'none' && val !== 'normal' && val !== '0px' && val !== 'auto' && val !== 'rgba(0, 0, 0, 0)') {
275
+ styles[prop] = val;
276
+ }
277
+ }
278
+ data[pseudo.replace('::', '')] = styles;
279
+ }
280
+ if (Object.keys(data).length > 0) {
281
+ var key = nthPath(el);
282
+ if (!result[key]) {
283
+ result[key] = data;
284
+ count++;
285
+ }
286
+ }
287
+ }
288
+ return result;
289
+ })()`) as any;
290
+ }
291
+
292
+ /**
293
+ * Responsive snapshot: snapshot des styles clés sur 360px + 768px.
294
+ */
295
+ export async function extractResponsiveSnapshot(
296
+ page: Page,
297
+ currentViewport: { width: number; height: number },
298
+ ): Promise<Record<number, Record<string, Record<string, string>>>> {
299
+ const TARGET_WIDTHS = [360, 768, 1440].filter(w => w !== currentViewport.width);
300
+ const result: Record<number, Record<string, Record<string, string>>> = {};
301
+ const originalSize = { width: currentViewport.width, height: currentViewport.height };
302
+
303
+ for (const width of TARGET_WIDTHS) {
304
+ try {
305
+ await page.setViewportSize({ width, height: 900 });
306
+ await page.waitForTimeout(250);
307
+
308
+ const snapshot = await page.evaluate(`(function() {
309
+ function getPath(el) {
310
+ if (!el) return '';
311
+ if (el.id) return '#' + el.id;
312
+ var classes = (el.className || '').toString().split(' ').filter(Boolean).slice(0, 2).join('.');
313
+ return el.tagName.toLowerCase() + (classes ? '.' + classes : '');
314
+ }
315
+ var targets = [
316
+ 'header', 'nav', 'main', 'footer',
317
+ 'section.hero, [class*="hero"]',
318
+ '[class*="card"]:not([class*="cards"])',
319
+ 'button, [class*="btn"]',
320
+ 'h1', 'h2',
321
+ '[class*="container"]',
322
+ ];
323
+ var result = {};
324
+ var CRITICAL_PROPS = [
325
+ 'display', 'flexDirection', 'gridTemplateColumns', 'gridTemplateRows',
326
+ 'padding', 'margin', 'gap',
327
+ 'fontSize', 'lineHeight', 'width', 'height', 'maxWidth',
328
+ 'position', 'top', 'left', 'right', 'bottom',
329
+ 'textAlign',
330
+ ];
331
+ for (var t = 0; t < targets.length; t++) {
332
+ var els = document.querySelectorAll(targets[t]);
333
+ var limit = Math.min(els.length, 3);
334
+ for (var i = 0; i < limit; i++) {
335
+ var el = els[i];
336
+ var path = getPath(el);
337
+ if (!path || result[path]) continue;
338
+ var cs = getComputedStyle(el);
339
+ var styles = {};
340
+ for (var p = 0; p < CRITICAL_PROPS.length; p++) {
341
+ var prop = CRITICAL_PROPS[p];
342
+ var val = cs[prop];
343
+ if (val && val !== 'none' && val !== 'normal' && val !== '0px' && val !== 'auto') {
344
+ styles[prop] = val;
345
+ }
346
+ }
347
+ if (Object.keys(styles).length > 0) {
348
+ result[path] = styles;
349
+ }
350
+ }
351
+ }
352
+ return result;
353
+ })()`) as Record<string, Record<string, string>>;
354
+
355
+ result[width] = snapshot;
356
+ } catch (err) {
357
+ console.warn(` ⚠️ Responsive ${width}px failed: ${(err as Error).message}`);
358
+ }
359
+ }
360
+
361
+ try {
362
+ await page.setViewportSize(originalSize);
363
+ await page.waitForTimeout(100);
364
+ } catch { /* ignore */ }
365
+
366
+ return result;
367
+ }
368
+
369
+ // ── Visual Effects Detection ───────────────────────────────────────────────
370
+
371
+ export interface VisualEffects {
372
+ hasCanvas: boolean;
373
+ hasWebGL: boolean;
374
+ hasSVGAnimations: boolean;
375
+ glassmorphism: Array<{ selector: string; blur: string; bg: string }>;
376
+ meshGradients: Array<{ selector: string; background: string }>;
377
+ clipPaths: Array<{ selector: string; clipPath: string }>;
378
+ mixBlendModes: Array<{ selector: string; mode: string }>;
379
+ backdropFilters: Array<{ selector: string; filter: string }>;
380
+ scrollAnimations: { cssScrollTimeline: boolean; intersectionObserver: boolean };
381
+ cssVariableAnimations: string[];
382
+ motionSummary: string;
383
+ }
384
+
385
+ /**
386
+ * Détecte les effets visuels au-delà du CSS statique.
387
+ * Couvre: Canvas/WebGL, glassmorphism, mesh gradients, clip-paths, blend modes,
388
+ * scroll-driven animations, SVG animations.
389
+ */
390
+ export async function extractVisualEffects(page: Page): Promise<VisualEffects> {
391
+ return page.evaluate(`(function() {
392
+ function getPath(el) {
393
+ if (!el || el === document.body) return 'body';
394
+ if (el.id) return '#' + el.id;
395
+ var cls = (el.className || '').toString().split(' ').filter(Boolean).slice(0, 2).join('.');
396
+ return el.tagName.toLowerCase() + (cls ? '.' + cls : '');
397
+ }
398
+
399
+ // Canvas / WebGL
400
+ var canvases = document.querySelectorAll('canvas');
401
+ var hasCanvas = canvases.length > 0;
402
+ var hasWebGL = false;
403
+ for (var c = 0; c < canvases.length && !hasWebGL; c++) {
404
+ try {
405
+ hasWebGL = !!(canvases[c].getContext('webgl') || canvases[c].getContext('webgl2') || canvases[c].getContext('experimental-webgl'));
406
+ } catch(e) {}
407
+ }
408
+
409
+ // SVG animations
410
+ var svgAnimates = document.querySelectorAll('animate, animateTransform, animateMotion');
411
+ var hasSVGAnimations = svgAnimates.length > 0 || document.querySelectorAll('svg [style*="animation"]').length > 0;
412
+
413
+ // Glassmorphism: backdrop-filter + semi-transparent bg
414
+ var glassmorphism = [];
415
+ var backdropFilters = [];
416
+ var elements = document.querySelectorAll('*');
417
+ for (var i = 0; i < elements.length && glassmorphism.length < 10; i++) {
418
+ var el = elements[i];
419
+ var cs = getComputedStyle(el);
420
+ var bf = cs.backdropFilter || cs.webkitBackdropFilter || '';
421
+ if (bf && bf !== 'none') {
422
+ var bg = cs.backgroundColor;
423
+ backdropFilters.push({ selector: getPath(el), filter: bf });
424
+ if (bf.indexOf('blur') !== -1 && bg && bg.indexOf('rgba') !== -1) {
425
+ glassmorphism.push({ selector: getPath(el), blur: bf, bg: bg });
426
+ }
427
+ }
428
+ }
429
+
430
+ // Mesh gradients: multiple radial/conic gradients stacked
431
+ var meshGradients = [];
432
+ for (var j = 0; j < elements.length && meshGradients.length < 5; j++) {
433
+ var bg = getComputedStyle(elements[j]).backgroundImage;
434
+ if (!bg || bg === 'none') continue;
435
+ var radialCount = (bg.match(/radial-gradient/g) || []).length;
436
+ var conicCount = (bg.match(/conic-gradient/g) || []).length;
437
+ if (radialCount >= 2 || conicCount >= 1) {
438
+ meshGradients.push({ selector: getPath(elements[j]), background: bg.slice(0, 200) });
439
+ }
440
+ }
441
+
442
+ // Clip-paths (non-rectangular shapes)
443
+ var clipPaths = [];
444
+ for (var k = 0; k < elements.length && clipPaths.length < 10; k++) {
445
+ var cp = getComputedStyle(elements[k]).clipPath;
446
+ if (cp && cp !== 'none' && cp !== 'unset') {
447
+ clipPaths.push({ selector: getPath(elements[k]), clipPath: cp });
448
+ }
449
+ }
450
+
451
+ // Mix-blend-modes
452
+ var mixBlendModes = [];
453
+ for (var m = 0; m < elements.length && mixBlendModes.length < 10; m++) {
454
+ var mbm = getComputedStyle(elements[m]).mixBlendMode;
455
+ if (mbm && mbm !== 'normal') {
456
+ mixBlendModes.push({ selector: getPath(elements[m]), mode: mbm });
457
+ }
458
+ }
459
+
460
+ // Scroll-driven animations
461
+ var hasCSSScrollTimeline = false;
462
+ try {
463
+ var sheets = document.styleSheets;
464
+ for (var s = 0; s < sheets.length; s++) {
465
+ try {
466
+ var rules = sheets[s].cssRules;
467
+ for (var r = 0; r < rules.length; r++) {
468
+ var cssText = rules[r].cssText || '';
469
+ if (cssText.indexOf('scroll-timeline') !== -1 || cssText.indexOf('animation-timeline') !== -1) {
470
+ hasCSSScrollTimeline = true;
471
+ }
472
+ }
473
+ } catch(e) {}
474
+ }
475
+ } catch(e) {}
476
+
477
+ var hasIntersectionObserver = typeof IntersectionObserver !== 'undefined' && (
478
+ document.querySelectorAll('[data-aos], [data-sal], [data-animate]').length > 0 ||
479
+ document.querySelectorAll('.animate__animated, .fade-in, .slide-in').length > 0
480
+ );
481
+
482
+ // CSS variable animations (custom properties being animated)
483
+ var cssVariableAnimations = [];
484
+ try {
485
+ for (var sa = 0; sa < (document.styleSheets || []).length; sa++) {
486
+ try {
487
+ var saRules = document.styleSheets[sa].cssRules;
488
+ for (var sr = 0; sr < saRules.length; sr++) {
489
+ var st = saRules[sr].cssText || '';
490
+ var matches = st.match(/@keyframes[^{]+\{[^}]*--[a-z]/g);
491
+ if (matches) cssVariableAnimations.push(...matches.map(function(m) { return m.slice(0, 60); }));
492
+ }
493
+ } catch(e) {}
494
+ }
495
+ } catch(e) {}
496
+
497
+ // Motion summary
498
+ var motionParts = [];
499
+ if (hasWebGL) motionParts.push('WebGL/3D canvas');
500
+ else if (hasCanvas) motionParts.push('Canvas 2D');
501
+ if (hasSVGAnimations) motionParts.push('SVG animations');
502
+ if (glassmorphism.length > 0) motionParts.push('glassmorphism (' + glassmorphism.length + ' layers)');
503
+ if (meshGradients.length > 0) motionParts.push('mesh gradients');
504
+ if (clipPaths.length > 0) motionParts.push('non-rectangular shapes (' + clipPaths.length + ')');
505
+ if (mixBlendModes.length > 0) motionParts.push('blend modes');
506
+ if (hasCSSScrollTimeline) motionParts.push('CSS scroll-driven animations');
507
+ if (hasIntersectionObserver) motionParts.push('scroll-triggered reveal');
508
+ var motionSummary = motionParts.length > 0 ? motionParts.join(', ') : 'standard CSS transitions only';
509
+
510
+ return {
511
+ hasCanvas: hasCanvas,
512
+ hasWebGL: hasWebGL,
513
+ hasSVGAnimations: hasSVGAnimations,
514
+ glassmorphism: glassmorphism.slice(0, 5),
515
+ meshGradients: meshGradients.slice(0, 5),
516
+ clipPaths: clipPaths.slice(0, 8),
517
+ mixBlendModes: mixBlendModes.slice(0, 8),
518
+ backdropFilters: backdropFilters.slice(0, 8),
519
+ scrollAnimations: { cssScrollTimeline: hasCSSScrollTimeline, intersectionObserver: hasIntersectionObserver },
520
+ cssVariableAnimations: cssVariableAnimations.slice(0, 5),
521
+ motionSummary: motionSummary,
522
+ };
523
+ })()`) as unknown as VisualEffects;
524
+ }