@saeroon/cli 0.2.6 → 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 +263 -48
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -3940,10 +3940,12 @@ var EXTRACTION_SCRIPT = `
|
|
|
3940
3940
|
|
|
3941
3941
|
function rgbToHex(rgb) {
|
|
3942
3942
|
if (!rgb || rgb === 'transparent') return 'transparent';
|
|
3943
|
-
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;
|
|
3944
3946
|
const match = rgb.match(/\\d+/g);
|
|
3945
|
-
if (!match || match.length < 3) return
|
|
3946
|
-
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))));
|
|
3947
3949
|
return '#' + [r, g, b].map(c => c.toString(16).padStart(2, '0')).join('');
|
|
3948
3950
|
}
|
|
3949
3951
|
|
|
@@ -3968,18 +3970,18 @@ var EXTRACTION_SCRIPT = `
|
|
|
3968
3970
|
|
|
3969
3971
|
if (tag === 'header' || tag === 'nav') return 'header';
|
|
3970
3972
|
if (tag === 'footer') return 'footer';
|
|
3971
|
-
if (/hero|banner|jumbotron|splash|main-?visual/.test(combined)) return 'hero';
|
|
3972
|
-
if (/feature|service|benefit|what-?we/.test(combined)) return 'features';
|
|
3973
|
-
if (/testimonial|review|feedback|client|customer/.test(combined)) return 'testimonials';
|
|
3974
|
-
if (/faq|accordion|question|q-?and-?a/.test(combined)) return 'faq';
|
|
3975
|
-
if (/team|staff|member|about-?us|who-?we/.test(combined)) return 'team';
|
|
3976
|
-
if (/pricing|plan|package/.test(combined)) return 'pricing';
|
|
3977
|
-
if (/contact|inquiry|form|cta|call-?to-?action|get-?started/.test(combined)) return 'cta';
|
|
3978
|
-
if (/gallery|portfolio|work|project|showcase/.test(combined)) return 'gallery';
|
|
3979
|
-
if (/blog|news|article|post/.test(combined)) return 'blog';
|
|
3980
|
-
if (/partner|client|logo|brand|trust/.test(combined)) return 'partners';
|
|
3981
|
-
if (/map|location|address|direction/.test(combined)) return 'map';
|
|
3982
|
-
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';
|
|
3983
3985
|
return 'section';
|
|
3984
3986
|
}
|
|
3985
3987
|
|
|
@@ -4019,11 +4021,68 @@ var EXTRACTION_SCRIPT = `
|
|
|
4019
4021
|
// \u2500\u2500 1. Structure \u2500\u2500
|
|
4020
4022
|
const sectionSelectors = 'body > header, body > footer, body > nav, body > main, body > section, body > div, body > article, body > aside';
|
|
4021
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
|
+
|
|
4022
4082
|
const sections = [];
|
|
4023
4083
|
let maxDepth = 0;
|
|
4024
4084
|
|
|
4025
|
-
|
|
4026
|
-
// \uC228\uACA8\uC9C4 \uC694\uC18C \uB610\uB294 \uB108\uBE44 0\uC778 \uC694\uC18C \uC81C\uC678
|
|
4085
|
+
sectionEls.forEach(el => {
|
|
4027
4086
|
const rect = el.getBoundingClientRect();
|
|
4028
4087
|
if (rect.height < 10) return;
|
|
4029
4088
|
|
|
@@ -4050,10 +4109,28 @@ var EXTRACTION_SCRIPT = `
|
|
|
4050
4109
|
if (depth > maxDepth) maxDepth = depth;
|
|
4051
4110
|
});
|
|
4052
4111
|
|
|
4053
|
-
// heading hierarchy
|
|
4112
|
+
// heading hierarchy (\uC2DC\uB9E8\uD2F1 + \uB300\uD615 \uD3F0\uD2B8 pseudo-heading)
|
|
4054
4113
|
const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
|
|
4055
4114
|
const headingHierarchy = headings.map(h => h.tagName.toLowerCase() + ': ' + (h.textContent || '').trim().slice(0, 80));
|
|
4056
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
|
+
|
|
4057
4134
|
// \u2500\u2500 2. Design Tokens \u2500\u2500
|
|
4058
4135
|
|
|
4059
4136
|
// 2a. Colors \u2014 \uBAA8\uB4E0 \uC694\uC18C\uC758 color, backgroundColor \uC218\uC9D1
|
|
@@ -4074,11 +4151,61 @@ var EXTRACTION_SCRIPT = `
|
|
|
4074
4151
|
const sortedColors = Object.entries(colorMap).sort((a, b) => b[1] - a[1]);
|
|
4075
4152
|
const sortedBgs = Object.entries(bgColorMap).sort((a, b) => b[1] - a[1]);
|
|
4076
4153
|
|
|
4077
|
-
// primary = body\uB098 heading\uC758 \uAC00\uC7A5 \uBE48\uBC88\uD55C \uD14D\uC2A4\uD2B8 \uC0C9\uC0C1\uC774 \uC544\uB2CC \uC0C9 \uC911 1\uC704
|
|
4078
4154
|
const bodyColor = rgbToHex(getComputedProp(document.body, 'color'));
|
|
4079
4155
|
const bodyBg = rgbToHex(getComputedProp(document.body, 'background-color'));
|
|
4080
|
-
|
|
4081
|
-
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;
|
|
4082
4209
|
|
|
4083
4210
|
// 2b. Typography
|
|
4084
4211
|
const typographyScale = {};
|
|
@@ -4171,13 +4298,30 @@ var EXTRACTION_SCRIPT = `
|
|
|
4171
4298
|
document.querySelector('details, [class*=accordion], [class*=collapse], [data-toggle=collapse]')
|
|
4172
4299
|
);
|
|
4173
4300
|
const hasModal = !!(
|
|
4174
|
-
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]')
|
|
4175
4308
|
);
|
|
4176
4309
|
const hasStickyHeader = (() => {
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
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;
|
|
4181
4325
|
})();
|
|
4182
4326
|
const hasScrollAnimations = !!(
|
|
4183
4327
|
document.querySelector('[class*=aos], [data-aos], [class*=wow], [class*=scroll-animate], [class*=animate-on-scroll]') ||
|
|
@@ -4199,6 +4343,7 @@ var EXTRACTION_SCRIPT = `
|
|
|
4199
4343
|
// \u2500\u2500 4. Images \u2500\u2500
|
|
4200
4344
|
const imgEls = document.querySelectorAll('img, picture source, [style*="background-image"]');
|
|
4201
4345
|
const images = [];
|
|
4346
|
+
const seenImageSrcs = new Set();
|
|
4202
4347
|
|
|
4203
4348
|
imgEls.forEach(el => {
|
|
4204
4349
|
let src = '';
|
|
@@ -4220,6 +4365,9 @@ var EXTRACTION_SCRIPT = `
|
|
|
4220
4365
|
}
|
|
4221
4366
|
|
|
4222
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);
|
|
4223
4371
|
|
|
4224
4372
|
const rect = el.getBoundingClientRect();
|
|
4225
4373
|
if (!width) width = Math.round(rect.width);
|
|
@@ -4271,20 +4419,21 @@ var EXTRACTION_SCRIPT = `
|
|
|
4271
4419
|
src = el.src || el.dataset.src || '';
|
|
4272
4420
|
if (/youtube.com|youtu.be/.test(src)) {
|
|
4273
4421
|
platform = 'youtube';
|
|
4274
|
-
type = 'embed';
|
|
4275
4422
|
// YouTube autoplay \uD30C\uB77C\uBBF8\uD130 \uAC10\uC9C0
|
|
4276
4423
|
autoplay = /autoplay=1/.test(src);
|
|
4277
4424
|
muted = /mute=1/.test(src);
|
|
4278
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';
|
|
4279
4428
|
// YouTube thumbnail \uCD94\uCD9C
|
|
4280
4429
|
const ytMatch = src.match(/(?:embed\\/|v=|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/);
|
|
4281
4430
|
if (ytMatch) posterSrc = 'https://img.youtube.com/vi/' + ytMatch[1] + '/hqdefault.jpg';
|
|
4282
4431
|
} else if (/vimeo.com/.test(src)) {
|
|
4283
4432
|
platform = 'vimeo';
|
|
4284
|
-
type = 'embed';
|
|
4285
4433
|
autoplay = /autoplay=1/.test(src);
|
|
4286
4434
|
muted = /muted=1/.test(src);
|
|
4287
4435
|
loop = /loop=1/.test(src);
|
|
4436
|
+
type = (autoplay && muted) ? 'background' : 'embed';
|
|
4288
4437
|
}
|
|
4289
4438
|
}
|
|
4290
4439
|
|
|
@@ -4375,11 +4524,11 @@ var EXTRACTION_SCRIPT = `
|
|
|
4375
4524
|
},
|
|
4376
4525
|
designTokens: {
|
|
4377
4526
|
colors: {
|
|
4378
|
-
primary: (nonBodyColors[0] || sortedColors[0] || ['#000000'])[0],
|
|
4379
|
-
secondary: (nonBodyColors[
|
|
4527
|
+
primary: topHeadingColor ? topHeadingColor[0] : (nonBodyColors[0] || sortedColors[0] || ['#000000'])[0],
|
|
4528
|
+
secondary: (nonBodyColors[0] || sortedColors[0] || ['#666666'])[0],
|
|
4380
4529
|
background: bodyBg || '#ffffff',
|
|
4381
4530
|
text: bodyColor || '#000000',
|
|
4382
|
-
accent: (nonBodyColors[
|
|
4531
|
+
accent: topAccent ? topAccent[0] : (nonBodyColors[1] || sortedColors[1] || ['#0066ff'])[0],
|
|
4383
4532
|
palette: allPalette,
|
|
4384
4533
|
},
|
|
4385
4534
|
typography: {
|
|
@@ -4404,6 +4553,8 @@ var EXTRACTION_SCRIPT = `
|
|
|
4404
4553
|
hasCarousel,
|
|
4405
4554
|
hasAccordion,
|
|
4406
4555
|
hasModal,
|
|
4556
|
+
hasTab,
|
|
4557
|
+
hasLightbox,
|
|
4407
4558
|
hasStickyHeader,
|
|
4408
4559
|
hasParallax,
|
|
4409
4560
|
detectedAnimations: [...animationNames].slice(0, 20),
|
|
@@ -4441,26 +4592,81 @@ async function analyzeReference(options) {
|
|
|
4441
4592
|
await writeFile7(analysisPath, JSON.stringify(analysis, null, 2), "utf-8");
|
|
4442
4593
|
return analysis;
|
|
4443
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
|
+
`;
|
|
4444
4622
|
function captureScreenshot(url, outputPath, width, height, timeout) {
|
|
4445
|
-
const
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
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], {
|
|
4458
4645
|
stdio: "pipe",
|
|
4459
|
-
timeout: timeout +
|
|
4646
|
+
timeout: timeout + 3e4,
|
|
4647
|
+
env: { ...process.env }
|
|
4460
4648
|
});
|
|
4461
4649
|
if (result.status !== 0) {
|
|
4462
4650
|
const stderr = result.stderr?.toString() ?? "";
|
|
4463
|
-
|
|
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
|
+
}
|
|
4464
4670
|
}
|
|
4465
4671
|
}
|
|
4466
4672
|
async function extractPageData(url, timeout) {
|
|
@@ -4473,8 +4679,13 @@ async function extractPageData(url, timeout) {
|
|
|
4473
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',
|
|
4474
4680
|
});
|
|
4475
4681
|
const page = await context.newPage();
|
|
4476
|
-
await page.goto(${JSON.stringify(url)}, { waitUntil: '
|
|
4477
|
-
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
|
+
|
|
4478
4689
|
const data = await page.evaluate(${JSON.stringify(EXTRACTION_SCRIPT)});
|
|
4479
4690
|
await browser.close();
|
|
4480
4691
|
process.stdout.write(JSON.stringify(data));
|
|
@@ -4536,6 +4747,8 @@ function createFallbackData() {
|
|
|
4536
4747
|
hasCarousel: false,
|
|
4537
4748
|
hasAccordion: false,
|
|
4538
4749
|
hasModal: false,
|
|
4750
|
+
hasTab: false,
|
|
4751
|
+
hasLightbox: false,
|
|
4539
4752
|
hasStickyHeader: false,
|
|
4540
4753
|
hasParallax: false,
|
|
4541
4754
|
detectedAnimations: [],
|
|
@@ -4738,6 +4951,8 @@ async function commandAnalyze(url, options) {
|
|
|
4738
4951
|
interactions.hasCarousel && "Carousel",
|
|
4739
4952
|
interactions.hasAccordion && "Accordion",
|
|
4740
4953
|
interactions.hasModal && "Modal",
|
|
4954
|
+
interactions.hasTab && "Tab",
|
|
4955
|
+
interactions.hasLightbox && "Lightbox",
|
|
4741
4956
|
interactions.hasParallax && "Parallax",
|
|
4742
4957
|
interactions.hasHoverEffects && "Hover Effects"
|
|
4743
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
|
},
|