popeye-cli 1.5.0 → 1.7.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.
Files changed (195) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +184 -31
  3. package/dist/cli/commands/create.d.ts.map +1 -1
  4. package/dist/cli/commands/create.js +54 -4
  5. package/dist/cli/commands/create.js.map +1 -1
  6. package/dist/cli/interactive.d.ts +29 -0
  7. package/dist/cli/interactive.d.ts.map +1 -1
  8. package/dist/cli/interactive.js +90 -7
  9. package/dist/cli/interactive.js.map +1 -1
  10. package/dist/generators/all.d.ts +4 -1
  11. package/dist/generators/all.d.ts.map +1 -1
  12. package/dist/generators/all.js +36 -316
  13. package/dist/generators/all.js.map +1 -1
  14. package/dist/generators/doc-parser.d.ts +18 -3
  15. package/dist/generators/doc-parser.d.ts.map +1 -1
  16. package/dist/generators/doc-parser.js +81 -10
  17. package/dist/generators/doc-parser.js.map +1 -1
  18. package/dist/generators/frontend-design-analyzer.d.ts +30 -0
  19. package/dist/generators/frontend-design-analyzer.d.ts.map +1 -0
  20. package/dist/generators/frontend-design-analyzer.js +208 -0
  21. package/dist/generators/frontend-design-analyzer.js.map +1 -0
  22. package/dist/generators/shared-packages.d.ts +45 -0
  23. package/dist/generators/shared-packages.d.ts.map +1 -0
  24. package/dist/generators/shared-packages.js +456 -0
  25. package/dist/generators/shared-packages.js.map +1 -0
  26. package/dist/generators/templates/index.d.ts +4 -0
  27. package/dist/generators/templates/index.d.ts.map +1 -1
  28. package/dist/generators/templates/index.js +4 -0
  29. package/dist/generators/templates/index.js.map +1 -1
  30. package/dist/generators/templates/website-components.d.ts.map +1 -1
  31. package/dist/generators/templates/website-components.js +36 -11
  32. package/dist/generators/templates/website-components.js.map +1 -1
  33. package/dist/generators/templates/website-config.d.ts +15 -1
  34. package/dist/generators/templates/website-config.d.ts.map +1 -1
  35. package/dist/generators/templates/website-config.js +155 -13
  36. package/dist/generators/templates/website-config.js.map +1 -1
  37. package/dist/generators/templates/website-landing.d.ts +24 -0
  38. package/dist/generators/templates/website-landing.d.ts.map +1 -0
  39. package/dist/generators/templates/website-landing.js +276 -0
  40. package/dist/generators/templates/website-landing.js.map +1 -0
  41. package/dist/generators/templates/website-layout.d.ts +42 -0
  42. package/dist/generators/templates/website-layout.d.ts.map +1 -0
  43. package/dist/generators/templates/website-layout.js +408 -0
  44. package/dist/generators/templates/website-layout.js.map +1 -0
  45. package/dist/generators/templates/website-pricing.d.ts +11 -0
  46. package/dist/generators/templates/website-pricing.d.ts.map +1 -0
  47. package/dist/generators/templates/website-pricing.js +313 -0
  48. package/dist/generators/templates/website-pricing.js.map +1 -0
  49. package/dist/generators/templates/website-sections.d.ts +102 -0
  50. package/dist/generators/templates/website-sections.d.ts.map +1 -0
  51. package/dist/generators/templates/website-sections.js +444 -0
  52. package/dist/generators/templates/website-sections.js.map +1 -0
  53. package/dist/generators/templates/website.d.ts +10 -50
  54. package/dist/generators/templates/website.d.ts.map +1 -1
  55. package/dist/generators/templates/website.js +12 -788
  56. package/dist/generators/templates/website.js.map +1 -1
  57. package/dist/generators/website-content-scanner.d.ts +37 -0
  58. package/dist/generators/website-content-scanner.d.ts.map +1 -0
  59. package/dist/generators/website-content-scanner.js +165 -0
  60. package/dist/generators/website-content-scanner.js.map +1 -0
  61. package/dist/generators/website-context.d.ts +38 -2
  62. package/dist/generators/website-context.d.ts.map +1 -1
  63. package/dist/generators/website-context.js +179 -19
  64. package/dist/generators/website-context.js.map +1 -1
  65. package/dist/generators/website-debug.d.ts +68 -0
  66. package/dist/generators/website-debug.d.ts.map +1 -0
  67. package/dist/generators/website-debug.js +93 -0
  68. package/dist/generators/website-debug.js.map +1 -0
  69. package/dist/generators/website.d.ts +2 -0
  70. package/dist/generators/website.d.ts.map +1 -1
  71. package/dist/generators/website.js +66 -4
  72. package/dist/generators/website.js.map +1 -1
  73. package/dist/generators/workspace-root.d.ts +27 -0
  74. package/dist/generators/workspace-root.d.ts.map +1 -0
  75. package/dist/generators/workspace-root.js +100 -0
  76. package/dist/generators/workspace-root.js.map +1 -0
  77. package/dist/state/index.d.ts +8 -0
  78. package/dist/state/index.d.ts.map +1 -1
  79. package/dist/state/index.js +11 -0
  80. package/dist/state/index.js.map +1 -1
  81. package/dist/types/consensus.d.ts +3 -0
  82. package/dist/types/consensus.d.ts.map +1 -1
  83. package/dist/types/consensus.js +1 -0
  84. package/dist/types/consensus.js.map +1 -1
  85. package/dist/types/index.d.ts +1 -0
  86. package/dist/types/index.d.ts.map +1 -1
  87. package/dist/types/index.js +2 -0
  88. package/dist/types/index.js.map +1 -1
  89. package/dist/types/tester.d.ts +138 -0
  90. package/dist/types/tester.d.ts.map +1 -0
  91. package/dist/types/tester.js +110 -0
  92. package/dist/types/tester.js.map +1 -0
  93. package/dist/types/workflow.d.ts +151 -0
  94. package/dist/types/workflow.d.ts.map +1 -1
  95. package/dist/types/workflow.js +14 -0
  96. package/dist/types/workflow.js.map +1 -1
  97. package/dist/upgrade/handlers.d.ts +15 -0
  98. package/dist/upgrade/handlers.d.ts.map +1 -1
  99. package/dist/upgrade/handlers.js +52 -0
  100. package/dist/upgrade/handlers.js.map +1 -1
  101. package/dist/workflow/auto-fix-bundler.d.ts +37 -0
  102. package/dist/workflow/auto-fix-bundler.d.ts.map +1 -0
  103. package/dist/workflow/auto-fix-bundler.js +320 -0
  104. package/dist/workflow/auto-fix-bundler.js.map +1 -0
  105. package/dist/workflow/auto-fix.d.ts.map +1 -1
  106. package/dist/workflow/auto-fix.js +10 -3
  107. package/dist/workflow/auto-fix.js.map +1 -1
  108. package/dist/workflow/execution-mode.js +2 -2
  109. package/dist/workflow/execution-mode.js.map +1 -1
  110. package/dist/workflow/index.d.ts +2 -0
  111. package/dist/workflow/index.d.ts.map +1 -1
  112. package/dist/workflow/index.js +13 -0
  113. package/dist/workflow/index.js.map +1 -1
  114. package/dist/workflow/overview.d.ts.map +1 -1
  115. package/dist/workflow/overview.js +4 -0
  116. package/dist/workflow/overview.js.map +1 -1
  117. package/dist/workflow/plan-mode.d.ts +4 -3
  118. package/dist/workflow/plan-mode.d.ts.map +1 -1
  119. package/dist/workflow/plan-mode.js +69 -5
  120. package/dist/workflow/plan-mode.js.map +1 -1
  121. package/dist/workflow/task-workflow.d.ts +5 -0
  122. package/dist/workflow/task-workflow.d.ts.map +1 -1
  123. package/dist/workflow/task-workflow.js +172 -6
  124. package/dist/workflow/task-workflow.js.map +1 -1
  125. package/dist/workflow/tester.d.ts +120 -0
  126. package/dist/workflow/tester.d.ts.map +1 -0
  127. package/dist/workflow/tester.js +589 -0
  128. package/dist/workflow/tester.js.map +1 -0
  129. package/dist/workflow/website-strategy.d.ts +9 -0
  130. package/dist/workflow/website-strategy.d.ts.map +1 -1
  131. package/dist/workflow/website-strategy.js +73 -1
  132. package/dist/workflow/website-strategy.js.map +1 -1
  133. package/dist/workflow/website-updater.d.ts.map +1 -1
  134. package/dist/workflow/website-updater.js +15 -4
  135. package/dist/workflow/website-updater.js.map +1 -1
  136. package/dist/workflow/workflow-logger.d.ts +1 -1
  137. package/dist/workflow/workflow-logger.d.ts.map +1 -1
  138. package/dist/workflow/workflow-logger.js.map +1 -1
  139. package/package.json +1 -1
  140. package/src/cli/commands/create.ts +58 -4
  141. package/src/cli/interactive.ts +96 -7
  142. package/src/generators/all.ts +44 -332
  143. package/src/generators/doc-parser.ts +87 -10
  144. package/src/generators/frontend-design-analyzer.ts +261 -0
  145. package/src/generators/shared-packages.ts +500 -0
  146. package/src/generators/templates/index.ts +4 -0
  147. package/src/generators/templates/website-components.ts +36 -11
  148. package/src/generators/templates/website-config.ts +166 -13
  149. package/src/generators/templates/website-landing.ts +331 -0
  150. package/src/generators/templates/website-layout.ts +443 -0
  151. package/src/generators/templates/website-pricing.ts +330 -0
  152. package/src/generators/templates/website-sections.ts +541 -0
  153. package/src/generators/templates/website.ts +38 -851
  154. package/src/generators/website-content-scanner.ts +208 -0
  155. package/src/generators/website-context.ts +248 -20
  156. package/src/generators/website-debug.ts +130 -0
  157. package/src/generators/website.ts +71 -3
  158. package/src/generators/workspace-root.ts +113 -0
  159. package/src/state/index.ts +15 -0
  160. package/src/types/consensus.ts +3 -0
  161. package/src/types/index.ts +21 -0
  162. package/src/types/tester.ts +136 -0
  163. package/src/types/workflow.ts +32 -0
  164. package/src/upgrade/handlers.ts +65 -0
  165. package/src/workflow/auto-fix-bundler.ts +392 -0
  166. package/src/workflow/auto-fix.ts +11 -3
  167. package/src/workflow/execution-mode.ts +2 -2
  168. package/src/workflow/index.ts +13 -0
  169. package/src/workflow/overview.ts +6 -0
  170. package/src/workflow/plan-mode.ts +81 -7
  171. package/src/workflow/task-workflow.ts +227 -5
  172. package/src/workflow/tester.ts +723 -0
  173. package/src/workflow/website-strategy.ts +75 -1
  174. package/src/workflow/website-updater.ts +17 -6
  175. package/src/workflow/workflow-logger.ts +2 -0
  176. package/tests/cli/project-naming.test.ts +136 -0
  177. package/tests/generators/doc-parser.test.ts +121 -0
  178. package/tests/generators/frontend-design-analyzer.test.ts +90 -0
  179. package/tests/generators/quality-gate.test.ts +183 -0
  180. package/tests/generators/shared-packages.test.ts +83 -0
  181. package/tests/generators/website-components.test.ts +1 -1
  182. package/tests/generators/website-config.test.ts +84 -0
  183. package/tests/generators/website-content-scanner.test.ts +181 -0
  184. package/tests/generators/website-context.test.ts +109 -0
  185. package/tests/generators/website-debug.test.ts +77 -0
  186. package/tests/generators/website-landing.test.ts +188 -0
  187. package/tests/generators/website-pricing.test.ts +98 -0
  188. package/tests/generators/website-sections.test.ts +245 -0
  189. package/tests/generators/workspace-root.test.ts +105 -0
  190. package/tests/types/tester.test.ts +174 -0
  191. package/tests/upgrade/handlers.test.ts +162 -0
  192. package/tests/workflow/auto-fix-bundler.test.ts +242 -0
  193. package/tests/workflow/plan-mode.test.ts +111 -1
  194. package/tests/workflow/tester.test.ts +401 -0
  195. package/tests/workflow/website-strategy.test.ts +55 -0
