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.
Files changed (125) hide show
  1. package/README.md +273 -0
  2. package/dist/agentBrowser.d.ts +0 -1
  3. package/dist/agentBrowser.js +13 -31
  4. package/dist/agentBrowser.js.map +1 -1
  5. package/dist/aggregate.d.ts +9 -0
  6. package/dist/aggregate.js +53 -0
  7. package/dist/aggregate.js.map +1 -0
  8. package/dist/batch.d.ts +16 -0
  9. package/dist/batch.js +44 -0
  10. package/dist/batch.js.map +1 -0
  11. package/dist/cli.js +250 -216
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands.d.ts +60 -1
  14. package/dist/commands.js +323 -10
  15. package/dist/commands.js.map +1 -1
  16. package/dist/compare.d.ts +33 -0
  17. package/dist/compare.js +83 -0
  18. package/dist/compare.js.map +1 -0
  19. package/dist/componentGraph.d.ts +22 -0
  20. package/dist/componentGraph.js +106 -0
  21. package/dist/componentGraph.js.map +1 -0
  22. package/dist/contextLayer.d.ts +12 -0
  23. package/dist/contextLayer.js +263 -0
  24. package/dist/contextLayer.js.map +1 -0
  25. package/dist/cssInJs.d.ts +9 -0
  26. package/dist/cssInJs.js +124 -0
  27. package/dist/cssInJs.js.map +1 -0
  28. package/dist/extractFromUrl.d.ts +8 -0
  29. package/dist/extractFromUrl.js +508 -414
  30. package/dist/extractFromUrl.js.map +1 -1
  31. package/dist/graphView.d.ts +21 -0
  32. package/dist/graphView.js +492 -0
  33. package/dist/graphView.js.map +1 -0
  34. package/dist/index.d.ts +26 -11
  35. package/dist/index.js +17 -10
  36. package/dist/index.js.map +1 -1
  37. package/dist/knowledge.d.ts +20 -0
  38. package/dist/knowledge.js +208 -0
  39. package/dist/knowledge.js.map +1 -0
  40. package/dist/liveView.d.ts +15 -0
  41. package/dist/liveView.js +475 -0
  42. package/dist/liveView.js.map +1 -0
  43. package/dist/llm.js +1 -9
  44. package/dist/llm.js.map +1 -1
  45. package/dist/moodboard.d.ts +3 -0
  46. package/dist/moodboard.js +152 -0
  47. package/dist/moodboard.js.map +1 -0
  48. package/dist/persona.d.ts +2 -2
  49. package/dist/persona.js +82 -210
  50. package/dist/persona.js.map +1 -1
  51. package/dist/query.js +1 -6
  52. package/dist/query.js.map +1 -1
  53. package/dist/render.d.ts +2 -10
  54. package/dist/render.js +80 -175
  55. package/dist/render.js.map +1 -1
  56. package/dist/reviewChecklist.d.ts +17 -0
  57. package/dist/reviewChecklist.js +126 -0
  58. package/dist/reviewChecklist.js.map +1 -0
  59. package/dist/scan.d.ts +19 -7
  60. package/dist/scan.js +132 -374
  61. package/dist/scan.js.map +1 -1
  62. package/dist/scorecard.d.ts +53 -0
  63. package/dist/scorecard.js +325 -0
  64. package/dist/scorecard.js.map +1 -0
  65. package/dist/skillPrompt.d.ts +1 -3
  66. package/dist/skillPrompt.js +22 -148
  67. package/dist/skillPrompt.js.map +1 -1
  68. package/dist/store.d.ts +2 -2
  69. package/dist/store.js +7 -9
  70. package/dist/store.js.map +1 -1
  71. package/dist/styleDictionary.d.ts +16 -0
  72. package/dist/styleDictionary.js +89 -0
  73. package/dist/styleDictionary.js.map +1 -0
  74. package/dist/svg.d.ts +5 -0
  75. package/dist/svg.js +162 -0
  76. package/dist/svg.js.map +1 -0
  77. package/dist/systemDiff.d.ts +28 -0
  78. package/dist/systemDiff.js +107 -0
  79. package/dist/systemDiff.js.map +1 -0
  80. package/dist/tailwind.d.ts +2 -0
  81. package/dist/tailwind.js +122 -0
  82. package/dist/tailwind.js.map +1 -0
  83. package/dist/taste.d.ts +3 -2
  84. package/dist/taste.js +349 -536
  85. package/dist/taste.js.map +1 -1
  86. package/dist/tasteRenderer.d.ts +2 -3
  87. package/dist/tasteRenderer.js +123 -119
  88. package/dist/tasteRenderer.js.map +1 -1
  89. package/dist/tokenNaming.d.ts +5 -0
  90. package/dist/tokenNaming.js +229 -0
  91. package/dist/tokenNaming.js.map +1 -0
  92. package/dist/tokens.d.ts +17 -0
  93. package/dist/tokens.js +44 -0
  94. package/dist/tokens.js.map +1 -0
  95. package/dist/trends.d.ts +12 -0
  96. package/dist/trends.js +178 -0
  97. package/dist/trends.js.map +1 -0
  98. package/dist/types.d.ts +47 -101
  99. package/dist/wiki.d.ts +10 -0
  100. package/dist/wiki.js +346 -0
  101. package/dist/wiki.js.map +1 -0
  102. package/dist/writingStyle.d.ts +38 -0
  103. package/dist/writingStyle.js +224 -0
  104. package/dist/writingStyle.js.map +1 -0
  105. package/package.json +5 -4
  106. package/dist/classify.d.ts +0 -21
  107. package/dist/classify.js +0 -205
  108. package/dist/classify.js.map +0 -1
  109. package/dist/scanRenderer.d.ts +0 -2
  110. package/dist/scanRenderer.js +0 -155
  111. package/dist/scanRenderer.js.map +0 -1
  112. package/dist/tasteDiff.d.ts +0 -19
  113. package/dist/tasteDiff.js +0 -340
  114. package/dist/tasteDiff.js.map +0 -1
  115. package/dist/tasteGenerate.d.ts +0 -12
  116. package/dist/tasteGenerate.js +0 -140
  117. package/dist/tasteGenerate.js.map +0 -1
  118. package/dist/tasteRefine.d.ts +0 -13
  119. package/dist/tasteRefine.js +0 -351
  120. package/dist/tasteRefine.js.map +0 -1
  121. package/dist/theatrical.d.ts +0 -5
  122. package/dist/theatrical.js +0 -258
  123. package/dist/theatrical.js.map +0 -1
  124. package/skills/SKILL.md +0 -36
  125. package/skills/design-brain/SKILL.md +0 -77
