skillui 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +202 -0
- package/dist/extractors/components.d.ts +11 -0
- package/dist/extractors/components.js +455 -0
- package/dist/extractors/framework.d.ts +4 -0
- package/dist/extractors/framework.js +126 -0
- package/dist/extractors/tokens/computed.d.ts +7 -0
- package/dist/extractors/tokens/computed.js +249 -0
- package/dist/extractors/tokens/css.d.ts +3 -0
- package/dist/extractors/tokens/css.js +510 -0
- package/dist/extractors/tokens/http-css.d.ts +14 -0
- package/dist/extractors/tokens/http-css.js +1689 -0
- package/dist/extractors/tokens/tailwind.d.ts +3 -0
- package/dist/extractors/tokens/tailwind.js +353 -0
- package/dist/extractors/tokens/tokens-file.d.ts +3 -0
- package/dist/extractors/tokens/tokens-file.js +229 -0
- package/dist/extractors/ultra/animations.d.ts +21 -0
- package/dist/extractors/ultra/animations.js +530 -0
- package/dist/extractors/ultra/components-dom.d.ts +13 -0
- package/dist/extractors/ultra/components-dom.js +152 -0
- package/dist/extractors/ultra/interactions.d.ts +14 -0
- package/dist/extractors/ultra/interactions.js +225 -0
- package/dist/extractors/ultra/layout.d.ts +14 -0
- package/dist/extractors/ultra/layout.js +126 -0
- package/dist/extractors/ultra/pages.d.ts +16 -0
- package/dist/extractors/ultra/pages.js +231 -0
- package/dist/font-resolver.d.ts +10 -0
- package/dist/font-resolver.js +280 -0
- package/dist/modes/dir.d.ts +6 -0
- package/dist/modes/dir.js +213 -0
- package/dist/modes/repo.d.ts +6 -0
- package/dist/modes/repo.js +76 -0
- package/dist/modes/ultra.d.ts +22 -0
- package/dist/modes/ultra.js +285 -0
- package/dist/modes/url.d.ts +14 -0
- package/dist/modes/url.js +161 -0
- package/dist/normalizer.d.ts +11 -0
- package/dist/normalizer.js +867 -0
- package/dist/screenshot.d.ts +9 -0
- package/dist/screenshot.js +94 -0
- package/dist/types-ultra.d.ts +157 -0
- package/dist/types-ultra.js +4 -0
- package/dist/types.d.ts +182 -0
- package/dist/types.js +4 -0
- package/dist/writers/animations-md.d.ts +17 -0
- package/dist/writers/animations-md.js +313 -0
- package/dist/writers/components-md.d.ts +8 -0
- package/dist/writers/components-md.js +151 -0
- package/dist/writers/design-md.d.ts +7 -0
- package/dist/writers/design-md.js +704 -0
- package/dist/writers/interactions-md.d.ts +8 -0
- package/dist/writers/interactions-md.js +146 -0
- package/dist/writers/layout-md.d.ts +8 -0
- package/dist/writers/layout-md.js +120 -0
- package/dist/writers/skill.d.ts +12 -0
- package/dist/writers/skill.js +1006 -0
- package/dist/writers/tokens-json.d.ts +11 -0
- package/dist/writers/tokens-json.js +164 -0
- package/package.json +78 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectDOMComponents = detectDOMComponents;
|
|
4
|
+
/**
|
|
5
|
+
* Ultra mode — DOM Component Detector
|
|
6
|
+
*
|
|
7
|
+
* Detects repeated UI components by analyzing DOM structure:
|
|
8
|
+
* - Elements with the same class pattern appearing 3+ times → component
|
|
9
|
+
* - Groups by normalized class fingerprint
|
|
10
|
+
* - Extracts representative HTML snippet
|
|
11
|
+
*
|
|
12
|
+
* Requires Playwright (optional peer dependency).
|
|
13
|
+
*/
|
|
14
|
+
async function detectDOMComponents(url) {
|
|
15
|
+
let playwright;
|
|
16
|
+
try {
|
|
17
|
+
playwright = require('playwright');
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
const browser = await playwright.chromium.launch({ headless: true });
|
|
23
|
+
try {
|
|
24
|
+
const context = await browser.newContext({
|
|
25
|
+
viewport: { width: 1440, height: 900 },
|
|
26
|
+
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
27
|
+
});
|
|
28
|
+
const page = await context.newPage();
|
|
29
|
+
await page.goto(url, { waitUntil: 'networkidle', timeout: 25000 });
|
|
30
|
+
await page.waitForTimeout(1000);
|
|
31
|
+
const components = await page.evaluate(() => {
|
|
32
|
+
// ── Fingerprint an element by its structure ───────────────────────
|
|
33
|
+
function fingerprint(el) {
|
|
34
|
+
const tag = el.tagName.toLowerCase();
|
|
35
|
+
// Normalize class names: remove dynamic/utility classes, sort
|
|
36
|
+
const stableClasses = Array.from(el.classList)
|
|
37
|
+
.filter(c => {
|
|
38
|
+
// Skip Tailwind utility classes, JS hooks, state classes
|
|
39
|
+
if (/^(js-|is-|has-|data-|aria-)/.test(c))
|
|
40
|
+
return false;
|
|
41
|
+
if (/^(hover:|focus:|active:|sm:|md:|lg:|xl:|2xl:)/.test(c))
|
|
42
|
+
return false;
|
|
43
|
+
// Keep semantic/BEM-like classes
|
|
44
|
+
return c.length >= 3 && c.length <= 40 && /^[a-zA-Z]/.test(c);
|
|
45
|
+
})
|
|
46
|
+
.sort()
|
|
47
|
+
.slice(0, 4);
|
|
48
|
+
const childTags = Array.from(el.children)
|
|
49
|
+
.slice(0, 4)
|
|
50
|
+
.map(c => c.tagName.toLowerCase())
|
|
51
|
+
.join(',');
|
|
52
|
+
return `${tag}[${stableClasses.join('.')}](${childTags})`;
|
|
53
|
+
}
|
|
54
|
+
// ── Serialize HTML snippet (truncated) ─────────────────────────────
|
|
55
|
+
function htmlSnippet(el) {
|
|
56
|
+
const clone = el.cloneNode(true);
|
|
57
|
+
// Remove deeply nested children to keep snippet readable
|
|
58
|
+
const children = clone.querySelectorAll('*');
|
|
59
|
+
if (children.length > 12) {
|
|
60
|
+
Array.from(children).slice(12).forEach(c => c.remove());
|
|
61
|
+
}
|
|
62
|
+
// Truncate text nodes
|
|
63
|
+
clone.querySelectorAll('*').forEach(n => {
|
|
64
|
+
if (n.children.length === 0 && n.textContent && n.textContent.length > 40) {
|
|
65
|
+
n.textContent = n.textContent.slice(0, 40) + '…';
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return clone.outerHTML.replace(/\s+/g, ' ').slice(0, 600);
|
|
69
|
+
}
|
|
70
|
+
// ── Categorize a component ────────────────────────────────────────
|
|
71
|
+
function categorize(el, classes) {
|
|
72
|
+
const tag = el.tagName.toLowerCase();
|
|
73
|
+
const classStr = classes.join(' ').toLowerCase();
|
|
74
|
+
if (/card|tile|item|product|post/.test(classStr))
|
|
75
|
+
return 'card';
|
|
76
|
+
if (/nav.*item|menu.*item|tab/.test(classStr))
|
|
77
|
+
return 'nav-item';
|
|
78
|
+
if (tag === 'li' || /list.*item/.test(classStr))
|
|
79
|
+
return 'list-item';
|
|
80
|
+
if (tag === 'button' || /btn|button/.test(classStr))
|
|
81
|
+
return 'button';
|
|
82
|
+
if (/badge|tag|chip|label/.test(classStr))
|
|
83
|
+
return 'badge';
|
|
84
|
+
if (/field|input|form/.test(classStr))
|
|
85
|
+
return 'form-field';
|
|
86
|
+
return 'unknown';
|
|
87
|
+
}
|
|
88
|
+
// ── Walk all elements with 1+ class ─────────────────────────────────
|
|
89
|
+
const allElements = document.querySelectorAll('[class]');
|
|
90
|
+
const groups = new Map();
|
|
91
|
+
allElements.forEach(el => {
|
|
92
|
+
const rect = el.getBoundingClientRect();
|
|
93
|
+
// Must be visible and reasonably sized
|
|
94
|
+
if (rect.width < 40 || rect.height < 20)
|
|
95
|
+
return;
|
|
96
|
+
// Skip wrapper-only elements (body, html, main, etc.)
|
|
97
|
+
const tag = el.tagName.toLowerCase();
|
|
98
|
+
if (['html', 'body', 'main', 'head', 'script', 'style', 'link', 'meta'].includes(tag))
|
|
99
|
+
return;
|
|
100
|
+
const fp = fingerprint(el);
|
|
101
|
+
if (!groups.has(fp))
|
|
102
|
+
groups.set(fp, []);
|
|
103
|
+
groups.get(fp).push(el);
|
|
104
|
+
});
|
|
105
|
+
// ── Filter to repeated components (3+ instances) ──────────────────
|
|
106
|
+
const results = [];
|
|
107
|
+
for (const [fp, els] of groups.entries()) {
|
|
108
|
+
if (els.length < 3)
|
|
109
|
+
continue;
|
|
110
|
+
const representative = els[0];
|
|
111
|
+
const stableClasses = Array.from(representative.classList)
|
|
112
|
+
.filter(c => {
|
|
113
|
+
if (/^(js-|is-|has-)/.test(c))
|
|
114
|
+
return false;
|
|
115
|
+
if (/^(hover:|focus:|sm:|md:|lg:)/.test(c))
|
|
116
|
+
return false;
|
|
117
|
+
return c.length >= 3;
|
|
118
|
+
})
|
|
119
|
+
.sort()
|
|
120
|
+
.slice(0, 6);
|
|
121
|
+
// Generate a human-readable component name
|
|
122
|
+
const tag = representative.tagName.toLowerCase();
|
|
123
|
+
const mainClass = stableClasses[0] || tag;
|
|
124
|
+
const name = mainClass
|
|
125
|
+
.replace(/[-_]/g, ' ')
|
|
126
|
+
.replace(/\b\w/g, l => l.toUpperCase())
|
|
127
|
+
.trim() || tag;
|
|
128
|
+
const category = categorize(representative, stableClasses);
|
|
129
|
+
results.push({
|
|
130
|
+
name,
|
|
131
|
+
pattern: fp,
|
|
132
|
+
instances: els.length,
|
|
133
|
+
commonClasses: stableClasses,
|
|
134
|
+
htmlSnippet: htmlSnippet(representative),
|
|
135
|
+
category,
|
|
136
|
+
});
|
|
137
|
+
if (results.length >= 20)
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
// Sort by instance count descending
|
|
141
|
+
return results.sort((a, b) => b.instances - a.instances);
|
|
142
|
+
});
|
|
143
|
+
await page.close();
|
|
144
|
+
await browser.close();
|
|
145
|
+
return components;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
await browser.close().catch(() => { });
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=components-dom.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { InteractionRecord } from '../../types-ultra';
|
|
2
|
+
/**
|
|
3
|
+
* Ultra mode — Micro-Interaction Extractor
|
|
4
|
+
*
|
|
5
|
+
* For each interactive element type (button, link, input, role-button):
|
|
6
|
+
* - Capture default screenshot
|
|
7
|
+
* - Simulate hover → capture screenshot + diff computed styles
|
|
8
|
+
* - Simulate focus → capture screenshot + diff computed styles
|
|
9
|
+
*
|
|
10
|
+
* Saves screenshots to screens/states/
|
|
11
|
+
* Returns InteractionRecord[] for INTERACTIONS.md generation.
|
|
12
|
+
*/
|
|
13
|
+
export declare function captureInteractions(url: string, skillDir: string): Promise<InteractionRecord[]>;
|
|
14
|
+
//# sourceMappingURL=interactions.d.ts.map
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.captureInteractions = captureInteractions;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const TRACKED_PROPS = [
|
|
40
|
+
'backgroundColor',
|
|
41
|
+
'color',
|
|
42
|
+
'borderColor',
|
|
43
|
+
'borderWidth',
|
|
44
|
+
'boxShadow',
|
|
45
|
+
'opacity',
|
|
46
|
+
'transform',
|
|
47
|
+
'outline',
|
|
48
|
+
'outlineColor',
|
|
49
|
+
'textDecoration',
|
|
50
|
+
'transition',
|
|
51
|
+
];
|
|
52
|
+
const INTERACTIVE_SELECTORS = [
|
|
53
|
+
{ type: 'button', selector: 'button:not([disabled])' },
|
|
54
|
+
{ type: 'role-button', selector: '[role="button"]:not([disabled])' },
|
|
55
|
+
{ type: 'link', selector: 'a[href]:not([href^="#"]):not([href^="mailto"])' },
|
|
56
|
+
{ type: 'input', selector: 'input:not([type="hidden"]):not([disabled])' },
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* Ultra mode — Micro-Interaction Extractor
|
|
60
|
+
*
|
|
61
|
+
* For each interactive element type (button, link, input, role-button):
|
|
62
|
+
* - Capture default screenshot
|
|
63
|
+
* - Simulate hover → capture screenshot + diff computed styles
|
|
64
|
+
* - Simulate focus → capture screenshot + diff computed styles
|
|
65
|
+
*
|
|
66
|
+
* Saves screenshots to screens/states/
|
|
67
|
+
* Returns InteractionRecord[] for INTERACTIONS.md generation.
|
|
68
|
+
*/
|
|
69
|
+
async function captureInteractions(url, skillDir) {
|
|
70
|
+
let playwright;
|
|
71
|
+
try {
|
|
72
|
+
playwright = require('playwright');
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
const statesDir = path.join(skillDir, 'screens', 'states');
|
|
78
|
+
fs.mkdirSync(statesDir, { recursive: true });
|
|
79
|
+
const records = [];
|
|
80
|
+
const browser = await playwright.chromium.launch({ headless: true });
|
|
81
|
+
try {
|
|
82
|
+
const context = await browser.newContext({
|
|
83
|
+
viewport: { width: 1440, height: 900 },
|
|
84
|
+
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
85
|
+
});
|
|
86
|
+
const page = await context.newPage();
|
|
87
|
+
await page.goto(url, { waitUntil: 'networkidle', timeout: 25000 });
|
|
88
|
+
await page.waitForTimeout(1500);
|
|
89
|
+
for (const { type, selector } of INTERACTIVE_SELECTORS) {
|
|
90
|
+
try {
|
|
91
|
+
// Find up to 3 visible elements of this type
|
|
92
|
+
const elements = await page.locator(selector).all();
|
|
93
|
+
const visible = [];
|
|
94
|
+
for (const el of elements) {
|
|
95
|
+
try {
|
|
96
|
+
const box = await el.boundingBox();
|
|
97
|
+
if (box && box.width > 0 && box.height > 0 && box.width < 800) {
|
|
98
|
+
visible.push(el);
|
|
99
|
+
if (visible.length >= 3)
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch { /* skip */ }
|
|
104
|
+
}
|
|
105
|
+
for (let i = 0; i < visible.length; i++) {
|
|
106
|
+
const el = visible[i];
|
|
107
|
+
const prefix = `${type}-${i + 1}`;
|
|
108
|
+
try {
|
|
109
|
+
// Get label text
|
|
110
|
+
const label = await el.evaluate((node) => {
|
|
111
|
+
const text = node.innerText?.trim() ||
|
|
112
|
+
node.getAttribute('aria-label') ||
|
|
113
|
+
node.getAttribute('placeholder') ||
|
|
114
|
+
node.getAttribute('type') ||
|
|
115
|
+
node.tagName.toLowerCase();
|
|
116
|
+
return text.slice(0, 40);
|
|
117
|
+
});
|
|
118
|
+
// ── Default state ─────────────────────────────────────
|
|
119
|
+
const defaultStyles = await getStyles(el);
|
|
120
|
+
const defaultFile = `${prefix}-default.png`;
|
|
121
|
+
await screenshotElement(el, path.join(statesDir, defaultFile));
|
|
122
|
+
// ── Hover state ───────────────────────────────────────
|
|
123
|
+
let hoverStyles = null;
|
|
124
|
+
let hoverFile;
|
|
125
|
+
try {
|
|
126
|
+
await el.hover({ force: true, timeout: 3000 });
|
|
127
|
+
await page.waitForTimeout(300);
|
|
128
|
+
hoverStyles = await getStyles(el);
|
|
129
|
+
hoverFile = `${prefix}-hover.png`;
|
|
130
|
+
await screenshotElement(el, path.join(statesDir, hoverFile));
|
|
131
|
+
// Move away
|
|
132
|
+
await page.mouse.move(0, 0);
|
|
133
|
+
await page.waitForTimeout(200);
|
|
134
|
+
}
|
|
135
|
+
catch { /* hover not supported */ }
|
|
136
|
+
// ── Focus state ───────────────────────────────────────
|
|
137
|
+
let focusStyles = null;
|
|
138
|
+
let focusFile;
|
|
139
|
+
try {
|
|
140
|
+
await el.focus({ timeout: 3000 });
|
|
141
|
+
await page.waitForTimeout(300);
|
|
142
|
+
focusStyles = await getStyles(el);
|
|
143
|
+
focusFile = `${prefix}-focus.png`;
|
|
144
|
+
await screenshotElement(el, path.join(statesDir, focusFile));
|
|
145
|
+
// Blur
|
|
146
|
+
await page.evaluate(() => document.activeElement?.blur?.());
|
|
147
|
+
await page.waitForTimeout(200);
|
|
148
|
+
}
|
|
149
|
+
catch { /* focus not supported */ }
|
|
150
|
+
const hoverChanges = hoverStyles
|
|
151
|
+
? diffStyles(defaultStyles, hoverStyles)
|
|
152
|
+
: [];
|
|
153
|
+
const focusChanges = focusStyles
|
|
154
|
+
? diffStyles(defaultStyles, focusStyles)
|
|
155
|
+
: [];
|
|
156
|
+
// Only record if there are actual visual changes
|
|
157
|
+
if (hoverChanges.length > 0 ||
|
|
158
|
+
focusChanges.length > 0 ||
|
|
159
|
+
defaultFile) {
|
|
160
|
+
records.push({
|
|
161
|
+
componentType: type,
|
|
162
|
+
label: label || type,
|
|
163
|
+
selector: `${selector}:nth-of-type(${i + 1})`,
|
|
164
|
+
index: i + 1,
|
|
165
|
+
screenshots: {
|
|
166
|
+
default: `screens/states/${defaultFile}`,
|
|
167
|
+
hover: hoverFile ? `screens/states/${hoverFile}` : undefined,
|
|
168
|
+
focus: focusFile ? `screens/states/${focusFile}` : undefined,
|
|
169
|
+
},
|
|
170
|
+
hoverChanges,
|
|
171
|
+
focusChanges,
|
|
172
|
+
transitionValue: defaultStyles.transition,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch { /* element failed — skip */ }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch { /* selector failed — skip */ }
|
|
180
|
+
}
|
|
181
|
+
await page.close();
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
await browser.close().catch(() => { });
|
|
185
|
+
}
|
|
186
|
+
return records;
|
|
187
|
+
}
|
|
188
|
+
async function getStyles(el) {
|
|
189
|
+
return el.evaluate((node) => {
|
|
190
|
+
const s = window.getComputedStyle(node);
|
|
191
|
+
return {
|
|
192
|
+
backgroundColor: s.backgroundColor,
|
|
193
|
+
color: s.color,
|
|
194
|
+
borderColor: s.borderColor,
|
|
195
|
+
borderWidth: s.borderWidth,
|
|
196
|
+
boxShadow: s.boxShadow,
|
|
197
|
+
opacity: s.opacity,
|
|
198
|
+
transform: s.transform,
|
|
199
|
+
outline: s.outline,
|
|
200
|
+
outlineColor: s.outlineColor,
|
|
201
|
+
textDecoration: s.textDecoration,
|
|
202
|
+
transition: s.transition,
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
async function screenshotElement(el, filePath) {
|
|
207
|
+
try {
|
|
208
|
+
await el.screenshot({ path: filePath });
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// Element screenshot failed — ignore
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function diffStyles(before, after) {
|
|
215
|
+
const diffs = [];
|
|
216
|
+
for (const prop of TRACKED_PROPS) {
|
|
217
|
+
const b = before[prop] || '';
|
|
218
|
+
const a = after[prop] || '';
|
|
219
|
+
if (b !== a && a !== '' && a !== 'none' && a !== 'normal') {
|
|
220
|
+
diffs.push({ property: prop, from: b, to: a });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return diffs;
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=interactions.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { LayoutRecord } from '../../types-ultra';
|
|
2
|
+
/**
|
|
3
|
+
* Ultra mode — DOM Layout Extractor
|
|
4
|
+
*
|
|
5
|
+
* Crawls the page DOM and extracts layout information from significant containers:
|
|
6
|
+
* - flex/grid parents
|
|
7
|
+
* - section-level wrappers
|
|
8
|
+
* - nav/header/footer
|
|
9
|
+
*
|
|
10
|
+
* Returns LayoutRecord[] for LAYOUT.md generation.
|
|
11
|
+
* Requires Playwright (optional peer dependency).
|
|
12
|
+
*/
|
|
13
|
+
export declare function extractLayouts(url: string): Promise<LayoutRecord[]>;
|
|
14
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractLayouts = extractLayouts;
|
|
4
|
+
/**
|
|
5
|
+
* Ultra mode — DOM Layout Extractor
|
|
6
|
+
*
|
|
7
|
+
* Crawls the page DOM and extracts layout information from significant containers:
|
|
8
|
+
* - flex/grid parents
|
|
9
|
+
* - section-level wrappers
|
|
10
|
+
* - nav/header/footer
|
|
11
|
+
*
|
|
12
|
+
* Returns LayoutRecord[] for LAYOUT.md generation.
|
|
13
|
+
* Requires Playwright (optional peer dependency).
|
|
14
|
+
*/
|
|
15
|
+
async function extractLayouts(url) {
|
|
16
|
+
let playwright;
|
|
17
|
+
try {
|
|
18
|
+
playwright = require('playwright');
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
const browser = await playwright.chromium.launch({ headless: true });
|
|
24
|
+
try {
|
|
25
|
+
const context = await browser.newContext({
|
|
26
|
+
viewport: { width: 1440, height: 900 },
|
|
27
|
+
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
28
|
+
});
|
|
29
|
+
const page = await context.newPage();
|
|
30
|
+
await page.goto(url, { waitUntil: 'networkidle', timeout: 25000 });
|
|
31
|
+
await page.waitForTimeout(1000);
|
|
32
|
+
const records = await page.evaluate(() => {
|
|
33
|
+
const LAYOUT_SELECTORS = [
|
|
34
|
+
'header',
|
|
35
|
+
'nav',
|
|
36
|
+
'main',
|
|
37
|
+
'footer',
|
|
38
|
+
'section',
|
|
39
|
+
'article',
|
|
40
|
+
'[class*="container"]',
|
|
41
|
+
'[class*="wrapper"]',
|
|
42
|
+
'[class*="layout"]',
|
|
43
|
+
'[class*="grid"]',
|
|
44
|
+
'[class*="flex"]',
|
|
45
|
+
'[class*="row"]',
|
|
46
|
+
'[class*="col"]',
|
|
47
|
+
'[class*="hero"]',
|
|
48
|
+
'[class*="card"]',
|
|
49
|
+
];
|
|
50
|
+
function getDepth(el) {
|
|
51
|
+
let depth = 0;
|
|
52
|
+
let node = el.parentElement;
|
|
53
|
+
while (node) {
|
|
54
|
+
depth++;
|
|
55
|
+
node = node.parentElement;
|
|
56
|
+
}
|
|
57
|
+
return depth;
|
|
58
|
+
}
|
|
59
|
+
function buildSelector(el) {
|
|
60
|
+
const tag = el.tagName.toLowerCase();
|
|
61
|
+
const id = el.id ? `#${el.id}` : '';
|
|
62
|
+
const classes = Array.from(el.classList)
|
|
63
|
+
.filter(c => !/^(js-|is-|has-)/.test(c))
|
|
64
|
+
.slice(0, 2)
|
|
65
|
+
.map(c => `.${c}`)
|
|
66
|
+
.join('');
|
|
67
|
+
return `${tag}${id}${classes}`.slice(0, 60);
|
|
68
|
+
}
|
|
69
|
+
const seen = new WeakSet();
|
|
70
|
+
const results = [];
|
|
71
|
+
for (const sel of LAYOUT_SELECTORS) {
|
|
72
|
+
const elements = document.querySelectorAll(sel);
|
|
73
|
+
elements.forEach((el) => {
|
|
74
|
+
if (seen.has(el))
|
|
75
|
+
return;
|
|
76
|
+
seen.add(el);
|
|
77
|
+
const s = window.getComputedStyle(el);
|
|
78
|
+
const display = s.display;
|
|
79
|
+
// Only capture flex/grid/block containers with children
|
|
80
|
+
if (!['flex', 'grid', 'block', 'inline-flex', 'inline-grid'].includes(display))
|
|
81
|
+
return;
|
|
82
|
+
const rect = el.getBoundingClientRect();
|
|
83
|
+
if (rect.width < 100 || rect.height < 30)
|
|
84
|
+
return;
|
|
85
|
+
const childCount = el.children.length;
|
|
86
|
+
if (childCount === 0)
|
|
87
|
+
return;
|
|
88
|
+
results.push({
|
|
89
|
+
tag: el.tagName.toLowerCase(),
|
|
90
|
+
selector: buildSelector(el),
|
|
91
|
+
display,
|
|
92
|
+
flexDirection: s.flexDirection || '',
|
|
93
|
+
flexWrap: s.flexWrap || '',
|
|
94
|
+
justifyContent: s.justifyContent || '',
|
|
95
|
+
alignItems: s.alignItems || '',
|
|
96
|
+
gap: s.gap || '',
|
|
97
|
+
rowGap: s.rowGap || '',
|
|
98
|
+
columnGap: s.columnGap || '',
|
|
99
|
+
padding: s.padding || '',
|
|
100
|
+
margin: s.margin || '',
|
|
101
|
+
gridTemplateColumns: s.gridTemplateColumns || '',
|
|
102
|
+
gridTemplateRows: s.gridTemplateRows || '',
|
|
103
|
+
maxWidth: s.maxWidth || '',
|
|
104
|
+
width: s.width || '',
|
|
105
|
+
height: s.height || '',
|
|
106
|
+
position: s.position || '',
|
|
107
|
+
childCount,
|
|
108
|
+
depth: getDepth(el),
|
|
109
|
+
});
|
|
110
|
+
if (results.length >= 60)
|
|
111
|
+
return;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// Sort by depth (shallower = more structural)
|
|
115
|
+
return results.sort((a, b) => a.depth - b.depth).slice(0, 40);
|
|
116
|
+
});
|
|
117
|
+
await page.close();
|
|
118
|
+
await browser.close();
|
|
119
|
+
return records;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
await browser.close().catch(() => { });
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PageScreenshot, SectionScreenshot } from '../../types-ultra';
|
|
2
|
+
/**
|
|
3
|
+
* Ultra mode — Page & Section Screenshots
|
|
4
|
+
*
|
|
5
|
+
* 1. Crawl up to `maxPages` internal links from the origin URL
|
|
6
|
+
* 2. Take a full-page screenshot for each (screens/pages/[slug].png)
|
|
7
|
+
* 3. Detect major sections (section, article, main > div, height > 300px)
|
|
8
|
+
* and clip one screenshot per section (screens/sections/[page]-section-N.png)
|
|
9
|
+
*
|
|
10
|
+
* Requires Playwright (optional peer dependency).
|
|
11
|
+
*/
|
|
12
|
+
export declare function capturePageScreenshots(originUrl: string, skillDir: string, maxPages: number): Promise<{
|
|
13
|
+
pages: PageScreenshot[];
|
|
14
|
+
sections: SectionScreenshot[];
|
|
15
|
+
}>;
|
|
16
|
+
//# sourceMappingURL=pages.d.ts.map
|