@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.
Files changed (2) hide show
  1. package/dist/index.js +265 -49
  2. 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 (10)
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 rgb;
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
- topLevelEls.forEach(el => {
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
- const nonBodyColors = sortedColors.filter(([c]) => c !== bodyColor && c !== '#ffffff' && c !== '#000000');
4080
- const allPalette = [...new Set([...sortedColors.map(c => c[0]), ...sortedBgs.map(c => c[0])])].slice(0, 20);
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], [class*=lightbox], [role=dialog]')
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
- const header = document.querySelector('header, [class*=header], nav');
4177
- if (!header) return false;
4178
- const pos = getComputedProp(header, 'position');
4179
- return pos === 'sticky' || pos === 'fixed';
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[1] || sortedColors[1] || ['#666666'])[0],
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[2] || sortedColors[2] || ['#0066ff'])[0],
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 result = spawnSync("npx", [
4445
- "playwright",
4446
- "screenshot",
4447
- "--browser",
4448
- "chromium",
4449
- "--viewport-size",
4450
- `${width},${height}`,
4451
- "--wait-for-timeout",
4452
- "3000",
4453
- "--full-page",
4454
- url,
4455
- outputPath
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 + 15e3
4646
+ timeout: timeout + 3e4,
4647
+ env: { ...process.env }
4459
4648
  });
4460
4649
  if (result.status !== 0) {
4461
4650
  const stderr = result.stderr?.toString() ?? "";
4462
- throw new Error(`\uC2A4\uD06C\uB9B0\uC0F7 \uCEA1\uCC98 \uC2E4\uD328 (${width}px): ${stderr || "unknown error"}`);
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: 'networkidle', timeout: ${timeout} });
4476
- await page.waitForTimeout(2000);
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.5",
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
  },