@@ -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
- // UNIFIED_SCRIPT: single eval that extracts design tokens, scrolls the page
12
- // to trigger lazy-loaded animations, observes animations, and returns
13
- // everything in one shot. This replaces 3 separate scripts + a scroll loop
14
- // that used to spawn 40-70 child processes.
15
- // ---------------------------------------------------------------------------
16
- const UNIFIED_SCRIPT = String.raw `(async () => {
17
- /* ── Shared helpers ── */
18
- function buildSelector(el) {
19
- if (!el || !el.tagName) return 'unknown';
20
- var tag = el.tagName.toLowerCase();
21
- if (el.id) return tag + '#' + el.id;
22
- if (typeof el.className === 'string' && el.className.trim()) {
23
- var firstClass = el.className.trim().split(/\s+/).slice(0, 2).join('.');
24
- if (firstClass) return tag + '.' + firstClass;
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
- return tag;
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) return null;
31
- var value = input.toString().trim().toLowerCase();
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) return '#' + value[1]+value[1]+value[2]+value[2]+value[3]+value[3];
34
- if (value.length === 7) return value;
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
- var m = value.match(/rgba?\(([^)]+)\)/);
38
- if (m) {
39
- var parts = m[1].split(',').map(function(p){return p.trim();});
40
- var r = Number(parts[0]), g = Number(parts[1]), b = Number(parts[2]);
41
- if ([r,g,b].some(isNaN)) return null;
42
- var h = function(n){return Math.max(0,Math.min(255,n)).toString(16).padStart(2,'0');};
43
- return '#'+h(r)+h(g)+h(b);
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 normalizeWhitespace(text) {
49
- return (text || '').replace(/\s+/g, ' ').trim();
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
- PHASE 1: Design token extraction
68
- ═══════════════════════════════════════════════ */
69
- var maxNodes = 2000;
70
- var nodes = Array.from(document.querySelectorAll('body *')).slice(0, maxNodes);
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
- var colorMap = new Map();
73
- var typographyMap = new Map();
74
- var componentList = [];
75
- var motionList = [];
76
- var layoutList = [];
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
- var componentTags = new Set(['button','a','input','select','textarea','nav','header','footer','form']);
81
- var maxComponents = 220, maxMotion = 220, maxStateStyles = 260, maxRulesPerSheet = 5000;
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
- function pushColor(value, sample) {
84
- if (!value || value === 'transparent' || value === 'rgba(0, 0, 0, 0)') return;
85
- var parsed = parseCssColor(value);
86
- if (!parsed) return;
87
- var key = parsed.toUpperCase();
88
- var entry = colorMap.get(key) || { hex: key, count: 0, samples: [] };
89
- entry.count += 1;
90
- if (sample && entry.samples.length < 5 && !entry.samples.includes(sample)) entry.samples.push(sample);
91
- colorMap.set(key, entry);
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(function(el) {
95
- var style = window.getComputedStyle(el);
96
- if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) return;
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
- var selector = buildSelector(el);
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 (normalizeWhitespace(el.textContent || '').length > 0) {
104
- var tKey = [style.fontFamily, style.fontSize, style.fontWeight, style.lineHeight].join('|');
105
- var existing = typographyMap.get(tKey) || { fontFamily: style.fontFamily, fontSize: style.fontSize, fontWeight: style.fontWeight, lineHeight: style.lineHeight, count: 0 };
106
- existing.count += 1;
107
- typographyMap.set(tKey, existing);
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
- var tag = el.tagName.toLowerCase();
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), tag: tag, selector: selector,
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
- styles: { color: style.color, backgroundColor: style.backgroundColor, borderRadius: style.borderRadius, border: style.border, padding: style.padding, margin: style.margin, fontSize: style.fontSize, fontWeight: style.fontWeight, boxShadow: style.boxShadow },
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
- var hasTransition = style.transitionDuration && style.transitionDuration !== '0s';
123
- var hasAnimation = style.animationName && style.animationName !== 'none';
124
- var hasTransform = style.transform && style.transform !== 'none';
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
- motionList.push({ selector: selector, transition: style.transition, animation: style.animation, transform: style.transform });
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).forEach(function(el) {
131
- layoutList.push({ tag: el.tagName.toLowerCase(), selector: buildSelector(el), role: el.getAttribute('role') || '', children: el.children.length });
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(function(root) {
135
- if (!root) return;
136
- var style = window.getComputedStyle(root);
137
- for (var prop of style) {
138
- if (!prop.startsWith('--')) continue;
139
- var val = style.getPropertyValue(prop).trim();
140
- if (val) variableMap[prop] = val;
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
- // Pseudo-state styles from stylesheets
145
- try {
146
- var stateKinds = [':hover', ':focus', ':active'];
147
- for (var sheet of Array.from(document.styleSheets)) {
148
- var rules;
149
- try { rules = sheet.cssRules; } catch(e) { continue; }
150
- if (!rules) continue;
151
- var rc = Math.min(rules.length, maxRulesPerSheet);
152
- for (var ri = 0; ri < rc; ri++) {
153
- var rule = rules[ri];
154
- var st = 'selectorText' in rule ? String(rule.selectorText || '') : '';
155
- var rs = 'style' in rule ? rule.style : undefined;
156
- if (!st || !rs) continue;
157
- for (var sk of stateKinds) {
158
- if (!st.includes(sk)) continue;
159
- var decl = {};
160
- for (var dp of ['color','background-color','border-color','box-shadow','transform','transition','opacity']) {
161
- var dv = rs.getPropertyValue(dp);
162
- if (dv) decl[dp] = dv;
163
- }
164
- if (Object.keys(decl).length === 0) continue;
165
- stateStyleList.push({ selector: st.split(sk).join('').trim() || st, state: sk.slice(1), declarations: decl });
166
- if (stateStyleList.length >= maxStateStyles) break;
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
- if (stateStyleList.length >= maxStateStyles) break;
356
+ steps.push({ offset: kf.keyText, declarations });
169
357
  }
170
- if (stateStyleList.length >= maxStateStyles) break;
358
+ keyframeList.push({ name, steps });
171
359
  }
172
- } catch(e) {}
360
+ }
173
361
 
174
- var designTokens = {
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: { width: window.innerWidth, height: window.innerHeight },
178
- colors: Array.from(colorMap.values()).sort(function(a,b){return b.count-a.count;}).slice(0, 70),
179
- typography: Array.from(typographyMap.values()).sort(function(a,b){return b.count-a.count;}).slice(0, 90),
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
- PHASE 2: Scroll through the page (in-browser)
189
- Triggers lazy-loaded GSAP, ScrollTrigger, etc.
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
- designTokens: designTokens,
310
- animations: animResult,
311
- pageSummary: { title: document.title, url: window.location.href, summary: [h1, 'nav-links:' + navCount, 'ctas:' + ctaCount].filter(Boolean).join(' | ') },
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 = ('library' in motion && 'motionIntent' in motion)
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
- // captureDesignFromUrl the main entry point.
531
- //
532
- // Old version: 70-90 child process spawns per URL.
533
- // New version: ~7 spawns: open, wait, set viewport, eval, snapshot, screenshot, close.
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
- // 1. Open the URL
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)], { session: sessionName, cwd: workingDir });
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
- timeoutMs: 45_000,
640
+ headed,
558
641
  });
559
- if (!result.success) {
560
- throw new Error(`Extraction failed: ${result.error ?? 'Unknown error'}`);
561
- }
562
- const unified = result.data.result;
563
- if (!unified) {
564
- throw new Error('Extraction returned empty result');
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(unified.designTokens);
655
+ const normalized = normalizeResult(extraction.data.result ?? {});
567
656
  captures.push(normalized);
568
657
  responsiveSnapshots.push(toResponsiveSnapshot(viewport.label, normalized));
569
- // Only process animations from the first (desktop) viewport
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
- // 4. Get accessibility snapshot
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
- const mergedMotion = [
593
- ...animationTokens,
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: merged.stateStyles ?? [],
695
+ stateStyles,
696
+ journey,
602
697
  };
603
698
  }
604
699
  finally {
605
- // 7. Always close the browser session
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