@saeroon/cli 0.2.5 → 0.2.7
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/index.js +265 -49
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -2520,12 +2520,13 @@ var ELEMENT_TYPE_PROPS = {
|
|
|
2520
2520
|
}
|
|
2521
2521
|
};
|
|
2522
2522
|
var VALID_BLOCK_TYPES = /* @__PURE__ */ new Set([
|
|
2523
|
-
// Primitives (
|
|
2523
|
+
// Primitives (11)
|
|
2524
2524
|
"container",
|
|
2525
2525
|
"text-block",
|
|
2526
2526
|
"heading-block",
|
|
2527
2527
|
"button-block",
|
|
2528
2528
|
"image-block",
|
|
2529
|
+
"video-block",
|
|
2529
2530
|
"embed-block",
|
|
2530
2531
|
"icon-block",
|
|
2531
2532
|
"input-block",
|
|
@@ -3939,10 +3940,12 @@ var EXTRACTION_SCRIPT = `
|
|
|
3939
3940
|
|
|
3940
3941
|
function rgbToHex(rgb) {
|
|
3941
3942
|
if (!rgb || rgb === 'transparent') return 'transparent';
|
|
3942
|
-
if (rgb.startsWith('#')) return rgb;
|
|
3943
|
+
if (rgb.startsWith('#')) return rgb.length === 7 ? rgb : null;
|
|
3944
|
+
// rgb()/rgba()\uB9CC \uCC98\uB9AC, oklch/lab/color() \uB4F1 \uCD5C\uC2E0 \uD3EC\uB9F7\uC740 \uAC74\uB108\uB700
|
|
3945
|
+
if (!rgb.startsWith('rgb')) return null;
|
|
3943
3946
|
const match = rgb.match(/\\d+/g);
|
|
3944
|
-
if (!match || match.length < 3) return
|
|
3945
|
-
const [r, g, b] = match.map(Number);
|
|
3947
|
+
if (!match || match.length < 3) return null;
|
|
3948
|
+
const [r, g, b] = match.map(n => Math.min(255, Math.max(0, Number(n))));
|
|
3946
3949
|
return '#' + [r, g, b].map(c => c.toString(16).padStart(2, '0')).join('');
|
|
3947
3950
|
}
|
|
3948
3951
|
|
|
@@ -3967,18 +3970,18 @@ var EXTRACTION_SCRIPT = `
|
|
|
3967
3970
|
|
|
3968
3971
|
if (tag === 'header' || tag === 'nav') return 'header';
|
|
3969
3972
|
if (tag === 'footer') return 'footer';
|
|
3970
|
-
if (/hero|banner|jumbotron|splash|main-?visual/.test(combined)) return 'hero';
|
|
3971
|
-
if (/feature|service|benefit|what-?we/.test(combined)) return 'features';
|
|
3972
|
-
if (/testimonial|review|feedback|client|customer/.test(combined)) return 'testimonials';
|
|
3973
|
-
if (/faq|accordion|question|q-?and-?a/.test(combined)) return 'faq';
|
|
3974
|
-
if (/team|staff|member|about-?us|who-?we/.test(combined)) return 'team';
|
|
3975
|
-
if (/pricing|plan|package/.test(combined)) return 'pricing';
|
|
3976
|
-
if (/contact|inquiry|form|cta|call-?to-?action|get-?started/.test(combined)) return 'cta';
|
|
3977
|
-
if (/gallery|portfolio|work|project|showcase/.test(combined)) return 'gallery';
|
|
3978
|
-
if (/blog|news|article|post/.test(combined)) return 'blog';
|
|
3979
|
-
if (/partner|client|logo|brand|trust/.test(combined)) return 'partners';
|
|
3980
|
-
if (/map|location|address|direction/.test(combined)) return 'map';
|
|
3981
|
-
if (/stat|counter|number|achievement/.test(combined)) return 'stats';
|
|
3973
|
+
if (/hero|banner|jumbotron|splash|main-?visual|\uBA54\uC778s?\uBE44\uC8FC\uC5BC|\uD788\uC5B4\uB85C/.test(combined)) return 'hero';
|
|
3974
|
+
if (/feature|service|benefit|what-?we|\uC5C5\uBB34s?\uC601\uC5ED|\uC11C\uBE44\uC2A4|\uC9C4\uB8CCs?\uACFC\uBAA9|\uCE58\uB8CCs?\uBC29\uBC95|\uBC95\uB960s?\uC11C\uBE44\uC2A4/.test(combined)) return 'features';
|
|
3975
|
+
if (/testimonial|review|feedback|client|customer|\uD6C4\uAE30|\uB9AC\uBDF0|\uC218\uAC15s?\uD6C4\uAE30|\uACE0\uAC1Ds?\uD6C4\uAE30/.test(combined)) return 'testimonials';
|
|
3976
|
+
if (/faq|accordion|question|q-?and-?a|\uC790\uC8FCs?\uBB3B\uB294|\uC9C8\uBB38/.test(combined)) return 'faq';
|
|
3977
|
+
if (/team|staff|member|about-?us|who-?we|\uC758\uB8CC\uC9C4|\uC6D0\uC7A5|\uBCC0\uD638\uC0AC|\uAC15\uC0AC|\uC18C\uAC1C|\uC778\uC0AC\uB9D0/.test(combined)) return 'team';
|
|
3978
|
+
if (/pricing|plan|package|\uAC00\uACA9|\uC694\uAE08|\uD50C\uB79C/.test(combined)) return 'pricing';
|
|
3979
|
+
if (/contact|inquiry|form|cta|call-?to-?action|get-?started|\uC0C1\uB2F4|\uBB38\uC758|\uC608\uC57D|\uC2E0\uCCAD/.test(combined)) return 'cta';
|
|
3980
|
+
if (/gallery|portfolio|work|project|showcase|\uAC24\uB7EC\uB9AC|\uB458\uB7EC\uBCF4\uAE30|\uC2DC\uC124|\uB0B4\uBD80/.test(combined)) return 'gallery';
|
|
3981
|
+
if (/blog|news|article|post|\uBE14\uB85C\uADF8|\uB274\uC2A4|\uC18C\uC2DD|\uCE7C\uB7FC/.test(combined)) return 'blog';
|
|
3982
|
+
if (/partner|client|logo|brand|trust|\uD30C\uD2B8\uB108|\uC81C\uD734/.test(combined)) return 'partners';
|
|
3983
|
+
if (/map|location|address|direction|\uC624\uC2DC\uB294s?\uAE38|\uCC3E\uC544\uC624\uC2DC\uB294s?\uAE38|\uC9C0\uB3C4|\uC704\uCE58/.test(combined)) return 'map';
|
|
3984
|
+
if (/stat|counter|number|achievement|\uC2E4\uC801|\uC131\uACFC|\uC218\uCE58/.test(combined)) return 'stats';
|
|
3982
3985
|
return 'section';
|
|
3983
3986
|
}
|
|
3984
3987
|
|
|
@@ -4018,11 +4021,68 @@ var EXTRACTION_SCRIPT = `
|
|
|
4018
4021
|
// \u2500\u2500 1. Structure \u2500\u2500
|
|
4019
4022
|
const sectionSelectors = 'body > header, body > footer, body > nav, body > main, body > section, body > div, body > article, body > aside';
|
|
4020
4023
|
const topLevelEls = document.querySelectorAll(sectionSelectors);
|
|
4024
|
+
|
|
4025
|
+
// wrapper \uAC10\uC9C0: body \uC9C1\uACC4 \uC790\uC2DD\uC5D0\uC11C \uC2DC\uC791\uD558\uC5EC wrapper(\uC790\uC2DD 1\uAC1C) \uD480\uAE30
|
|
4026
|
+
let sectionEls = Array.from(topLevelEls).filter(el => el.getBoundingClientRect().height >= 10);
|
|
4027
|
+
|
|
4028
|
+
// \uC758\uBBF8 \uC788\uB294 \uC139\uC158\uC744 \uCC3E\uC744 \uB54C\uAE4C\uC9C0 wrapper\uB97C \uD480\uC5B4\uB098\uAC10 (\uCD5C\uB300 5\uB2E8\uACC4)
|
|
4029
|
+
function unwrapSections(els, depth) {
|
|
4030
|
+
if (depth <= 0) return els;
|
|
4031
|
+
const result = [];
|
|
4032
|
+
let expanded = false;
|
|
4033
|
+
els.forEach(el => {
|
|
4034
|
+
const tag = el.tagName.toLowerCase();
|
|
4035
|
+
// header, footer, nav, section, article \uB4F1 \uC2DC\uB9E8\uD2F1 \uD0DC\uADF8\uB294 \uC720\uC9C0
|
|
4036
|
+
if (['header', 'footer', 'nav', 'section', 'article', 'aside', 'main'].includes(tag)) {
|
|
4037
|
+
// main\uC740 \uADF8 \uC548\uC758 \uC790\uC2DD\uC744 \uD3BC\uCE68
|
|
4038
|
+
if (tag === 'main') {
|
|
4039
|
+
const children = Array.from(el.children).filter(c => c.getBoundingClientRect().height >= 10);
|
|
4040
|
+
if (children.length > 1) {
|
|
4041
|
+
result.push(...children);
|
|
4042
|
+
expanded = true;
|
|
4043
|
+
} else {
|
|
4044
|
+
result.push(el);
|
|
4045
|
+
}
|
|
4046
|
+
} else {
|
|
4047
|
+
result.push(el);
|
|
4048
|
+
}
|
|
4049
|
+
return;
|
|
4050
|
+
}
|
|
4051
|
+
// div \uB798\uD37C \uBC97\uAE30\uAE30
|
|
4052
|
+
const children = Array.from(el.children).filter(c => c.getBoundingClientRect().height >= 10);
|
|
4053
|
+
if (children.length === 1) {
|
|
4054
|
+
// \uB2E8\uC77C \uC790\uC2DD wrapper \u2192 \uBC97\uAE40
|
|
4055
|
+
result.push(...children);
|
|
4056
|
+
expanded = true;
|
|
4057
|
+
} else if (children.length > 1 && depth >= 4) {
|
|
4058
|
+
// \uAE4A\uC774 \uC0C1\uC704 2\uB2E8\uACC4(depth 5\u21924)\uC5D0\uC11C\uB9CC multi-child \uD655\uC7A5
|
|
4059
|
+
result.push(...children);
|
|
4060
|
+
expanded = true;
|
|
4061
|
+
} else {
|
|
4062
|
+
result.push(el);
|
|
4063
|
+
}
|
|
4064
|
+
});
|
|
4065
|
+
if (!expanded) return result;
|
|
4066
|
+
return unwrapSections(result, depth - 1);
|
|
4067
|
+
}
|
|
4068
|
+
|
|
4069
|
+
sectionEls = unwrapSections(sectionEls, 5);
|
|
4070
|
+
|
|
4071
|
+
// leaf \uC694\uC18C \uD544\uD130 \u2014 \uC139\uC158\uC774 \uB420 \uC218 \uC5C6\uB294 \uD0DC\uADF8 \uC81C\uAC70
|
|
4072
|
+
const leafTags = new Set(['img', 'br', 'hr', 'span', 'a', 'input', 'button', 'label', 'iframe', 'video', 'source', 'svg', 'canvas', 'p', 'strong', 'em', 'b', 'i', 'small', 'path', 'circle', 'rect', 'line', 'polygon', 'polyline', 'ellipse', 'g', 'use', 'defs', 'symbol']);
|
|
4073
|
+
sectionEls = sectionEls.filter(el => {
|
|
4074
|
+
const tag = el.tagName.toLowerCase();
|
|
4075
|
+
// heading(h1-h6)\uC740 \uC139\uC158\uC774 \uC544\uB2CC heading \u2014 \uD56D\uC0C1 \uC81C\uC678
|
|
4076
|
+
if (/^h[1-6]$/.test(tag)) return false;
|
|
4077
|
+
// li\uB294 \uB9AC\uC2A4\uD2B8 \uC544\uC774\uD15C\uC774\uC9C0 \uC139\uC158\uC774 \uC544\uB2D8
|
|
4078
|
+
if (tag === 'li') return false;
|
|
4079
|
+
return !leafTags.has(tag);
|
|
4080
|
+
});
|
|
4081
|
+
|
|
4021
4082
|
const sections = [];
|
|
4022
4083
|
let maxDepth = 0;
|
|
4023
4084
|
|
|
4024
|
-
|
|
4025
|
-
// \uC228\uACA8\uC9C4 \uC694\uC18C \uB610\uB294 \uB108\uBE44 0\uC778 \uC694\uC18C \uC81C\uC678
|
|
4085
|
+
sectionEls.forEach(el => {
|
|
4026
4086
|
const rect = el.getBoundingClientRect();
|
|
4027
4087
|
if (rect.height < 10) return;
|
|
4028
4088
|
|
|
@@ -4049,10 +4109,28 @@ var EXTRACTION_SCRIPT = `
|
|
|
4049
4109
|
if (depth > maxDepth) maxDepth = depth;
|
|
4050
4110
|
});
|
|
4051
4111
|
|
|
4052
|
-
// heading hierarchy
|
|
4112
|
+
// heading hierarchy (\uC2DC\uB9E8\uD2F1 + \uB300\uD615 \uD3F0\uD2B8 pseudo-heading)
|
|
4053
4113
|
const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
|
|
4054
4114
|
const headingHierarchy = headings.map(h => h.tagName.toLowerCase() + ': ' + (h.textContent || '').trim().slice(0, 80));
|
|
4055
4115
|
|
|
4116
|
+
// \uC2DC\uB9E8\uD2F1 heading\uC774 \uBD80\uC871\uD558\uBA74 \uD070 \uD3F0\uD2B8 \uC694\uC18C\uB97C pseudo-heading\uC73C\uB85C \uBCF4\uC644
|
|
4117
|
+
if (headingHierarchy.length < 3) {
|
|
4118
|
+
const largeFontEls = [];
|
|
4119
|
+
document.querySelectorAll('p, span, div, a, strong, em').forEach(el => {
|
|
4120
|
+
const fs = parsePixel(getComputedProp(el, 'font-size'));
|
|
4121
|
+
const text = (el.textContent || '').trim();
|
|
4122
|
+
if (fs >= 24 && text.length > 0 && text.length < 100 && el.children.length <= 2) {
|
|
4123
|
+
largeFontEls.push({ el, fs, text });
|
|
4124
|
+
}
|
|
4125
|
+
});
|
|
4126
|
+
// \uD070 \uC21C\uC11C \uC815\uB82C \uD6C4 \uC0C1\uC704 20\uAC1C
|
|
4127
|
+
largeFontEls.sort((a, b) => b.fs - a.fs);
|
|
4128
|
+
largeFontEls.slice(0, 20).forEach(({ fs, text }) => {
|
|
4129
|
+
const level = fs >= 60 ? 'pseudo-h1' : fs >= 36 ? 'pseudo-h2' : 'pseudo-h3';
|
|
4130
|
+
headingHierarchy.push(level + ' (' + fs + 'px): ' + text.slice(0, 80));
|
|
4131
|
+
});
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4056
4134
|
// \u2500\u2500 2. Design Tokens \u2500\u2500
|
|
4057
4135
|
|
|
4058
4136
|
// 2a. Colors \u2014 \uBAA8\uB4E0 \uC694\uC18C\uC758 color, backgroundColor \uC218\uC9D1
|
|
@@ -4073,11 +4151,61 @@ var EXTRACTION_SCRIPT = `
|
|
|
4073
4151
|
const sortedColors = Object.entries(colorMap).sort((a, b) => b[1] - a[1]);
|
|
4074
4152
|
const sortedBgs = Object.entries(bgColorMap).sort((a, b) => b[1] - a[1]);
|
|
4075
4153
|
|
|
4076
|
-
// primary = body\uB098 heading\uC758 \uAC00\uC7A5 \uBE48\uBC88\uD55C \uD14D\uC2A4\uD2B8 \uC0C9\uC0C1\uC774 \uC544\uB2CC \uC0C9 \uC911 1\uC704
|
|
4077
4154
|
const bodyColor = rgbToHex(getComputedProp(document.body, 'color'));
|
|
4078
4155
|
const bodyBg = rgbToHex(getComputedProp(document.body, 'background-color'));
|
|
4079
|
-
|
|
4080
|
-
const
|
|
4156
|
+
// \uAE30\uBCF8 \uB9C1\uD06C\uC0C9(#0000ee), \uD770/\uAC80 \uC81C\uC678
|
|
4157
|
+
const defaultLinkColors = ['#0000ee', '#0000ff', '#551a8b'];
|
|
4158
|
+
const nonBodyColors = sortedColors.filter(([c]) =>
|
|
4159
|
+
c !== bodyColor && c !== '#ffffff' && c !== '#000000' && !defaultLinkColors.includes(c)
|
|
4160
|
+
);
|
|
4161
|
+
|
|
4162
|
+
// heading \uC0C9\uC0C1 \uC218\uC9D1 \u2014 h1-h3 + \uB300\uD615 \uD3F0\uD2B8(24px+) \uC694\uC18C \uD3EC\uD568
|
|
4163
|
+
const headingColorMap = {};
|
|
4164
|
+
document.querySelectorAll('h1, h2, h3, h4').forEach(h => {
|
|
4165
|
+
const c = rgbToHex(getComputedProp(h, 'color'));
|
|
4166
|
+
if (c && c !== 'transparent' && c !== '#ffffff' && c !== '#000000' && !defaultLinkColors.includes(c)) {
|
|
4167
|
+
headingColorMap[c] = (headingColorMap[c] || 0) + 1;
|
|
4168
|
+
}
|
|
4169
|
+
});
|
|
4170
|
+
// \uC2DC\uB9E8\uD2F1 heading\uC774 \uC5C6\uC73C\uBA74 \uB300\uD615 \uD3F0\uD2B8 \uC694\uC18C\uC5D0\uC11C \uC0C9\uC0C1 \uCD94\uCD9C
|
|
4171
|
+
if (Object.keys(headingColorMap).length === 0) {
|
|
4172
|
+
document.querySelectorAll('p, span, div, strong, a').forEach(el => {
|
|
4173
|
+
const fs = parsePixel(getComputedProp(el, 'font-size'));
|
|
4174
|
+
if (fs >= 24 && (el.textContent || '').trim().length > 0 && (el.textContent || '').trim().length < 100) {
|
|
4175
|
+
const c = rgbToHex(getComputedProp(el, 'color'));
|
|
4176
|
+
if (c && c !== 'transparent' && c !== '#ffffff' && c !== '#000000' && !defaultLinkColors.includes(c)) {
|
|
4177
|
+
headingColorMap[c] = (headingColorMap[c] || 0) + 1;
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
});
|
|
4181
|
+
}
|
|
4182
|
+
const topHeadingColor = Object.entries(headingColorMap).sort((a, b) => b[1] - a[1])[0];
|
|
4183
|
+
|
|
4184
|
+
const allPalette = [...new Set([...sortedColors.map(c => c[0]), ...sortedBgs.map(c => c[0])])].filter(c => !defaultLinkColors.includes(c)).slice(0, 20);
|
|
4185
|
+
|
|
4186
|
+
// accent \uD6C4\uBCF4: \uCC44\uB3C4\uAC00 \uB192\uC740 \uBE44-\uC911\uB9BD \uC0C9\uC0C1 (\uAC15\uC870/CTA\uC6A9)
|
|
4187
|
+
function hexToHsl(hex) {
|
|
4188
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
4189
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
4190
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
4191
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
4192
|
+
const l = (max + min) / 2;
|
|
4193
|
+
if (max === min) return { h: 0, s: 0, l };
|
|
4194
|
+
const d = max - min;
|
|
4195
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
4196
|
+
let h = 0;
|
|
4197
|
+
if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
4198
|
+
else if (max === g) h = ((b - r) / d + 2) / 6;
|
|
4199
|
+
else h = ((r - g) / d + 4) / 6;
|
|
4200
|
+
return { h, s, l };
|
|
4201
|
+
}
|
|
4202
|
+
|
|
4203
|
+
const accentFromPalette = allPalette
|
|
4204
|
+
.filter(c => c.length === 7 && c.startsWith('#'))
|
|
4205
|
+
.map(c => ({ color: c, ...hexToHsl(c) }))
|
|
4206
|
+
.filter(c => c.s > 0.3 && c.l > 0.15 && c.l < 0.85)
|
|
4207
|
+
.sort((a, b) => b.s - a.s);
|
|
4208
|
+
const topAccent = accentFromPalette.length > 0 ? [accentFromPalette[0].color] : null;
|
|
4081
4209
|
|
|
4082
4210
|
// 2b. Typography
|
|
4083
4211
|
const typographyScale = {};
|
|
@@ -4170,13 +4298,30 @@ var EXTRACTION_SCRIPT = `
|
|
|
4170
4298
|
document.querySelector('details, [class*=accordion], [class*=collapse], [data-toggle=collapse]')
|
|
4171
4299
|
);
|
|
4172
4300
|
const hasModal = !!(
|
|
4173
|
-
document.querySelector('dialog, [class*=modal], [
|
|
4301
|
+
document.querySelector('dialog, [class*=modal], [role=dialog], [class*=popup]')
|
|
4302
|
+
);
|
|
4303
|
+
const hasTab = !!(
|
|
4304
|
+
document.querySelector('[role=tablist], [role=tab], [class*=tab-content], [class*=tab-pane], [data-toggle=tab], .elementor-tab-title, .e-n-tabs, .e-n-tab-title')
|
|
4305
|
+
);
|
|
4306
|
+
const hasLightbox = !!(
|
|
4307
|
+
document.querySelector('[class*=lightbox], [data-lightbox], [class*=fancybox], [data-fancybox], [class*=glightbox], [data-gallery], [data-elementor-open-lightbox]')
|
|
4174
4308
|
);
|
|
4175
4309
|
const hasStickyHeader = (() => {
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4310
|
+
// \uC2DC\uB9E8\uD2F1 \uD0DC\uADF8 \uC6B0\uC120
|
|
4311
|
+
const header = document.querySelector('header, [class*=header], nav, [class*=gnb], [class*=lnb], [id*=header], [id*=gnb]');
|
|
4312
|
+
if (header) {
|
|
4313
|
+
const pos = getComputedProp(header, 'position');
|
|
4314
|
+
if (pos === 'sticky' || pos === 'fixed') return true;
|
|
4315
|
+
}
|
|
4316
|
+
// \uC0C1\uB2E8 fixed/sticky \uC694\uC18C \uD0D0\uC0C9 (\uBE44\uC2DC\uB9E8\uD2F1 \uB9C8\uD06C\uC5C5 \uB300\uC751)
|
|
4317
|
+
const topEls = document.querySelectorAll('body > div, body > div > div');
|
|
4318
|
+
for (const el of topEls) {
|
|
4319
|
+
const pos = getComputedProp(el, 'position');
|
|
4320
|
+
if ((pos === 'sticky' || pos === 'fixed') && el.getBoundingClientRect().top <= 0 && el.getBoundingClientRect().height < 200) {
|
|
4321
|
+
return true;
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
return false;
|
|
4180
4325
|
})();
|
|
4181
4326
|
const hasScrollAnimations = !!(
|
|
4182
4327
|
document.querySelector('[class*=aos], [data-aos], [class*=wow], [class*=scroll-animate], [class*=animate-on-scroll]') ||
|
|
@@ -4198,6 +4343,7 @@ var EXTRACTION_SCRIPT = `
|
|
|
4198
4343
|
// \u2500\u2500 4. Images \u2500\u2500
|
|
4199
4344
|
const imgEls = document.querySelectorAll('img, picture source, [style*="background-image"]');
|
|
4200
4345
|
const images = [];
|
|
4346
|
+
const seenImageSrcs = new Set();
|
|
4201
4347
|
|
|
4202
4348
|
imgEls.forEach(el => {
|
|
4203
4349
|
let src = '';
|
|
@@ -4219,6 +4365,9 @@ var EXTRACTION_SCRIPT = `
|
|
|
4219
4365
|
}
|
|
4220
4366
|
|
|
4221
4367
|
if (!src || src.startsWith('data:image/svg') || src.includes('.svg')) return;
|
|
4368
|
+
// src \uC911\uBCF5 \uC81C\uAC70
|
|
4369
|
+
if (seenImageSrcs.has(src)) return;
|
|
4370
|
+
seenImageSrcs.add(src);
|
|
4222
4371
|
|
|
4223
4372
|
const rect = el.getBoundingClientRect();
|
|
4224
4373
|
if (!width) width = Math.round(rect.width);
|
|
@@ -4270,20 +4419,21 @@ var EXTRACTION_SCRIPT = `
|
|
|
4270
4419
|
src = el.src || el.dataset.src || '';
|
|
4271
4420
|
if (/youtube.com|youtu.be/.test(src)) {
|
|
4272
4421
|
platform = 'youtube';
|
|
4273
|
-
type = 'embed';
|
|
4274
4422
|
// YouTube autoplay \uD30C\uB77C\uBBF8\uD130 \uAC10\uC9C0
|
|
4275
4423
|
autoplay = /autoplay=1/.test(src);
|
|
4276
4424
|
muted = /mute=1/.test(src);
|
|
4277
4425
|
loop = /loop=1/.test(src);
|
|
4426
|
+
// autoplay+muted \u2192 background \uBE44\uB514\uC624\uB85C \uBD84\uB958 (YouTube \uBC30\uACBD \uC601\uC0C1 \uD328\uD134)
|
|
4427
|
+
type = (autoplay && muted) ? 'background' : 'embed';
|
|
4278
4428
|
// YouTube thumbnail \uCD94\uCD9C
|
|
4279
4429
|
const ytMatch = src.match(/(?:embed\\/|v=|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/);
|
|
4280
4430
|
if (ytMatch) posterSrc = 'https://img.youtube.com/vi/' + ytMatch[1] + '/hqdefault.jpg';
|
|
4281
4431
|
} else if (/vimeo.com/.test(src)) {
|
|
4282
4432
|
platform = 'vimeo';
|
|
4283
|
-
type = 'embed';
|
|
4284
4433
|
autoplay = /autoplay=1/.test(src);
|
|
4285
4434
|
muted = /muted=1/.test(src);
|
|
4286
4435
|
loop = /loop=1/.test(src);
|
|
4436
|
+
type = (autoplay && muted) ? 'background' : 'embed';
|
|
4287
4437
|
}
|
|
4288
4438
|
}
|
|
4289
4439
|
|
|
@@ -4374,11 +4524,11 @@ var EXTRACTION_SCRIPT = `
|
|
|
4374
4524
|
},
|
|
4375
4525
|
designTokens: {
|
|
4376
4526
|
colors: {
|
|
4377
|
-
primary: (nonBodyColors[0] || sortedColors[0] || ['#000000'])[0],
|
|
4378
|
-
secondary: (nonBodyColors[
|
|
4527
|
+
primary: topHeadingColor ? topHeadingColor[0] : (nonBodyColors[0] || sortedColors[0] || ['#000000'])[0],
|
|
4528
|
+
secondary: (nonBodyColors[0] || sortedColors[0] || ['#666666'])[0],
|
|
4379
4529
|
background: bodyBg || '#ffffff',
|
|
4380
4530
|
text: bodyColor || '#000000',
|
|
4381
|
-
accent: (nonBodyColors[
|
|
4531
|
+
accent: topAccent ? topAccent[0] : (nonBodyColors[1] || sortedColors[1] || ['#0066ff'])[0],
|
|
4382
4532
|
palette: allPalette,
|
|
4383
4533
|
},
|
|
4384
4534
|
typography: {
|
|
@@ -4403,6 +4553,8 @@ var EXTRACTION_SCRIPT = `
|
|
|
4403
4553
|
hasCarousel,
|
|
4404
4554
|
hasAccordion,
|
|
4405
4555
|
hasModal,
|
|
4556
|
+
hasTab,
|
|
4557
|
+
hasLightbox,
|
|
4406
4558
|
hasStickyHeader,
|
|
4407
4559
|
hasParallax,
|
|
4408
4560
|
detectedAnimations: [...animationNames].slice(0, 20),
|
|
@@ -4440,26 +4592,81 @@ async function analyzeReference(options) {
|
|
|
4440
4592
|
await writeFile7(analysisPath, JSON.stringify(analysis, null, 2), "utf-8");
|
|
4441
4593
|
return analysis;
|
|
4442
4594
|
}
|
|
4595
|
+
var POPUP_DISMISS_SCRIPT = `
|
|
4596
|
+
// 1. HTML dialog \uB2EB\uAE30
|
|
4597
|
+
document.querySelectorAll('dialog[open]').forEach(d => d.close());
|
|
4598
|
+
// 2. \uC77C\uBC18 \uD31D\uC5C5/\uBAA8\uB2EC \uB2EB\uAE30 \uBC84\uD2BC \uD074\uB9AD
|
|
4599
|
+
const closeSelectors = [
|
|
4600
|
+
'[class*=popup] [class*=close]', '[class*=modal] [class*=close]',
|
|
4601
|
+
'[class*=popup] [class*=btn-close]', '[class*=modal] [class*=btn-close]',
|
|
4602
|
+
'[aria-label*=close]', '[aria-label*="\uB2EB\uAE30"]', '[aria-label*=Close]',
|
|
4603
|
+
'.popup-close', '.modal-close', '.btn-close',
|
|
4604
|
+
'[class*=overlay] [class*=close]',
|
|
4605
|
+
];
|
|
4606
|
+
for (const sel of closeSelectors) {
|
|
4607
|
+
document.querySelectorAll(sel).forEach(btn => {
|
|
4608
|
+
try { btn.click(); } catch {}
|
|
4609
|
+
});
|
|
4610
|
+
}
|
|
4611
|
+
// 3. \uC624\uBC84\uB808\uC774/\uB524 \uB808\uC774\uC5B4 \uC81C\uAC70
|
|
4612
|
+
document.querySelectorAll('[class*=overlay], [class*=dimmed], .modal-backdrop, [class*=popup-bg]').forEach(el => {
|
|
4613
|
+
if (el.getBoundingClientRect().width >= window.innerWidth * 0.8) {
|
|
4614
|
+
el.style.display = 'none';
|
|
4615
|
+
}
|
|
4616
|
+
});
|
|
4617
|
+
// 4. body overflow \uBCF5\uC6D0
|
|
4618
|
+
document.body.style.overflow = '';
|
|
4619
|
+
document.body.style.position = '';
|
|
4620
|
+
document.documentElement.style.overflow = '';
|
|
4621
|
+
`;
|
|
4443
4622
|
function captureScreenshot(url, outputPath, width, height, timeout) {
|
|
4444
|
-
const
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4623
|
+
const scriptContent = `
|
|
4624
|
+
const { chromium } = require('playwright');
|
|
4625
|
+
(async () => {
|
|
4626
|
+
const browser = await chromium.launch({ headless: true });
|
|
4627
|
+
const context = await browser.newContext({
|
|
4628
|
+
viewport: { width: ${width}, height: ${height} },
|
|
4629
|
+
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
4630
|
+
});
|
|
4631
|
+
const page = await context.newPage();
|
|
4632
|
+
await page.goto(${JSON.stringify(url)}, { waitUntil: 'domcontentloaded', timeout: ${timeout} });
|
|
4633
|
+
await page.waitForTimeout(3000);
|
|
4634
|
+
// \uD31D\uC5C5 \uC790\uB3D9 \uB2EB\uAE30
|
|
4635
|
+
await page.evaluate(() => { ${POPUP_DISMISS_SCRIPT} });
|
|
4636
|
+
await page.waitForTimeout(500);
|
|
4637
|
+
await page.screenshot({ path: ${JSON.stringify(outputPath)}, fullPage: true });
|
|
4638
|
+
await browser.close();
|
|
4639
|
+
})().catch(e => {
|
|
4640
|
+
process.stderr.write(e.message);
|
|
4641
|
+
process.exit(1);
|
|
4642
|
+
});
|
|
4643
|
+
`;
|
|
4644
|
+
const result = spawnSync("node", ["-e", scriptContent], {
|
|
4457
4645
|
stdio: "pipe",
|
|
4458
|
-
timeout: timeout +
|
|
4646
|
+
timeout: timeout + 3e4,
|
|
4647
|
+
env: { ...process.env }
|
|
4459
4648
|
});
|
|
4460
4649
|
if (result.status !== 0) {
|
|
4461
4650
|
const stderr = result.stderr?.toString() ?? "";
|
|
4462
|
-
|
|
4651
|
+
const fallbackResult = spawnSync("npx", [
|
|
4652
|
+
"playwright",
|
|
4653
|
+
"screenshot",
|
|
4654
|
+
"--browser",
|
|
4655
|
+
"chromium",
|
|
4656
|
+
"--viewport-size",
|
|
4657
|
+
`${width},${height}`,
|
|
4658
|
+
"--wait-for-timeout",
|
|
4659
|
+
"3000",
|
|
4660
|
+
"--full-page",
|
|
4661
|
+
url,
|
|
4662
|
+
outputPath
|
|
4663
|
+
], {
|
|
4664
|
+
stdio: "pipe",
|
|
4665
|
+
timeout: timeout + 15e3
|
|
4666
|
+
});
|
|
4667
|
+
if (fallbackResult.status !== 0) {
|
|
4668
|
+
throw new Error(`\uC2A4\uD06C\uB9B0\uC0F7 \uCEA1\uCC98 \uC2E4\uD328 (${width}px): ${stderr || "unknown error"}`);
|
|
4669
|
+
}
|
|
4463
4670
|
}
|
|
4464
4671
|
}
|
|
4465
4672
|
async function extractPageData(url, timeout) {
|
|
@@ -4472,8 +4679,13 @@ async function extractPageData(url, timeout) {
|
|
|
4472
4679
|
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
4473
4680
|
});
|
|
4474
4681
|
const page = await context.newPage();
|
|
4475
|
-
await page.goto(${JSON.stringify(url)}, { waitUntil: '
|
|
4476
|
-
await page.waitForTimeout(
|
|
4682
|
+
await page.goto(${JSON.stringify(url)}, { waitUntil: 'domcontentloaded', timeout: ${timeout} });
|
|
4683
|
+
await page.waitForTimeout(5000);
|
|
4684
|
+
|
|
4685
|
+
// \uD31D\uC5C5/\uBAA8\uB2EC/\uC624\uBC84\uB808\uC774 \uC790\uB3D9 \uB2EB\uAE30
|
|
4686
|
+
await page.evaluate(() => { ${POPUP_DISMISS_SCRIPT} });
|
|
4687
|
+
await page.waitForTimeout(500);
|
|
4688
|
+
|
|
4477
4689
|
const data = await page.evaluate(${JSON.stringify(EXTRACTION_SCRIPT)});
|
|
4478
4690
|
await browser.close();
|
|
4479
4691
|
process.stdout.write(JSON.stringify(data));
|
|
@@ -4535,6 +4747,8 @@ function createFallbackData() {
|
|
|
4535
4747
|
hasCarousel: false,
|
|
4536
4748
|
hasAccordion: false,
|
|
4537
4749
|
hasModal: false,
|
|
4750
|
+
hasTab: false,
|
|
4751
|
+
hasLightbox: false,
|
|
4538
4752
|
hasStickyHeader: false,
|
|
4539
4753
|
hasParallax: false,
|
|
4540
4754
|
detectedAnimations: [],
|
|
@@ -4737,6 +4951,8 @@ async function commandAnalyze(url, options) {
|
|
|
4737
4951
|
interactions.hasCarousel && "Carousel",
|
|
4738
4952
|
interactions.hasAccordion && "Accordion",
|
|
4739
4953
|
interactions.hasModal && "Modal",
|
|
4954
|
+
interactions.hasTab && "Tab",
|
|
4955
|
+
interactions.hasLightbox && "Lightbox",
|
|
4740
4956
|
interactions.hasParallax && "Parallax",
|
|
4741
4957
|
interactions.hasHoverEffects && "Hover Effects"
|
|
4742
4958
|
].filter(Boolean);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saeroon/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Saeroon Hosting developer CLI — preview, validate, and deploy sites & templates",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"@saeroon/tsconfig": "workspace:*",
|
|
41
41
|
"@types/node": "^25.0.3",
|
|
42
42
|
"@types/ws": "^8.5.0",
|
|
43
|
+
"playwright": "^1.57.0",
|
|
43
44
|
"tsup": "^8.3.5",
|
|
44
45
|
"typescript": "~5.9.3"
|
|
45
46
|
},
|