design-brain-memory 0.9.3 → 0.9.4
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/README.md +273 -0
- package/dist/agentBrowser.d.ts +0 -1
- package/dist/agentBrowser.js +13 -31
- package/dist/agentBrowser.js.map +1 -1
- package/dist/aggregate.d.ts +9 -0
- package/dist/aggregate.js +53 -0
- package/dist/aggregate.js.map +1 -0
- package/dist/batch.d.ts +16 -0
- package/dist/batch.js +44 -0
- package/dist/batch.js.map +1 -0
- package/dist/cli.js +250 -216
- package/dist/cli.js.map +1 -1
- package/dist/commands.d.ts +60 -1
- package/dist/commands.js +323 -10
- package/dist/commands.js.map +1 -1
- package/dist/compare.d.ts +33 -0
- package/dist/compare.js +83 -0
- package/dist/compare.js.map +1 -0
- package/dist/componentGraph.d.ts +22 -0
- package/dist/componentGraph.js +106 -0
- package/dist/componentGraph.js.map +1 -0
- package/dist/contextLayer.d.ts +12 -0
- package/dist/contextLayer.js +263 -0
- package/dist/contextLayer.js.map +1 -0
- package/dist/cssInJs.d.ts +9 -0
- package/dist/cssInJs.js +124 -0
- package/dist/cssInJs.js.map +1 -0
- package/dist/extractFromUrl.d.ts +8 -0
- package/dist/extractFromUrl.js +508 -414
- package/dist/extractFromUrl.js.map +1 -1
- package/dist/graphView.d.ts +21 -0
- package/dist/graphView.js +492 -0
- package/dist/graphView.js.map +1 -0
- package/dist/index.d.ts +26 -11
- package/dist/index.js +17 -10
- package/dist/index.js.map +1 -1
- package/dist/knowledge.d.ts +20 -0
- package/dist/knowledge.js +208 -0
- package/dist/knowledge.js.map +1 -0
- package/dist/liveView.d.ts +15 -0
- package/dist/liveView.js +475 -0
- package/dist/liveView.js.map +1 -0
- package/dist/llm.js +1 -9
- package/dist/llm.js.map +1 -1
- package/dist/moodboard.d.ts +3 -0
- package/dist/moodboard.js +152 -0
- package/dist/moodboard.js.map +1 -0
- package/dist/persona.d.ts +2 -2
- package/dist/persona.js +82 -210
- package/dist/persona.js.map +1 -1
- package/dist/query.js +1 -6
- package/dist/query.js.map +1 -1
- package/dist/render.d.ts +2 -10
- package/dist/render.js +80 -175
- package/dist/render.js.map +1 -1
- package/dist/reviewChecklist.d.ts +17 -0
- package/dist/reviewChecklist.js +126 -0
- package/dist/reviewChecklist.js.map +1 -0
- package/dist/scan.d.ts +19 -7
- package/dist/scan.js +132 -374
- package/dist/scan.js.map +1 -1
- package/dist/scorecard.d.ts +53 -0
- package/dist/scorecard.js +325 -0
- package/dist/scorecard.js.map +1 -0
- package/dist/skillPrompt.d.ts +1 -3
- package/dist/skillPrompt.js +22 -148
- package/dist/skillPrompt.js.map +1 -1
- package/dist/store.d.ts +2 -2
- package/dist/store.js +7 -9
- package/dist/store.js.map +1 -1
- package/dist/styleDictionary.d.ts +16 -0
- package/dist/styleDictionary.js +89 -0
- package/dist/styleDictionary.js.map +1 -0
- package/dist/svg.d.ts +5 -0
- package/dist/svg.js +162 -0
- package/dist/svg.js.map +1 -0
- package/dist/systemDiff.d.ts +28 -0
- package/dist/systemDiff.js +107 -0
- package/dist/systemDiff.js.map +1 -0
- package/dist/tailwind.d.ts +2 -0
- package/dist/tailwind.js +122 -0
- package/dist/tailwind.js.map +1 -0
- package/dist/taste.d.ts +3 -2
- package/dist/taste.js +349 -536
- package/dist/taste.js.map +1 -1
- package/dist/tasteRenderer.d.ts +2 -3
- package/dist/tasteRenderer.js +123 -119
- package/dist/tasteRenderer.js.map +1 -1
- package/dist/tokenNaming.d.ts +5 -0
- package/dist/tokenNaming.js +229 -0
- package/dist/tokenNaming.js.map +1 -0
- package/dist/tokens.d.ts +17 -0
- package/dist/tokens.js +44 -0
- package/dist/tokens.js.map +1 -0
- package/dist/trends.d.ts +12 -0
- package/dist/trends.js +178 -0
- package/dist/trends.js.map +1 -0
- package/dist/types.d.ts +47 -101
- package/dist/wiki.d.ts +10 -0
- package/dist/wiki.js +346 -0
- package/dist/wiki.js.map +1 -0
- package/dist/writingStyle.d.ts +38 -0
- package/dist/writingStyle.js +224 -0
- package/dist/writingStyle.js.map +1 -0
- package/package.json +5 -4
- package/dist/classify.d.ts +0 -21
- package/dist/classify.js +0 -205
- package/dist/classify.js.map +0 -1
- package/dist/scanRenderer.d.ts +0 -2
- package/dist/scanRenderer.js +0 -155
- package/dist/scanRenderer.js.map +0 -1
- package/dist/tasteDiff.d.ts +0 -19
- package/dist/tasteDiff.js +0 -340
- package/dist/tasteDiff.js.map +0 -1
- package/dist/tasteGenerate.d.ts +0 -12
- package/dist/tasteGenerate.js +0 -140
- package/dist/tasteGenerate.js.map +0 -1
- package/dist/tasteRefine.d.ts +0 -13
- package/dist/tasteRefine.js +0 -351
- package/dist/tasteRefine.js.map +0 -1
- package/dist/theatrical.d.ts +0 -5
- package/dist/theatrical.js +0 -258
- package/dist/theatrical.js.map +0 -1
- package/skills/SKILL.md +0 -36
- package/skills/design-brain/SKILL.md +0 -77
package/dist/extractFromUrl.js
CHANGED
|
@@ -1,428 +1,394 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import { runAgentBrowserJson } from './agentBrowser.js';
|
|
4
|
-
import { classifyMotionIntent, detectPhysics, detectAnimationGroups, classifyGsapEasing } from './classify.js';
|
|
5
4
|
const DEFAULT_VIEWPORTS = [
|
|
6
5
|
{ label: 'desktop', width: 1440, height: 1200 },
|
|
7
6
|
{ label: 'tablet', width: 1024, height: 1366 },
|
|
8
7
|
{ label: 'mobile', width: 390, height: 844 },
|
|
9
8
|
];
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
const EXTRACTION_SCRIPT = String.raw `(() => {
|
|
10
|
+
const maxNodes = 2000;
|
|
11
|
+
const nodes = Array.from(document.querySelectorAll('body *')).slice(0, maxNodes);
|
|
12
|
+
|
|
13
|
+
const colorMap = new Map();
|
|
14
|
+
const typographyMap = new Map();
|
|
15
|
+
const componentList = [];
|
|
16
|
+
const motionList = [];
|
|
17
|
+
const layoutList = [];
|
|
18
|
+
const variableMap = {};
|
|
19
|
+
const stateStyleList = [];
|
|
20
|
+
|
|
21
|
+
const componentTags = new Set(['button', 'a', 'input', 'select', 'textarea', 'nav', 'header', 'footer', 'form']);
|
|
22
|
+
const maxComponents = 220;
|
|
23
|
+
const maxMotion = 220;
|
|
24
|
+
const maxStateStyles = 260;
|
|
25
|
+
|
|
26
|
+
function normalizeWhitespace(text) {
|
|
27
|
+
return (text || '').replace(/\s+/g, ' ').trim();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function pushColor(value, sample) {
|
|
31
|
+
if (!value || value === 'transparent' || value === 'rgba(0, 0, 0, 0)') {
|
|
32
|
+
return;
|
|
25
33
|
}
|
|
26
|
-
|
|
34
|
+
|
|
35
|
+
const parsed = parseCssColor(value);
|
|
36
|
+
if (!parsed) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const key = parsed.toUpperCase();
|
|
41
|
+
const entry = colorMap.get(key) || { hex: key, count: 0, samples: [] };
|
|
42
|
+
entry.count += 1;
|
|
43
|
+
if (sample && entry.samples.length < 5 && !entry.samples.includes(sample)) {
|
|
44
|
+
entry.samples.push(sample);
|
|
45
|
+
}
|
|
46
|
+
colorMap.set(key, entry);
|
|
27
47
|
}
|
|
28
48
|
|
|
29
49
|
function parseCssColor(input) {
|
|
30
|
-
if (!input)
|
|
31
|
-
|
|
50
|
+
if (!input) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const value = input.toString().trim().toLowerCase();
|
|
55
|
+
|
|
32
56
|
if (value.startsWith('#')) {
|
|
33
|
-
if (value.length === 4)
|
|
34
|
-
|
|
57
|
+
if (value.length === 4) {
|
|
58
|
+
return ('#' + value[1] + value[1] + value[2] + value[2] + value[3] + value[3]);
|
|
59
|
+
}
|
|
60
|
+
if (value.length === 7) {
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
35
63
|
return null;
|
|
36
64
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
65
|
+
|
|
66
|
+
const rgbMatch = value.match(/rgba?\(([^)]+)\)/);
|
|
67
|
+
if (rgbMatch) {
|
|
68
|
+
const parts = rgbMatch[1].split(',').map((part) => part.trim());
|
|
69
|
+
const r = Number(parts[0]);
|
|
70
|
+
const g = Number(parts[1]);
|
|
71
|
+
const b = Number(parts[2]);
|
|
72
|
+
if ([r, g, b].some((n) => Number.isNaN(n))) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const toHex = (n) => Math.max(0, Math.min(255, n)).toString(16).padStart(2, '0');
|
|
76
|
+
return '#' + toHex(r) + toHex(g) + toHex(b);
|
|
44
77
|
}
|
|
78
|
+
|
|
45
79
|
return null;
|
|
46
80
|
}
|
|
47
81
|
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
82
|
+
function buildSelector(el) {
|
|
83
|
+
const tag = el.tagName.toLowerCase();
|
|
84
|
+
if (el.id) {
|
|
85
|
+
return tag + '#' + el.id;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof el.className === 'string' && el.className.trim()) {
|
|
89
|
+
const firstClass = el.className.trim().split(/\s+/).slice(0, 2).join('.');
|
|
90
|
+
if (firstClass) {
|
|
91
|
+
return tag + '.' + firstClass;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
51
94
|
|
|
52
|
-
function inferComponentKind(el) {
|
|
53
|
-
var tag = el.tagName.toLowerCase();
|
|
54
|
-
var cn = typeof el.className === 'string' ? el.className.toLowerCase() : '';
|
|
55
|
-
if (tag === 'button' || cn.includes('btn')) return 'button';
|
|
56
|
-
if (tag === 'a') return 'link';
|
|
57
|
-
if (tag === 'input' || tag === 'textarea' || tag === 'select') return 'form-control';
|
|
58
|
-
if (tag === 'nav' || cn.includes('nav')) return 'navigation';
|
|
59
|
-
if (cn.includes('card')) return 'card';
|
|
60
|
-
if (tag === 'header') return 'header';
|
|
61
|
-
if (tag === 'footer') return 'footer';
|
|
62
|
-
if (cn.includes('hero')) return 'hero';
|
|
63
95
|
return tag;
|
|
64
96
|
}
|
|
65
97
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
98
|
+
function addTypography(style) {
|
|
99
|
+
const key = [style.fontFamily, style.fontSize, style.fontWeight, style.lineHeight].join('|');
|
|
100
|
+
const existing = typographyMap.get(key) || {
|
|
101
|
+
fontFamily: style.fontFamily,
|
|
102
|
+
fontSize: style.fontSize,
|
|
103
|
+
fontWeight: style.fontWeight,
|
|
104
|
+
lineHeight: style.lineHeight,
|
|
105
|
+
count: 0,
|
|
106
|
+
};
|
|
107
|
+
existing.count += 1;
|
|
108
|
+
typographyMap.set(key, existing);
|
|
109
|
+
}
|
|
71
110
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
var variableMap = {};
|
|
78
|
-
var stateStyleList = [];
|
|
111
|
+
function shouldCaptureAsComponent(el, selector) {
|
|
112
|
+
const tag = el.tagName.toLowerCase();
|
|
113
|
+
if (componentTags.has(tag)) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
79
116
|
|
|
80
|
-
|
|
81
|
-
|
|
117
|
+
const className = typeof el.className === 'string' ? el.className.toLowerCase() : '';
|
|
118
|
+
if (className.includes('btn') || className.includes('button') || className.includes('card') || className.includes('hero')) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
82
121
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
122
|
+
if (el.getAttribute('role') === 'button' || el.getAttribute('role') === 'navigation') {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return selector.startsWith('section.') || selector.startsWith('div.card');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function addPseudoStateStyles() {
|
|
130
|
+
const stateKinds = [':hover', ':focus', ':active'];
|
|
131
|
+
|
|
132
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
133
|
+
let rules;
|
|
134
|
+
try {
|
|
135
|
+
rules = sheet.cssRules;
|
|
136
|
+
} catch {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!rules) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const rule of Array.from(rules)) {
|
|
145
|
+
const selectorText = 'selectorText' in rule ? String(rule.selectorText || '') : '';
|
|
146
|
+
const style = 'style' in rule ? rule.style : undefined;
|
|
147
|
+
if (!selectorText || !style) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const stateKind of stateKinds) {
|
|
152
|
+
if (!selectorText.includes(stateKind)) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const declarations = {};
|
|
157
|
+
for (const prop of ['color', 'background-color', 'border-color', 'box-shadow', 'transform', 'transition', 'opacity']) {
|
|
158
|
+
const value = style.getPropertyValue(prop);
|
|
159
|
+
if (value) {
|
|
160
|
+
declarations[prop] = value;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (Object.keys(declarations).length === 0) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const cleanSelector = selectorText.split(stateKind).join('').trim();
|
|
169
|
+
stateStyleList.push({
|
|
170
|
+
selector: cleanSelector || selectorText,
|
|
171
|
+
state: stateKind.slice(1),
|
|
172
|
+
declarations,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (stateStyleList.length >= maxStateStyles) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
92
181
|
}
|
|
93
182
|
|
|
94
|
-
nodes.forEach(
|
|
95
|
-
|
|
96
|
-
|
|
183
|
+
nodes.forEach((el) => {
|
|
184
|
+
const style = window.getComputedStyle(el);
|
|
185
|
+
|
|
186
|
+
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
97
189
|
|
|
98
|
-
|
|
190
|
+
const selector = buildSelector(el);
|
|
99
191
|
pushColor(style.color, selector + ':color');
|
|
100
192
|
pushColor(style.backgroundColor, selector + ':background');
|
|
101
|
-
if (style.borderColor && style.borderColor !== 'rgba(0, 0, 0, 0)') pushColor(style.borderColor, selector + ':border');
|
|
102
193
|
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
194
|
+
if (style.borderColor && style.borderColor !== 'rgba(0, 0, 0, 0)') {
|
|
195
|
+
pushColor(style.borderColor, selector + ':border');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const hasText = normalizeWhitespace(el.textContent || '').length > 0;
|
|
199
|
+
if (hasText) {
|
|
200
|
+
addTypography(style);
|
|
108
201
|
}
|
|
109
202
|
|
|
110
|
-
|
|
111
|
-
var cn = typeof el.className === 'string' ? el.className.toLowerCase() : '';
|
|
112
|
-
var isComponent = componentTags.has(tag) || cn.includes('btn') || cn.includes('button') || cn.includes('card') || cn.includes('hero') || el.getAttribute('role') === 'button' || el.getAttribute('role') === 'navigation' || selector.startsWith('section.') || selector.startsWith('div.card');
|
|
113
|
-
if (isComponent && componentList.length < maxComponents) {
|
|
203
|
+
if (shouldCaptureAsComponent(el, selector) && componentList.length < maxComponents) {
|
|
114
204
|
componentList.push({
|
|
115
|
-
kind: inferComponentKind(el),
|
|
205
|
+
kind: inferComponentKind(el),
|
|
206
|
+
tag: el.tagName.toLowerCase(),
|
|
207
|
+
selector,
|
|
116
208
|
text: normalizeWhitespace(el.textContent || '').slice(0, 90),
|
|
117
209
|
className: typeof el.className === 'string' ? normalizeWhitespace(el.className) : '',
|
|
118
|
-
|
|
210
|
+
html: el.outerHTML.slice(0, 2048),
|
|
211
|
+
styles: {
|
|
212
|
+
color: style.color,
|
|
213
|
+
backgroundColor: style.backgroundColor,
|
|
214
|
+
borderRadius: style.borderRadius,
|
|
215
|
+
border: style.border,
|
|
216
|
+
padding: style.padding,
|
|
217
|
+
margin: style.margin,
|
|
218
|
+
fontSize: style.fontSize,
|
|
219
|
+
fontWeight: style.fontWeight,
|
|
220
|
+
boxShadow: style.boxShadow,
|
|
221
|
+
},
|
|
119
222
|
});
|
|
120
223
|
}
|
|
121
224
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
225
|
+
const hasTransition = style.transitionDuration && style.transitionDuration !== '0s';
|
|
226
|
+
const hasAnimation = style.animationName && style.animationName !== 'none';
|
|
227
|
+
const hasTransform = style.transform && style.transform !== 'none';
|
|
228
|
+
|
|
125
229
|
if ((hasTransition || hasAnimation || hasTransform) && motionList.length < maxMotion) {
|
|
126
|
-
|
|
230
|
+
const motionEntry = {
|
|
231
|
+
selector,
|
|
232
|
+
transition: style.transition,
|
|
233
|
+
animation: style.animation,
|
|
234
|
+
transform: style.transform,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
if (hasTransition) {
|
|
238
|
+
const props = style.transitionProperty.split(',').map(s => s.trim());
|
|
239
|
+
const durs = style.transitionDuration.split(',').map(s => s.trim());
|
|
240
|
+
const funcs = style.transitionTimingFunction.split(',').map(s => s.trim());
|
|
241
|
+
const delays = style.transitionDelay.split(',').map(s => s.trim());
|
|
242
|
+
motionEntry.transitions = props.map((p, i) => ({
|
|
243
|
+
property: p,
|
|
244
|
+
duration: durs[i % durs.length] || '0s',
|
|
245
|
+
timingFunction: funcs[i % funcs.length] || 'ease',
|
|
246
|
+
delay: delays[i % delays.length] || '0s',
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (hasAnimation) {
|
|
251
|
+
const names = style.animationName.split(',').map(s => s.trim());
|
|
252
|
+
const durs = style.animationDuration.split(',').map(s => s.trim());
|
|
253
|
+
const funcs = style.animationTimingFunction.split(',').map(s => s.trim());
|
|
254
|
+
const delays = style.animationDelay.split(',').map(s => s.trim());
|
|
255
|
+
const iters = style.animationIterationCount.split(',').map(s => s.trim());
|
|
256
|
+
const dirs = style.animationDirection.split(',').map(s => s.trim());
|
|
257
|
+
const fills = style.animationFillMode.split(',').map(s => s.trim());
|
|
258
|
+
motionEntry.animations = names.map((n, i) => ({
|
|
259
|
+
name: n,
|
|
260
|
+
duration: durs[i % durs.length] || '0s',
|
|
261
|
+
timingFunction: funcs[i % funcs.length] || 'ease',
|
|
262
|
+
delay: delays[i % delays.length] || '0s',
|
|
263
|
+
iterationCount: iters[i % iters.length] || '1',
|
|
264
|
+
direction: dirs[i % dirs.length] || 'normal',
|
|
265
|
+
fillMode: fills[i % fills.length] || 'none',
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
motionList.push(motionEntry);
|
|
127
270
|
}
|
|
128
271
|
});
|
|
129
272
|
|
|
130
|
-
Array.from(document.querySelectorAll('header, nav, main, section, article, aside, footer')).slice(0, 160)
|
|
131
|
-
|
|
273
|
+
const layoutSelectors = Array.from(document.querySelectorAll('header, nav, main, section, article, aside, footer')).slice(0, 160);
|
|
274
|
+
|
|
275
|
+
layoutSelectors.forEach((el) => {
|
|
276
|
+
layoutList.push({
|
|
277
|
+
tag: el.tagName.toLowerCase(),
|
|
278
|
+
selector: buildSelector(el),
|
|
279
|
+
role: el.getAttribute('role') || '',
|
|
280
|
+
children: el.children.length,
|
|
281
|
+
});
|
|
132
282
|
});
|
|
133
283
|
|
|
134
|
-
[document.documentElement, document.body].forEach(
|
|
135
|
-
if (!root)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
284
|
+
[document.documentElement, document.body].forEach((root) => {
|
|
285
|
+
if (!root) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const style = window.getComputedStyle(root);
|
|
290
|
+
for (const propertyName of style) {
|
|
291
|
+
if (!propertyName.startsWith('--')) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const value = style.getPropertyValue(propertyName).trim();
|
|
295
|
+
if (value) {
|
|
296
|
+
variableMap[propertyName] = value;
|
|
297
|
+
}
|
|
141
298
|
}
|
|
142
299
|
});
|
|
143
300
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
301
|
+
function inferComponentKind(el) {
|
|
302
|
+
const tag = el.tagName.toLowerCase();
|
|
303
|
+
const className = typeof el.className === 'string' ? el.className.toLowerCase() : '';
|
|
304
|
+
if (tag === 'button' || className.includes('btn')) {
|
|
305
|
+
return 'button';
|
|
306
|
+
}
|
|
307
|
+
if (tag === 'a') {
|
|
308
|
+
return 'link';
|
|
309
|
+
}
|
|
310
|
+
if (tag === 'input' || tag === 'textarea' || tag === 'select') {
|
|
311
|
+
return 'form-control';
|
|
312
|
+
}
|
|
313
|
+
if (tag === 'nav' || className.includes('nav')) {
|
|
314
|
+
return 'navigation';
|
|
315
|
+
}
|
|
316
|
+
if (className.includes('card')) {
|
|
317
|
+
return 'card';
|
|
318
|
+
}
|
|
319
|
+
if (tag === 'header') {
|
|
320
|
+
return 'header';
|
|
321
|
+
}
|
|
322
|
+
if (tag === 'footer') {
|
|
323
|
+
return 'footer';
|
|
324
|
+
}
|
|
325
|
+
if (className.includes('hero')) {
|
|
326
|
+
return 'hero';
|
|
327
|
+
}
|
|
328
|
+
return tag;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
addPseudoStateStyles();
|
|
332
|
+
|
|
333
|
+
const keyframeList = [];
|
|
334
|
+
const seenKeyframes = new Set();
|
|
335
|
+
const maxKeyframes = 60;
|
|
336
|
+
|
|
337
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
338
|
+
let rules;
|
|
339
|
+
try { rules = sheet.cssRules; } catch { continue; }
|
|
340
|
+
if (!rules) continue;
|
|
341
|
+
|
|
342
|
+
for (const rule of Array.from(rules)) {
|
|
343
|
+
if (rule.constructor.name !== 'CSSKeyframesRule') continue;
|
|
344
|
+
const kfRule = rule;
|
|
345
|
+
const name = kfRule.name;
|
|
346
|
+
if (seenKeyframes.has(name) || keyframeList.length >= maxKeyframes) continue;
|
|
347
|
+
seenKeyframes.add(name);
|
|
348
|
+
|
|
349
|
+
const steps = [];
|
|
350
|
+
for (const kf of Array.from(kfRule.cssRules)) {
|
|
351
|
+
const declarations = {};
|
|
352
|
+
for (let i = 0; i < kf.style.length; i++) {
|
|
353
|
+
const prop = kf.style[i];
|
|
354
|
+
declarations[prop] = kf.style.getPropertyValue(prop);
|
|
167
355
|
}
|
|
168
|
-
|
|
356
|
+
steps.push({ offset: kf.keyText, declarations });
|
|
169
357
|
}
|
|
170
|
-
|
|
358
|
+
keyframeList.push({ name, steps });
|
|
171
359
|
}
|
|
172
|
-
}
|
|
360
|
+
}
|
|
173
361
|
|
|
174
|
-
|
|
362
|
+
const colors = Array.from(colorMap.values()).sort((a, b) => b.count - a.count).slice(0, 70);
|
|
363
|
+
const typography = Array.from(typographyMap.values()).sort((a, b) => b.count - a.count).slice(0, 90);
|
|
364
|
+
|
|
365
|
+
return {
|
|
175
366
|
pageTitle: document.title,
|
|
176
367
|
pageUrl: window.location.href,
|
|
177
|
-
viewport: {
|
|
178
|
-
|
|
179
|
-
|
|
368
|
+
viewport: {
|
|
369
|
+
width: window.innerWidth,
|
|
370
|
+
height: window.innerHeight,
|
|
371
|
+
},
|
|
372
|
+
colors,
|
|
373
|
+
typography,
|
|
180
374
|
components: componentList,
|
|
181
375
|
motion: motionList,
|
|
376
|
+
keyframes: keyframeList,
|
|
182
377
|
layout: layoutList,
|
|
183
378
|
cssVariables: variableMap,
|
|
184
379
|
stateStyles: stateStyleList,
|
|
185
380
|
};
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
var height = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
|
193
|
-
var vh = window.innerHeight;
|
|
194
|
-
var scrollSteps = Math.min(Math.ceil(height / vh), 12);
|
|
195
|
-
var stepSize = Math.ceil(height / scrollSteps);
|
|
196
|
-
for (var si = 1; si <= scrollSteps; si++) {
|
|
197
|
-
window.scrollTo({ top: si * stepSize, behavior: 'instant' });
|
|
198
|
-
await new Promise(function(r) { setTimeout(r, 150); });
|
|
199
|
-
}
|
|
200
|
-
window.scrollTo({ top: 0, behavior: 'instant' });
|
|
201
|
-
await new Promise(function(r) { setTimeout(r, 800); });
|
|
202
|
-
} catch(e) {}
|
|
203
|
-
|
|
204
|
-
/* ═══════════════════════════════════════════════
|
|
205
|
-
PHASE 3: Animation observation (post-scroll)
|
|
206
|
-
═══════════════════════════════════════════════ */
|
|
207
|
-
var animResult = {
|
|
208
|
-
libraries: [],
|
|
209
|
-
webAnimations: [],
|
|
210
|
-
gsapTweens: [],
|
|
211
|
-
lottiePlayers: [],
|
|
212
|
-
scrollBindings: { hasScrollTrigger: false, hasIntersectionObserver: false, scrollTriggers: [], hasScrollSnap: false, hasScrollTimeline: false },
|
|
213
|
-
};
|
|
214
|
-
var maxAnims = 200;
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
if (typeof gsap !== 'undefined') animResult.libraries.push({ name: 'gsap', version: typeof gsap.version === 'string' ? gsap.version : 'unknown' });
|
|
218
|
-
if (typeof ScrollTrigger !== 'undefined') { animResult.libraries.push({ name: 'scrolltrigger', version: 'detected' }); animResult.scrollBindings.hasScrollTrigger = true; }
|
|
219
|
-
if (typeof lottie !== 'undefined' || typeof bodymovin !== 'undefined') animResult.libraries.push({ name: 'lottie', version: 'detected' });
|
|
220
|
-
if (document.querySelector('lottie-player, dotlottie-player') && !animResult.libraries.some(function(l){return l.name==='lottie';})) animResult.libraries.push({ name: 'lottie', version: 'web-component' });
|
|
221
|
-
if (document.querySelector('[data-framer-name], [data-framer-component-type]')) animResult.libraries.push({ name: 'framer-motion', version: 'detected' });
|
|
222
|
-
} catch(e) {}
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
var anims = document.getAnimations();
|
|
226
|
-
for (var ai = 0; ai < Math.min(anims.length, maxAnims); ai++) {
|
|
227
|
-
var anim = anims[ai];
|
|
228
|
-
var effect = anim.effect;
|
|
229
|
-
if (!effect || !effect.target) continue;
|
|
230
|
-
var sel = buildSelector(effect.target);
|
|
231
|
-
var kfs = []; try { kfs = effect.getKeyframes(); } catch(e) {}
|
|
232
|
-
var ct = {}; try { ct = effect.getComputedTiming(); } catch(e) {}
|
|
233
|
-
var tlt = 'document';
|
|
234
|
-
try { if (anim.timeline && anim.timeline.constructor) { var tn = anim.timeline.constructor.name; if (tn === 'ScrollTimeline') tlt = 'scroll'; else if (tn === 'ViewTimeline') tlt = 'view'; } } catch(e) {}
|
|
235
|
-
if (tlt !== 'document') animResult.scrollBindings.hasScrollTimeline = true;
|
|
236
|
-
animResult.webAnimations.push({
|
|
237
|
-
selector: sel, playState: anim.playState, animationName: anim.animationName || anim.id || '',
|
|
238
|
-
keyframes: kfs.map(function(kf) {
|
|
239
|
-
var props = {};
|
|
240
|
-
for (var k in kf) { if (k !== 'offset' && k !== 'computedOffset' && k !== 'easing' && k !== 'composite') props[k] = String(kf[k]); }
|
|
241
|
-
return { offset: kf.computedOffset != null ? kf.computedOffset : (kf.offset || 0), properties: props, easing: kf.easing || 'linear' };
|
|
242
|
-
}),
|
|
243
|
-
timing: { duration: typeof ct.duration === 'number' ? ct.duration : 0, delay: ct.delay || 0, easing: ct.easing || 'linear', iterations: ct.iterations != null ? ct.iterations : 1, direction: ct.direction || 'normal', fillMode: ct.fill || 'none' },
|
|
244
|
-
timelineType: tlt,
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
} catch(e) {}
|
|
248
|
-
|
|
249
|
-
try {
|
|
250
|
-
if (typeof gsap !== 'undefined' && gsap.globalTimeline) {
|
|
251
|
-
var children = gsap.globalTimeline.getChildren(true, true, false);
|
|
252
|
-
for (var gi = 0; gi < Math.min(children.length, maxAnims); gi++) {
|
|
253
|
-
var tw = children[gi];
|
|
254
|
-
var targets = tw.targets ? tw.targets() : [];
|
|
255
|
-
if (targets.length === 0) continue;
|
|
256
|
-
var vars = {};
|
|
257
|
-
if (tw.vars) { for (var vk in tw.vars) { var vv = tw.vars[vk]; if (typeof vv === 'number' || typeof vv === 'string') vars[vk] = vv; } }
|
|
258
|
-
animResult.gsapTweens.push({
|
|
259
|
-
selector: buildSelector(targets[0]),
|
|
260
|
-
duration: typeof tw.duration === 'function' ? tw.duration() : 0,
|
|
261
|
-
delay: typeof tw.delay === 'function' ? tw.delay() : 0,
|
|
262
|
-
ease: tw.vars && tw.vars.ease ? tw.vars.ease : 'power1.out',
|
|
263
|
-
vars: vars,
|
|
264
|
-
startTime: typeof tw.startTime === 'function' ? tw.startTime() : 0,
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
} catch(e) {}
|
|
269
|
-
|
|
270
|
-
try {
|
|
271
|
-
var lr = (typeof lottie !== 'undefined') ? lottie : ((typeof bodymovin !== 'undefined') ? bodymovin : null);
|
|
272
|
-
if (lr && typeof lr.getRegisteredAnimations === 'function') {
|
|
273
|
-
var regs = lr.getRegisteredAnimations();
|
|
274
|
-
for (var li = 0; li < Math.min(regs.length, 20); li++) {
|
|
275
|
-
var la = regs[li]; var c = la.wrapper || la.container;
|
|
276
|
-
animResult.lottiePlayers.push({ selector: c ? buildSelector(c) : 'lottie-unknown', frameRate: la.frameRate || (la.animationData && la.animationData.fr) || 0, totalFrames: la.totalFrames || (la.animationData && la.animationData.op) || 0, duration: typeof la.getDuration === 'function' ? la.getDuration(false) : 0 });
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
var ps = document.querySelectorAll('lottie-player, dotlottie-player');
|
|
280
|
-
for (var pi = 0; pi < Math.min(ps.length, 20); pi++) {
|
|
281
|
-
var pSel = buildSelector(ps[pi]);
|
|
282
|
-
if (!animResult.lottiePlayers.some(function(l){return l.selector === pSel;})) animResult.lottiePlayers.push({ selector: pSel, frameRate: 0, totalFrames: 0, duration: 0 });
|
|
283
|
-
}
|
|
284
|
-
} catch(e) {}
|
|
285
|
-
|
|
286
|
-
try {
|
|
287
|
-
if (typeof ScrollTrigger !== 'undefined' && typeof ScrollTrigger.getAll === 'function') {
|
|
288
|
-
var trigs = ScrollTrigger.getAll();
|
|
289
|
-
for (var ti = 0; ti < Math.min(trigs.length, 50); ti++) {
|
|
290
|
-
var tr = trigs[ti];
|
|
291
|
-
animResult.scrollBindings.scrollTriggers.push({ triggerSelector: tr.trigger ? buildSelector(tr.trigger) : '', isActive: !!tr.isActive });
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
} catch(e) {}
|
|
295
|
-
|
|
296
|
-
try {
|
|
297
|
-
var snap = window.getComputedStyle(document.documentElement).scrollSnapType;
|
|
298
|
-
if (snap && snap !== 'none') animResult.scrollBindings.hasScrollSnap = true;
|
|
299
|
-
} catch(e) {}
|
|
300
|
-
|
|
301
|
-
/* ═══════════════════════════════════════════════
|
|
302
|
-
PHASE 4: Page summary (for journey context)
|
|
303
|
-
═══════════════════════════════════════════════ */
|
|
304
|
-
var h1 = ''; try { h1 = (document.querySelector('h1') || {}).textContent || ''; h1 = h1.trim(); } catch(e) {}
|
|
305
|
-
var navCount = 0; try { navCount = document.querySelectorAll('nav a, header a').length; } catch(e) {}
|
|
306
|
-
var ctaCount = 0; try { ctaCount = document.querySelectorAll('button, a[role="button"], .btn, .button').length; } catch(e) {}
|
|
307
|
-
|
|
381
|
+
})();`;
|
|
382
|
+
const PAGE_SUMMARY_SCRIPT = String.raw `(() => {
|
|
383
|
+
const h1 = document.querySelector('h1')?.textContent?.trim() || '';
|
|
384
|
+
const navCount = document.querySelectorAll('nav a, header a').length;
|
|
385
|
+
const ctaCount = document.querySelectorAll('button, a[role="button"], .btn, .button').length;
|
|
308
386
|
return {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
387
|
+
title: document.title,
|
|
388
|
+
url: window.location.href,
|
|
389
|
+
summary: [h1, 'nav-links:' + navCount, 'ctas:' + ctaCount].filter(Boolean).join(' | '),
|
|
312
390
|
};
|
|
313
|
-
})()
|
|
314
|
-
function convertObserverResult(raw) {
|
|
315
|
-
const tokens = [];
|
|
316
|
-
const libraryNames = new Set(raw.libraries.map((l) => l.name));
|
|
317
|
-
for (const wa of raw.webAnimations) {
|
|
318
|
-
const intent = classifyMotionIntent(wa.keyframes);
|
|
319
|
-
let library = 'css';
|
|
320
|
-
if (libraryNames.has('framer-motion')) {
|
|
321
|
-
library = 'framer-motion';
|
|
322
|
-
}
|
|
323
|
-
const token = {
|
|
324
|
-
selector: wa.selector,
|
|
325
|
-
library,
|
|
326
|
-
motionIntent: intent,
|
|
327
|
-
timing: {
|
|
328
|
-
duration: wa.timing.duration,
|
|
329
|
-
delay: wa.timing.delay,
|
|
330
|
-
easing: wa.timing.easing,
|
|
331
|
-
iterations: wa.timing.iterations === Infinity ? Infinity : wa.timing.iterations,
|
|
332
|
-
direction: wa.timing.direction,
|
|
333
|
-
fillMode: wa.timing.fillMode,
|
|
334
|
-
},
|
|
335
|
-
keyframes: wa.keyframes,
|
|
336
|
-
trigger: wa.timelineType === 'scroll' || wa.timelineType === 'view' ? 'scroll' : 'load',
|
|
337
|
-
};
|
|
338
|
-
if (wa.timelineType !== 'document') {
|
|
339
|
-
token.scrollBinding = {
|
|
340
|
-
hasScrollTrigger: false,
|
|
341
|
-
hasIntersectionObserver: false,
|
|
342
|
-
scrollTimelineAxis: 'block',
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
const allProps = new Set();
|
|
346
|
-
for (const kf of wa.keyframes) {
|
|
347
|
-
for (const prop of Object.keys(kf.properties))
|
|
348
|
-
allProps.add(prop);
|
|
349
|
-
}
|
|
350
|
-
if (allProps.has('transform')) {
|
|
351
|
-
for (const fnName of ['translateX', 'translateY', 'scale', 'rotate']) {
|
|
352
|
-
const physics = detectPhysics(wa.keyframes, fnName);
|
|
353
|
-
if (physics) {
|
|
354
|
-
token.physics = physics;
|
|
355
|
-
token.motionIntent = physics.type === 'spring' ? 'spring' : 'bounce';
|
|
356
|
-
break;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
tokens.push(token);
|
|
361
|
-
}
|
|
362
|
-
for (const tween of raw.gsapTweens) {
|
|
363
|
-
const token = {
|
|
364
|
-
selector: tween.selector,
|
|
365
|
-
library: 'gsap',
|
|
366
|
-
motionIntent: 'complex',
|
|
367
|
-
timing: {
|
|
368
|
-
duration: tween.duration * 1000,
|
|
369
|
-
delay: tween.delay * 1000,
|
|
370
|
-
easing: tween.ease || 'power1.out',
|
|
371
|
-
iterations: 1,
|
|
372
|
-
direction: 'normal',
|
|
373
|
-
fillMode: 'none',
|
|
374
|
-
},
|
|
375
|
-
trigger: 'load',
|
|
376
|
-
gsapVars: tween.vars,
|
|
377
|
-
};
|
|
378
|
-
const physics = classifyGsapEasing(tween.ease);
|
|
379
|
-
if (physics) {
|
|
380
|
-
token.physics = physics;
|
|
381
|
-
token.motionIntent = physics.type === 'spring' ? 'spring' : 'bounce';
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
const varKeys = Object.keys(tween.vars);
|
|
385
|
-
if (varKeys.some((k) => ['x', 'y', 'xPercent', 'yPercent'].includes(k))) {
|
|
386
|
-
token.motionIntent = 'slide';
|
|
387
|
-
}
|
|
388
|
-
else if (varKeys.some((k) => ['scale', 'scaleX', 'scaleY'].includes(k))) {
|
|
389
|
-
token.motionIntent = 'scale';
|
|
390
|
-
}
|
|
391
|
-
else if (varKeys.some((k) => ['rotation', 'rotationX', 'rotationY'].includes(k))) {
|
|
392
|
-
token.motionIntent = 'rotate';
|
|
393
|
-
}
|
|
394
|
-
else if (varKeys.includes('opacity')) {
|
|
395
|
-
token.motionIntent = 'fade';
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
if (raw.scrollBindings.hasScrollTrigger) {
|
|
399
|
-
const matchingTrigger = raw.scrollBindings.scrollTriggers.find((st) => st.triggerSelector === tween.selector);
|
|
400
|
-
if (matchingTrigger) {
|
|
401
|
-
token.trigger = 'scroll';
|
|
402
|
-
token.scrollBinding = {
|
|
403
|
-
triggerSelector: matchingTrigger.triggerSelector,
|
|
404
|
-
hasScrollTrigger: true,
|
|
405
|
-
hasIntersectionObserver: false,
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
tokens.push(token);
|
|
410
|
-
}
|
|
411
|
-
for (const lp of raw.lottiePlayers) {
|
|
412
|
-
tokens.push({
|
|
413
|
-
selector: lp.selector,
|
|
414
|
-
library: 'lottie',
|
|
415
|
-
motionIntent: 'complex',
|
|
416
|
-
trigger: 'load',
|
|
417
|
-
lottieMetadata: {
|
|
418
|
-
frameRate: lp.frameRate,
|
|
419
|
-
totalFrames: lp.totalFrames,
|
|
420
|
-
duration: lp.duration,
|
|
421
|
-
},
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
return tokens;
|
|
425
|
-
}
|
|
391
|
+
})();`;
|
|
426
392
|
function normalizeResult(result) {
|
|
427
393
|
return {
|
|
428
394
|
pageTitle: result.pageTitle ?? undefined,
|
|
@@ -432,6 +398,7 @@ function normalizeResult(result) {
|
|
|
432
398
|
typography: result.typography ?? [],
|
|
433
399
|
components: result.components ?? [],
|
|
434
400
|
motion: result.motion ?? [],
|
|
401
|
+
keyframes: result.keyframes ?? [],
|
|
435
402
|
layout: result.layout ?? [],
|
|
436
403
|
cssVariables: result.cssVariables ?? {},
|
|
437
404
|
stateStyles: result.stateStyles ?? [],
|
|
@@ -470,6 +437,7 @@ function mergeAnalysis(all) {
|
|
|
470
437
|
}
|
|
471
438
|
const componentMap = new Map();
|
|
472
439
|
const motionMap = new Map();
|
|
440
|
+
const keyframeMap = new Map();
|
|
473
441
|
const layoutMap = new Map();
|
|
474
442
|
const stateMap = new Map();
|
|
475
443
|
const cssVariables = {};
|
|
@@ -482,13 +450,16 @@ function mergeAnalysis(all) {
|
|
|
482
450
|
}
|
|
483
451
|
}
|
|
484
452
|
for (const motion of entry.motion) {
|
|
485
|
-
const key =
|
|
486
|
-
? `${motion.selector}|${motion.library}|${motion.motionIntent}`
|
|
487
|
-
: `${motion.selector}|${motion.transition}|${motion.animation}|${motion.transform}`;
|
|
453
|
+
const key = `${motion.selector}|${motion.transition}|${motion.animation}|${motion.transform}`;
|
|
488
454
|
if (!motionMap.has(key)) {
|
|
489
455
|
motionMap.set(key, motion);
|
|
490
456
|
}
|
|
491
457
|
}
|
|
458
|
+
for (const kf of entry.keyframes ?? []) {
|
|
459
|
+
if (!keyframeMap.has(kf.name)) {
|
|
460
|
+
keyframeMap.set(kf.name, kf);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
492
463
|
for (const layout of entry.layout) {
|
|
493
464
|
const key = `${layout.tag}|${layout.selector}|${layout.role}`;
|
|
494
465
|
if (!layoutMap.has(key)) {
|
|
@@ -510,11 +481,70 @@ function mergeAnalysis(all) {
|
|
|
510
481
|
typography: [...typographyMap.values()].sort((a, b) => b.count - a.count).slice(0, 120),
|
|
511
482
|
components: [...componentMap.values()].slice(0, 260),
|
|
512
483
|
motion: [...motionMap.values()].slice(0, 260),
|
|
484
|
+
keyframes: [...keyframeMap.values()].slice(0, 80),
|
|
513
485
|
layout: [...layoutMap.values()].slice(0, 220),
|
|
514
486
|
cssVariables,
|
|
515
487
|
stateStyles: [...stateMap.values()].slice(0, 300),
|
|
516
488
|
};
|
|
517
489
|
}
|
|
490
|
+
function parseStylesFromGetStyles(data) {
|
|
491
|
+
const payload = data;
|
|
492
|
+
const first = payload.elements?.[0];
|
|
493
|
+
const styles = first?.styles ?? {};
|
|
494
|
+
const out = {};
|
|
495
|
+
for (const [key, value] of Object.entries(styles)) {
|
|
496
|
+
if (typeof value === 'string' && value.trim()) {
|
|
497
|
+
out[key] = value;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return out;
|
|
501
|
+
}
|
|
502
|
+
async function collectInteractiveStateStyles(params) {
|
|
503
|
+
const states = [];
|
|
504
|
+
const snapshot = await runAgentBrowserJson(['snapshot', '-i', '-d', '2'], {
|
|
505
|
+
session: params.sessionName,
|
|
506
|
+
cwd: params.workingDir,
|
|
507
|
+
headed: params.headed,
|
|
508
|
+
});
|
|
509
|
+
const refs = (snapshot.data.refs ?? {});
|
|
510
|
+
const refIds = Object.keys(refs).slice(0, 8);
|
|
511
|
+
for (const refId of refIds) {
|
|
512
|
+
const ref = `@${refId}`;
|
|
513
|
+
await runAgentBrowserJson(['hover', ref], {
|
|
514
|
+
session: params.sessionName,
|
|
515
|
+
cwd: params.workingDir,
|
|
516
|
+
headed: params.headed,
|
|
517
|
+
}).catch(() => undefined);
|
|
518
|
+
const hoverStyles = await runAgentBrowserJson(['get', 'styles', ref], {
|
|
519
|
+
session: params.sessionName,
|
|
520
|
+
cwd: params.workingDir,
|
|
521
|
+
headed: params.headed,
|
|
522
|
+
}).catch(() => undefined);
|
|
523
|
+
if (hoverStyles?.success) {
|
|
524
|
+
const declarations = parseStylesFromGetStyles(hoverStyles.data);
|
|
525
|
+
if (Object.keys(declarations).length > 0) {
|
|
526
|
+
states.push({ selector: ref, state: 'hover', declarations });
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
await runAgentBrowserJson(['focus', ref], {
|
|
530
|
+
session: params.sessionName,
|
|
531
|
+
cwd: params.workingDir,
|
|
532
|
+
headed: params.headed,
|
|
533
|
+
}).catch(() => undefined);
|
|
534
|
+
const focusStyles = await runAgentBrowserJson(['get', 'styles', ref], {
|
|
535
|
+
session: params.sessionName,
|
|
536
|
+
cwd: params.workingDir,
|
|
537
|
+
headed: params.headed,
|
|
538
|
+
}).catch(() => undefined);
|
|
539
|
+
if (focusStyles?.success) {
|
|
540
|
+
const declarations = parseStylesFromGetStyles(focusStyles.data);
|
|
541
|
+
if (Object.keys(declarations).length > 0) {
|
|
542
|
+
states.push({ selector: ref, state: 'focus', declarations });
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return states;
|
|
547
|
+
}
|
|
518
548
|
function toResponsiveSnapshot(label, analysis) {
|
|
519
549
|
return {
|
|
520
550
|
label,
|
|
@@ -526,84 +556,148 @@ function toResponsiveSnapshot(label, analysis) {
|
|
|
526
556
|
typographyCount: analysis.typography.length,
|
|
527
557
|
};
|
|
528
558
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
559
|
+
async function getCurrentUrl(params) {
|
|
560
|
+
const response = await runAgentBrowserJson(['get', 'url'], {
|
|
561
|
+
session: params.sessionName,
|
|
562
|
+
cwd: params.workingDir,
|
|
563
|
+
headed: params.headed,
|
|
564
|
+
}).catch(() => undefined);
|
|
565
|
+
return response?.success ? response.data.url : undefined;
|
|
566
|
+
}
|
|
567
|
+
async function collectJourney(params) {
|
|
568
|
+
const steps = [];
|
|
569
|
+
if (params.maxSteps <= 0) {
|
|
570
|
+
return steps;
|
|
571
|
+
}
|
|
572
|
+
const snapshot = await runAgentBrowserJson(['snapshot', '-i', '-d', '2'], {
|
|
573
|
+
session: params.sessionName,
|
|
574
|
+
cwd: params.workingDir,
|
|
575
|
+
headed: params.headed,
|
|
576
|
+
});
|
|
577
|
+
const refs = (snapshot.data.refs ?? {});
|
|
578
|
+
const clickable = Object.entries(refs)
|
|
579
|
+
.filter(([, value]) => ['link', 'button', 'menuitem', 'tab'].includes(String(value?.role ?? '')))
|
|
580
|
+
.slice(0, params.maxSteps);
|
|
581
|
+
for (let i = 0; i < clickable.length; i += 1) {
|
|
582
|
+
const [refId] = clickable[i];
|
|
583
|
+
const ref = `@${refId}`;
|
|
584
|
+
const fromUrl = await getCurrentUrl(params);
|
|
585
|
+
const clickResult = await runAgentBrowserJson(['click', ref], {
|
|
586
|
+
session: params.sessionName,
|
|
587
|
+
cwd: params.workingDir,
|
|
588
|
+
headed: params.headed,
|
|
589
|
+
}).catch(() => undefined);
|
|
590
|
+
if (!clickResult?.success) {
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
await runAgentBrowserJson(['wait', '1200'], {
|
|
594
|
+
session: params.sessionName,
|
|
595
|
+
cwd: params.workingDir,
|
|
596
|
+
headed: params.headed,
|
|
597
|
+
}).catch(() => undefined);
|
|
598
|
+
const summaryResult = await runAgentBrowserJson(['eval', PAGE_SUMMARY_SCRIPT], {
|
|
599
|
+
session: params.sessionName,
|
|
600
|
+
cwd: params.workingDir,
|
|
601
|
+
headed: params.headed,
|
|
602
|
+
}).catch(() => undefined);
|
|
603
|
+
const summaryData = summaryResult?.data.result ?? {};
|
|
604
|
+
const toUrl = summaryData.url ?? (await getCurrentUrl(params));
|
|
605
|
+
if (toUrl && fromUrl && toUrl !== fromUrl) {
|
|
606
|
+
steps.push({
|
|
607
|
+
step: i + 1,
|
|
608
|
+
action: `click ${ref}`,
|
|
609
|
+
fromUrl,
|
|
610
|
+
toUrl,
|
|
611
|
+
title: summaryData.title,
|
|
612
|
+
summary: summaryData.summary,
|
|
613
|
+
});
|
|
614
|
+
params.onStep?.(i + 1, toUrl);
|
|
615
|
+
await runAgentBrowserJson(['back'], {
|
|
616
|
+
session: params.sessionName,
|
|
617
|
+
cwd: params.workingDir,
|
|
618
|
+
headed: params.headed,
|
|
619
|
+
}).catch(() => undefined);
|
|
620
|
+
await runAgentBrowserJson(['wait', '900'], {
|
|
621
|
+
session: params.sessionName,
|
|
622
|
+
cwd: params.workingDir,
|
|
623
|
+
headed: params.headed,
|
|
624
|
+
}).catch(() => undefined);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return steps;
|
|
628
|
+
}
|
|
535
629
|
export async function captureDesignFromUrl(params) {
|
|
536
|
-
const { url, sessionName, screenshotPath, workingDir, responsiveViewports = DEFAULT_VIEWPORTS, } = params;
|
|
630
|
+
const { url, sessionName, screenshotPath, workingDir, headed = false, journeySteps = 3, responsiveViewports = DEFAULT_VIEWPORTS, callbacks, } = params;
|
|
537
631
|
await fs.ensureDir(path.dirname(screenshotPath));
|
|
538
|
-
|
|
539
|
-
await runAgentBrowserJson(['open', url], { session: sessionName, cwd: workingDir });
|
|
540
|
-
// 2. Wait for page to fully load (networkidle = no requests for 500ms).
|
|
541
|
-
// Falls back gracefully if the site has persistent connections (analytics, chat, etc.)
|
|
542
|
-
await runAgentBrowserJson(['wait', '--load', 'networkidle'], {
|
|
543
|
-
session: sessionName,
|
|
544
|
-
cwd: workingDir,
|
|
545
|
-
timeoutMs: 30_000,
|
|
546
|
-
}).catch(() => { });
|
|
632
|
+
await runAgentBrowserJson(['open', url], { session: sessionName, cwd: workingDir, headed });
|
|
547
633
|
try {
|
|
548
634
|
const captures = [];
|
|
549
635
|
const responsiveSnapshots = [];
|
|
550
|
-
let animationTokens = [];
|
|
551
|
-
// 3. For each viewport: set size → run unified extraction (tokens + scroll + animations)
|
|
552
636
|
for (const viewport of responsiveViewports) {
|
|
553
|
-
await runAgentBrowserJson(['set', 'viewport', String(viewport.width), String(viewport.height)], {
|
|
554
|
-
const result = await runAgentBrowserJson(['eval', UNIFIED_SCRIPT], {
|
|
637
|
+
await runAgentBrowserJson(['set', 'viewport', String(viewport.width), String(viewport.height)], {
|
|
555
638
|
session: sessionName,
|
|
556
639
|
cwd: workingDir,
|
|
557
|
-
|
|
640
|
+
headed,
|
|
558
641
|
});
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
642
|
+
await runAgentBrowserJson(['wait', '900'], {
|
|
643
|
+
session: sessionName,
|
|
644
|
+
cwd: workingDir,
|
|
645
|
+
headed,
|
|
646
|
+
});
|
|
647
|
+
const extraction = await runAgentBrowserJson(['eval', EXTRACTION_SCRIPT], {
|
|
648
|
+
session: sessionName,
|
|
649
|
+
cwd: workingDir,
|
|
650
|
+
headed,
|
|
651
|
+
});
|
|
652
|
+
if (!extraction.success) {
|
|
653
|
+
throw new Error(`Extraction failed: ${extraction.error ?? 'Unknown error'}`);
|
|
565
654
|
}
|
|
566
|
-
const normalized = normalizeResult(
|
|
655
|
+
const normalized = normalizeResult(extraction.data.result ?? {});
|
|
567
656
|
captures.push(normalized);
|
|
568
657
|
responsiveSnapshots.push(toResponsiveSnapshot(viewport.label, normalized));
|
|
569
|
-
|
|
570
|
-
if (animationTokens.length === 0 && unified.animations) {
|
|
571
|
-
try {
|
|
572
|
-
animationTokens = convertObserverResult(unified.animations);
|
|
573
|
-
animationTokens = detectAnimationGroups(animationTokens);
|
|
574
|
-
}
|
|
575
|
-
catch {
|
|
576
|
-
// Animation conversion is best-effort
|
|
577
|
-
}
|
|
578
|
-
}
|
|
658
|
+
callbacks?.onViewportExtracted?.(viewport.label, normalized);
|
|
579
659
|
}
|
|
580
|
-
|
|
660
|
+
await runAgentBrowserJson(['set', 'viewport', '1440', '1200'], {
|
|
661
|
+
session: sessionName,
|
|
662
|
+
cwd: workingDir,
|
|
663
|
+
headed,
|
|
664
|
+
}).catch(() => undefined);
|
|
581
665
|
const snapshotResult = await runAgentBrowserJson(['snapshot', '-c', '-d', '3'], {
|
|
582
666
|
session: sessionName,
|
|
583
667
|
cwd: workingDir,
|
|
668
|
+
headed,
|
|
669
|
+
});
|
|
670
|
+
const interactiveStates = await collectInteractiveStateStyles({
|
|
671
|
+
sessionName,
|
|
672
|
+
workingDir,
|
|
673
|
+
headed,
|
|
674
|
+
});
|
|
675
|
+
callbacks?.onInteractiveStates?.(interactiveStates.length);
|
|
676
|
+
const journey = await collectJourney({
|
|
677
|
+
sessionName,
|
|
678
|
+
workingDir,
|
|
679
|
+
headed,
|
|
680
|
+
maxSteps: journeySteps,
|
|
681
|
+
onStep: callbacks?.onJourneyStep,
|
|
584
682
|
});
|
|
585
|
-
// 5. Take full-page screenshot
|
|
586
683
|
await runAgentBrowserJson(['screenshot', screenshotPath, '--full'], {
|
|
587
684
|
session: sessionName,
|
|
588
685
|
cwd: workingDir,
|
|
686
|
+
headed,
|
|
589
687
|
});
|
|
590
|
-
// 6. Merge results
|
|
591
688
|
const merged = mergeAnalysis(captures);
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
...merged.motion.filter((legacyToken) => !animationTokens.some((at) => at.selector === legacyToken.selector)),
|
|
595
|
-
];
|
|
689
|
+
callbacks?.onMerged?.(merged);
|
|
690
|
+
const stateStyles = [...(merged.stateStyles ?? []), ...interactiveStates].slice(0, 400);
|
|
596
691
|
return {
|
|
597
692
|
...merged,
|
|
598
|
-
motion: mergedMotion,
|
|
599
693
|
accessibilitySnapshot: snapshotResult.data.snapshot ?? undefined,
|
|
600
694
|
responsiveSnapshots,
|
|
601
|
-
stateStyles
|
|
695
|
+
stateStyles,
|
|
696
|
+
journey,
|
|
602
697
|
};
|
|
603
698
|
}
|
|
604
699
|
finally {
|
|
605
|
-
|
|
606
|
-
await runAgentBrowserJson(['close'], { session: sessionName, cwd: workingDir }).catch(() => { });
|
|
700
|
+
await runAgentBrowserJson(['close'], { session: sessionName, cwd: workingDir, headed }).catch(() => undefined);
|
|
607
701
|
}
|
|
608
702
|
}
|
|
609
703
|
//# sourceMappingURL=extractFromUrl.js.map
|