popeye-cli 1.5.0 → 1.6.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 (161) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +50 -8
  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 +10 -0
  80. package/dist/state/index.js.map +1 -1
  81. package/dist/types/workflow.d.ts +6 -0
  82. package/dist/types/workflow.d.ts.map +1 -1
  83. package/dist/types/workflow.js +2 -0
  84. package/dist/types/workflow.js.map +1 -1
  85. package/dist/upgrade/handlers.d.ts +15 -0
  86. package/dist/upgrade/handlers.d.ts.map +1 -1
  87. package/dist/upgrade/handlers.js +52 -0
  88. package/dist/upgrade/handlers.js.map +1 -1
  89. package/dist/workflow/auto-fix-bundler.d.ts +37 -0
  90. package/dist/workflow/auto-fix-bundler.d.ts.map +1 -0
  91. package/dist/workflow/auto-fix-bundler.js +320 -0
  92. package/dist/workflow/auto-fix-bundler.js.map +1 -0
  93. package/dist/workflow/auto-fix.d.ts.map +1 -1
  94. package/dist/workflow/auto-fix.js +10 -3
  95. package/dist/workflow/auto-fix.js.map +1 -1
  96. package/dist/workflow/index.d.ts +1 -0
  97. package/dist/workflow/index.d.ts.map +1 -1
  98. package/dist/workflow/index.js +12 -0
  99. package/dist/workflow/index.js.map +1 -1
  100. package/dist/workflow/overview.d.ts.map +1 -1
  101. package/dist/workflow/overview.js +4 -0
  102. package/dist/workflow/overview.js.map +1 -1
  103. package/dist/workflow/plan-mode.d.ts +4 -3
  104. package/dist/workflow/plan-mode.d.ts.map +1 -1
  105. package/dist/workflow/plan-mode.js +69 -5
  106. package/dist/workflow/plan-mode.js.map +1 -1
  107. package/dist/workflow/website-strategy.d.ts +9 -0
  108. package/dist/workflow/website-strategy.d.ts.map +1 -1
  109. package/dist/workflow/website-strategy.js +73 -1
  110. package/dist/workflow/website-strategy.js.map +1 -1
  111. package/dist/workflow/website-updater.d.ts.map +1 -1
  112. package/dist/workflow/website-updater.js +15 -4
  113. package/dist/workflow/website-updater.js.map +1 -1
  114. package/package.json +1 -1
  115. package/src/cli/commands/create.ts +58 -4
  116. package/src/cli/interactive.ts +96 -7
  117. package/src/generators/all.ts +44 -332
  118. package/src/generators/doc-parser.ts +87 -10
  119. package/src/generators/frontend-design-analyzer.ts +261 -0
  120. package/src/generators/shared-packages.ts +500 -0
  121. package/src/generators/templates/index.ts +4 -0
  122. package/src/generators/templates/website-components.ts +36 -11
  123. package/src/generators/templates/website-config.ts +166 -13
  124. package/src/generators/templates/website-landing.ts +331 -0
  125. package/src/generators/templates/website-layout.ts +443 -0
  126. package/src/generators/templates/website-pricing.ts +330 -0
  127. package/src/generators/templates/website-sections.ts +541 -0
  128. package/src/generators/templates/website.ts +38 -851
  129. package/src/generators/website-content-scanner.ts +208 -0
  130. package/src/generators/website-context.ts +248 -20
  131. package/src/generators/website-debug.ts +130 -0
  132. package/src/generators/website.ts +71 -3
  133. package/src/generators/workspace-root.ts +113 -0
  134. package/src/state/index.ts +14 -0
  135. package/src/types/workflow.ts +6 -0
  136. package/src/upgrade/handlers.ts +65 -0
  137. package/src/workflow/auto-fix-bundler.ts +392 -0
  138. package/src/workflow/auto-fix.ts +11 -3
  139. package/src/workflow/index.ts +12 -0
  140. package/src/workflow/overview.ts +6 -0
  141. package/src/workflow/plan-mode.ts +81 -7
  142. package/src/workflow/website-strategy.ts +75 -1
  143. package/src/workflow/website-updater.ts +17 -6
  144. package/tests/cli/project-naming.test.ts +136 -0
  145. package/tests/generators/doc-parser.test.ts +121 -0
  146. package/tests/generators/frontend-design-analyzer.test.ts +90 -0
  147. package/tests/generators/quality-gate.test.ts +183 -0
  148. package/tests/generators/shared-packages.test.ts +83 -0
  149. package/tests/generators/website-components.test.ts +1 -1
  150. package/tests/generators/website-config.test.ts +84 -0
  151. package/tests/generators/website-content-scanner.test.ts +181 -0
  152. package/tests/generators/website-context.test.ts +109 -0
  153. package/tests/generators/website-debug.test.ts +77 -0
  154. package/tests/generators/website-landing.test.ts +188 -0
  155. package/tests/generators/website-pricing.test.ts +98 -0
  156. package/tests/generators/website-sections.test.ts +245 -0
  157. package/tests/generators/workspace-root.test.ts +105 -0
  158. package/tests/upgrade/handlers.test.ts +162 -0
  159. package/tests/workflow/auto-fix-bundler.test.ts +242 -0
  160. package/tests/workflow/plan-mode.test.ts +111 -1
  161. package/tests/workflow/website-strategy.test.ts +55 -0