@@ -124,14 +124,41 @@ export function generateWebsiteTsconfig(): string {
124
124
  `;
125
125
  }
126
126
 
127
+ /**
128
+ * Options for generating the Tailwind config
129
+ */
130
+ export interface TailwindConfigOptions {
131
+ /** Primary brand color (hex) to generate color scale from */
132
+ primaryColor?: string;
133
+ /** Whether to import workspace design-tokens preset */
134
+ workspaceMode?: boolean;
135
+ /** Project name (for workspace preset import path) */
136
+ projectName?: string;
137
+ }
138
+
127
139
  /**
128
140
  * Generate Tailwind config for website
141
+ *
142
+ * @param options - Optional configuration for brand colors and workspace mode
143
+ * @returns Tailwind config source code
129
144
  */
130
- export function generateWebsiteTailwindConfig(): string {
131
- return `import type { Config } from 'tailwindcss';
145
+ export function generateWebsiteTailwindConfig(options?: TailwindConfigOptions): string {
146
+ const colorScale = options?.primaryColor
147
+ ? generateInlineColorScale(options.primaryColor)
148
+ : defaultColorScale();
149
+
150
+ const presetImport = options?.workspaceMode && options?.projectName
151
+ ? `import designPreset from '@${options.projectName}/design-tokens/tailwind';\n`
152
+ : '';
132
153
 
154
+ const presetsBlock = options?.workspaceMode && options?.projectName
155
+ ? ` presets: [designPreset],\n`
156
+ : '';
157
+
158
+ return `import type { Config } from 'tailwindcss';
159
+ ${presetImport}
133
160
  const config: Config = {
134
- content: [
161
+ ${presetsBlock} content: [
135
162
  './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
136
163
  './src/components/**/*.{js,ts,jsx,tsx,mdx}',
137
164
  './src/app/**/*.{js,ts,jsx,tsx,mdx}',
@@ -140,21 +167,50 @@ const config: Config = {
140
167
  extend: {
141
168
  colors: {
142
169
  primary: {
143
- 50: '#f0f9ff',
144
- 100: '#e0f2fe',
145
- 200: '#bae6fd',
146
- 300: '#7dd3fc',
147
- 400: '#38bdf8',
148
- 500: '#0ea5e9',
149
- 600: '#0284c7',
150
- 700: '#0369a1',
151
- 800: '#075985',
152
- 900: '#0c4a6e',
170
+ ${colorScale}
171
+ },
172
+ background: 'hsl(var(--background))',
173
+ foreground: 'hsl(var(--foreground))',
174
+ muted: {
175
+ DEFAULT: 'hsl(var(--muted))',
176
+ foreground: 'hsl(var(--muted-foreground))',
177
+ },
178
+ accent: {
179
+ DEFAULT: 'hsl(var(--accent))',
180
+ foreground: 'hsl(var(--accent-foreground))',
153
181
  },
182
+ card: {
183
+ DEFAULT: 'hsl(var(--card))',
184
+ foreground: 'hsl(var(--card-foreground))',
185
+ },
186
+ border: 'hsl(var(--border))',
187
+ ring: 'hsl(var(--ring))',
154
188
  },
155
189
  fontFamily: {
156
190
  sans: ['var(--font-inter)', 'system-ui', 'sans-serif'],
157
191
  },
192
+ borderColor: {
193
+ DEFAULT: 'hsl(var(--border))',
194
+ },
195
+ borderRadius: {
196
+ lg: 'var(--radius)',
197
+ md: 'calc(var(--radius) - 2px)',
198
+ sm: 'calc(var(--radius) - 4px)',
199
+ },
200
+ keyframes: {
201
+ fadeIn: {
202
+ '0%': { opacity: '0' },
203
+ '100%': { opacity: '1' },
204
+ },
205
+ slideUp: {
206
+ '0%': { opacity: '0', transform: 'translateY(20px)' },
207
+ '100%': { opacity: '1', transform: 'translateY(0)' },
208
+ },
209
+ },
210
+ animation: {
211
+ fadeIn: 'fadeIn 0.5s ease-out',
212
+ slideUp: 'slideUp 0.5s ease-out',
213
+ },
158
214
  },
159
215
  },
160
216
  plugins: [],
@@ -164,6 +220,103 @@ export default config;
164
220
  `;
165
221
  }
166
222
 
223
+ /**
224
+ * Generate inline color scale entries from a hex color
225
+ */
226
+ function generateInlineColorScale(hex: string): string {
227
+ const rgb = hexToRgb(hex);
228
+ if (!rgb) return defaultColorScale();
229
+
230
+ const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
231
+ const stops = [
232
+ { key: '50', lightness: 0.95 },
233
+ { key: '100', lightness: 0.90 },
234
+ { key: '200', lightness: 0.80 },
235
+ { key: '300', lightness: 0.68 },
236
+ { key: '400', lightness: 0.56 },
237
+ { key: '500', lightness: 0.48 },
238
+ { key: '600', lightness: 0.40 },
239
+ { key: '700', lightness: 0.32 },
240
+ { key: '800', lightness: 0.24 },
241
+ { key: '900', lightness: 0.15 },
242
+ ];
243
+
244
+ return stops
245
+ .map(stop => ` ${stop.key}: '${hslToHex(hsl.h, hsl.s, stop.lightness)}',`)
246
+ .join('\n');
247
+ }
248
+
249
+ function defaultColorScale(): string {
250
+ return ` 50: '#f0f9ff',
251
+ 100: '#e0f2fe',
252
+ 200: '#bae6fd',
253
+ 300: '#7dd3fc',
254
+ 400: '#38bdf8',
255
+ 500: '#0ea5e9',
256
+ 600: '#0284c7',
257
+ 700: '#0369a1',
258
+ 800: '#075985',
259
+ 900: '#0c4a6e',`;
260
+ }
261
+
262
+ // --- Color conversion helpers (inline to avoid circular deps) ---
263
+
264
+ function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
265
+ const cleaned = hex.replace(/^#/, '');
266
+ if (cleaned.length !== 6) return null;
267
+ const r = parseInt(cleaned.substring(0, 2), 16);
268
+ const g = parseInt(cleaned.substring(2, 4), 16);
269
+ const b = parseInt(cleaned.substring(4, 6), 16);
270
+ if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
271
+ return { r, g, b };
272
+ }
273
+
274
+ function rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {
275
+ const rn = r / 255;
276
+ const gn = g / 255;
277
+ const bn = b / 255;
278
+ const max = Math.max(rn, gn, bn);
279
+ const min = Math.min(rn, gn, bn);
280
+ const l = (max + min) / 2;
281
+ let h = 0;
282
+ let s = 0;
283
+ if (max !== min) {
284
+ const d = max - min;
285
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
286
+ if (max === rn) h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6;
287
+ else if (max === gn) h = ((bn - rn) / d + 2) / 6;
288
+ else h = ((rn - gn) / d + 4) / 6;
289
+ }
290
+ return { h, s, l };
291
+ }
292
+
293
+ function hslToHex(h: number, s: number, l: number): string {
294
+ const hue2rgb = (p: number, q: number, t: number): number => {
295
+ let tn = t;
296
+ if (tn < 0) tn += 1;
297
+ if (tn > 1) tn -= 1;
298
+ if (tn < 1 / 6) return p + (q - p) * 6 * tn;
299
+ if (tn < 1 / 2) return q;
300
+ if (tn < 2 / 3) return p + (q - p) * (2 / 3 - tn) * 6;
301
+ return p;
302
+ };
303
+ let r: number, g: number, b: number;
304
+ if (s === 0) {
305
+ r = g = b = l;
306
+ } else {
307
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
308
+ const p = 2 * l - q;
309
+ r = hue2rgb(p, q, h + 1 / 3);
310
+ g = hue2rgb(p, q, h);
311
+ b = hue2rgb(p, q, h - 1 / 3);
312
+ }
313
+ const toHex = (n: number): string => {
314
+ const val = Math.round(n * 255).toString(16);
315
+ return val.length === 1 ? '0' + val : val;
316
+ };
317
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
318
+ }
319
+
167
320
  /**
168
321
  * Generate PostCSS config for website
169
322
  */
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Landing page generator with 10 data-driven sections
3
+ * Each section: strategy data -> fallback -> graceful skip
4
+ */
5
+
6
+ import type { WebsiteContentContext } from '../website-context.js';
7
+ import {
8
+ mapFeatureIcon,
9
+ generatePainPointsSection,
10
+ generateDifferentiatorsSection,
11
+ generateHowItWorksSection,
12
+ generateStatsSection,
13
+ generateSocialProofSection,
14
+ generatePricingTeaserSection,
15
+ generateFaqSection,
16
+ buildFaqItemsDeclaration,
17
+ generateFaqItemComponent,
18
+ buildFaqSchema,
19
+ type SectionRenderInfo,
20
+ } from './website-sections.js';
21
+
22
+ /**
23
+ * Escape a string for safe use inside JSX template literals
24
+ */
25
+ function escapeJsx(str: string): string {
26
+ return str
27
+ .replace(/\\/g, '\\\\')
28
+ .replace(/'/g, "\\'")
29
+ .replace(/`/g, '\\`')
30
+ .replace(/\$/g, '\\$');
31
+ }
32
+
33
+ /**
34
+ * Result of landing page generation with section metadata for tracing
35
+ */
36
+ export interface LandingPageResult {
37
+ code: string;
38
+ sections: SectionRenderInfo[];
39
+ }
40
+
41
+ /**
42
+ * Generate landing page.tsx with 10 data-driven sections
43
+ * Sections: Hero, PainPoints, Differentiators, Features, HowItWorks,
44
+ * Stats, SocialProof, PricingTeaser, FAQ, FinalCTA
45
+ */
46
+ export function generateWebsiteLandingPage(
47
+ projectName: string,
48
+ context?: WebsiteContentContext
49
+ ): string {
50
+ const result = generateWebsiteLandingPageWithInfo(projectName, context);
51
+ return result.code;
52
+ }
53
+
54
+ /**
55
+ * Generate landing page with section render info for debug tracing
56
+ */
57
+ export function generateWebsiteLandingPageWithInfo(
58
+ projectName: string,
59
+ context?: WebsiteContentContext
60
+ ): LandingPageResult {
61
+ const title = projectName
62
+ .split('-')
63
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
64
+ .join(' ');
65
+
66
+ const strategy = context?.strategy;
67
+ const displayName = context?.productName || title;
68
+ const sections: SectionRenderInfo[] = [];
69
+
70
+ // --- Hero data ---
71
+ const headline = strategy?.messaging.headline || displayName;
72
+ const subheadline = strategy?.messaging.subheadline || '';
73
+ const eyebrow = strategy?.positioning.category || '';
74
+ const heroText = strategy?.messaging.longDescription
75
+ ? escapeJsx(strategy.messaging.longDescription)
76
+ : context?.description
77
+ ? escapeJsx(context.description)
78
+ : null;
79
+
80
+ const primaryCtaText = strategy?.conversionStrategy.primaryCta.text || 'Get started';
81
+ const primaryCtaHref = strategy?.conversionStrategy.primaryCta.href || '/pricing';
82
+ const secondaryCtaText = strategy?.conversionStrategy.secondaryCta.text || 'Learn more';
83
+ const secondaryCtaHref = strategy?.conversionStrategy.secondaryCta.href || '/docs';
84
+
85
+ sections.push({
86
+ name: 'Hero',
87
+ dataSource: strategy?.messaging ? 'strategy' : heroText ? 'docs' : 'defaults',
88
+ itemCount: 1,
89
+ });
90
+
91
+ // --- Features data ---
92
+ const features = context?.features && context.features.length > 0
93
+ ? context.features.slice(0, 6)
94
+ : null;
95
+
96
+ const featuresHeading = strategy?.positioning.valueProposition || 'Everything you need';
97
+ const featuresSubtitle = context?.description
98
+ ? escapeJsx(context.description).slice(0, 120)
99
+ : '';
100
+
101
+ sections.push({
102
+ name: 'Features',
103
+ dataSource: features ? 'docs' : 'defaults',
104
+ itemCount: features?.length || 0,
105
+ });
106
+
107
+ // --- Build features block ---
108
+ const featuresBlock = features
109
+ ? features.map((f) => {
110
+ const icon = mapFeatureIcon(f.title);
111
+ return ` {
112
+ title: '${escapeJsx(f.title)}',
113
+ description: '${escapeJsx(f.description)}',
114
+ icon: '${icon}',
115
+ }`;
116
+ }).join(',\n')
117
+ : '';
118
+
119
+ // --- Trust signals ---
120
+ const trustSignals = strategy?.conversionStrategy.trustSignals || [];
121
+
122
+ // --- Generate conditional sections ---
123
+ const painPoints = generatePainPointsSection(strategy);
124
+ sections.push(painPoints.info);
125
+
126
+ const differentiators = generateDifferentiatorsSection(strategy);
127
+ sections.push(differentiators.info);
128
+
129
+ const howItWorks = generateHowItWorksSection(strategy);
130
+ sections.push(howItWorks.info);
131
+
132
+ const stats = generateStatsSection(strategy);
133
+ sections.push(stats.info);
134
+
135
+ const socialProof = generateSocialProofSection(strategy);
136
+ sections.push(socialProof.info);
137
+
138
+ const pricingTeaser = generatePricingTeaserSection(context);
139
+ sections.push(pricingTeaser.info);
140
+
141
+ const faq = generateFaqSection(strategy);
142
+ sections.push(faq.info);
143
+
144
+ // Final CTA section
145
+ sections.push({
146
+ name: 'FinalCTA',
147
+ dataSource: strategy?.messaging ? 'strategy' : 'defaults',
148
+ itemCount: 1,
149
+ });
150
+
151
+ // Determine which lucide icons are needed
152
+ const iconSet = new Set<string>();
153
+ if (features) {
154
+ features.forEach((f) => iconSet.add(mapFeatureIcon(f.title)));
155
+ }
156
+ // Pain points icons
157
+ if (painPoints.jsx) {
158
+ iconSet.add('AlertTriangle');
159
+ iconSet.add('XCircle');
160
+ iconSet.add('AlertOctagon');
161
+ }
162
+ // Differentiators
163
+ if (differentiators.jsx) {
164
+ iconSet.add('CheckCircle');
165
+ }
166
+ // Stats
167
+ if (stats.jsx) {
168
+ iconSet.add('CheckCircle');
169
+ }
170
+ // FAQ
171
+ if (faq.jsx) {
172
+ iconSet.add('ChevronDown');
173
+ }
174
+ // Always useful
175
+ iconSet.add('ArrowRight');
176
+
177
+ const iconImports = Array.from(iconSet).sort().join(', ');
178
+
179
+ // SEO metadata
180
+ const metaTitle = strategy?.seoStrategy.titleTemplates?.home || 'Welcome';
181
+ const metaDesc = strategy?.seoStrategy.metaDescriptions?.home || `Welcome to ${displayName}`;
182
+
183
+ // FAQ data declarations
184
+ const faqItemsDecl = buildFaqItemsDeclaration(strategy);
185
+ const faqItemComponent = faq.needsClientDirective ? generateFaqItemComponent() : '';
186
+ const faqSchemaDecl = buildFaqSchema(strategy);
187
+
188
+ // Build icon mapping for features
189
+ const iconComponentMap = features
190
+ ? `const ICON_MAP: Record<string, React.ElementType> = {\n${Array.from(new Set(features.map(f => mapFeatureIcon(f.title)))).map(icon => ` ${icon},`).join('\n')}\n};\n`
191
+ : '';
192
+
193
+ const code = `${faq.needsClientDirective ? "'use client';\n\nimport { useState } from 'react';\n" : ''}import type { Metadata } from 'next';
194
+ import Link from 'next/link';
195
+ import { ${iconImports} } from 'lucide-react';
196
+ import Header from '@/components/Header';
197
+ import Footer from '@/components/Footer';
198
+ import JsonLd from '@/components/JsonLd';
199
+
200
+ export const metadata: Metadata = {
201
+ title: '${escapeJsx(metaTitle)}',
202
+ description: '${escapeJsx(metaDesc)}',
203
+ };
204
+
205
+ const ORG_SCHEMA = {
206
+ '@context': 'https://schema.org',
207
+ '@type': 'Organization',
208
+ name: '${escapeJsx(displayName)}',
209
+ url: process.env.NEXT_PUBLIC_SITE_URL || 'https://${projectName}.com',
210
+ };
211
+
212
+ const PRODUCT_SCHEMA = {
213
+ '@context': 'https://schema.org',
214
+ '@type': 'SoftwareApplication',
215
+ name: '${escapeJsx(displayName)}',
216
+ applicationCategory: 'BusinessApplication',
217
+ operatingSystem: 'Web',
218
+ };
219
+
220
+ ${faqSchemaDecl ? faqSchemaDecl + '\n' : ''}${faqItemsDecl ? '\n' + faqItemsDecl : ''}${iconComponentMap ? '\n' + iconComponentMap : ''}${features ? `\nconst features = [\n${featuresBlock}\n];\n` : ''}
221
+ ${faqItemComponent ? '\n' + faqItemComponent + '\n' : ''}
222
+ export default function HomePage() {
223
+ return (
224
+ <>
225
+ <Header />
226
+ <JsonLd schema={ORG_SCHEMA} />
227
+ <JsonLd schema={PRODUCT_SCHEMA} />
228
+ ${faqSchemaDecl ? ' <JsonLd schema={FAQ_SCHEMA} />\n' : ''} <main className="flex min-h-screen flex-col">
229
+ {/* Hero Section */}
230
+ <section className="relative overflow-hidden bg-gradient-to-br from-primary-50 via-white to-primary-50/30 py-24 sm:py-36">
231
+ <div className="container">
232
+ <div className="mx-auto max-w-3xl text-center">
233
+ ${eyebrow ? ` <p className="mb-4 inline-block rounded-full bg-primary-100 px-4 py-1.5 text-sm font-medium text-primary-700">\n ${escapeJsx(eyebrow)}\n </p>\n` : ''} <h1 className="text-4xl font-bold tracking-tight text-foreground sm:text-6xl lg:text-7xl">
234
+ ${escapeJsx(headline)}
235
+ </h1>
236
+ ${subheadline ? ` <p className="mt-4 text-xl font-medium text-primary-600">\n ${escapeJsx(subheadline)}\n </p>` : ''}
237
+ <p className="mt-6 text-lg leading-8 text-muted-foreground">
238
+ ${heroText || ''}
239
+ </p>
240
+ <div className="mt-10 flex items-center justify-center gap-x-4">
241
+ <Link
242
+ href="${escapeJsx(primaryCtaHref)}"
243
+ className="rounded-lg bg-primary-600 px-6 py-3 text-sm font-semibold text-white shadow-lg shadow-primary-600/25 hover:bg-primary-500 transition-all hover:shadow-primary-600/40 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600"
244
+ >
245
+ ${escapeJsx(primaryCtaText)}
246
+ </Link>
247
+ <Link
248
+ href="${escapeJsx(secondaryCtaHref)}"
249
+ className="group flex items-center gap-1 text-sm font-semibold text-foreground hover:text-primary-600 transition-colors"
250
+ >
251
+ ${escapeJsx(secondaryCtaText)}
252
+ <ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-0.5" />
253
+ </Link>
254
+ </div>
255
+ ${trustSignals.length > 0 ? ` <div className="mt-10 flex flex-wrap items-center justify-center gap-x-8 gap-y-2">
256
+ ${trustSignals.map(s => ` <p className="text-sm font-medium text-muted-foreground">${escapeJsx(s)}</p>`).join('\n')}
257
+ </div>` : ''}
258
+ </div>
259
+ </div>
260
+ </section>
261
+ ${painPoints.jsx}${differentiators.jsx}
262
+ {/* Features Section */}
263
+ ${features ? ` <section id="features" className="py-20 sm:py-28">
264
+ <div className="container">
265
+ <div className="mx-auto max-w-2xl text-center">
266
+ <h2 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
267
+ ${escapeJsx(featuresHeading)}
268
+ </h2>
269
+ ${featuresSubtitle ? ` <p className="mt-4 text-lg text-muted-foreground">\n ${featuresSubtitle}\n </p>` : ''}
270
+ </div>
271
+ <div className="mx-auto mt-16 max-w-5xl">
272
+ <div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
273
+ {features.map((feature) => {
274
+ const Icon = ICON_MAP[feature.icon] || Star;
275
+ return (
276
+ <div
277
+ key={feature.title}
278
+ className="group rounded-2xl border border-border bg-card p-8 transition-all hover:shadow-lg hover:-translate-y-1"
279
+ >
280
+ <div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-primary-100">
281
+ <Icon className="h-5 w-5 text-primary-600" />
282
+ </div>
283
+ <h3 className="text-lg font-semibold text-foreground">
284
+ {feature.title}
285
+ </h3>
286
+ <p className="mt-2 text-muted-foreground">{feature.description}</p>
287
+ </div>
288
+ );
289
+ })}
290
+ </div>
291
+ </div>
292
+ </div>
293
+ </section>` : ''}
294
+ ${howItWorks.jsx}${stats.jsx}${socialProof.jsx}${pricingTeaser.jsx}${faq.jsx}
295
+ {/* Final CTA Section */}
296
+ <section className="bg-gradient-to-br from-primary-600 to-primary-700 py-20 sm:py-28">
297
+ <div className="container text-center">
298
+ <h2 className="text-3xl font-bold tracking-tight text-white sm:text-4xl">
299
+ ${strategy?.messaging.elevatorPitch ? escapeJsx(strategy.messaging.elevatorPitch) : 'Ready to get started?'}
300
+ </h2>
301
+ <p className="mt-4 text-lg text-primary-100">
302
+ ${strategy?.messaging.subheadline ? escapeJsx(strategy.messaging.subheadline) : 'Start building today.'}
303
+ </p>
304
+ <div className="mt-8 flex items-center justify-center gap-x-4">
305
+ <Link
306
+ href="${escapeJsx(primaryCtaHref)}"
307
+ className="rounded-lg bg-white px-6 py-3 text-sm font-semibold text-primary-600 shadow-lg hover:bg-primary-50 transition-colors"
308
+ >
309
+ ${escapeJsx(primaryCtaText)}
310
+ </Link>
311
+ <Link
312
+ href="${escapeJsx(secondaryCtaHref)}"
313
+ className="rounded-lg border border-primary-300 px-6 py-3 text-sm font-semibold text-white hover:bg-primary-500 transition-colors"
314
+ >
315
+ ${escapeJsx(secondaryCtaText)}
316
+ </Link>
317
+ </div>
318
+ ${trustSignals.length > 0 ? ` <div className="mt-8 flex flex-wrap items-center justify-center gap-x-6 gap-y-2">
319
+ ${trustSignals.map(s => ` <p className="text-sm text-primary-200">${escapeJsx(s)}</p>`).join('\n')}
320
+ </div>` : ''}
321
+ </div>
322
+ </section>
323
+ </main>
324
+ <Footer />
325
+ </>
326
+ );
327
+ }
328
+ `;
329
+
330
+ return { code, sections };
331
+ }