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
@@ -0,0 +1,500 @@
1
+ /**
2
+ * Shared package generators for monorepo workspaces
3
+ * Generates design-tokens and UI component packages
4
+ * with support for brand-specific color palettes
5
+ */
6
+
7
+ /**
8
+ * Brand color options for design tokens
9
+ */
10
+ export interface BrandColorOptions {
11
+ primaryColor?: string;
12
+ }
13
+
14
+ /**
15
+ * Generate a color scale from a single hex color
16
+ * Converts hex to HSL and varies lightness across 10 stops (50-900)
17
+ *
18
+ * @param hex - Primary hex color (e.g., "#2563EB")
19
+ * @returns Record of color stops (50-900) with hex values
20
+ */
21
+ export function generateColorScale(hex: string): Record<string, string> {
22
+ const rgb = hexToRgb(hex);
23
+ if (!rgb) {
24
+ // Fallback to default sky-blue on parse failure
25
+ return getDefaultPrimaryScale();
26
+ }
27
+
28
+ const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
29
+ const stops = [
30
+ { key: '50', lightness: 0.95 },
31
+ { key: '100', lightness: 0.90 },
32
+ { key: '200', lightness: 0.80 },
33
+ { key: '300', lightness: 0.68 },
34
+ { key: '400', lightness: 0.56 },
35
+ { key: '500', lightness: 0.48 },
36
+ { key: '600', lightness: 0.40 },
37
+ { key: '700', lightness: 0.32 },
38
+ { key: '800', lightness: 0.24 },
39
+ { key: '900', lightness: 0.15 },
40
+ ];
41
+
42
+ const scale: Record<string, string> = {};
43
+ for (const stop of stops) {
44
+ scale[stop.key] = hslToHex(hsl.h, hsl.s, stop.lightness);
45
+ }
46
+
47
+ return scale;
48
+ }
49
+
50
+ /**
51
+ * Default sky-blue primary color scale (backward compatible)
52
+ */
53
+ function getDefaultPrimaryScale(): Record<string, string> {
54
+ return {
55
+ 50: '#f0f9ff',
56
+ 100: '#e0f2fe',
57
+ 200: '#bae6fd',
58
+ 300: '#7dd3fc',
59
+ 400: '#38bdf8',
60
+ 500: '#0ea5e9',
61
+ 600: '#0284c7',
62
+ 700: '#0369a1',
63
+ 800: '#075985',
64
+ 900: '#0c4a6e',
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Generate design tokens package with optional brand colors
70
+ *
71
+ * @param projectName - Project name for package naming
72
+ * @param brandColors - Optional brand color configuration
73
+ * @returns Package files array
74
+ */
75
+ export function generateDesignTokensPackage(
76
+ projectName: string,
77
+ brandColors?: BrandColorOptions
78
+ ): {
79
+ files: Array<{ path: string; content: string }>;
80
+ } {
81
+ const primaryScale = brandColors?.primaryColor
82
+ ? generateColorScale(brandColors.primaryColor)
83
+ : getDefaultPrimaryScale();
84
+
85
+ const colorsContent = generateColorsModule(primaryScale);
86
+
87
+ return {
88
+ files: [
89
+ {
90
+ path: 'package.json',
91
+ content: JSON.stringify(
92
+ {
93
+ name: `@${projectName}/design-tokens`,
94
+ version: '1.0.0',
95
+ type: 'module',
96
+ main: './dist/index.js',
97
+ types: './dist/index.d.ts',
98
+ exports: {
99
+ '.': './dist/index.js',
100
+ './tailwind': './dist/tailwind-preset.js',
101
+ },
102
+ scripts: {
103
+ build: 'tsc',
104
+ dev: 'tsc --watch',
105
+ },
106
+ devDependencies: {
107
+ typescript: '^5.3.3',
108
+ },
109
+ },
110
+ null,
111
+ 2
112
+ ),
113
+ },
114
+ {
115
+ path: 'tsconfig.json',
116
+ content: JSON.stringify(
117
+ {
118
+ compilerOptions: {
119
+ target: 'ES2020',
120
+ module: 'ESNext',
121
+ moduleResolution: 'bundler',
122
+ declaration: true,
123
+ outDir: './dist',
124
+ strict: true,
125
+ esModuleInterop: true,
126
+ skipLibCheck: true,
127
+ },
128
+ include: ['src'],
129
+ },
130
+ null,
131
+ 2
132
+ ),
133
+ },
134
+ {
135
+ path: 'src/index.ts',
136
+ content: `/**
137
+ * Design tokens for ${projectName}
138
+ */
139
+
140
+ export * from './colors.js';
141
+ export * from './typography.js';
142
+ `,
143
+ },
144
+ {
145
+ path: 'src/colors.ts',
146
+ content: colorsContent,
147
+ },
148
+ {
149
+ path: 'src/typography.ts',
150
+ content: `/**
151
+ * Typography settings
152
+ */
153
+
154
+ export const typography = {
155
+ fontFamily: {
156
+ sans: ['Inter', 'system-ui', 'sans-serif'],
157
+ mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
158
+ },
159
+ fontSize: {
160
+ xs: ['0.75rem', { lineHeight: '1rem' }],
161
+ sm: ['0.875rem', { lineHeight: '1.25rem' }],
162
+ base: ['1rem', { lineHeight: '1.5rem' }],
163
+ lg: ['1.125rem', { lineHeight: '1.75rem' }],
164
+ xl: ['1.25rem', { lineHeight: '1.75rem' }],
165
+ '2xl': ['1.5rem', { lineHeight: '2rem' }],
166
+ '3xl': ['1.875rem', { lineHeight: '2.25rem' }],
167
+ '4xl': ['2.25rem', { lineHeight: '2.5rem' }],
168
+ '5xl': ['3rem', { lineHeight: '1' }],
169
+ '6xl': ['3.75rem', { lineHeight: '1' }],
170
+ },
171
+ } as const;
172
+
173
+ export type Typography = typeof typography;
174
+ `,
175
+ },
176
+ {
177
+ path: 'src/tailwind-preset.ts',
178
+ content: `/**
179
+ * Tailwind CSS preset with design tokens
180
+ */
181
+
182
+ import { colors } from './colors.js';
183
+ import { typography } from './typography.js';
184
+
185
+ export const preset = {
186
+ theme: {
187
+ extend: {
188
+ colors,
189
+ fontFamily: typography.fontFamily,
190
+ fontSize: typography.fontSize,
191
+ },
192
+ },
193
+ };
194
+
195
+ export default preset;
196
+ `,
197
+ },
198
+ ],
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Generate the colors.ts module content with given primary scale
204
+ */
205
+ function generateColorsModule(primaryScale: Record<string, string>): string {
206
+ const entries = Object.entries(primaryScale)
207
+ .map(([key, value]) => ` ${key}: '${value}',`)
208
+ .join('\n');
209
+
210
+ return `/**
211
+ * Color palette
212
+ */
213
+
214
+ export const colors = {
215
+ primary: {
216
+ ${entries}
217
+ },
218
+ secondary: {
219
+ 50: '#f8fafc',
220
+ 100: '#f1f5f9',
221
+ 200: '#e2e8f0',
222
+ 300: '#cbd5e1',
223
+ 400: '#94a3b8',
224
+ 500: '#64748b',
225
+ 600: '#475569',
226
+ 700: '#334155',
227
+ 800: '#1e293b',
228
+ 900: '#0f172a',
229
+ },
230
+ } as const;
231
+
232
+ export type ColorScale = typeof colors.primary;
233
+ export type Colors = typeof colors;
234
+ `;
235
+ }
236
+
237
+ /**
238
+ * Generate UI components package
239
+ *
240
+ * @param projectName - Project name for package naming
241
+ * @returns Package files array
242
+ */
243
+ export function generateUiPackage(projectName: string): {
244
+ files: Array<{ path: string; content: string }>;
245
+ } {
246
+ return {
247
+ files: [
248
+ {
249
+ path: 'package.json',
250
+ content: JSON.stringify(
251
+ {
252
+ name: `@${projectName}/ui`,
253
+ version: '1.0.0',
254
+ type: 'module',
255
+ main: './dist/index.js',
256
+ types: './dist/index.d.ts',
257
+ exports: {
258
+ '.': './dist/index.js',
259
+ './button': './dist/button.js',
260
+ './card': './dist/card.js',
261
+ },
262
+ scripts: {
263
+ build: 'tsc',
264
+ dev: 'tsc --watch',
265
+ },
266
+ dependencies: {
267
+ clsx: '^2.1.0',
268
+ 'tailwind-merge': '^2.2.0',
269
+ },
270
+ peerDependencies: {
271
+ react: '>=18.0.0',
272
+ 'react-dom': '>=18.0.0',
273
+ },
274
+ devDependencies: {
275
+ '@types/react': '^18.2.0',
276
+ '@types/react-dom': '^18.2.0',
277
+ typescript: '^5.3.3',
278
+ },
279
+ },
280
+ null,
281
+ 2
282
+ ),
283
+ },
284
+ {
285
+ path: 'tsconfig.json',
286
+ content: JSON.stringify(
287
+ {
288
+ compilerOptions: {
289
+ target: 'ES2020',
290
+ module: 'ESNext',
291
+ moduleResolution: 'bundler',
292
+ declaration: true,
293
+ outDir: './dist',
294
+ strict: true,
295
+ esModuleInterop: true,
296
+ skipLibCheck: true,
297
+ jsx: 'react-jsx',
298
+ },
299
+ include: ['src'],
300
+ },
301
+ null,
302
+ 2
303
+ ),
304
+ },
305
+ {
306
+ path: 'src/index.ts',
307
+ content: `/**
308
+ * Shared UI components for ${projectName}
309
+ */
310
+
311
+ export * from './button.js';
312
+ export * from './card.js';
313
+ export * from './utils.js';
314
+ `,
315
+ },
316
+ {
317
+ path: 'src/utils.ts',
318
+ content: `import { clsx, type ClassValue } from 'clsx';
319
+ import { twMerge } from 'tailwind-merge';
320
+
321
+ export function cn(...inputs: ClassValue[]) {
322
+ return twMerge(clsx(inputs));
323
+ }
324
+ `,
325
+ },
326
+ {
327
+ path: 'src/button.tsx',
328
+ content: `import * as React from 'react';
329
+ import { cn } from './utils.js';
330
+
331
+ export interface ButtonProps
332
+ extends React.ButtonHTMLAttributes<HTMLButtonElement> {
333
+ variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
334
+ size?: 'sm' | 'md' | 'lg';
335
+ }
336
+
337
+ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
338
+ ({ className, variant = 'primary', size = 'md', ...props }, ref) => {
339
+ return (
340
+ <button
341
+ className={cn(
342
+ 'inline-flex items-center justify-center rounded-md font-medium transition-colors',
343
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
344
+ 'disabled:pointer-events-none disabled:opacity-50',
345
+ {
346
+ // Variants
347
+ 'bg-primary-600 text-white hover:bg-primary-500': variant === 'primary',
348
+ 'bg-secondary-100 text-secondary-900 hover:bg-secondary-200': variant === 'secondary',
349
+ 'border border-secondary-300 bg-transparent hover:bg-secondary-50': variant === 'outline',
350
+ 'bg-transparent hover:bg-secondary-100': variant === 'ghost',
351
+ // Sizes
352
+ 'h-8 px-3 text-sm': size === 'sm',
353
+ 'h-10 px-4 text-sm': size === 'md',
354
+ 'h-12 px-6 text-base': size === 'lg',
355
+ },
356
+ className
357
+ )}
358
+ ref={ref}
359
+ {...props}
360
+ />
361
+ );
362
+ }
363
+ );
364
+
365
+ Button.displayName = 'Button';
366
+ `,
367
+ },
368
+ {
369
+ path: 'src/card.tsx',
370
+ content: `import * as React from 'react';
371
+ import { cn } from './utils.js';
372
+
373
+ export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {}
374
+
375
+ export const Card = React.forwardRef<HTMLDivElement, CardProps>(
376
+ ({ className, ...props }, ref) => (
377
+ <div
378
+ ref={ref}
379
+ className={cn(
380
+ 'rounded-lg border border-secondary-200 bg-white shadow-sm',
381
+ className
382
+ )}
383
+ {...props}
384
+ />
385
+ )
386
+ );
387
+
388
+ Card.displayName = 'Card';
389
+
390
+ export const CardHeader = React.forwardRef<
391
+ HTMLDivElement,
392
+ React.HTMLAttributes<HTMLDivElement>
393
+ >(({ className, ...props }, ref) => (
394
+ <div
395
+ ref={ref}
396
+ className={cn('flex flex-col space-y-1.5 p-6', className)}
397
+ {...props}
398
+ />
399
+ ));
400
+
401
+ CardHeader.displayName = 'CardHeader';
402
+
403
+ export const CardTitle = React.forwardRef<
404
+ HTMLParagraphElement,
405
+ React.HTMLAttributes<HTMLHeadingElement>
406
+ >(({ className, ...props }, ref) => (
407
+ <h3
408
+ ref={ref}
409
+ className={cn('text-lg font-semibold leading-none tracking-tight', className)}
410
+ {...props}
411
+ />
412
+ ));
413
+
414
+ CardTitle.displayName = 'CardTitle';
415
+
416
+ export const CardContent = React.forwardRef<
417
+ HTMLDivElement,
418
+ React.HTMLAttributes<HTMLDivElement>
419
+ >(({ className, ...props }, ref) => (
420
+ <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
421
+ ));
422
+
423
+ CardContent.displayName = 'CardContent';
424
+ `,
425
+ },
426
+ ],
427
+ };
428
+ }
429
+
430
+ // --- Color conversion helpers ---
431
+
432
+ function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
433
+ const cleaned = hex.replace(/^#/, '');
434
+ if (cleaned.length !== 6) return null;
435
+
436
+ const r = parseInt(cleaned.substring(0, 2), 16);
437
+ const g = parseInt(cleaned.substring(2, 4), 16);
438
+ const b = parseInt(cleaned.substring(4, 6), 16);
439
+
440
+ if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
441
+ return { r, g, b };
442
+ }
443
+
444
+ function rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {
445
+ const rn = r / 255;
446
+ const gn = g / 255;
447
+ const bn = b / 255;
448
+
449
+ const max = Math.max(rn, gn, bn);
450
+ const min = Math.min(rn, gn, bn);
451
+ const l = (max + min) / 2;
452
+ let h = 0;
453
+ let s = 0;
454
+
455
+ if (max !== min) {
456
+ const d = max - min;
457
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
458
+
459
+ if (max === rn) {
460
+ h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6;
461
+ } else if (max === gn) {
462
+ h = ((bn - rn) / d + 2) / 6;
463
+ } else {
464
+ h = ((rn - gn) / d + 4) / 6;
465
+ }
466
+ }
467
+
468
+ return { h, s, l };
469
+ }
470
+
471
+ function hslToHex(h: number, s: number, l: number): string {
472
+ const hue2rgb = (p: number, q: number, t: number): number => {
473
+ let tn = t;
474
+ if (tn < 0) tn += 1;
475
+ if (tn > 1) tn -= 1;
476
+ if (tn < 1 / 6) return p + (q - p) * 6 * tn;
477
+ if (tn < 1 / 2) return q;
478
+ if (tn < 2 / 3) return p + (q - p) * (2 / 3 - tn) * 6;
479
+ return p;
480
+ };
481
+
482
+ let r: number, g: number, b: number;
483
+
484
+ if (s === 0) {
485
+ r = g = b = l;
486
+ } else {
487
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
488
+ const p = 2 * l - q;
489
+ r = hue2rgb(p, q, h + 1 / 3);
490
+ g = hue2rgb(p, q, h);
491
+ b = hue2rgb(p, q, h - 1 / 3);
492
+ }
493
+
494
+ const toHex = (n: number): string => {
495
+ const hex = Math.round(n * 255).toString(16);
496
+ return hex.length === 1 ? '0' + hex : hex;
497
+ };
498
+
499
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
500
+ }
@@ -10,3 +10,7 @@ export * as websiteConfigTemplates from './website-config.js';
10
10
  export * as websiteComponentTemplates from './website-components.js';
11
11
  export * as websiteSeoTemplates from './website-seo.js';
12
12
  export * as websiteConversionTemplates from './website-conversion.js';
13
+ export * as websiteLandingTemplates from './website-landing.js';
14
+ export * as websitePricingTemplates from './website-pricing.js';
15
+ export * as websiteLayoutTemplates from './website-layout.js';
16
+ export * as websiteSectionTemplates from './website-sections.js';
@@ -37,9 +37,10 @@ export function generateWebsiteHeader(
37
37
  .join(' ');
38
38
 
39
39
  const hasLogo = !!(context?.brandAssets?.logoOutputPath || context?.brand?.logoPath);
40
+ // Reason: Next.js serves public/ at root, so public/brand/logo.svg -> /brand/logo.svg
40
41
  const logoPath = context?.brandAssets?.logoOutputPath
41
- ? `/${context.brandAssets.logoOutputPath}`
42
- : '/logo.svg';
42
+ ? `/${context.brandAssets.logoOutputPath.replace(/^public\//, '')}`
43
+ : '/brand/logo.svg';
43
44
 
44
45
  // Build nav items from strategy or defaults
45
46
  const navItems = strategy?.siteArchitecture.navigation || [
@@ -57,10 +58,14 @@ export function generateWebsiteHeader(
57
58
  const ctaText = strategy?.conversionStrategy.primaryCta.text || 'Get Started';
58
59
  const ctaHref = strategy?.conversionStrategy.primaryCta.href || '/pricing';
59
60
 
60
- // Logo rendering
61
+ // Logo rendering: Image if available, product initial circle if not
62
+ const initialLetter = displayName.charAt(0).toUpperCase();
61
63
  const logoBlock = hasLogo
62
64
  ? `<Image src="${logoPath}" alt="${escapeJsx(displayName)}" width={32} height={32} className="h-8 w-auto" />`
63
- : `<span className="text-xl font-bold text-primary-600">${escapeJsx(displayName)}</span>`;
65
+ : `<div className="flex items-center gap-2">
66
+ <div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary-600 text-sm font-bold text-white">${initialLetter}</div>
67
+ <span className="text-lg font-bold text-foreground">${escapeJsx(displayName)}</span>
68
+ </div>`;
64
69
 
65
70
  return `'use client';
66
71
 
@@ -217,29 +222,49 @@ ${sectionsStr}
217
222
  */
218
223
  export default function Footer() {
219
224
  return (
220
- <footer className="border-t border-gray-200 bg-gray-50">
225
+ <footer className="border-t border-border bg-muted/50">
221
226
  <div className="container py-12">
222
227
  <div className="grid grid-cols-2 gap-8 md:grid-cols-${Math.min(sections.length + 1, 4)}">
223
228
  {/* Brand column */}
224
229
  <div className="col-span-2 md:col-span-1">
225
- <Link href="/" className="text-lg font-bold text-gray-900">
230
+ <Link href="/" className="text-lg font-bold text-foreground">
226
231
  ${escapeJsx(displayName)}
227
232
  </Link>
228
- <p className="mt-2 text-sm text-gray-600">
233
+ <p className="mt-2 text-sm text-muted-foreground">
229
234
  ${context?.tagline ? escapeJsx(context.tagline) : 'Build something amazing.'}
230
235
  </p>
236
+ {/* Newsletter */}
237
+ <form className="mt-6" onSubmit={(e) => e.preventDefault()}>
238
+ <label htmlFor="newsletter-email" className="text-sm font-medium text-foreground">
239
+ Stay updated
240
+ </label>
241
+ <div className="mt-2 flex gap-2">
242
+ <input
243
+ id="newsletter-email"
244
+ type="email"
245
+ placeholder="you@example.com"
246
+ className="flex-1 rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary-600"
247
+ />
248
+ <button
249
+ type="submit"
250
+ className="rounded-lg bg-primary-600 px-4 py-2 text-sm font-medium text-white hover:bg-primary-500 transition-colors"
251
+ >
252
+ Subscribe
253
+ </button>
254
+ </div>
255
+ </form>
231
256
  </div>
232
257
 
233
258
  {/* Link columns */}
234
259
  {FOOTER_SECTIONS.map((section) => (
235
260
  <div key={section.title}>
236
- <h3 className="text-sm font-semibold text-gray-900">{section.title}</h3>
261
+ <h3 className="text-sm font-semibold text-foreground">{section.title}</h3>
237
262
  <ul className="mt-4 space-y-2">
238
263
  {section.links.map((link) => (
239
264
  <li key={link.href}>
240
265
  <Link
241
266
  href={link.href}
242
- className="text-sm text-gray-600 hover:text-primary-600 transition-colors"
267
+ className="text-sm text-muted-foreground hover:text-primary-600 transition-colors"
243
268
  >
244
269
  {link.label}
245
270
  </Link>
@@ -250,8 +275,8 @@ export default function Footer() {
250
275
  ))}
251
276
  </div>
252
277
 
253
- <div className="mt-12 border-t border-gray-200 pt-8">
254
- <p className="text-center text-sm text-gray-500">
278
+ <div className="mt-12 border-t border-border pt-8">
279
+ <p className="text-center text-sm text-muted-foreground">
255
280
  &copy; {new Date().getFullYear()} ${escapeJsx(displayName)}. All rights reserved.
256
281
  </p>
257
282
  </div>