@@ -1,852 +1,39 @@
1
1
  /**
2
- * Website content templates for Next.js marketing sites
3
- * Generates SEO-ready content pages with optional project context
4
- * and strategy-driven marketing content
5
- */
6
-
7
- import type { WebsiteContentContext } from '../website-context.js';
8
- // Strategy data is accessed via context.strategy (WebsiteContentContext includes it)
9
-
10
- /**
11
- * Generate root layout.tsx with metadata
12
- */
13
- export function generateWebsiteLayout(
14
- projectName: string,
15
- context?: WebsiteContentContext
16
- ): string {
17
- const title = projectName
18
- .split('-')
19
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
20
- .join(' ');
21
-
22
- const strategy = context?.strategy;
23
- const displayName = context?.productName || title;
24
- const desc = strategy?.messaging.longDescription
25
- || context?.description
26
- || `${displayName} - Your modern web application`;
27
-
28
- // SEO keywords from strategy or defaults
29
- const keywords = strategy?.seoStrategy.primaryKeywords
30
- ? strategy.seoStrategy.primaryKeywords.map(k => `'${escapeJsx(k)}'`).join(', ')
31
- : `'${projectName}', 'web app', 'nextjs'`;
32
-
33
- return `import type { Metadata } from 'next';
34
- import { Inter } from 'next/font/google';
35
- import './globals.css';
36
-
37
- const inter = Inter({
38
- subsets: ['latin'],
39
- variable: '--font-inter',
40
- });
41
-
42
- const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://${projectName}.com';
43
-
44
- export const metadata: Metadata = {
45
- metadataBase: new URL(BASE_URL),
46
- title: {
47
- default: '${escapeJsx(displayName)}',
48
- template: '%s | ${escapeJsx(displayName)}',
49
- },
50
- description: '${escapeJsx(desc)}',
51
- keywords: [${keywords}],
52
- authors: [{ name: '${escapeJsx(displayName)} Team' }],
53
- creator: '${escapeJsx(displayName)}',
54
- openGraph: {
55
- type: 'website',
56
- locale: 'en_US',
57
- url: BASE_URL,
58
- siteName: '${escapeJsx(displayName)}',
59
- title: '${escapeJsx(displayName)}',
60
- description: '${escapeJsx(desc)}',
61
- },
62
- twitter: {
63
- card: 'summary_large_image',
64
- title: '${escapeJsx(displayName)}',
65
- description: '${escapeJsx(desc)}',
66
- },
67
- robots: {
68
- index: true,
69
- follow: true,
70
- },
71
- };
72
-
73
- export default function RootLayout({
74
- children,
75
- }: {
76
- children: React.ReactNode;
77
- }) {
78
- return (
79
- <html lang="en" className={inter.variable}>
80
- <body className="min-h-screen bg-white antialiased">
81
- {children}
82
- </body>
83
- </html>
84
- );
85
- }
86
- `;
87
- }
88
-
89
- /**
90
- * Generate globals.css with optional brand colors
91
- */
92
- export function generateWebsiteGlobalsCss(
93
- context?: WebsiteContentContext
94
- ): string {
95
- // Convert hex to HSL for CSS custom properties if brand color provided
96
- const primaryHsl = context?.brand?.primaryColor
97
- ? hexToHslString(context.brand.primaryColor)
98
- : '199 89% 48%';
99
-
100
- return `@tailwind base;
101
- @tailwind components;
102
- @tailwind utilities;
103
-
104
- @layer base {
105
- :root {
106
- --background: 0 0% 100%;
107
- --foreground: 222.2 84% 4.9%;
108
- --primary: ${primaryHsl};
109
- --primary-foreground: 210 40% 98%;
110
- }
111
-
112
- body {
113
- @apply bg-background text-foreground;
114
- }
115
- }
116
-
117
- @layer components {
118
- .container {
119
- @apply mx-auto max-w-7xl px-4 sm:px-6 lg:px-8;
120
- }
121
- }
122
- `;
123
- }
124
-
125
- /**
126
- * Generate landing page.tsx with optional context-driven content
127
- * When strategy is available, uses strategy messaging, trust signals, and CTAs
128
- */
129
- export function generateWebsiteLandingPage(
130
- projectName: string,
131
- context?: WebsiteContentContext
132
- ): string {
133
- const title = projectName
134
- .split('-')
135
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
136
- .join(' ');
137
-
138
- const strategy = context?.strategy;
139
- const displayName = context?.productName || title;
140
-
141
- // Strategy-driven or context-driven hero
142
- const headline = strategy?.messaging.headline || displayName;
143
- const subheadline = strategy?.messaging.subheadline || '';
144
- const heroText = strategy?.messaging.longDescription
145
- ? escapeJsx(strategy.messaging.longDescription)
146
- : context?.description
147
- ? escapeJsx(context.description)
148
- : null;
149
-
150
- const features = context?.features && context.features.length > 0
151
- ? context.features.slice(0, 6)
152
- : null;
153
-
154
- // CTAs from strategy or defaults
155
- const primaryCtaText = strategy?.conversionStrategy.primaryCta.text || 'Get started';
156
- const primaryCtaHref = strategy?.conversionStrategy.primaryCta.href || '/pricing';
157
- const secondaryCtaText = strategy?.conversionStrategy.secondaryCta.text || 'Learn more';
158
- const secondaryCtaHref = strategy?.conversionStrategy.secondaryCta.href || '/docs';
159
-
160
- // Build hero paragraph
161
- const heroParagraph = heroText
162
- ? ` ${heroText}`
163
- : ` {/* TODO: populate from project specification */}`;
164
-
165
- // Build features array
166
- const featuresBlock = features
167
- ? features.map((f) =>
168
- ` {\n title: '${escapeJsx(f.title)}',\n description: '${escapeJsx(f.description)}',\n }`
169
- ).join(',\n')
170
- : ` {\n title: 'Feature 1',\n description: '/* TODO: populate from project specification */',\n },\n {\n title: 'Feature 2',\n description: '/* TODO: populate from project specification */',\n },\n {\n title: 'Feature 3',\n description: '/* TODO: populate from project specification */',\n }`;
171
-
172
- // Trust signals from strategy
173
- const trustSignals = strategy?.conversionStrategy.trustSignals || [];
174
- const trustSignalsBlock = trustSignals.length > 0
175
- ? trustSignals.map(s => ` '${escapeJsx(s)}'`).join(',\n')
176
- : '';
177
-
178
- // Social proof from strategy
179
- const socialProof = strategy?.conversionStrategy.socialProof || [];
180
- const socialProofBlock = socialProof.length > 0
181
- ? socialProof.map(s => ` '${escapeJsx(s)}'`).join(',\n')
182
- : '';
183
-
184
- // Build optional sections
185
- const trustSection = trustSignals.length > 0 ? `
186
- {/* Trust Signals */}
187
- <section className="border-y border-gray-100 bg-gray-50 py-12">
188
- <div className="container">
189
- <div className="flex flex-wrap items-center justify-center gap-x-8 gap-y-4">
190
- {[
191
- ${trustSignalsBlock}
192
- ].map((signal) => (
193
- <p key={signal} className="text-sm font-medium text-gray-600">{signal}</p>
194
- ))}
195
- </div>
196
- </div>
197
- </section>
198
- ` : '';
199
-
200
- const socialProofSection = socialProof.length > 0 ? `
201
- {/* Social Proof */}
202
- <section className="py-16 sm:py-24">
203
- <div className="container">
204
- <h2 className="text-center text-3xl font-bold tracking-tight text-gray-900">
205
- Trusted by teams everywhere
206
- </h2>
207
- <div className="mx-auto mt-12 grid max-w-4xl grid-cols-1 gap-8 md:grid-cols-2">
208
- {[
209
- ${socialProofBlock}
210
- ].map((quote, i) => (
211
- <blockquote key={i} className="rounded-2xl border border-gray-200 p-6">
212
- <p className="text-gray-700">&ldquo;{quote}&rdquo;</p>
213
- </blockquote>
214
- ))}
215
- </div>
216
- </div>
217
- </section>
218
- ` : '';
219
-
220
- // Metadata: strategy-driven or default
221
- const metaTitle = strategy?.seoStrategy.titleTemplates?.home || 'Welcome';
222
- const metaDesc = strategy?.seoStrategy.metaDescriptions?.home || `Welcome to ${displayName}`;
223
-
224
- return `import type { Metadata } from 'next';
225
- import Link from 'next/link';
226
- import Header from '@/components/Header';
227
- import Footer from '@/components/Footer';
228
- import JsonLd from '@/components/JsonLd';
229
-
230
- export const metadata: Metadata = {
231
- title: '${escapeJsx(metaTitle)}',
232
- description: '${escapeJsx(metaDesc)}',
233
- };
234
-
235
- const ORG_SCHEMA = {
236
- '@context': 'https://schema.org',
237
- '@type': 'Organization',
238
- name: '${escapeJsx(displayName)}',
239
- url: process.env.NEXT_PUBLIC_SITE_URL || 'https://${projectName}.com',
240
- };
241
-
242
- const PRODUCT_SCHEMA = {
243
- '@context': 'https://schema.org',
244
- '@type': 'SoftwareApplication',
245
- name: '${escapeJsx(displayName)}',
246
- applicationCategory: 'BusinessApplication',
247
- operatingSystem: 'Web',
248
- };
249
-
250
- export default function HomePage() {
251
- return (
252
- <>
253
- <Header />
254
- <JsonLd schema={ORG_SCHEMA} />
255
- <JsonLd schema={PRODUCT_SCHEMA} />
256
- <main className="flex min-h-screen flex-col">
257
- {/* Hero Section */}
258
- <section className="relative overflow-hidden bg-gradient-to-b from-primary-50 to-white py-20 sm:py-32">
259
- <div className="container">
260
- <div className="mx-auto max-w-2xl text-center">
261
- <h1 className="text-4xl font-bold tracking-tight text-gray-900 sm:text-6xl">
262
- ${escapeJsx(headline)}
263
- </h1>
264
- ${subheadline ? ` <p className="mt-4 text-xl font-medium text-primary-600">\n ${escapeJsx(subheadline)}\n </p>` : ''}
265
- <p className="mt-6 text-lg leading-8 text-gray-600">
266
- ${heroParagraph}
267
- </p>
268
- <div className="mt-10 flex items-center justify-center gap-x-6">
269
- <Link
270
- href="${escapeJsx(primaryCtaHref)}"
271
- className="rounded-md bg-primary-600 px-6 py-3 text-sm font-semibold text-white shadow-sm hover:bg-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600"
272
- >
273
- ${escapeJsx(primaryCtaText)}
274
- </Link>
275
- <Link
276
- href="${escapeJsx(secondaryCtaHref)}"
277
- className="text-sm font-semibold leading-6 text-gray-900 hover:text-primary-600"
278
- >
279
- ${escapeJsx(secondaryCtaText)} <span aria-hidden="true">-&gt;</span>
280
- </Link>
281
- </div>
282
- </div>
283
- </div>
284
- </section>
285
- ${trustSection}
286
- {/* Features Section */}
287
- <section id="features" className="py-20 sm:py-32">
288
- <div className="container">
289
- <div className="mx-auto max-w-2xl text-center">
290
- <h2 className="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">
291
- Everything you need
292
- </h2>
293
- <p className="mt-4 text-lg text-gray-600">
294
- {/* TODO: populate section subtitle from project specification */}
295
- </p>
296
- </div>
297
- <div className="mx-auto mt-16 max-w-5xl">
298
- <div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
299
- {[
300
- ${featuresBlock}
301
- ].map((feature) => (
302
- <div
303
- key={feature.title}
304
- className="rounded-2xl border border-gray-200 p-8"
305
- >
306
- <h3 className="text-lg font-semibold text-gray-900">
307
- {feature.title}
308
- </h3>
309
- <p className="mt-2 text-gray-600">{feature.description}</p>
310
- </div>
311
- ))}
312
- </div>
313
- </div>
314
- </div>
315
- </section>
316
- ${socialProofSection}
317
- {/* CTA Section */}
318
- <section className="bg-primary-600 py-16 sm:py-24">
319
- <div className="container text-center">
320
- <h2 className="text-3xl font-bold tracking-tight text-white sm:text-4xl">
321
- Ready to get started?
322
- </h2>
323
- <p className="mt-4 text-lg text-primary-100">
324
- ${strategy?.messaging.elevatorPitch ? escapeJsx(strategy.messaging.elevatorPitch) : 'Start building today.'}
325
- </p>
326
- <div className="mt-8">
327
- <Link
328
- href="${escapeJsx(primaryCtaHref)}"
329
- className="rounded-md bg-white px-6 py-3 text-sm font-semibold text-primary-600 shadow-sm hover:bg-primary-50"
330
- >
331
- ${escapeJsx(primaryCtaText)}
332
- </Link>
333
- </div>
334
- </div>
335
- </section>
336
- </main>
337
- <Footer />
338
- </>
339
- );
340
- }
341
- `;
342
- }
343
-
344
- /**
345
- * Generate pricing page with optional context-driven tiers and FAQ
346
- */
347
- export function generateWebsitePricingPage(
348
- projectName: string,
349
- context?: WebsiteContentContext
350
- ): string {
351
- const title = projectName
352
- .split('-')
353
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
354
- .join(' ');
355
-
356
- const strategy = context?.strategy;
357
- const displayName = context?.productName || title;
358
- const tiers = context?.pricing && context.pricing.length > 0
359
- ? context.pricing
360
- : null;
361
-
362
- // Build tiers array
363
- const tiersBlock = tiers
364
- ? tiers.map((t) => {
365
- const featuresStr = t.features.map((f) => ` '${escapeJsx(f)}'`).join(',\n');
366
- return ` {
367
- name: '${escapeJsx(t.name)}',
368
- price: '${escapeJsx(t.price)}',
369
- description: '${escapeJsx(t.description)}',
370
- features: [
371
- ${featuresStr}
372
- ],
373
- cta: '${escapeJsx(t.cta)}',
374
- featured: ${t.featured ? 'true' : 'false'},
375
- }`;
376
- }).join(',\n')
377
- : ` {
378
- name: '/* TODO: tier name */',
379
- price: '/* TODO */',
380
- description: '/* TODO: populate from project specification */',
381
- features: ['/* TODO: populate from project specification */'],
382
- cta: 'Get started',
383
- featured: false,
384
- },
385
- {
386
- name: '/* TODO: tier name */',
387
- price: '/* TODO */',
388
- description: '/* TODO: populate from project specification */',
389
- features: ['/* TODO: populate from project specification */'],
390
- cta: 'Start free trial',
391
- featured: true,
392
- },
393
- {
394
- name: '/* TODO: tier name */',
395
- price: '/* TODO */',
396
- description: '/* TODO: populate from project specification */',
397
- features: ['/* TODO: populate from project specification */'],
398
- cta: 'Contact sales',
399
- featured: false,
400
- }`;
401
-
402
- // Pricing metadata from strategy or defaults
403
- const metaTitle = strategy?.seoStrategy.titleTemplates?.pricing || 'Pricing';
404
- const metaDesc = strategy?.seoStrategy.metaDescriptions?.pricing || `Choose the perfect plan for your needs - ${displayName}`;
405
-
406
- // Enterprise CTA from strategy
407
- const enterpriseCtaText = strategy?.conversionStrategy.primaryCta.text || 'Contact Sales';
408
-
409
- return `import type { Metadata } from 'next';
410
- import Link from 'next/link';
411
- import Header from '@/components/Header';
412
- import Footer from '@/components/Footer';
413
-
414
- export const metadata: Metadata = {
415
- title: '${escapeJsx(metaTitle)}',
416
- description: '${escapeJsx(metaDesc)}',
417
- };
418
-
419
- const tiers = [
420
- ${tiersBlock}
421
- ];
422
-
423
- export default function PricingPage() {
424
- return (
425
- <>
426
- <Header />
427
- <main className="py-20 sm:py-32">
428
- <div className="container">
429
- <div className="mx-auto max-w-2xl text-center">
430
- <h1 className="text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl">
431
- Simple, transparent pricing
432
- </h1>
433
- <p className="mt-6 text-lg text-gray-600">
434
- Choose the plan that works best for you.
435
- </p>
436
- </div>
437
-
438
- <div className="mx-auto mt-16 grid max-w-lg grid-cols-1 gap-8 lg:max-w-5xl lg:grid-cols-3">
439
- {tiers.map((tier) => (
440
- <div
441
- key={tier.name}
442
- className={\`rounded-2xl p-8 \${
443
- tier.featured
444
- ? 'bg-primary-600 text-white ring-2 ring-primary-600'
445
- : 'border border-gray-200 bg-white'
446
- }\`}
447
- >
448
- <h2
449
- className={\`text-lg font-semibold \${
450
- tier.featured ? 'text-white' : 'text-gray-900'
451
- }\`}
452
- >
453
- {tier.name}
454
- </h2>
455
- <p
456
- className={\`mt-2 text-sm \${
457
- tier.featured ? 'text-primary-100' : 'text-gray-600'
458
- }\`}
459
- >
460
- {tier.description}
461
- </p>
462
- <p className="mt-6">
463
- <span
464
- className={\`text-4xl font-bold \${
465
- tier.featured ? 'text-white' : 'text-gray-900'
466
- }\`}
467
- >
468
- {tier.price}
469
- </span>
470
- {tier.price !== 'Custom' && (
471
- <span
472
- className={\`text-sm \${
473
- tier.featured ? 'text-primary-100' : 'text-gray-600'
474
- }\`}
475
- >
476
- /month
477
- </span>
478
- )}
479
- </p>
480
- <ul className="mt-8 space-y-4">
481
- {tier.features.map((feature) => (
482
- <li
483
- key={feature}
484
- className={\`flex text-sm \${
485
- tier.featured ? 'text-primary-100' : 'text-gray-600'
486
- }\`}
487
- >
488
- <svg
489
- className={\`h-5 w-5 flex-shrink-0 \${
490
- tier.featured ? 'text-white' : 'text-primary-600'
491
- }\`}
492
- viewBox="0 0 20 20"
493
- fill="currentColor"
494
- >
495
- <path
496
- fillRule="evenodd"
497
- d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
498
- clipRule="evenodd"
499
- />
500
- </svg>
501
- <span className="ml-3">{feature}</span>
502
- </li>
503
- ))}
504
- </ul>
505
- <button
506
- className={\`mt-8 w-full rounded-md px-4 py-2 text-sm font-semibold \${
507
- tier.featured
508
- ? 'bg-white text-primary-600 hover:bg-primary-50'
509
- : 'bg-primary-600 text-white hover:bg-primary-500'
510
- }\`}
511
- >
512
- {tier.cta}
513
- </button>
514
- </div>
515
- ))}
516
- </div>
517
-
518
- {/* Enterprise CTA */}
519
- <div className="mx-auto mt-16 max-w-2xl text-center">
520
- <h2 className="text-2xl font-bold text-gray-900">
521
- Need a custom plan?
522
- </h2>
523
- <p className="mt-4 text-gray-600">
524
- Contact our sales team for enterprise pricing and custom solutions.
525
- </p>
526
- <Link
527
- href="/contact"
528
- className="mt-6 inline-block rounded-md border border-primary-600 px-6 py-3 text-sm font-semibold text-primary-600 hover:bg-primary-50"
529
- >
530
- ${escapeJsx(enterpriseCtaText)}
531
- </Link>
532
- </div>
533
- </div>
534
- </main>
535
- <Footer />
536
- </>
537
- );
538
- }
539
- `;
540
- }
541
-
542
- /**
543
- * Generate sitemap.ts
544
- */
545
- export function generateWebsiteSitemap(projectName: string): string {
546
- return `import { MetadataRoute } from 'next';
547
-
548
- export default function sitemap(): MetadataRoute.Sitemap {
549
- const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://${projectName}.com';
550
-
551
- return [
552
- {
553
- url: baseUrl,
554
- lastModified: new Date(),
555
- changeFrequency: 'weekly',
556
- priority: 1,
557
- },
558
- {
559
- url: \`\${baseUrl}/pricing\`,
560
- lastModified: new Date(),
561
- changeFrequency: 'monthly',
562
- priority: 0.8,
563
- },
564
- {
565
- url: \`\${baseUrl}/docs\`,
566
- lastModified: new Date(),
567
- changeFrequency: 'weekly',
568
- priority: 0.8,
569
- },
570
- {
571
- url: \`\${baseUrl}/blog\`,
572
- lastModified: new Date(),
573
- changeFrequency: 'daily',
574
- priority: 0.7,
575
- },
576
- ];
577
- }
578
- `;
579
- }
580
-
581
- /**
582
- * Generate robots.ts
583
- */
584
- export function generateWebsiteRobots(projectName: string): string {
585
- return `import { MetadataRoute } from 'next';
586
-
587
- export default function robots(): MetadataRoute.Robots {
588
- const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://${projectName}.com';
589
-
590
- return {
591
- rules: [
592
- {
593
- userAgent: '*',
594
- allow: '/',
595
- disallow: ['/api/', '/admin/'],
596
- },
597
- ],
598
- sitemap: \`\${baseUrl}/sitemap.xml\`,
599
- };
600
- }
601
- `;
602
- }
603
-
604
- /**
605
- * Generate website README
606
- */
607
- export function generateWebsiteReadme(projectName: string): string {
608
- const title = projectName
609
- .split('-')
610
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
611
- .join(' ');
612
-
613
- return `# ${title} Website
614
-
615
- Next.js marketing website with SEO optimization.
616
-
617
- ## Getting Started
618
-
619
- \`\`\`bash
620
- # Install dependencies
621
- npm install
622
-
623
- # Run development server (port 3001)
624
- npm run dev
625
-
626
- # Build for production
627
- npm run build
628
-
629
- # Run production server
630
- npm start
631
- \`\`\`
632
-
633
- ## SEO Features
634
-
635
- - Server-side rendering (SSR)
636
- - Auto-generated sitemap
637
- - robots.txt configuration
638
- - OpenGraph and Twitter meta tags
639
- - Structured data support
640
-
641
- ## Project Structure
642
-
643
- \`\`\`
644
- src/
645
- app/
646
- layout.tsx # Root layout with metadata
647
- page.tsx # Landing page
648
- pricing/ # Pricing page
649
- docs/ # Documentation
650
- blog/ # Blog
651
- sitemap.ts # Auto-generated sitemap
652
- robots.ts # robots.txt config
653
- components/ # UI components
654
- lib/ # Utilities
655
- content/
656
- blog/ # MDX blog posts
657
- docs/ # MDX documentation
658
- \`\`\`
659
-
660
- ## Development
661
-
662
- - Port: 3001 (to avoid conflicts with frontend on 5173)
663
- - API URL: Configure via NEXT_PUBLIC_APP_URL
664
- `;
665
- }
666
-
667
- /**
668
- * Generate website spec JSON with optional context
669
- */
670
- export function generateWebsiteSpec(
671
- projectName: string,
672
- context?: WebsiteContentContext
673
- ): string {
674
- const title = projectName
675
- .split('-')
676
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
677
- .join(' ');
678
-
679
- const displayName = context?.productName || title;
680
- const tagline = context?.tagline || context?.description || 'Build something amazing';
681
- const primaryColor = context?.brand?.primaryColor || '#0ea5e9';
682
-
683
- return JSON.stringify(
684
- {
685
- version: '1.0',
686
- brand: {
687
- name: displayName,
688
- tagline,
689
- colors: {
690
- primary: primaryColor,
691
- secondary: '#64748b',
692
- accent: '#f59e0b',
693
- background: '#ffffff',
694
- foreground: '#0f172a',
695
- },
696
- typography: {
697
- headingFont: 'Inter',
698
- bodyFont: 'Inter',
699
- },
700
- },
701
- seo: {
702
- title: displayName,
703
- description: context?.description || `${displayName} - Your modern web application`,
704
- keywords: [projectName, 'web app', 'nextjs', 'saas'],
705
- locale: 'en_US',
706
- },
707
- pages: [
708
- { name: 'Home', path: '/', type: 'landing' },
709
- { name: 'Pricing', path: '/pricing', type: 'pricing' },
710
- { name: 'Documentation', path: '/docs', type: 'docs' },
711
- { name: 'Blog', path: '/blog', type: 'blog' },
712
- ],
713
- cta: {
714
- primary: { text: 'Get Started', href: '/pricing' },
715
- secondary: { text: 'Learn More', href: '/docs' },
716
- },
717
- features: {
718
- analytics: true,
719
- newsletter: false,
720
- mdxBlog: true,
721
- docsSearch: false,
722
- },
723
- },
724
- null,
725
- 2
726
- );
727
- }
728
-
729
- /**
730
- * Generate sample test for website
731
- */
732
- export function generateWebsiteTest(projectName: string): string {
733
- const title = projectName
734
- .split('-')
735
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
736
- .join(' ');
737
-
738
- return `import { describe, it, expect } from 'vitest';
739
- import { render, screen } from '@testing-library/react';
740
- import HomePage from '@/app/page';
741
-
742
- describe('HomePage', () => {
743
- it('renders the title', () => {
744
- render(<HomePage />);
745
- expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('${title}');
746
- });
747
-
748
- it('renders the call-to-action buttons', () => {
749
- render(<HomePage />);
750
- expect(screen.getByRole('link', { name: /get started/i })).toBeInTheDocument();
751
- expect(screen.getByRole('link', { name: /learn more/i })).toBeInTheDocument();
752
- });
753
- });
754
- `;
755
- }
756
-
757
- /**
758
- * Generate docs page placeholder
759
- */
760
- export function generateWebsiteDocsPage(): string {
761
- return `import type { Metadata } from 'next';
762
-
763
- export const metadata: Metadata = {
764
- title: 'Documentation',
765
- description: 'Learn how to use our platform with comprehensive documentation.',
766
- };
767
-
768
- export default function DocsPage() {
769
- return (
770
- <main className="py-20">
771
- <div className="container">
772
- <h1 className="text-4xl font-bold text-gray-900">Documentation</h1>
773
- <p className="mt-4 text-lg text-gray-600">
774
- Documentation coming soon...
775
- </p>
776
- </div>
777
- </main>
778
- );
779
- }
780
- `;
781
- }
782
-
783
- /**
784
- * Generate blog listing page placeholder
785
- */
786
- export function generateWebsiteBlogPage(): string {
787
- return `import type { Metadata } from 'next';
788
-
789
- export const metadata: Metadata = {
790
- title: 'Blog',
791
- description: 'Latest news, updates, and insights from our team.',
792
- };
793
-
794
- export default function BlogPage() {
795
- return (
796
- <main className="py-20">
797
- <div className="container">
798
- <h1 className="text-4xl font-bold text-gray-900">Blog</h1>
799
- <p className="mt-4 text-lg text-gray-600">
800
- Blog posts coming soon...
801
- </p>
802
- </div>
803
- </main>
804
- );
805
- }
806
- `;
807
- }
808
-
809
- /**
810
- * Escape a string for safe use inside JSX template literals
811
- */
812
- function escapeJsx(str: string): string {
813
- return str
814
- .replace(/\\/g, '\\\\')
815
- .replace(/'/g, "\\'")
816
- .replace(/`/g, '\\`')
817
- .replace(/\$/g, '\\$');
818
- }
819
-
820
- /**
821
- * Convert hex color to HSL string for CSS custom properties
822
- * Returns format: "H S% L%"
823
- */
824
- function hexToHslString(hex: string): string {
825
- // Remove # prefix
826
- const h = hex.replace('#', '');
827
- const r = parseInt(h.substring(0, 2), 16) / 255;
828
- const g = parseInt(h.substring(2, 4), 16) / 255;
829
- const b = parseInt(h.substring(4, 6), 16) / 255;
830
-
831
- const max = Math.max(r, g, b);
832
- const min = Math.min(r, g, b);
833
- const l = (max + min) / 2;
834
-
835
- if (max === min) {
836
- return `0 0% ${Math.round(l * 100)}%`;
837
- }
838
-
839
- const d = max - min;
840
- const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
841
-
842
- let hue = 0;
843
- if (max === r) {
844
- hue = ((g - b) / d + (g < b ? 6 : 0)) / 6;
845
- } else if (max === g) {
846
- hue = ((b - r) / d + 2) / 6;
847
- } else {
848
- hue = ((r - g) / d + 4) / 6;
849
- }
850
-
851
- return `${Math.round(hue * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;
852
- }
2
+ * Website content templates - re-export module
3
+ * Backward compatibility: all functions are available from their new homes
4
+ * but can still be imported from this file
5
+ */
6
+
7
+ // Landing page (10-section redesign)
8
+ export { generateWebsiteLandingPage, generateWebsiteLandingPageWithInfo } from './website-landing.js';
9
+ export type { LandingPageResult } from './website-landing.js';
10
+
11
+ // Pricing page (with toggle + comparison)
12
+ export { generateWebsitePricingPage } from './website-pricing.js';
13
+
14
+ // Layout, CSS, utility pages
15
+ export {
16
+ generateWebsiteLayout,
17
+ generateWebsiteGlobalsCss,
18
+ generateWebsiteSitemap,
19
+ generateWebsiteRobots,
20
+ generateWebsiteReadme,
21
+ generateWebsiteSpec,
22
+ generateWebsiteTest,
23
+ generateWebsiteDocsPage,
24
+ generateWebsiteBlogPage,
25
+ } from './website-layout.js';
26
+
27
+ // Reusable section generators
28
+ export {
29
+ mapFeatureIcon,
30
+ isNumericMetric,
31
+ generatePainPointsSection,
32
+ generateDifferentiatorsSection,
33
+ generateHowItWorksSection,
34
+ generateStatsSection,
35
+ generateSocialProofSection,
36
+ generatePricingTeaserSection,
37
+ generateFaqSection,
38
+ } from './website-sections.js';
39
+ export type { SectionRenderInfo } from './website-sections.js';