@jsonpages/cli 3.0.13 → 3.0.15

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.
@@ -30,7 +30,9 @@ cat << 'END_OF_FILE_CONTENT' > "src/App.tsx"
30
30
  /**
31
31
  * Thin Entry Point (Tenant).
32
32
  */
33
+ import { useState, useEffect } from 'react';
33
34
  import { JsonPagesEngine } from '@jsonpages/core';
35
+ import type { LibraryImageEntry } from '@jsonpages/core';
34
36
  import { ComponentRegistry } from '@/lib/ComponentRegistry';
35
37
  import { SECTION_SCHEMAS } from '@/lib/schemas';
36
38
  import { addSectionConfig } from '@/lib/addSectionConfig';
@@ -54,19 +56,84 @@ const pages: Record<string, PageConfig> = {
54
56
  home: homeData as unknown as PageConfig,
55
57
  };
56
58
 
57
- const config: JsonPagesConfig = {
58
- tenantId: 'alpha', // 🛡️ Identificativo per asset resolution
59
- registry: ComponentRegistry as JsonPagesConfig['registry'],
60
- schemas: SECTION_SCHEMAS as unknown as JsonPagesConfig['schemas'],
61
- pages,
62
- siteConfig,
63
- themeConfig,
64
- menuConfig,
65
- themeCss: { tenant: tenantCss },
66
- addSection: addSectionConfig,
67
- };
68
-
69
59
  function App() {
60
+ const [assetsManifest, setAssetsManifest] = useState<LibraryImageEntry[]>([]);
61
+
62
+ // #region agent log
63
+ useEffect(() => {
64
+ fetch('http://127.0.0.1:7588/ingest/86d71502-47e1-433c-9b6d-5a1390d00813',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'34bba5'},body:JSON.stringify({sessionId:'34bba5',location:'App.tsx',message:'App mounted',data:{},timestamp:Date.now(),hypothesisId:'H1,H3'})}).catch(()=>{});
65
+ const onSubmit = (e: Event) => {
66
+ fetch('http://127.0.0.1:7588/ingest/86d71502-47e1-433c-9b6d-5a1390d00813',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'34bba5'},body:JSON.stringify({sessionId:'34bba5',location:'App.tsx:submit',message:'form submit captured',data:{targetTag:(e.target as HTMLElement)?.tagName},timestamp:Date.now(),hypothesisId:'H4'})}).catch(()=>{});
67
+ };
68
+ document.addEventListener('submit', onSubmit, true);
69
+ return () => document.removeEventListener('submit', onSubmit, true);
70
+ }, []);
71
+ // #endregion
72
+
73
+ useEffect(() => {
74
+ fetch('/api/list-assets')
75
+ .then((r) => (r.ok ? r.json() : []))
76
+ .then((list: LibraryImageEntry[]) => setAssetsManifest(Array.isArray(list) ? list : []))
77
+ .catch(() => setAssetsManifest([]));
78
+ }, []);
79
+
80
+ const config: JsonPagesConfig = {
81
+ tenantId: 'alpha',
82
+ registry: ComponentRegistry as JsonPagesConfig['registry'],
83
+ schemas: SECTION_SCHEMAS as unknown as JsonPagesConfig['schemas'],
84
+ pages,
85
+ siteConfig,
86
+ themeConfig,
87
+ menuConfig,
88
+ themeCss: { tenant: tenantCss },
89
+ addSection: addSectionConfig,
90
+ persistence: {
91
+ async flushUploadedAssets(urls: string[]): Promise<Record<string, string>> {
92
+ const res = await fetch('/api/flush-uploaded-assets', {
93
+ method: 'POST',
94
+ headers: { 'Content-Type': 'application/json' },
95
+ body: JSON.stringify({ urls }),
96
+ });
97
+ if (!res.ok) throw new Error(`Flush failed: ${res.status}`);
98
+ const { urlMap } = (await res.json()) as { urlMap?: Record<string, string> };
99
+ return urlMap ?? {};
100
+ },
101
+ },
102
+ assets: {
103
+ assetsBaseUrl: '/assets',
104
+ assetsManifest,
105
+ async onAssetUpload(file: File): Promise<string> {
106
+ // #region agent log
107
+ fetch('http://127.0.0.1:7588/ingest/86d71502-47e1-433c-9b6d-5a1390d00813',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'34bba5'},body:JSON.stringify({sessionId:'34bba5',location:'App.tsx:onAssetUpload',message:'onAssetUpload entry',data:{fileName:file.name},timestamp:Date.now(),hypothesisId:'H2,H5'})}).catch(()=>{});
108
+ // #endregion
109
+ const base64 = await new Promise<string>((resolve, reject) => {
110
+ const reader = new FileReader();
111
+ reader.onload = () => {
112
+ const dataUrl = reader.result as string;
113
+ const base64 = dataUrl.includes(',') ? dataUrl.split(',')[1] : '';
114
+ resolve(base64 || '');
115
+ };
116
+ reader.onerror = () => reject(reader.error);
117
+ reader.readAsDataURL(file);
118
+ });
119
+ const res = await fetch('/api/upload-asset', {
120
+ method: 'POST',
121
+ headers: { 'Content-Type': 'application/json' },
122
+ body: JSON.stringify({ filename: file.name, mimeType: file.type || undefined, data: base64 }),
123
+ });
124
+ if (!res.ok) {
125
+ const err = await res.json().catch(() => ({}));
126
+ throw new Error((err as { error?: string }).error || `Upload failed: ${res.status}`);
127
+ }
128
+ const { url } = (await res.json()) as { url: string };
129
+ // In-memory upload only: do not update assetsManifest here to avoid reload.
130
+ // Preview in Upload tab uses this URL; Library tab will get new items on next list-assets load.
131
+ // Disk write can be added later on Save.
132
+ return url;
133
+ },
134
+ },
135
+ };
136
+
70
137
  return <JsonPagesEngine config={config} />;
71
138
  }
72
139
 
@@ -111,665 +178,665 @@ END_OF_FILE_CONTENT
111
178
  mkdir -p "src/components/arch-layers"
112
179
  echo "Creating src/components/arch-layers/View.tsx..."
113
180
  cat << 'END_OF_FILE_CONTENT' > "src/components/arch-layers/View.tsx"
114
- import React from 'react';
115
- import { cn } from '@/lib/utils';
116
- import type { ArchLayersData, ArchLayersSettings, ArchLayerLevel, SyntaxTokenType } from './types';
117
-
118
- const layerBgStyles: Record<ArchLayerLevel, string> = {
119
- l0: 'bg-[#3b82f6]',
120
- l1: 'bg-[rgba(59,130,246,0.6)]',
121
- l2: 'bg-[rgba(59,130,246,0.35)]',
122
- };
123
-
124
- const tokenStyles: Record<SyntaxTokenType, string> = {
125
- plain: 'text-[#cbd5e1]',
126
- keyword: 'text-[#60a5fa]',
127
- type: 'text-[#22d3ee]',
128
- string: 'text-[#4ade80]',
129
- comment: 'text-[#64748b] italic',
130
- operator: 'text-[#f472b6]',
131
- };
132
-
133
- export const ArchLayers: React.FC<{ data: ArchLayersData; settings?: ArchLayersSettings }> = ({ data }) => {
134
- return (
135
- <section
136
- style={{
137
- '--local-bg': 'var(--card)',
138
- '--local-text': 'var(--foreground)',
139
- '--local-text-muted': 'var(--muted-foreground)',
140
- '--local-primary': 'var(--primary)',
141
- '--local-accent': 'var(--color-accent, #60a5fa)',
142
- '--local-border': 'var(--border)',
143
- '--local-deep': 'var(--background)',
144
- } as React.CSSProperties}
145
- className="relative z-0 py-28 bg-[var(--local-bg)]"
146
- >
147
- <div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.1)] to-transparent" />
148
- <div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.1)] to-transparent" />
149
- <div className="max-w-[1200px] mx-auto px-8">
150
- <div className="text-center">
151
- {data.label && (
152
- <div className="jp-section-label inline-flex items-center gap-2 text-[0.72rem] font-bold uppercase tracking-[0.12em] text-[var(--local-accent)] mb-4" data-jp-field="label">
153
- <span className="w-5 h-px bg-[var(--local-primary)]" />
154
- {data.label}
155
- </div>
156
- )}
157
- <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
158
- {data.title}
159
- </h2>
160
- {data.description && (
161
- <p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed" data-jp-field="description">
162
- {data.description}
163
- </p>
164
- )}
165
- </div>
166
- <div className="mt-14 max-w-[740px] mx-auto">
167
- {data.layers.map((layer, idx) => (
168
- <div
169
- key={layer.id ?? idx}
170
- className="group border border-[rgba(255,255,255,0.06)] rounded-[7px] p-8 mb-4 bg-[rgba(255,255,255,0.015)] flex items-start gap-6 transition-all duration-300 hover:border-[rgba(59,130,246,0.2)] hover:translate-x-1.5"
171
- data-jp-item-id={layer.id ?? `legacy-${idx}`}
172
- data-jp-item-field="layers"
173
- >
174
- <div className={cn(
175
- 'shrink-0 w-9 h-9 rounded-lg flex items-center justify-center font-mono text-[0.85rem] font-bold text-white',
176
- layerBgStyles[layer.layerLevel]
177
- )}>
178
- {layer.number}
179
- </div>
180
- <div>
181
- <h4 className="text-[1.05rem] font-bold text-[var(--local-text)] mb-1.5">
182
- {layer.title}
183
- </h4>
184
- <p className="text-[0.92rem] text-[var(--local-text-muted)] leading-relaxed">
185
- {layer.description}
186
- </p>
187
- </div>
188
- </div>
189
- ))}
190
- </div>
191
- {data.codeLines && data.codeLines.length > 0 && (
192
- <div className="mt-12 max-w-[740px] mx-auto">
193
- <div className="border border-[rgba(255,255,255,0.08)] rounded-[7px] overflow-hidden bg-[var(--local-deep)]">
194
- <div className="flex items-center gap-2 px-5 py-3 bg-[rgba(255,255,255,0.03)] border-b border-[rgba(255,255,255,0.06)]">
195
- <span className="w-2.5 h-2.5 rounded-full bg-[#ef4444]" />
196
- <span className="w-2.5 h-2.5 rounded-full bg-[#f59e0b]" />
197
- <span className="w-2.5 h-2.5 rounded-full bg-[#22c55e]" />
198
- {data.codeFilename && (
199
- <span className="ml-3 font-mono text-[0.75rem] text-[var(--local-text-muted)] opacity-60" data-jp-field="codeFilename">
200
- {data.codeFilename}
201
- </span>
202
- )}
203
- </div>
204
- <div className="p-6 font-mono text-[0.82rem] leading-[1.7] overflow-x-auto">
205
- {data.codeLines.map((line, idx) => (
206
- <div key={idx} data-jp-item-id={(line as { id?: string }).id ?? `legacy-${idx}`} data-jp-item-field="codeLines">
207
- <span className={tokenStyles[line.tokenType]}>
208
- {line.content}
209
- </span>
210
- </div>
211
- ))}
212
- </div>
213
- </div>
214
- </div>
215
- )}
216
- </div>
217
- </section>
218
- );
219
- };
181
+ import React from 'react';
182
+ import { cn } from '@/lib/utils';
183
+ import type { ArchLayersData, ArchLayersSettings, ArchLayerLevel, SyntaxTokenType } from './types';
184
+
185
+ const layerBgStyles: Record<ArchLayerLevel, string> = {
186
+ l0: 'bg-[#3b82f6]',
187
+ l1: 'bg-[rgba(59,130,246,0.6)]',
188
+ l2: 'bg-[rgba(59,130,246,0.35)]',
189
+ };
190
+
191
+ const tokenStyles: Record<SyntaxTokenType, string> = {
192
+ plain: 'text-[#cbd5e1]',
193
+ keyword: 'text-[#60a5fa]',
194
+ type: 'text-[#22d3ee]',
195
+ string: 'text-[#4ade80]',
196
+ comment: 'text-[#64748b] italic',
197
+ operator: 'text-[#f472b6]',
198
+ };
199
+
200
+ export const ArchLayers: React.FC<{ data: ArchLayersData; settings?: ArchLayersSettings }> = ({ data }) => {
201
+ return (
202
+ <section
203
+ style={{
204
+ '--local-bg': 'var(--card)',
205
+ '--local-text': 'var(--foreground)',
206
+ '--local-text-muted': 'var(--muted-foreground)',
207
+ '--local-primary': 'var(--primary)',
208
+ '--local-accent': 'var(--color-accent, #60a5fa)',
209
+ '--local-border': 'var(--border)',
210
+ '--local-deep': 'var(--background)',
211
+ } as React.CSSProperties}
212
+ className="relative z-0 py-28 bg-[var(--local-bg)]"
213
+ >
214
+ <div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.1)] to-transparent" />
215
+ <div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.1)] to-transparent" />
216
+ <div className="max-w-[1200px] mx-auto px-8">
217
+ <div className="text-center">
218
+ {data.label && (
219
+ <div className="jp-section-label inline-flex items-center gap-2 text-[0.72rem] font-bold uppercase tracking-[0.12em] text-[var(--local-accent)] mb-4" data-jp-field="label">
220
+ <span className="w-5 h-px bg-[var(--local-primary)]" />
221
+ {data.label}
222
+ </div>
223
+ )}
224
+ <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
225
+ {data.title}
226
+ </h2>
227
+ {data.description && (
228
+ <p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed" data-jp-field="description">
229
+ {data.description}
230
+ </p>
231
+ )}
232
+ </div>
233
+ <div className="mt-14 max-w-[740px] mx-auto">
234
+ {data.layers.map((layer, idx) => (
235
+ <div
236
+ key={layer.id ?? idx}
237
+ className="group border border-[rgba(255,255,255,0.06)] rounded-[7px] p-8 mb-4 bg-[rgba(255,255,255,0.015)] flex items-start gap-6 transition-all duration-300 hover:border-[rgba(59,130,246,0.2)] hover:translate-x-1.5"
238
+ data-jp-item-id={layer.id ?? `legacy-${idx}`}
239
+ data-jp-item-field="layers"
240
+ >
241
+ <div className={cn(
242
+ 'shrink-0 w-9 h-9 rounded-lg flex items-center justify-center font-mono text-[0.85rem] font-bold text-white',
243
+ layerBgStyles[layer.layerLevel]
244
+ )}>
245
+ {layer.number}
246
+ </div>
247
+ <div>
248
+ <h4 className="text-[1.05rem] font-bold text-[var(--local-text)] mb-1.5">
249
+ {layer.title}
250
+ </h4>
251
+ <p className="text-[0.92rem] text-[var(--local-text-muted)] leading-relaxed">
252
+ {layer.description}
253
+ </p>
254
+ </div>
255
+ </div>
256
+ ))}
257
+ </div>
258
+ {data.codeLines && data.codeLines.length > 0 && (
259
+ <div className="mt-12 max-w-[740px] mx-auto">
260
+ <div className="border border-[rgba(255,255,255,0.08)] rounded-[7px] overflow-hidden bg-[var(--local-deep)]">
261
+ <div className="flex items-center gap-2 px-5 py-3 bg-[rgba(255,255,255,0.03)] border-b border-[rgba(255,255,255,0.06)]">
262
+ <span className="w-2.5 h-2.5 rounded-full bg-[#ef4444]" />
263
+ <span className="w-2.5 h-2.5 rounded-full bg-[#f59e0b]" />
264
+ <span className="w-2.5 h-2.5 rounded-full bg-[#22c55e]" />
265
+ {data.codeFilename && (
266
+ <span className="ml-3 font-mono text-[0.75rem] text-[var(--local-text-muted)] opacity-60" data-jp-field="codeFilename">
267
+ {data.codeFilename}
268
+ </span>
269
+ )}
270
+ </div>
271
+ <div className="p-6 font-mono text-[0.82rem] leading-[1.7] overflow-x-auto">
272
+ {data.codeLines.map((line, idx) => (
273
+ <div key={idx} data-jp-item-id={(line as { id?: string }).id ?? `legacy-${idx}`} data-jp-item-field="codeLines">
274
+ <span className={tokenStyles[line.tokenType]}>
275
+ {line.content}
276
+ </span>
277
+ </div>
278
+ ))}
279
+ </div>
280
+ </div>
281
+ </div>
282
+ )}
283
+ </div>
284
+ </section>
285
+ );
286
+ };
220
287
 
221
288
  END_OF_FILE_CONTENT
222
289
  echo "Creating src/components/arch-layers/index.ts..."
223
290
  cat << 'END_OF_FILE_CONTENT' > "src/components/arch-layers/index.ts"
224
- export * from './View';
225
- export * from './schema';
226
- export * from './types';
291
+ export * from './View';
292
+ export * from './schema';
293
+ export * from './types';
227
294
 
228
295
  END_OF_FILE_CONTENT
229
296
  echo "Creating src/components/arch-layers/schema.ts..."
230
297
  cat << 'END_OF_FILE_CONTENT' > "src/components/arch-layers/schema.ts"
231
- import { z } from 'zod';
232
- import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
233
-
234
- export const ArchLayerLevelSchema = z.enum(['l0', 'l1', 'l2']);
235
- export const SyntaxTokenTypeSchema = z.enum(['plain', 'keyword', 'type', 'string', 'comment', 'operator']);
236
-
237
- const ArchLayerItemSchema = BaseArrayItem.extend({
238
- number: z.string().describe('ui:text'),
239
- layerLevel: ArchLayerLevelSchema.describe('ui:select'),
240
- title: z.string().describe('ui:text'),
241
- description: z.string().describe('ui:textarea'),
242
- });
243
-
244
- const SyntaxLineSchema = z.object({
245
- content: z.string().describe('ui:text'),
246
- tokenType: SyntaxTokenTypeSchema.default('plain').describe('ui:select'),
247
- });
248
-
249
- export const ArchLayersSchema = BaseSectionData.extend({
250
- label: z.string().optional().describe('ui:text'),
251
- title: z.string().describe('ui:text'),
252
- description: z.string().optional().describe('ui:textarea'),
253
- layers: z.array(ArchLayerItemSchema).describe('ui:list'),
254
- codeFilename: z.string().optional().describe('ui:text'),
255
- codeLines: z.array(SyntaxLineSchema).optional().describe('ui:list'),
256
- });
298
+ import { z } from 'zod';
299
+ import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
300
+
301
+ export const ArchLayerLevelSchema = z.enum(['l0', 'l1', 'l2']);
302
+ export const SyntaxTokenTypeSchema = z.enum(['plain', 'keyword', 'type', 'string', 'comment', 'operator']);
303
+
304
+ const ArchLayerItemSchema = BaseArrayItem.extend({
305
+ number: z.string().describe('ui:text'),
306
+ layerLevel: ArchLayerLevelSchema.describe('ui:select'),
307
+ title: z.string().describe('ui:text'),
308
+ description: z.string().describe('ui:textarea'),
309
+ });
310
+
311
+ const SyntaxLineSchema = z.object({
312
+ content: z.string().describe('ui:text'),
313
+ tokenType: SyntaxTokenTypeSchema.default('plain').describe('ui:select'),
314
+ });
315
+
316
+ export const ArchLayersSchema = BaseSectionData.extend({
317
+ label: z.string().optional().describe('ui:text'),
318
+ title: z.string().describe('ui:text'),
319
+ description: z.string().optional().describe('ui:textarea'),
320
+ layers: z.array(ArchLayerItemSchema).describe('ui:list'),
321
+ codeFilename: z.string().optional().describe('ui:text'),
322
+ codeLines: z.array(SyntaxLineSchema).optional().describe('ui:list'),
323
+ });
257
324
 
258
325
  END_OF_FILE_CONTENT
259
326
  echo "Creating src/components/arch-layers/types.ts..."
260
327
  cat << 'END_OF_FILE_CONTENT' > "src/components/arch-layers/types.ts"
261
- import { z } from 'zod';
262
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
263
- import { ArchLayersSchema, ArchLayerLevelSchema, SyntaxTokenTypeSchema } from './schema';
264
-
265
- export type ArchLayersData = z.infer<typeof ArchLayersSchema>;
266
- export type ArchLayersSettings = z.infer<typeof BaseSectionSettingsSchema>;
267
- export type ArchLayerLevel = z.infer<typeof ArchLayerLevelSchema>;
268
- export type SyntaxTokenType = z.infer<typeof SyntaxTokenTypeSchema>;
328
+ import { z } from 'zod';
329
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
330
+ import { ArchLayersSchema, ArchLayerLevelSchema, SyntaxTokenTypeSchema } from './schema';
331
+
332
+ export type ArchLayersData = z.infer<typeof ArchLayersSchema>;
333
+ export type ArchLayersSettings = z.infer<typeof BaseSectionSettingsSchema>;
334
+ export type ArchLayerLevel = z.infer<typeof ArchLayerLevelSchema>;
335
+ export type SyntaxTokenType = z.infer<typeof SyntaxTokenTypeSchema>;
269
336
 
270
337
  END_OF_FILE_CONTENT
271
338
  mkdir -p "src/components/code-block"
272
339
  echo "Creating src/components/code-block/View.tsx..."
273
340
  cat << 'END_OF_FILE_CONTENT' > "src/components/code-block/View.tsx"
274
- import React from 'react';
275
- import { cn } from '@/lib/utils';
276
- import { Icon } from '@/lib/IconResolver';
277
- import type { CodeBlockData, CodeBlockSettings } from './types';
278
-
279
- export const CodeBlock: React.FC<{ data: CodeBlockData; settings?: CodeBlockSettings }> = ({ data, settings }) => {
280
- const showLineNumbers = settings?.showLineNumbers ?? true;
281
-
282
- return (
283
- <section
284
- style={{
285
- '--local-surface': 'var(--card)',
286
- '--local-text-muted': 'var(--muted-foreground)',
287
- '--local-bg': 'var(--background)',
288
- '--local-border': 'var(--border)',
289
- '--local-text': 'var(--foreground)',
290
- '--local-accent': 'var(--primary)',
291
- '--local-radius-lg': 'var(--radius)',
292
- } as React.CSSProperties}
293
- className="py-16 bg-[var(--local-surface)]"
294
- >
295
- <div className="container mx-auto px-6 max-w-4xl">
296
- {data.label && (
297
- <div className="flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-[var(--local-text-muted)] mb-4" data-jp-field="label">
298
- <Icon name="terminal" size={14} />
299
- <span>{data.label}</span>
300
- </div>
301
- )}
302
- <div className="rounded-[var(--local-radius-lg)] bg-[var(--local-bg)] border border-[var(--local-border)] overflow-hidden">
303
- <div className="p-6 font-mono text-sm overflow-x-auto">
304
- {data.lines.map((line, idx) => (
305
- <div key={idx} className="flex items-start gap-4 py-1" data-jp-item-id={(line as { id?: string }).id ?? `legacy-${idx}`} data-jp-item-field="lines">
306
- {showLineNumbers && (
307
- <span className="select-none w-6 text-right text-[var(--local-text-muted)]/50">
308
- {idx + 1}
309
- </span>
310
- )}
311
- <span
312
- className={cn(
313
- line.isComment
314
- ? 'text-[var(--local-text-muted)]/60'
315
- : 'text-[var(--local-text)]'
316
- )}
317
- >
318
- {!line.isComment && (
319
- <span className="text-[var(--local-accent)] mr-2">$</span>
320
- )}
321
- {line.content}
322
- </span>
323
- </div>
324
- ))}
325
- </div>
326
- </div>
327
- </div>
328
- </section>
329
- );
330
- };
341
+ import React from 'react';
342
+ import { cn } from '@/lib/utils';
343
+ import { Icon } from '@/lib/IconResolver';
344
+ import type { CodeBlockData, CodeBlockSettings } from './types';
345
+
346
+ export const CodeBlock: React.FC<{ data: CodeBlockData; settings?: CodeBlockSettings }> = ({ data, settings }) => {
347
+ const showLineNumbers = settings?.showLineNumbers ?? true;
348
+
349
+ return (
350
+ <section
351
+ style={{
352
+ '--local-surface': 'var(--card)',
353
+ '--local-text-muted': 'var(--muted-foreground)',
354
+ '--local-bg': 'var(--background)',
355
+ '--local-border': 'var(--border)',
356
+ '--local-text': 'var(--foreground)',
357
+ '--local-accent': 'var(--primary)',
358
+ '--local-radius-lg': 'var(--radius)',
359
+ } as React.CSSProperties}
360
+ className="py-16 bg-[var(--local-surface)]"
361
+ >
362
+ <div className="container mx-auto px-6 max-w-4xl">
363
+ {data.label && (
364
+ <div className="flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-[var(--local-text-muted)] mb-4" data-jp-field="label">
365
+ <Icon name="terminal" size={14} />
366
+ <span>{data.label}</span>
367
+ </div>
368
+ )}
369
+ <div className="rounded-[var(--local-radius-lg)] bg-[var(--local-bg)] border border-[var(--local-border)] overflow-hidden">
370
+ <div className="p-6 font-mono text-sm overflow-x-auto">
371
+ {data.lines.map((line, idx) => (
372
+ <div key={idx} className="flex items-start gap-4 py-1" data-jp-item-id={(line as { id?: string }).id ?? `legacy-${idx}`} data-jp-item-field="lines">
373
+ {showLineNumbers && (
374
+ <span className="select-none w-6 text-right text-[var(--local-text-muted)]/50">
375
+ {idx + 1}
376
+ </span>
377
+ )}
378
+ <span
379
+ className={cn(
380
+ line.isComment
381
+ ? 'text-[var(--local-text-muted)]/60'
382
+ : 'text-[var(--local-text)]'
383
+ )}
384
+ >
385
+ {!line.isComment && (
386
+ <span className="text-[var(--local-accent)] mr-2">$</span>
387
+ )}
388
+ {line.content}
389
+ </span>
390
+ </div>
391
+ ))}
392
+ </div>
393
+ </div>
394
+ </div>
395
+ </section>
396
+ );
397
+ };
331
398
 
332
399
  END_OF_FILE_CONTENT
333
400
  echo "Creating src/components/code-block/index.ts..."
334
401
  cat << 'END_OF_FILE_CONTENT' > "src/components/code-block/index.ts"
335
- export * from './View';
336
- export * from './schema';
337
- export * from './types';
402
+ export * from './View';
403
+ export * from './schema';
404
+ export * from './types';
338
405
 
339
406
  END_OF_FILE_CONTENT
340
407
  echo "Creating src/components/code-block/schema.ts..."
341
408
  cat << 'END_OF_FILE_CONTENT' > "src/components/code-block/schema.ts"
342
- import { z } from 'zod';
343
- import { BaseSectionData } from '@/lib/base-schemas';
344
-
345
- export const LegacyCodeLineSchema = z.object({
346
- content: z.string().describe('ui:text'),
347
- isComment: z.boolean().default(false).describe('ui:checkbox'),
348
- });
349
-
350
- export const CodeBlockSchema = BaseSectionData.extend({
351
- label: z.string().optional().describe('ui:text'),
352
- lines: z.array(LegacyCodeLineSchema).describe('ui:list'),
353
- });
354
-
355
- export const CodeBlockSettingsSchema = z.object({
356
- showLineNumbers: z.boolean().optional().describe('ui:checkbox'),
357
- });
409
+ import { z } from 'zod';
410
+ import { BaseSectionData } from '@/lib/base-schemas';
411
+
412
+ export const LegacyCodeLineSchema = z.object({
413
+ content: z.string().describe('ui:text'),
414
+ isComment: z.boolean().default(false).describe('ui:checkbox'),
415
+ });
416
+
417
+ export const CodeBlockSchema = BaseSectionData.extend({
418
+ label: z.string().optional().describe('ui:text'),
419
+ lines: z.array(LegacyCodeLineSchema).describe('ui:list'),
420
+ });
421
+
422
+ export const CodeBlockSettingsSchema = z.object({
423
+ showLineNumbers: z.boolean().optional().describe('ui:checkbox'),
424
+ });
358
425
 
359
426
  END_OF_FILE_CONTENT
360
427
  echo "Creating src/components/code-block/types.ts..."
361
428
  cat << 'END_OF_FILE_CONTENT' > "src/components/code-block/types.ts"
362
- import { z } from 'zod';
363
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
364
- import { CodeBlockSchema, CodeBlockSettingsSchema } from './schema';
365
-
366
- export type CodeBlockData = z.infer<typeof CodeBlockSchema>;
367
- export type CodeBlockSettings = z.infer<typeof BaseSectionSettingsSchema> & z.infer<typeof CodeBlockSettingsSchema>;
429
+ import { z } from 'zod';
430
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
431
+ import { CodeBlockSchema, CodeBlockSettingsSchema } from './schema';
432
+
433
+ export type CodeBlockData = z.infer<typeof CodeBlockSchema>;
434
+ export type CodeBlockSettings = z.infer<typeof BaseSectionSettingsSchema> & z.infer<typeof CodeBlockSettingsSchema>;
368
435
 
369
436
  END_OF_FILE_CONTENT
370
437
  mkdir -p "src/components/cta-banner"
371
438
  echo "Creating src/components/cta-banner/View.tsx..."
372
439
  cat << 'END_OF_FILE_CONTENT' > "src/components/cta-banner/View.tsx"
373
- import React from 'react';
374
- import { cn } from '@/lib/utils';
375
- import type { CtaBannerData, CtaBannerSettings } from './types';
376
-
377
- export const CtaBanner: React.FC<{ data: CtaBannerData; settings?: CtaBannerSettings }> = ({ data }) => {
378
- return (
379
- <section
380
- style={{
381
- '--local-bg': 'var(--background)',
382
- '--local-text': 'var(--foreground)',
383
- '--local-text-muted': 'var(--muted-foreground)',
384
- '--local-primary': 'var(--primary)',
385
- '--local-accent': 'var(--color-accent, #60a5fa)',
386
- } as React.CSSProperties}
387
- className="relative py-28 bg-[var(--local-bg)] overflow-hidden text-center"
388
- >
389
- <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[60vw] h-[60vw] bg-[radial-gradient(circle,rgba(59,130,246,0.08)_0%,transparent_60%)] pointer-events-none" />
390
- <div className="relative max-w-[1200px] mx-auto px-8">
391
- {data.label && (
392
- <div className="jp-section-label inline-flex items-center gap-2 text-[0.72rem] font-bold uppercase tracking-[0.12em] text-[var(--local-accent)] mb-4" data-jp-field="label">
393
- <span className="w-5 h-px bg-[var(--local-primary)]" />
394
- {data.label}
395
- </div>
396
- )}
397
- <h2
398
- className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-6"
399
- data-jp-field="title"
400
- >
401
- {data.title}
402
- </h2>
403
- {data.description && (
404
- <p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed mb-10" data-jp-field="description">
405
- {data.description}
406
- </p>
407
- )}
408
- {data.ctas && data.ctas.length > 0 && (
409
- <div className="flex gap-4 justify-center flex-wrap">
410
- {data.ctas.map((cta, idx) => (
411
- <a
412
- key={cta.id ?? idx}
413
- href={cta.href}
414
- data-jp-item-id={cta.id ?? `legacy-${idx}`}
415
- data-jp-item-field="ctas"
416
- className={cn(
417
- 'inline-flex items-center gap-2 px-8 py-3.5 rounded-[5px] font-semibold text-base transition-all duration-200 no-underline',
418
- cta.variant === 'primary'
419
- ? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-0.5 hover:shadow-[0_8px_30px_rgba(59,130,246,0.3)]'
420
- : 'bg-transparent text-[var(--local-text)] border border-[rgba(255,255,255,0.12)] hover:border-[rgba(255,255,255,0.3)] hover:bg-[rgba(255,255,255,0.04)]'
421
- )}
422
- >
423
- {cta.label}
424
- </a>
425
- ))}
426
- </div>
427
- )}
428
- </div>
429
- </section>
430
- );
431
- };
440
+ import React from 'react';
441
+ import { cn } from '@/lib/utils';
442
+ import type { CtaBannerData, CtaBannerSettings } from './types';
443
+
444
+ export const CtaBanner: React.FC<{ data: CtaBannerData; settings?: CtaBannerSettings }> = ({ data }) => {
445
+ return (
446
+ <section
447
+ style={{
448
+ '--local-bg': 'var(--background)',
449
+ '--local-text': 'var(--foreground)',
450
+ '--local-text-muted': 'var(--muted-foreground)',
451
+ '--local-primary': 'var(--primary)',
452
+ '--local-accent': 'var(--color-accent, #60a5fa)',
453
+ } as React.CSSProperties}
454
+ className="relative py-28 bg-[var(--local-bg)] overflow-hidden text-center"
455
+ >
456
+ <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[60vw] h-[60vw] bg-[radial-gradient(circle,rgba(59,130,246,0.08)_0%,transparent_60%)] pointer-events-none" />
457
+ <div className="relative max-w-[1200px] mx-auto px-8">
458
+ {data.label && (
459
+ <div className="jp-section-label inline-flex items-center gap-2 text-[0.72rem] font-bold uppercase tracking-[0.12em] text-[var(--local-accent)] mb-4" data-jp-field="label">
460
+ <span className="w-5 h-px bg-[var(--local-primary)]" />
461
+ {data.label}
462
+ </div>
463
+ )}
464
+ <h2
465
+ className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-6"
466
+ data-jp-field="title"
467
+ >
468
+ {data.title}
469
+ </h2>
470
+ {data.description && (
471
+ <p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed mb-10" data-jp-field="description">
472
+ {data.description}
473
+ </p>
474
+ )}
475
+ {data.ctas && data.ctas.length > 0 && (
476
+ <div className="flex gap-4 justify-center flex-wrap">
477
+ {data.ctas.map((cta, idx) => (
478
+ <a
479
+ key={cta.id ?? idx}
480
+ href={cta.href}
481
+ data-jp-item-id={cta.id ?? `legacy-${idx}`}
482
+ data-jp-item-field="ctas"
483
+ className={cn(
484
+ 'inline-flex items-center gap-2 px-8 py-3.5 rounded-[5px] font-semibold text-base transition-all duration-200 no-underline',
485
+ cta.variant === 'primary'
486
+ ? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-0.5 hover:shadow-[0_8px_30px_rgba(59,130,246,0.3)]'
487
+ : 'bg-transparent text-[var(--local-text)] border border-[rgba(255,255,255,0.12)] hover:border-[rgba(255,255,255,0.3)] hover:bg-[rgba(255,255,255,0.04)]'
488
+ )}
489
+ >
490
+ {cta.label}
491
+ </a>
492
+ ))}
493
+ </div>
494
+ )}
495
+ </div>
496
+ </section>
497
+ );
498
+ };
432
499
 
433
500
  END_OF_FILE_CONTENT
434
501
  echo "Creating src/components/cta-banner/index.ts..."
435
502
  cat << 'END_OF_FILE_CONTENT' > "src/components/cta-banner/index.ts"
436
- export * from './View';
437
- export * from './schema';
438
- export * from './types';
503
+ export * from './View';
504
+ export * from './schema';
505
+ export * from './types';
439
506
 
440
507
  END_OF_FILE_CONTENT
441
508
  echo "Creating src/components/cta-banner/schema.ts..."
442
509
  cat << 'END_OF_FILE_CONTENT' > "src/components/cta-banner/schema.ts"
443
- import { z } from 'zod';
444
- import { BaseSectionData, CtaSchema } from '@/lib/base-schemas';
445
-
446
- export const CtaBannerSchema = BaseSectionData.extend({
447
- label: z.string().optional().describe('ui:text'),
448
- title: z.string().describe('ui:text'),
449
- description: z.string().optional().describe('ui:textarea'),
450
- ctas: z.array(CtaSchema).optional().describe('ui:list'),
451
- });
510
+ import { z } from 'zod';
511
+ import { BaseSectionData, CtaSchema } from '@/lib/base-schemas';
512
+
513
+ export const CtaBannerSchema = BaseSectionData.extend({
514
+ label: z.string().optional().describe('ui:text'),
515
+ title: z.string().describe('ui:text'),
516
+ description: z.string().optional().describe('ui:textarea'),
517
+ ctas: z.array(CtaSchema).optional().describe('ui:list'),
518
+ });
452
519
 
453
520
  END_OF_FILE_CONTENT
454
521
  echo "Creating src/components/cta-banner/types.ts..."
455
522
  cat << 'END_OF_FILE_CONTENT' > "src/components/cta-banner/types.ts"
456
- import { z } from 'zod';
457
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
458
- import { CtaBannerSchema } from './schema';
459
-
460
- export type CtaBannerData = z.infer<typeof CtaBannerSchema>;
461
- export type CtaBannerSettings = z.infer<typeof BaseSectionSettingsSchema>;
523
+ import { z } from 'zod';
524
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
525
+ import { CtaBannerSchema } from './schema';
526
+
527
+ export type CtaBannerData = z.infer<typeof CtaBannerSchema>;
528
+ export type CtaBannerSettings = z.infer<typeof BaseSectionSettingsSchema>;
462
529
 
463
530
  END_OF_FILE_CONTENT
464
531
  mkdir -p "src/components/feature-grid"
465
532
  echo "Creating src/components/feature-grid/View.tsx..."
466
533
  cat << 'END_OF_FILE_CONTENT' > "src/components/feature-grid/View.tsx"
467
- import React from 'react';
468
- import { cn } from '@/lib/utils';
469
- import { Icon } from '@/lib/IconResolver';
470
- import type { FeatureGridData, FeatureGridSettings } from './types';
471
-
472
- const columnsMap: Record<2 | 3 | 4, string> = {
473
- 2: 'md:grid-cols-2',
474
- 3: 'md:grid-cols-3',
475
- 4: 'md:grid-cols-4',
476
- };
477
-
478
- export const FeatureGrid: React.FC<{ data: FeatureGridData; settings?: FeatureGridSettings }> = ({ data, settings }) => {
479
- const colKey = settings?.columns ?? 3;
480
- const cols = (colKey === 2 || colKey === 3 || colKey === 4) ? columnsMap[colKey] : columnsMap[3];
481
- const isBordered = settings?.cardStyle === 'bordered';
482
-
483
- const localStyles = {
484
- '--local-bg': 'var(--background)',
485
- '--local-text': 'var(--foreground)',
486
- '--local-text-muted': 'var(--muted-foreground)',
487
- '--local-surface': 'var(--card)',
488
- '--local-surface-alt': 'var(--muted)',
489
- '--local-border': 'var(--border)',
490
- '--local-radius-lg': 'var(--radius)',
491
- '--local-radius-md': 'calc(var(--radius) - 2px)',
492
- } as React.CSSProperties;
493
-
494
- return (
495
- <section style={localStyles} className="py-20 bg-[var(--local-bg)] relative z-0">
496
- <div className="container mx-auto px-6">
497
- <h2 className="text-3xl md:text-4xl font-bold text-center text-[var(--local-text)] mb-16" data-jp-field="sectionTitle">
498
- {data.sectionTitle}
499
- </h2>
500
- <div className={cn('grid grid-cols-1 gap-6', cols)}>
501
- {data.cards.map((card, idx) => (
502
- <div
503
- key={card.id ?? idx}
504
- className={cn(
505
- 'p-6 rounded-[var(--local-radius-lg)] bg-[var(--local-surface)]',
506
- isBordered && 'border border-[var(--local-border)]'
507
- )}
508
- data-jp-item-id={card.id ?? `legacy-${idx}`}
509
- data-jp-item-field="cards"
510
- >
511
- {card.icon && (
512
- <div className="w-10 h-10 rounded-[var(--local-radius-md)] bg-[var(--local-surface-alt)] flex items-center justify-center mb-4">
513
- <Icon name={card.icon} size={20} className="text-[var(--local-text-muted)]" />
514
- </div>
515
- )}
516
- <h3 className="text-lg font-semibold text-[var(--local-text)] mb-2">
517
- {card.emoji && <span className="mr-2">{card.emoji}</span>}
518
- {card.title}
519
- </h3>
520
- <p className="text-sm text-[var(--local-text-muted)] leading-relaxed">
521
- {card.description}
522
- </p>
523
- </div>
524
- ))}
525
- </div>
526
- </div>
527
- </section>
528
- );
529
- };
534
+ import React from 'react';
535
+ import { cn } from '@/lib/utils';
536
+ import { Icon } from '@/lib/IconResolver';
537
+ import type { FeatureGridData, FeatureGridSettings } from './types';
538
+
539
+ const columnsMap: Record<2 | 3 | 4, string> = {
540
+ 2: 'md:grid-cols-2',
541
+ 3: 'md:grid-cols-3',
542
+ 4: 'md:grid-cols-4',
543
+ };
544
+
545
+ export const FeatureGrid: React.FC<{ data: FeatureGridData; settings?: FeatureGridSettings }> = ({ data, settings }) => {
546
+ const colKey = settings?.columns ?? 3;
547
+ const cols = (colKey === 2 || colKey === 3 || colKey === 4) ? columnsMap[colKey] : columnsMap[3];
548
+ const isBordered = settings?.cardStyle === 'bordered';
549
+
550
+ const localStyles = {
551
+ '--local-bg': 'var(--background)',
552
+ '--local-text': 'var(--foreground)',
553
+ '--local-text-muted': 'var(--muted-foreground)',
554
+ '--local-surface': 'var(--card)',
555
+ '--local-surface-alt': 'var(--muted)',
556
+ '--local-border': 'var(--border)',
557
+ '--local-radius-lg': 'var(--radius)',
558
+ '--local-radius-md': 'calc(var(--radius) - 2px)',
559
+ } as React.CSSProperties;
560
+
561
+ return (
562
+ <section style={localStyles} className="py-20 bg-[var(--local-bg)] relative z-0">
563
+ <div className="container mx-auto px-6">
564
+ <h2 className="text-3xl md:text-4xl font-bold text-center text-[var(--local-text)] mb-16" data-jp-field="sectionTitle">
565
+ {data.sectionTitle}
566
+ </h2>
567
+ <div className={cn('grid grid-cols-1 gap-6', cols)}>
568
+ {data.cards.map((card, idx) => (
569
+ <div
570
+ key={card.id ?? idx}
571
+ className={cn(
572
+ 'p-6 rounded-[var(--local-radius-lg)] bg-[var(--local-surface)]',
573
+ isBordered && 'border border-[var(--local-border)]'
574
+ )}
575
+ data-jp-item-id={card.id ?? `legacy-${idx}`}
576
+ data-jp-item-field="cards"
577
+ >
578
+ {card.icon && (
579
+ <div className="w-10 h-10 rounded-[var(--local-radius-md)] bg-[var(--local-surface-alt)] flex items-center justify-center mb-4">
580
+ <Icon name={card.icon} size={20} className="text-[var(--local-text-muted)]" />
581
+ </div>
582
+ )}
583
+ <h3 className="text-lg font-semibold text-[var(--local-text)] mb-2">
584
+ {card.emoji && <span className="mr-2">{card.emoji}</span>}
585
+ {card.title}
586
+ </h3>
587
+ <p className="text-sm text-[var(--local-text-muted)] leading-relaxed">
588
+ {card.description}
589
+ </p>
590
+ </div>
591
+ ))}
592
+ </div>
593
+ </div>
594
+ </section>
595
+ );
596
+ };
530
597
 
531
598
  END_OF_FILE_CONTENT
532
599
  echo "Creating src/components/feature-grid/index.ts..."
533
600
  cat << 'END_OF_FILE_CONTENT' > "src/components/feature-grid/index.ts"
534
- export * from './View';
535
- export * from './schema';
536
- export * from './types';
601
+ export * from './View';
602
+ export * from './schema';
603
+ export * from './types';
537
604
 
538
605
  END_OF_FILE_CONTENT
539
606
  echo "Creating src/components/feature-grid/schema.ts..."
540
607
  cat << 'END_OF_FILE_CONTENT' > "src/components/feature-grid/schema.ts"
541
- import { z } from 'zod';
542
- import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
543
-
544
- export const FeatureCardSchema = BaseArrayItem.extend({
545
- icon: z.string().optional().describe('ui:icon-picker'),
546
- emoji: z.string().optional().describe('ui:text'),
547
- title: z.string().describe('ui:text'),
548
- description: z.string().describe('ui:textarea'),
549
- });
550
-
551
- export const FeatureGridSchema = BaseSectionData.extend({
552
- sectionTitle: z.string().describe('ui:text'),
553
- cards: z.array(FeatureCardSchema).describe('ui:list'),
554
- });
555
-
556
- export const FeatureGridSettingsSchema = z.object({
557
- columns: z.union([z.literal(2), z.literal(3), z.literal(4)]).optional().describe('ui:number'),
558
- cardStyle: z.enum(['plain', 'bordered']).optional().describe('ui:select'),
559
- });
608
+ import { z } from 'zod';
609
+ import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
610
+
611
+ export const FeatureCardSchema = BaseArrayItem.extend({
612
+ icon: z.string().optional().describe('ui:icon-picker'),
613
+ emoji: z.string().optional().describe('ui:text'),
614
+ title: z.string().describe('ui:text'),
615
+ description: z.string().describe('ui:textarea'),
616
+ });
617
+
618
+ export const FeatureGridSchema = BaseSectionData.extend({
619
+ sectionTitle: z.string().describe('ui:text'),
620
+ cards: z.array(FeatureCardSchema).describe('ui:list'),
621
+ });
622
+
623
+ export const FeatureGridSettingsSchema = z.object({
624
+ columns: z.union([z.literal(2), z.literal(3), z.literal(4)]).optional().describe('ui:number'),
625
+ cardStyle: z.enum(['plain', 'bordered']).optional().describe('ui:select'),
626
+ });
560
627
 
561
628
  END_OF_FILE_CONTENT
562
629
  echo "Creating src/components/feature-grid/types.ts..."
563
630
  cat << 'END_OF_FILE_CONTENT' > "src/components/feature-grid/types.ts"
564
- import { z } from 'zod';
565
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
566
- import { FeatureGridSchema, FeatureGridSettingsSchema } from './schema';
567
-
568
- export type FeatureGridData = z.infer<typeof FeatureGridSchema>;
569
- export type FeatureGridSettings = z.infer<typeof BaseSectionSettingsSchema> & z.infer<typeof FeatureGridSettingsSchema>;
631
+ import { z } from 'zod';
632
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
633
+ import { FeatureGridSchema, FeatureGridSettingsSchema } from './schema';
634
+
635
+ export type FeatureGridData = z.infer<typeof FeatureGridSchema>;
636
+ export type FeatureGridSettings = z.infer<typeof BaseSectionSettingsSchema> & z.infer<typeof FeatureGridSettingsSchema>;
570
637
 
571
638
  END_OF_FILE_CONTENT
572
639
  mkdir -p "src/components/footer"
573
640
  echo "Creating src/components/footer/View.tsx..."
574
641
  cat << 'END_OF_FILE_CONTENT' > "src/components/footer/View.tsx"
575
- import React from 'react';
576
- import type { FooterData, FooterSettings } from './types';
577
-
578
- export const Footer: React.FC<{ data: FooterData; settings?: FooterSettings }> = ({ data }) => {
579
- return (
580
- <footer
581
- style={{
582
- '--local-bg': 'var(--background)',
583
- '--local-text': 'var(--foreground)',
584
- '--local-text-muted': 'var(--muted-foreground)',
585
- '--local-accent': 'var(--color-accent, #60a5fa)',
586
- '--local-border': 'rgba(255,255,255,0.05)',
587
- } as React.CSSProperties}
588
- className="py-12 border-t border-[var(--local-border)] bg-[var(--local-bg)] relative z-0"
589
- >
590
- <div className="max-w-[1200px] mx-auto px-8">
591
- <div className="flex flex-col md:flex-row items-center justify-between gap-4">
592
- <div className="flex items-center gap-2 font-bold text-[0.9rem] text-[var(--local-text-muted)]" data-jp-field="brandText">
593
- {data.brandText}
594
- {data.brandHighlight && (
595
- <span className="text-[var(--local-accent)]" data-jp-field="brandHighlight">{data.brandHighlight}</span>
596
- )}
597
- </div>
598
- {data.links && data.links.length > 0 && (
599
- <nav className="flex gap-6">
600
- {data.links.map((link, idx) => (
601
- <a
602
- key={idx}
603
- href={link.href}
604
- className="text-[0.82rem] text-[var(--local-text-muted)] hover:text-[var(--local-accent)] transition-colors no-underline"
605
- data-jp-item-id={(link as { id?: string }).id ?? `legacy-${idx}`}
606
- data-jp-item-field="links"
607
- >
608
- {link.label}
609
- </a>
610
- ))}
611
- </nav>
612
- )}
613
- <div className="text-[0.8rem] text-[var(--local-text-muted)] opacity-60" data-jp-field="copyright">
614
- {data.copyright}
615
- </div>
616
- </div>
617
- </div>
618
- </footer>
619
- );
620
- };
642
+ import React from 'react';
643
+ import type { FooterData, FooterSettings } from './types';
644
+
645
+ export const Footer: React.FC<{ data: FooterData; settings?: FooterSettings }> = ({ data }) => {
646
+ return (
647
+ <footer
648
+ style={{
649
+ '--local-bg': 'var(--background)',
650
+ '--local-text': 'var(--foreground)',
651
+ '--local-text-muted': 'var(--muted-foreground)',
652
+ '--local-accent': 'var(--color-accent, #60a5fa)',
653
+ '--local-border': 'rgba(255,255,255,0.05)',
654
+ } as React.CSSProperties}
655
+ className="py-12 border-t border-[var(--local-border)] bg-[var(--local-bg)] relative z-0"
656
+ >
657
+ <div className="max-w-[1200px] mx-auto px-8">
658
+ <div className="flex flex-col md:flex-row items-center justify-between gap-4">
659
+ <div className="flex items-center gap-2 font-bold text-[0.9rem] text-[var(--local-text-muted)]" data-jp-field="brandText">
660
+ {data.brandText}
661
+ {data.brandHighlight && (
662
+ <span className="text-[var(--local-accent)]" data-jp-field="brandHighlight">{data.brandHighlight}</span>
663
+ )}
664
+ </div>
665
+ {data.links && data.links.length > 0 && (
666
+ <nav className="flex gap-6">
667
+ {data.links.map((link, idx) => (
668
+ <a
669
+ key={idx}
670
+ href={link.href}
671
+ className="text-[0.82rem] text-[var(--local-text-muted)] hover:text-[var(--local-accent)] transition-colors no-underline"
672
+ data-jp-item-id={(link as { id?: string }).id ?? `legacy-${idx}`}
673
+ data-jp-item-field="links"
674
+ >
675
+ {link.label}
676
+ </a>
677
+ ))}
678
+ </nav>
679
+ )}
680
+ <div className="text-[0.8rem] text-[var(--local-text-muted)] opacity-60" data-jp-field="copyright">
681
+ {data.copyright}
682
+ </div>
683
+ </div>
684
+ </div>
685
+ </footer>
686
+ );
687
+ };
621
688
 
622
689
  END_OF_FILE_CONTENT
623
690
  echo "Creating src/components/footer/index.ts..."
624
691
  cat << 'END_OF_FILE_CONTENT' > "src/components/footer/index.ts"
625
- export * from './View';
626
- export * from './schema';
627
- export * from './types';
692
+ export * from './View';
693
+ export * from './schema';
694
+ export * from './types';
628
695
 
629
696
  END_OF_FILE_CONTENT
630
697
  echo "Creating src/components/footer/schema.ts..."
631
698
  cat << 'END_OF_FILE_CONTENT' > "src/components/footer/schema.ts"
632
- import { z } from 'zod';
633
-
634
- export const FooterSchema = z.object({
635
- brandText: z.string().describe('ui:text'),
636
- brandHighlight: z.string().optional().describe('ui:text'),
637
- copyright: z.string().describe('ui:text'),
638
- links: z.array(z.object({
639
- label: z.string().describe('ui:text'),
640
- href: z.string().describe('ui:text'),
641
- })).optional().describe('ui:list'),
642
- });
643
-
644
- export const FooterSettingsSchema = z.object({
645
- showLogo: z.boolean().optional().describe('ui:checkbox'),
646
- });
699
+ import { z } from 'zod';
700
+
701
+ export const FooterSchema = z.object({
702
+ brandText: z.string().describe('ui:text'),
703
+ brandHighlight: z.string().optional().describe('ui:text'),
704
+ copyright: z.string().describe('ui:text'),
705
+ links: z.array(z.object({
706
+ label: z.string().describe('ui:text'),
707
+ href: z.string().describe('ui:text'),
708
+ })).optional().describe('ui:list'),
709
+ });
710
+
711
+ export const FooterSettingsSchema = z.object({
712
+ showLogo: z.boolean().optional().describe('ui:checkbox'),
713
+ });
647
714
 
648
715
  END_OF_FILE_CONTENT
649
716
  echo "Creating src/components/footer/types.ts..."
650
717
  cat << 'END_OF_FILE_CONTENT' > "src/components/footer/types.ts"
651
- import { z } from 'zod';
652
- import { FooterSchema, FooterSettingsSchema } from './schema';
653
-
654
- export type FooterData = z.infer<typeof FooterSchema>;
655
- export type FooterSettings = z.infer<typeof FooterSettingsSchema>;
718
+ import { z } from 'zod';
719
+ import { FooterSchema, FooterSettingsSchema } from './schema';
720
+
721
+ export type FooterData = z.infer<typeof FooterSchema>;
722
+ export type FooterSettings = z.infer<typeof FooterSettingsSchema>;
656
723
 
657
724
  END_OF_FILE_CONTENT
658
725
  mkdir -p "src/components/header"
659
726
  echo "Creating src/components/header/View.tsx..."
660
727
  cat << 'END_OF_FILE_CONTENT' > "src/components/header/View.tsx"
661
- import React, { useState, useEffect } from 'react';
662
- import { cn } from '@/lib/utils';
663
- import type { MenuItem } from '@jsonpages/core';
664
- import type { HeaderData, HeaderSettings } from './types';
665
-
666
- export const Header: React.FC<{
667
- data: HeaderData;
668
- settings?: HeaderSettings;
669
- menu: MenuItem[];
670
- }> = ({ data, menu }) => {
671
- const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
672
- const [scrolled, setScrolled] = useState(false);
673
-
674
- useEffect(() => {
675
- const handleScroll = () => setScrolled(window.scrollY > 40);
676
- window.addEventListener('scroll', handleScroll, { passive: true });
677
- return () => window.removeEventListener('scroll', handleScroll);
678
- }, []);
679
-
680
- return (
681
- <header
682
- style={{
683
- '--local-bg': 'rgba(6,13,27,0.92)',
684
- '--local-text': 'var(--foreground)',
685
- '--local-text-muted': 'var(--muted-foreground)',
686
- '--local-primary': 'var(--primary)',
687
- '--local-accent': 'var(--color-accent, #60a5fa)',
688
- '--local-border': 'rgba(59,130,246,0.08)',
689
- } as React.CSSProperties}
690
- className={cn(
691
- 'w-full py-4 transition-all duration-300 z-0',
692
- scrolled
693
- ? 'bg-[var(--local-bg)] backdrop-blur-[20px] border-b border-[var(--local-border)]'
694
- : 'bg-transparent border-b border-transparent'
695
- )}
696
- >
697
- <div className="max-w-[1200px] mx-auto px-8 flex justify-between items-center">
698
- <a
699
- href="/"
700
- className="flex items-center gap-2.5 no-underline font-bold text-xl tracking-tight text-[var(--local-text)]"
701
- >
702
- {data.logoIconText && (
703
- <div className="w-8 h-8 rounded-md bg-gradient-to-br from-[var(--local-primary)] to-[var(--local-accent)] flex items-center justify-center font-mono text-[0.8rem] font-bold text-[var(--background)]" data-jp-field="logoIconText">
704
- {data.logoIconText}
705
- </div>
706
- )}
707
- <span data-jp-field="logoText">
708
- {data.logoText}
709
- {data.logoHighlight && (
710
- <span className="text-[var(--local-accent)]" data-jp-field="logoHighlight">{data.logoHighlight}</span>
711
- )}
712
- </span>
713
- </a>
714
-
715
- <nav className="hidden md:flex items-center gap-10">
716
- {menu.map((item, idx) => (
717
- <a
718
- key={(item as { id?: string }).id ?? idx}
719
- href={item.href}
720
- data-jp-item-id={(item as { id?: string }).id ?? `legacy-${idx}`}
721
- data-jp-item-field="links"
722
- target={item.external ? '_blank' : undefined}
723
- rel={item.external ? 'noopener noreferrer' : undefined}
724
- className={cn(
725
- 'no-underline text-sm font-medium transition-colors',
726
- item.isCta
727
- ? 'bg-[var(--local-primary)] text-white px-5 py-2 rounded-lg font-semibold hover:brightness-110 hover:-translate-y-px'
728
- : 'text-[var(--local-text-muted)] hover:text-[var(--local-text)]'
729
- )}
730
- >
731
- {item.label}
732
- </a>
733
- ))}
734
- </nav>
735
-
736
- <button
737
- type="button"
738
- className="md:hidden p-2 text-[var(--local-text-muted)] hover:text-[var(--local-text)]"
739
- onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
740
- aria-label={mobileMenuOpen ? 'Close menu' : 'Open menu'}
741
- >
742
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
743
- {mobileMenuOpen ? (
744
- <><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></>
745
- ) : (
746
- <><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="18" x2="21" y2="18" /></>
747
- )}
748
- </svg>
749
- </button>
750
- </div>
751
-
752
- {mobileMenuOpen && (
753
- <nav className="md:hidden border-t border-[var(--local-border)] bg-[var(--local-bg)] backdrop-blur-[20px]">
754
- <div className="max-w-[1200px] mx-auto px-8 py-4 flex flex-col gap-4">
755
- {menu.map((item, idx) => (
756
- <a
757
- key={(item as { id?: string }).id ?? idx}
758
- href={item.href}
759
- className="text-base font-medium text-[var(--local-text-muted)] hover:text-[var(--local-text)] transition-colors py-2 no-underline"
760
- onClick={() => setMobileMenuOpen(false)}
761
- data-jp-item-id={(item as { id?: string }).id ?? `legacy-${idx}`}
762
- data-jp-item-field="links"
763
- >
764
- {item.label}
765
- </a>
766
- ))}
767
- </div>
768
- </nav>
769
- )}
770
- </header>
771
- );
772
- };
728
+ import React, { useState, useEffect } from 'react';
729
+ import { cn } from '@/lib/utils';
730
+ import type { MenuItem } from '@jsonpages/core';
731
+ import type { HeaderData, HeaderSettings } from './types';
732
+
733
+ export const Header: React.FC<{
734
+ data: HeaderData;
735
+ settings?: HeaderSettings;
736
+ menu: MenuItem[];
737
+ }> = ({ data, menu }) => {
738
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
739
+ const [scrolled, setScrolled] = useState(false);
740
+
741
+ useEffect(() => {
742
+ const handleScroll = () => setScrolled(window.scrollY > 40);
743
+ window.addEventListener('scroll', handleScroll, { passive: true });
744
+ return () => window.removeEventListener('scroll', handleScroll);
745
+ }, []);
746
+
747
+ return (
748
+ <header
749
+ style={{
750
+ '--local-bg': 'rgba(6,13,27,0.92)',
751
+ '--local-text': 'var(--foreground)',
752
+ '--local-text-muted': 'var(--muted-foreground)',
753
+ '--local-primary': 'var(--primary)',
754
+ '--local-accent': 'var(--color-accent, #60a5fa)',
755
+ '--local-border': 'rgba(59,130,246,0.08)',
756
+ } as React.CSSProperties}
757
+ className={cn(
758
+ 'w-full py-4 transition-all duration-300 z-0',
759
+ scrolled
760
+ ? 'bg-[var(--local-bg)] backdrop-blur-[20px] border-b border-[var(--local-border)]'
761
+ : 'bg-transparent border-b border-transparent'
762
+ )}
763
+ >
764
+ <div className="max-w-[1200px] mx-auto px-8 flex justify-between items-center">
765
+ <a
766
+ href="/"
767
+ className="flex items-center gap-2.5 no-underline font-bold text-xl tracking-tight text-[var(--local-text)]"
768
+ >
769
+ {data.logoIconText && (
770
+ <div className="w-8 h-8 rounded-md bg-gradient-to-br from-[var(--local-primary)] to-[var(--local-accent)] flex items-center justify-center font-mono text-[0.8rem] font-bold text-[var(--background)]" data-jp-field="logoIconText">
771
+ {data.logoIconText}
772
+ </div>
773
+ )}
774
+ <span data-jp-field="logoText">
775
+ {data.logoText}
776
+ {data.logoHighlight && (
777
+ <span className="text-[var(--local-accent)]" data-jp-field="logoHighlight">{data.logoHighlight}</span>
778
+ )}
779
+ </span>
780
+ </a>
781
+
782
+ <nav className="hidden md:flex items-center gap-10">
783
+ {menu.map((item, idx) => (
784
+ <a
785
+ key={(item as { id?: string }).id ?? idx}
786
+ href={item.href}
787
+ data-jp-item-id={(item as { id?: string }).id ?? `legacy-${idx}`}
788
+ data-jp-item-field="links"
789
+ target={item.external ? '_blank' : undefined}
790
+ rel={item.external ? 'noopener noreferrer' : undefined}
791
+ className={cn(
792
+ 'no-underline text-sm font-medium transition-colors',
793
+ item.isCta
794
+ ? 'bg-[var(--local-primary)] text-white px-5 py-2 rounded-lg font-semibold hover:brightness-110 hover:-translate-y-px'
795
+ : 'text-[var(--local-text-muted)] hover:text-[var(--local-text)]'
796
+ )}
797
+ >
798
+ {item.label}
799
+ </a>
800
+ ))}
801
+ </nav>
802
+
803
+ <button
804
+ type="button"
805
+ className="md:hidden p-2 text-[var(--local-text-muted)] hover:text-[var(--local-text)]"
806
+ onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
807
+ aria-label={mobileMenuOpen ? 'Close menu' : 'Open menu'}
808
+ >
809
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
810
+ {mobileMenuOpen ? (
811
+ <><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></>
812
+ ) : (
813
+ <><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="18" x2="21" y2="18" /></>
814
+ )}
815
+ </svg>
816
+ </button>
817
+ </div>
818
+
819
+ {mobileMenuOpen && (
820
+ <nav className="md:hidden border-t border-[var(--local-border)] bg-[var(--local-bg)] backdrop-blur-[20px]">
821
+ <div className="max-w-[1200px] mx-auto px-8 py-4 flex flex-col gap-4">
822
+ {menu.map((item, idx) => (
823
+ <a
824
+ key={(item as { id?: string }).id ?? idx}
825
+ href={item.href}
826
+ className="text-base font-medium text-[var(--local-text-muted)] hover:text-[var(--local-text)] transition-colors py-2 no-underline"
827
+ onClick={() => setMobileMenuOpen(false)}
828
+ data-jp-item-id={(item as { id?: string }).id ?? `legacy-${idx}`}
829
+ data-jp-item-field="links"
830
+ >
831
+ {item.label}
832
+ </a>
833
+ ))}
834
+ </div>
835
+ </nav>
836
+ )}
837
+ </header>
838
+ );
839
+ };
773
840
 
774
841
  END_OF_FILE_CONTENT
775
842
  echo "Creating src/components/header/index.ts..."
@@ -827,780 +894,868 @@ END_OF_FILE_CONTENT
827
894
  mkdir -p "src/components/hero"
828
895
  echo "Creating src/components/hero/View.tsx..."
829
896
  cat << 'END_OF_FILE_CONTENT' > "src/components/hero/View.tsx"
897
+ import React from 'react';
898
+ import { cn } from '@/lib/utils';
899
+ import type { HeroData, HeroSettings } from './types';
900
+
901
+ export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ data }) => {
902
+ return (
903
+ <section
904
+ style={{
905
+ '--local-bg': 'var(--background)',
906
+ '--local-text': 'var(--foreground)',
907
+ '--local-text-muted': 'var(--muted-foreground)',
908
+ '--local-primary': 'var(--primary)',
909
+ '--local-accent': 'var(--color-accent, #60a5fa)',
910
+ '--local-cyan': 'var(--color-secondary, #22d3ee)',
911
+ '--local-border': 'var(--border)',
912
+ } as React.CSSProperties}
913
+ className="jp-hero relative min-h-screen flex items-center overflow-hidden pt-24 bg-[var(--local-bg)]"
914
+ >
915
+ <div className="absolute -top-[40%] -right-[20%] w-[70vw] h-[70vw] rounded-full bg-[radial-gradient(circle,rgba(59,130,246,0.06)_0%,transparent_70%)] pointer-events-none" />
916
+ <div className="absolute -bottom-[10%] -left-[10%] w-[50vw] h-[50vw] rounded-full bg-[radial-gradient(circle,rgba(34,211,238,0.03)_0%,transparent_60%)] pointer-events-none" />
917
+
918
+ <div className="relative max-w-[1200px] mx-auto px-8 w-full">
919
+ <div className="max-w-[820px]">
920
+ {data.badge && (
921
+ <div className="inline-flex items-center gap-2 bg-[rgba(59,130,246,0.08)] border border-[rgba(59,130,246,0.2)] px-4 py-1.5 rounded-full text-[0.78rem] font-semibold text-[var(--local-accent)] mb-8 tracking-wide jp-animate-in" data-jp-field="badge">
922
+ <span className="w-1.5 h-1.5 rounded-full bg-[var(--local-accent)] jp-pulse-dot" />
923
+ {data.badge}
924
+ </div>
925
+ )}
926
+ <h1
927
+ className="font-display text-[clamp(2.8rem,6vw,4.8rem)] font-black text-[var(--local-text)] leading-[1.08] tracking-tight mb-6 jp-animate-in jp-d1"
928
+ data-jp-field="title"
929
+ >
930
+ {data.title}
931
+ {data.titleHighlight && (
932
+ <>
933
+ <br />
934
+ <em className="not-italic bg-gradient-to-br from-[var(--local-accent)] to-[var(--local-cyan)] bg-clip-text text-transparent" data-jp-field="titleHighlight">
935
+ {data.titleHighlight}
936
+ </em>
937
+ </>
938
+ )}
939
+ </h1>
940
+ {data.description && (
941
+ <p className="text-xl text-[var(--local-text-muted)] max-w-[600px] leading-relaxed mb-10 jp-animate-in jp-d2" data-jp-field="description">
942
+ {data.description}
943
+ </p>
944
+ )}
945
+ {data.ctas && data.ctas.length > 0 && (
946
+ <div className="flex gap-4 flex-wrap jp-animate-in jp-d3">
947
+ {data.ctas.map((cta, idx) => (
948
+ <a
949
+ key={cta.id ?? idx}
950
+ href={cta.href}
951
+ data-jp-item-id={cta.id ?? `legacy-${idx}`}
952
+ data-jp-item-field="ctas"
953
+ className={cn(
954
+ 'inline-flex items-center gap-2 px-8 py-3.5 rounded-[5px] font-semibold text-base transition-all duration-200 no-underline',
955
+ cta.variant === 'primary'
956
+ ? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-0.5 hover:shadow-[0_8px_30px_rgba(59,130,246,0.3)]'
957
+ : 'bg-transparent text-[var(--local-text)] border border-[rgba(255,255,255,0.12)] hover:border-[rgba(255,255,255,0.3)] hover:bg-[rgba(255,255,255,0.04)]'
958
+ )}
959
+ >
960
+ {cta.label}
961
+ </a>
962
+ ))}
963
+ </div>
964
+ )}
965
+ {data.metrics && data.metrics.length > 0 && (
966
+ <div className="flex gap-12 mt-16 pt-12 border-t border-[rgba(255,255,255,0.06)] flex-wrap jp-animate-in jp-d4">
967
+ {data.metrics.map((metric, idx) => (
968
+ <div
969
+ key={(metric as { id?: string }).id ?? idx}
970
+ data-jp-item-id={(metric as { id?: string }).id ?? `legacy-${idx}`}
971
+ data-jp-item-field="metrics"
972
+ >
973
+ <div className="text-[2rem] font-bold text-[var(--local-text)] font-display">
974
+ {metric.val}
975
+ </div>
976
+ <div className="text-[0.82rem] text-[var(--muted-foreground)] mt-0.5 opacity-70">
977
+ {metric.label}
978
+ </div>
979
+ </div>
980
+ ))}
981
+ </div>
982
+ )}
983
+ </div>
984
+ </div>
985
+ </section>
986
+ );
987
+ };
988
+
989
+ END_OF_FILE_CONTENT
990
+ echo "Creating src/components/hero/index.ts..."
991
+ cat << 'END_OF_FILE_CONTENT' > "src/components/hero/index.ts"
992
+ export * from './View';
993
+ export * from './schema';
994
+ export * from './types';
995
+
996
+ END_OF_FILE_CONTENT
997
+ echo "Creating src/components/hero/schema.ts..."
998
+ cat << 'END_OF_FILE_CONTENT' > "src/components/hero/schema.ts"
999
+ import { z } from 'zod';
1000
+ import { BaseSectionData, CtaSchema } from '@/lib/base-schemas';
1001
+
1002
+ const HeroMetricSchema = z.object({
1003
+ val: z.string().describe('ui:text'),
1004
+ label: z.string().describe('ui:text'),
1005
+ });
1006
+
1007
+ export const HeroSchema = BaseSectionData.extend({
1008
+ badge: z.string().optional().describe('ui:text'),
1009
+ title: z.string().describe('ui:text'),
1010
+ titleHighlight: z.string().optional().describe('ui:text'),
1011
+ description: z.string().optional().describe('ui:textarea'),
1012
+ ctas: z.array(CtaSchema).optional().describe('ui:list'),
1013
+ metrics: z.array(HeroMetricSchema).optional().describe('ui:list'),
1014
+ });
1015
+
1016
+ END_OF_FILE_CONTENT
1017
+ echo "Creating src/components/hero/types.ts..."
1018
+ cat << 'END_OF_FILE_CONTENT' > "src/components/hero/types.ts"
1019
+ import { z } from 'zod';
1020
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
1021
+ import { HeroSchema } from './schema';
1022
+
1023
+ export type HeroData = z.infer<typeof HeroSchema>;
1024
+ export type HeroSettings = z.infer<typeof BaseSectionSettingsSchema>;
1025
+
1026
+ END_OF_FILE_CONTENT
1027
+ mkdir -p "src/components/image-break"
1028
+ echo "Creating src/components/image-break/View.tsx..."
1029
+ cat << 'END_OF_FILE_CONTENT' > "src/components/image-break/View.tsx"
830
1030
  import React from 'react';
831
- import { cn } from '@/lib/utils';
832
- import type { HeroData, HeroSettings } from './types';
1031
+ import { resolveAssetUrl, useConfig } from '@jsonpages/core';
1032
+ import type { ImageBreakData, ImageBreakSettings } from './types';
1033
+
1034
+ export const ImageBreak: React.FC<{ data: ImageBreakData; settings?: ImageBreakSettings }> = ({ data }) => {
1035
+ const { tenantId = 'default' } = useConfig();
1036
+ const imageUrl = data.image?.url ? resolveAssetUrl(data.image.url, tenantId) : '';
833
1037
 
834
- export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ data }) => {
835
1038
  return (
836
1039
  <section
837
1040
  style={{
838
1041
  '--local-bg': 'var(--background)',
839
1042
  '--local-text': 'var(--foreground)',
840
1043
  '--local-text-muted': 'var(--muted-foreground)',
841
- '--local-primary': 'var(--primary)',
842
- '--local-accent': 'var(--color-accent, #60a5fa)',
843
- '--local-cyan': 'var(--color-secondary, #22d3ee)',
844
- '--local-border': 'var(--border)',
845
1044
  } as React.CSSProperties}
846
- className="jp-hero relative min-h-screen flex items-center overflow-hidden pt-24 bg-[var(--local-bg)]"
1045
+ className="relative z-0 bg-[var(--local-bg)]"
847
1046
  >
848
- <div className="absolute -top-[40%] -right-[20%] w-[70vw] h-[70vw] rounded-full bg-[radial-gradient(circle,rgba(59,130,246,0.06)_0%,transparent_70%)] pointer-events-none" />
849
- <div className="absolute -bottom-[10%] -left-[10%] w-[50vw] h-[50vw] rounded-full bg-[radial-gradient(circle,rgba(34,211,238,0.03)_0%,transparent_60%)] pointer-events-none" />
850
-
851
- <div className="relative max-w-[1200px] mx-auto px-8 w-full">
852
- <div className="max-w-[820px]">
853
- {data.badge && (
854
- <div className="inline-flex items-center gap-2 bg-[rgba(59,130,246,0.08)] border border-[rgba(59,130,246,0.2)] px-4 py-1.5 rounded-full text-[0.78rem] font-semibold text-[var(--local-accent)] mb-8 tracking-wide jp-animate-in" data-jp-field="badge">
855
- <span className="w-1.5 h-1.5 rounded-full bg-[var(--local-accent)] jp-pulse-dot" />
856
- {data.badge}
857
- </div>
858
- )}
859
- <h1
860
- className="font-display text-[clamp(2.8rem,6vw,4.8rem)] font-black text-[var(--local-text)] leading-[1.08] tracking-tight mb-6 jp-animate-in jp-d1"
861
- data-jp-field="title"
862
- >
863
- {data.title}
864
- {data.titleHighlight && (
865
- <>
866
- <br />
867
- <em className="not-italic bg-gradient-to-br from-[var(--local-accent)] to-[var(--local-cyan)] bg-clip-text text-transparent" data-jp-field="titleHighlight">
868
- {data.titleHighlight}
869
- </em>
870
- </>
1047
+ {imageUrl ? (
1048
+ <>
1049
+ <div className="relative w-full aspect-[21/9] min-h-[200px]">
1050
+ <img
1051
+ src={imageUrl}
1052
+ alt={data.image?.alt ?? ''}
1053
+ className="w-full h-full object-cover"
1054
+ data-jp-field="image"
1055
+ />
1056
+ {data.caption && (
1057
+ <div
1058
+ className="absolute bottom-0 inset-x-0 bg-black/60 backdrop-blur-sm px-6 py-4"
1059
+ data-jp-field="caption"
1060
+ >
1061
+ <p className="text-sm text-zinc-300 italic">{data.caption}</p>
1062
+ </div>
871
1063
  )}
872
- </h1>
873
- {data.description && (
874
- <p className="text-xl text-[var(--local-text-muted)] max-w-[600px] leading-relaxed mb-10 jp-animate-in jp-d2" data-jp-field="description">
875
- {data.description}
876
- </p>
877
- )}
878
- {data.ctas && data.ctas.length > 0 && (
879
- <div className="flex gap-4 flex-wrap jp-animate-in jp-d3">
880
- {data.ctas.map((cta, idx) => (
881
- <a
882
- key={cta.id ?? idx}
883
- href={cta.href}
884
- data-jp-item-id={cta.id ?? `legacy-${idx}`}
885
- data-jp-item-field="ctas"
886
- className={cn(
887
- 'inline-flex items-center gap-2 px-8 py-3.5 rounded-[5px] font-semibold text-base transition-all duration-200 no-underline',
888
- cta.variant === 'primary'
889
- ? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-0.5 hover:shadow-[0_8px_30px_rgba(59,130,246,0.3)]'
890
- : 'bg-transparent text-[var(--local-text)] border border-[rgba(255,255,255,0.12)] hover:border-[rgba(255,255,255,0.3)] hover:bg-[rgba(255,255,255,0.04)]'
891
- )}
892
- >
893
- {cta.label}
894
- </a>
895
- ))}
896
- </div>
897
- )}
898
- {data.metrics && data.metrics.length > 0 && (
899
- <div className="flex gap-12 mt-16 pt-12 border-t border-[rgba(255,255,255,0.06)] flex-wrap jp-animate-in jp-d4">
900
- {data.metrics.map((metric, idx) => (
901
- <div
902
- key={(metric as { id?: string }).id ?? idx}
903
- data-jp-item-id={(metric as { id?: string }).id ?? `legacy-${idx}`}
904
- data-jp-item-field="metrics"
905
- >
906
- <div className="text-[2rem] font-bold text-[var(--local-text)] font-display">
907
- {metric.val}
908
- </div>
909
- <div className="text-[0.82rem] text-[var(--muted-foreground)] mt-0.5 opacity-70">
910
- {metric.label}
911
- </div>
912
- </div>
913
- ))}
1064
+ </div>
1065
+ {data.label && (
1066
+ <div className="jp-section-label sr-only" data-jp-field="label">
1067
+ {data.label}
914
1068
  </div>
915
1069
  )}
1070
+ </>
1071
+ ) : (
1072
+ <div className="flex flex-col items-center justify-center py-24 text-[var(--local-text-muted)]">
1073
+ <span className="text-sm">Nessuna immagine</span>
1074
+ <span className="text-xs mt-1">Seleziona la section e usa Image Picker nell’Inspector</span>
916
1075
  </div>
917
- </div>
1076
+ )}
918
1077
  </section>
919
1078
  );
920
1079
  };
921
1080
 
922
1081
  END_OF_FILE_CONTENT
923
- echo "Creating src/components/hero/index.ts..."
924
- cat << 'END_OF_FILE_CONTENT' > "src/components/hero/index.ts"
925
- export * from './View';
926
- export * from './schema';
927
- export * from './types';
1082
+ echo "Creating src/components/image-break/index.ts..."
1083
+ cat << 'END_OF_FILE_CONTENT' > "src/components/image-break/index.ts"
1084
+ export { ImageBreak } from './View';
1085
+ export { ImageBreakSchema } from './schema';
1086
+ export type { ImageBreakData, ImageBreakSettings } from './types';
928
1087
 
929
1088
  END_OF_FILE_CONTENT
930
- echo "Creating src/components/hero/schema.ts..."
931
- cat << 'END_OF_FILE_CONTENT' > "src/components/hero/schema.ts"
1089
+ echo "Creating src/components/image-break/schema.ts..."
1090
+ cat << 'END_OF_FILE_CONTENT' > "src/components/image-break/schema.ts"
932
1091
  import { z } from 'zod';
933
- import { BaseSectionData, CtaSchema } from '@/lib/base-schemas';
1092
+ import { BaseSectionData } from '@/lib/base-schemas';
934
1093
 
935
- const HeroMetricSchema = z.object({
936
- val: z.string().describe('ui:text'),
937
- label: z.string().describe('ui:text'),
938
- });
1094
+ const ImageSelectionSchema = z.object({
1095
+ url: z.string(),
1096
+ alt: z.string().optional(),
1097
+ }).describe('ui:image-picker');
939
1098
 
940
- export const HeroSchema = BaseSectionData.extend({
941
- badge: z.string().optional().describe('ui:text'),
942
- title: z.string().describe('ui:text'),
943
- titleHighlight: z.string().optional().describe('ui:text'),
944
- description: z.string().optional().describe('ui:textarea'),
945
- ctas: z.array(CtaSchema).optional().describe('ui:list'),
946
- metrics: z.array(HeroMetricSchema).optional().describe('ui:list'),
1099
+ export const ImageBreakSchema = BaseSectionData.extend({
1100
+ label: z.string().optional().describe('ui:text'),
1101
+ image: ImageSelectionSchema.default({ url: '', alt: '' }),
1102
+ caption: z.string().optional().describe('ui:textarea'),
947
1103
  });
948
1104
 
949
1105
  END_OF_FILE_CONTENT
950
- echo "Creating src/components/hero/types.ts..."
951
- cat << 'END_OF_FILE_CONTENT' > "src/components/hero/types.ts"
952
- import { z } from 'zod';
953
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
954
- import { HeroSchema } from './schema';
1106
+ echo "Creating src/components/image-break/types.ts..."
1107
+ cat << 'END_OF_FILE_CONTENT' > "src/components/image-break/types.ts"
1108
+ import type { z } from 'zod';
1109
+ import type { ImageBreakSchema } from './schema';
955
1110
 
956
- export type HeroData = z.infer<typeof HeroSchema>;
957
- export type HeroSettings = z.infer<typeof BaseSectionSettingsSchema>;
1111
+ export type ImageBreakData = z.infer<typeof ImageBreakSchema>;
1112
+ export type ImageBreakSettings = Record<string, unknown>;
958
1113
 
959
1114
  END_OF_FILE_CONTENT
960
1115
  mkdir -p "src/components/image-test"
961
1116
  mkdir -p "src/components/pa-section"
962
1117
  echo "Creating src/components/pa-section/View.tsx..."
963
1118
  cat << 'END_OF_FILE_CONTENT' > "src/components/pa-section/View.tsx"
964
- import React from 'react';
965
- import type { PaSectionData, PaSectionSettings } from './types';
966
-
967
- export const PaSection: React.FC<{ data: PaSectionData; settings?: PaSectionSettings }> = ({ data }) => {
968
- return (
969
- <section
970
- style={{
971
- '--local-bg': 'var(--card)',
972
- '--local-text': 'var(--foreground)',
973
- '--local-text-muted': 'var(--muted-foreground)',
974
- '--local-primary': 'var(--primary)',
975
- '--local-accent': 'var(--color-accent, #60a5fa)',
976
- '--local-deep': 'var(--background)',
977
- } as React.CSSProperties}
978
- className="relative z-0 py-28 bg-[var(--local-bg)]"
979
- >
980
- <div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.1)] to-transparent" />
981
- <div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.1)] to-transparent" />
982
- <div className="max-w-[1200px] mx-auto px-8">
983
- {data.label && (
984
- <div className="jp-section-label inline-flex items-center gap-2 text-[0.72rem] font-bold uppercase tracking-[0.12em] text-[var(--local-accent)] mb-4" data-jp-field="label">
985
- <span className="w-5 h-px bg-[var(--local-primary)]" />
986
- {data.label}
987
- </div>
988
- )}
989
- <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
990
- {data.title}
991
- </h2>
992
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center mt-12">
993
- <div>
994
- <h3 className="text-2xl font-bold text-[var(--local-text)] mb-4" data-jp-field="subtitle">
995
- {data.subtitle}
996
- </h3>
997
- {data.paragraphs.map((p, idx) => (
998
- <p key={idx} className="text-[var(--local-text-muted)] mb-5 text-[1.05rem] leading-relaxed" data-jp-item-id={(p as { id?: string }).id ?? `legacy-${idx}`} data-jp-item-field="paragraphs">
999
- {p.text}
1000
- </p>
1001
- ))}
1002
- {data.badges && data.badges.length > 0 && (
1003
- <div className="flex gap-2.5 flex-wrap mt-4">
1004
- {data.badges.map((badge, idx) => (
1005
- <span
1006
- key={idx}
1007
- className="inline-flex items-center gap-1.5 bg-[rgba(34,197,94,0.08)] border border-[rgba(34,197,94,0.2)] text-[#4ade80] px-3 py-1.5 rounded-md text-[0.78rem] font-semibold"
1008
- data-jp-item-id={(badge as { id?: string }).id ?? `legacy-${idx}`}
1009
- data-jp-item-field="badges"
1010
- >
1011
- {badge.label}
1012
- </span>
1013
- ))}
1014
- </div>
1015
- )}
1016
- </div>
1017
- <div className="border border-[rgba(255,255,255,0.06)] rounded-lg p-12 bg-[rgba(255,255,255,0.02)] text-center">
1018
- {data.engines && data.engines.length >= 2 && (
1019
- <div className="flex items-center justify-center gap-6 mb-8">
1020
- {data.engines.map((engine, idx) => (
1021
- <React.Fragment key={idx}>
1022
- {idx > 0 && (
1023
- <span className="text-[var(--local-text-muted)] text-2xl opacity-50">⇄</span>
1024
- )}
1025
- <div
1026
- className={
1027
- engine.variant === 'tailwind'
1028
- ? 'px-6 py-4 rounded-xl font-bold text-[0.95rem] border bg-[rgba(59,130,246,0.08)] border-[rgba(59,130,246,0.2)] text-[#60a5fa]'
1029
- : 'px-6 py-4 rounded-xl font-bold text-[0.95rem] border bg-[rgba(34,197,94,0.08)] border-[rgba(34,197,94,0.2)] text-[#4ade80]'
1030
- }
1031
- data-jp-item-id={(engine as { id?: string }).id ?? `legacy-${idx}`}
1032
- data-jp-item-field="engines"
1033
- >
1034
- {engine.label}
1035
- </div>
1036
- </React.Fragment>
1037
- ))}
1038
- </div>
1039
- )}
1040
- {data.codeSnippet && (
1041
- <div className="font-mono text-[0.85rem] text-[var(--local-text-muted)] bg-[var(--local-deep)] p-4 rounded-lg text-left border border-[rgba(255,255,255,0.04)]" data-jp-field="codeSnippet">
1042
- <pre className="whitespace-pre-wrap m-0">{data.codeSnippet}</pre>
1043
- <div className="mt-4 text-[0.75rem] text-center opacity-50">
1044
- Same JSON. Different Render Engine.
1045
- </div>
1046
- </div>
1047
- )}
1048
- </div>
1049
- </div>
1050
- </div>
1051
- </section>
1052
- );
1053
- };
1119
+ import React from 'react';
1120
+ import type { PaSectionData, PaSectionSettings } from './types';
1121
+
1122
+ export const PaSection: React.FC<{ data: PaSectionData; settings?: PaSectionSettings }> = ({ data }) => {
1123
+ return (
1124
+ <section
1125
+ style={{
1126
+ '--local-bg': 'var(--card)',
1127
+ '--local-text': 'var(--foreground)',
1128
+ '--local-text-muted': 'var(--muted-foreground)',
1129
+ '--local-primary': 'var(--primary)',
1130
+ '--local-accent': 'var(--color-accent, #60a5fa)',
1131
+ '--local-deep': 'var(--background)',
1132
+ } as React.CSSProperties}
1133
+ className="relative z-0 py-28 bg-[var(--local-bg)]"
1134
+ >
1135
+ <div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.1)] to-transparent" />
1136
+ <div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.1)] to-transparent" />
1137
+ <div className="max-w-[1200px] mx-auto px-8">
1138
+ {data.label && (
1139
+ <div className="jp-section-label inline-flex items-center gap-2 text-[0.72rem] font-bold uppercase tracking-[0.12em] text-[var(--local-accent)] mb-4" data-jp-field="label">
1140
+ <span className="w-5 h-px bg-[var(--local-primary)]" />
1141
+ {data.label}
1142
+ </div>
1143
+ )}
1144
+ <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
1145
+ {data.title}
1146
+ </h2>
1147
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center mt-12">
1148
+ <div>
1149
+ <h3 className="text-2xl font-bold text-[var(--local-text)] mb-4" data-jp-field="subtitle">
1150
+ {data.subtitle}
1151
+ </h3>
1152
+ {data.paragraphs.map((p, idx) => (
1153
+ <p key={idx} className="text-[var(--local-text-muted)] mb-5 text-[1.05rem] leading-relaxed" data-jp-item-id={(p as { id?: string }).id ?? `legacy-${idx}`} data-jp-item-field="paragraphs">
1154
+ {p.text}
1155
+ </p>
1156
+ ))}
1157
+ {data.badges && data.badges.length > 0 && (
1158
+ <div className="flex gap-2.5 flex-wrap mt-4">
1159
+ {data.badges.map((badge, idx) => (
1160
+ <span
1161
+ key={idx}
1162
+ className="inline-flex items-center gap-1.5 bg-[rgba(34,197,94,0.08)] border border-[rgba(34,197,94,0.2)] text-[#4ade80] px-3 py-1.5 rounded-md text-[0.78rem] font-semibold"
1163
+ data-jp-item-id={(badge as { id?: string }).id ?? `legacy-${idx}`}
1164
+ data-jp-item-field="badges"
1165
+ >
1166
+ {badge.label}
1167
+ </span>
1168
+ ))}
1169
+ </div>
1170
+ )}
1171
+ </div>
1172
+ <div className="border border-[rgba(255,255,255,0.06)] rounded-lg p-12 bg-[rgba(255,255,255,0.02)] text-center">
1173
+ {data.engines && data.engines.length >= 2 && (
1174
+ <div className="flex items-center justify-center gap-6 mb-8">
1175
+ {data.engines.map((engine, idx) => (
1176
+ <React.Fragment key={idx}>
1177
+ {idx > 0 && (
1178
+ <span className="text-[var(--local-text-muted)] text-2xl opacity-50">⇄</span>
1179
+ )}
1180
+ <div
1181
+ className={
1182
+ engine.variant === 'tailwind'
1183
+ ? 'px-6 py-4 rounded-xl font-bold text-[0.95rem] border bg-[rgba(59,130,246,0.08)] border-[rgba(59,130,246,0.2)] text-[#60a5fa]'
1184
+ : 'px-6 py-4 rounded-xl font-bold text-[0.95rem] border bg-[rgba(34,197,94,0.08)] border-[rgba(34,197,94,0.2)] text-[#4ade80]'
1185
+ }
1186
+ data-jp-item-id={(engine as { id?: string }).id ?? `legacy-${idx}`}
1187
+ data-jp-item-field="engines"
1188
+ >
1189
+ {engine.label}
1190
+ </div>
1191
+ </React.Fragment>
1192
+ ))}
1193
+ </div>
1194
+ )}
1195
+ {data.codeSnippet && (
1196
+ <div className="font-mono text-[0.85rem] text-[var(--local-text-muted)] bg-[var(--local-deep)] p-4 rounded-lg text-left border border-[rgba(255,255,255,0.04)]" data-jp-field="codeSnippet">
1197
+ <pre className="whitespace-pre-wrap m-0">{data.codeSnippet}</pre>
1198
+ <div className="mt-4 text-[0.75rem] text-center opacity-50">
1199
+ Same JSON. Different Render Engine.
1200
+ </div>
1201
+ </div>
1202
+ )}
1203
+ </div>
1204
+ </div>
1205
+ </div>
1206
+ </section>
1207
+ );
1208
+ };
1054
1209
 
1055
1210
  END_OF_FILE_CONTENT
1056
1211
  echo "Creating src/components/pa-section/index.ts..."
1057
1212
  cat << 'END_OF_FILE_CONTENT' > "src/components/pa-section/index.ts"
1058
- export * from './View';
1059
- export * from './schema';
1060
- export * from './types';
1213
+ export * from './View';
1214
+ export * from './schema';
1215
+ export * from './types';
1061
1216
 
1062
1217
  END_OF_FILE_CONTENT
1063
1218
  echo "Creating src/components/pa-section/schema.ts..."
1064
1219
  cat << 'END_OF_FILE_CONTENT' > "src/components/pa-section/schema.ts"
1065
- import { z } from 'zod';
1066
- import { BaseSectionData } from '@/lib/base-schemas';
1067
-
1068
- const PaBadgeSchema = z.object({
1069
- label: z.string().describe('ui:text'),
1070
- });
1071
-
1072
- const PaEngineSchema = z.object({
1073
- label: z.string().describe('ui:text'),
1074
- variant: z.enum(['tailwind', 'bootstrap']).describe('ui:select'),
1075
- });
1076
-
1077
- export const PaSectionSchema = BaseSectionData.extend({
1078
- label: z.string().optional().describe('ui:text'),
1079
- title: z.string().describe('ui:text'),
1080
- subtitle: z.string().describe('ui:text'),
1081
- paragraphs: z.array(z.object({ text: z.string().describe('ui:textarea') })).describe('ui:list'),
1082
- badges: z.array(PaBadgeSchema).optional().describe('ui:list'),
1083
- engines: z.array(PaEngineSchema).optional().describe('ui:list'),
1084
- codeSnippet: z.string().optional().describe('ui:textarea'),
1085
- });
1220
+ import { z } from 'zod';
1221
+ import { BaseSectionData } from '@/lib/base-schemas';
1222
+
1223
+ const PaBadgeSchema = z.object({
1224
+ label: z.string().describe('ui:text'),
1225
+ });
1226
+
1227
+ const PaEngineSchema = z.object({
1228
+ label: z.string().describe('ui:text'),
1229
+ variant: z.enum(['tailwind', 'bootstrap']).describe('ui:select'),
1230
+ });
1231
+
1232
+ export const PaSectionSchema = BaseSectionData.extend({
1233
+ label: z.string().optional().describe('ui:text'),
1234
+ title: z.string().describe('ui:text'),
1235
+ subtitle: z.string().describe('ui:text'),
1236
+ paragraphs: z.array(z.object({ text: z.string().describe('ui:textarea') })).describe('ui:list'),
1237
+ badges: z.array(PaBadgeSchema).optional().describe('ui:list'),
1238
+ engines: z.array(PaEngineSchema).optional().describe('ui:list'),
1239
+ codeSnippet: z.string().optional().describe('ui:textarea'),
1240
+ });
1086
1241
 
1087
1242
  END_OF_FILE_CONTENT
1088
1243
  echo "Creating src/components/pa-section/types.ts..."
1089
1244
  cat << 'END_OF_FILE_CONTENT' > "src/components/pa-section/types.ts"
1090
- import { z } from 'zod';
1091
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
1092
- import { PaSectionSchema } from './schema';
1093
-
1094
- export type PaSectionData = z.infer<typeof PaSectionSchema>;
1095
- export type PaSectionSettings = z.infer<typeof BaseSectionSettingsSchema>;
1245
+ import { z } from 'zod';
1246
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
1247
+ import { PaSectionSchema } from './schema';
1248
+
1249
+ export type PaSectionData = z.infer<typeof PaSectionSchema>;
1250
+ export type PaSectionSettings = z.infer<typeof BaseSectionSettingsSchema>;
1096
1251
 
1097
1252
  END_OF_FILE_CONTENT
1098
1253
  mkdir -p "src/components/philosophy"
1099
1254
  echo "Creating src/components/philosophy/View.tsx..."
1100
1255
  cat << 'END_OF_FILE_CONTENT' > "src/components/philosophy/View.tsx"
1101
- import React from 'react';
1102
- import type { PhilosophyData, PhilosophySettings } from './types';
1103
-
1104
- export const Philosophy: React.FC<{ data: PhilosophyData; settings?: PhilosophySettings }> = ({ data }) => {
1105
- const renderQuote = () => {
1106
- if (!data.quoteHighlightWord) {
1107
- return <>{data.quote}</>;
1108
- }
1109
- const parts = data.quote.split(data.quoteHighlightWord);
1110
- return (
1111
- <>
1112
- {parts.map((part, idx) => (
1113
- <React.Fragment key={idx}>
1114
- {part}
1115
- {idx < parts.length - 1 && (
1116
- <em className="not-italic text-[var(--local-accent)]">
1117
- {data.quoteHighlightWord}
1118
- </em>
1119
- )}
1120
- </React.Fragment>
1121
- ))}
1122
- </>
1123
- );
1124
- };
1125
-
1126
- return (
1127
- <section
1128
- style={{
1129
- '--local-bg': 'var(--background)',
1130
- '--local-text': 'var(--foreground)',
1131
- '--local-text-muted': 'var(--muted-foreground)',
1132
- '--local-accent': 'var(--color-accent, #60a5fa)',
1133
- '--local-primary': 'var(--primary)',
1134
- } as React.CSSProperties}
1135
- className="relative z-0 py-28 bg-[var(--local-bg)]"
1136
- >
1137
- <div className="max-w-[1200px] mx-auto px-8">
1138
- <div className="max-w-[760px] mx-auto text-center">
1139
- {data.label && (
1140
- <div className="jp-section-label inline-flex items-center gap-2 text-[0.72rem] font-bold uppercase tracking-[0.12em] text-[var(--local-accent)] mb-4" data-jp-field="label">
1141
- <span className="w-5 h-px bg-[var(--local-primary)]" />
1142
- {data.label}
1143
- </div>
1144
- )}
1145
- <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
1146
- {data.title}
1147
- </h2>
1148
- <blockquote className="font-display text-[clamp(1.6rem,3vw,2.4rem)] text-[var(--local-text)] font-bold leading-[1.35] my-8" data-jp-field="quote">
1149
- &ldquo;{renderQuote()}&rdquo;
1150
- </blockquote>
1151
- {data.description && (
1152
- <p className="text-[1.05rem] text-[var(--local-text-muted)] max-w-[560px] mx-auto leading-relaxed" data-jp-field="description">
1153
- {data.description}
1154
- </p>
1155
- )}
1156
- </div>
1157
- </div>
1158
- </section>
1159
- );
1160
- };
1256
+ import React from 'react';
1257
+ import type { PhilosophyData, PhilosophySettings } from './types';
1258
+
1259
+ export const Philosophy: React.FC<{ data: PhilosophyData; settings?: PhilosophySettings }> = ({ data }) => {
1260
+ const renderQuote = () => {
1261
+ if (!data.quoteHighlightWord) {
1262
+ return <>{data.quote}</>;
1263
+ }
1264
+ const parts = data.quote.split(data.quoteHighlightWord);
1265
+ return (
1266
+ <>
1267
+ {parts.map((part, idx) => (
1268
+ <React.Fragment key={idx}>
1269
+ {part}
1270
+ {idx < parts.length - 1 && (
1271
+ <em className="not-italic text-[var(--local-accent)]">
1272
+ {data.quoteHighlightWord}
1273
+ </em>
1274
+ )}
1275
+ </React.Fragment>
1276
+ ))}
1277
+ </>
1278
+ );
1279
+ };
1280
+
1281
+ return (
1282
+ <section
1283
+ style={{
1284
+ '--local-bg': 'var(--background)',
1285
+ '--local-text': 'var(--foreground)',
1286
+ '--local-text-muted': 'var(--muted-foreground)',
1287
+ '--local-accent': 'var(--color-accent, #60a5fa)',
1288
+ '--local-primary': 'var(--primary)',
1289
+ } as React.CSSProperties}
1290
+ className="relative z-0 py-28 bg-[var(--local-bg)]"
1291
+ >
1292
+ <div className="max-w-[1200px] mx-auto px-8">
1293
+ <div className="max-w-[760px] mx-auto text-center">
1294
+ {data.label && (
1295
+ <div className="jp-section-label inline-flex items-center gap-2 text-[0.72rem] font-bold uppercase tracking-[0.12em] text-[var(--local-accent)] mb-4" data-jp-field="label">
1296
+ <span className="w-5 h-px bg-[var(--local-primary)]" />
1297
+ {data.label}
1298
+ </div>
1299
+ )}
1300
+ <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
1301
+ {data.title}
1302
+ </h2>
1303
+ <blockquote className="font-display text-[clamp(1.6rem,3vw,2.4rem)] text-[var(--local-text)] font-bold leading-[1.35] my-8" data-jp-field="quote">
1304
+ &ldquo;{renderQuote()}&rdquo;
1305
+ </blockquote>
1306
+ {data.description && (
1307
+ <p className="text-[1.05rem] text-[var(--local-text-muted)] max-w-[560px] mx-auto leading-relaxed" data-jp-field="description">
1308
+ {data.description}
1309
+ </p>
1310
+ )}
1311
+ </div>
1312
+ </div>
1313
+ </section>
1314
+ );
1315
+ };
1161
1316
 
1162
1317
  END_OF_FILE_CONTENT
1163
1318
  echo "Creating src/components/philosophy/index.ts..."
1164
1319
  cat << 'END_OF_FILE_CONTENT' > "src/components/philosophy/index.ts"
1165
- export * from './View';
1166
- export * from './schema';
1167
- export * from './types';
1320
+ export * from './View';
1321
+ export * from './schema';
1322
+ export * from './types';
1168
1323
 
1169
1324
  END_OF_FILE_CONTENT
1170
1325
  echo "Creating src/components/philosophy/schema.ts..."
1171
1326
  cat << 'END_OF_FILE_CONTENT' > "src/components/philosophy/schema.ts"
1172
- import { z } from 'zod';
1173
- import { BaseSectionData } from '@/lib/base-schemas';
1174
-
1175
- export const PhilosophySchema = BaseSectionData.extend({
1176
- label: z.string().optional().describe('ui:text'),
1177
- title: z.string().describe('ui:text'),
1178
- quote: z.string().describe('ui:textarea'),
1179
- quoteHighlightWord: z.string().optional().describe('ui:text'),
1180
- description: z.string().optional().describe('ui:textarea'),
1181
- });
1327
+ import { z } from 'zod';
1328
+ import { BaseSectionData } from '@/lib/base-schemas';
1329
+
1330
+ export const PhilosophySchema = BaseSectionData.extend({
1331
+ label: z.string().optional().describe('ui:text'),
1332
+ title: z.string().describe('ui:text'),
1333
+ quote: z.string().describe('ui:textarea'),
1334
+ quoteHighlightWord: z.string().optional().describe('ui:text'),
1335
+ description: z.string().optional().describe('ui:textarea'),
1336
+ });
1182
1337
 
1183
1338
  END_OF_FILE_CONTENT
1184
1339
  echo "Creating src/components/philosophy/types.ts..."
1185
1340
  cat << 'END_OF_FILE_CONTENT' > "src/components/philosophy/types.ts"
1186
- import { z } from 'zod';
1187
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
1188
- import { PhilosophySchema } from './schema';
1189
-
1190
- export type PhilosophyData = z.infer<typeof PhilosophySchema>;
1191
- export type PhilosophySettings = z.infer<typeof BaseSectionSettingsSchema>;
1341
+ import { z } from 'zod';
1342
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
1343
+ import { PhilosophySchema } from './schema';
1344
+
1345
+ export type PhilosophyData = z.infer<typeof PhilosophySchema>;
1346
+ export type PhilosophySettings = z.infer<typeof BaseSectionSettingsSchema>;
1192
1347
 
1193
1348
  END_OF_FILE_CONTENT
1194
1349
  mkdir -p "src/components/pillars-grid"
1195
1350
  echo "Creating src/components/pillars-grid/View.tsx..."
1196
1351
  cat << 'END_OF_FILE_CONTENT' > "src/components/pillars-grid/View.tsx"
1197
- import React from 'react';
1198
- import { cn } from '@/lib/utils';
1199
- import { Icon, isIconName } from '@/lib/IconResolver';
1200
- import type { PillarsGridData, PillarsGridSettings, PillarIconVariant, PillarTagVariant } from './types';
1201
-
1202
- const iconVariantStyles: Record<PillarIconVariant, string> = {
1203
- split: 'bg-[rgba(59,130,246,0.1)] text-[#60a5fa]',
1204
- registry: 'bg-[rgba(34,211,238,0.1)] text-[#22d3ee]',
1205
- federation: 'bg-[rgba(168,85,247,0.1)] text-[#c084fc]',
1206
- };
1207
-
1208
- const tagVariantStyles: Record<PillarTagVariant, string> = {
1209
- core: 'bg-[rgba(59,130,246,0.1)] text-[#60a5fa]',
1210
- pattern: 'bg-[rgba(34,211,238,0.1)] text-[#22d3ee]',
1211
- enterprise: 'bg-[rgba(168,85,247,0.1)] text-[#c084fc]',
1212
- };
1213
-
1214
- export const PillarsGrid: React.FC<{ data: PillarsGridData; settings?: PillarsGridSettings }> = ({ data }) => {
1215
- return (
1216
- <section
1217
- style={{
1218
- '--local-bg': 'var(--background)',
1219
- '--local-text': 'var(--foreground)',
1220
- '--local-text-muted': 'var(--muted-foreground)',
1221
- '--local-primary': 'var(--primary)',
1222
- '--local-accent': 'var(--color-accent, #60a5fa)',
1223
- '--local-border': 'var(--border)',
1224
- } as React.CSSProperties}
1225
- className="relative z-0 py-28 bg-[var(--local-bg)]"
1226
- >
1227
- <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[80%] h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.15)] to-transparent" />
1228
- <div className="max-w-[1200px] mx-auto px-8">
1229
- {data.label && (
1230
- <div className="jp-section-label inline-flex items-center gap-2 text-[0.72rem] font-bold uppercase tracking-[0.12em] text-[var(--local-accent)] mb-4" data-jp-field="label">
1231
- <span className="w-5 h-px bg-[var(--local-primary)]" />
1232
- {data.label}
1233
- </div>
1234
- )}
1235
- <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
1236
- {data.title}
1237
- </h2>
1238
- {data.description && (
1239
- <p className="text-lg text-[var(--local-text-muted)] max-w-[600px] leading-relaxed" data-jp-field="description">
1240
- {data.description}
1241
- </p>
1242
- )}
1243
- <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mt-14">
1244
- {data.pillars.map((pillar, idx) => (
1245
- <div
1246
- key={pillar.id ?? idx}
1247
- className="jp-pillar-card group relative border border-[rgba(255,255,255,0.06)] rounded-lg p-10 bg-[rgba(255,255,255,0.015)] transition-all duration-300 overflow-hidden hover:border-[rgba(59,130,246,0.2)] hover:-translate-y-1 hover:bg-[rgba(59,130,246,0.03)]"
1248
- data-jp-item-id={pillar.id ?? `legacy-${idx}`}
1249
- data-jp-item-field="pillars"
1250
- >
1251
- <div className="absolute top-0 left-0 right-0 h-[3px] bg-gradient-to-r from-[var(--local-primary)] to-[#22d3ee] opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
1252
- <div className={cn(
1253
- 'w-12 h-12 rounded-xl flex items-center justify-center mb-6 text-xl font-bold',
1254
- iconVariantStyles[pillar.iconVariant]
1255
- )}>
1256
- {pillar.icon && isIconName(pillar.icon) ? (
1257
- <Icon name={pillar.icon} size={24} className="shrink-0" />
1258
- ) : pillar.icon ? (
1259
- <span>{pillar.icon}</span>
1260
- ) : null}
1261
- </div>
1262
- <h3 className="text-xl font-bold text-[var(--local-text)] mb-3">
1263
- {pillar.title}
1264
- </h3>
1265
- <p className="text-[0.95rem] text-[var(--local-text-muted)] leading-relaxed">
1266
- {pillar.description}
1267
- </p>
1268
- <span className={cn(
1269
- 'inline-block text-[0.7rem] font-semibold uppercase tracking-wide px-3 py-1 rounded mt-4',
1270
- tagVariantStyles[pillar.tagVariant]
1271
- )}>
1272
- {pillar.tag}
1273
- </span>
1274
- </div>
1275
- ))}
1276
- </div>
1277
- </div>
1278
- </section>
1279
- );
1280
- };
1352
+ import React from 'react';
1353
+ import { cn } from '@/lib/utils';
1354
+ import { Icon, isIconName } from '@/lib/IconResolver';
1355
+ import type { PillarsGridData, PillarsGridSettings, PillarIconVariant, PillarTagVariant } from './types';
1356
+
1357
+ const iconVariantStyles: Record<PillarIconVariant, string> = {
1358
+ split: 'bg-[rgba(59,130,246,0.1)] text-[#60a5fa]',
1359
+ registry: 'bg-[rgba(34,211,238,0.1)] text-[#22d3ee]',
1360
+ federation: 'bg-[rgba(168,85,247,0.1)] text-[#c084fc]',
1361
+ };
1362
+
1363
+ const tagVariantStyles: Record<PillarTagVariant, string> = {
1364
+ core: 'bg-[rgba(59,130,246,0.1)] text-[#60a5fa]',
1365
+ pattern: 'bg-[rgba(34,211,238,0.1)] text-[#22d3ee]',
1366
+ enterprise: 'bg-[rgba(168,85,247,0.1)] text-[#c084fc]',
1367
+ };
1368
+
1369
+ export const PillarsGrid: React.FC<{ data: PillarsGridData; settings?: PillarsGridSettings }> = ({ data }) => {
1370
+ return (
1371
+ <section
1372
+ style={{
1373
+ '--local-bg': 'var(--background)',
1374
+ '--local-text': 'var(--foreground)',
1375
+ '--local-text-muted': 'var(--muted-foreground)',
1376
+ '--local-primary': 'var(--primary)',
1377
+ '--local-accent': 'var(--color-accent, #60a5fa)',
1378
+ '--local-border': 'var(--border)',
1379
+ } as React.CSSProperties}
1380
+ className="relative z-0 py-28 bg-[var(--local-bg)]"
1381
+ >
1382
+ <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[80%] h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.15)] to-transparent" />
1383
+ <div className="max-w-[1200px] mx-auto px-8">
1384
+ {data.label && (
1385
+ <div className="jp-section-label inline-flex items-center gap-2 text-[0.72rem] font-bold uppercase tracking-[0.12em] text-[var(--local-accent)] mb-4" data-jp-field="label">
1386
+ <span className="w-5 h-px bg-[var(--local-primary)]" />
1387
+ {data.label}
1388
+ </div>
1389
+ )}
1390
+ <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
1391
+ {data.title}
1392
+ </h2>
1393
+ {data.description && (
1394
+ <p className="text-lg text-[var(--local-text-muted)] max-w-[600px] leading-relaxed" data-jp-field="description">
1395
+ {data.description}
1396
+ </p>
1397
+ )}
1398
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mt-14">
1399
+ {data.pillars.map((pillar, idx) => (
1400
+ <div
1401
+ key={pillar.id ?? idx}
1402
+ className="jp-pillar-card group relative border border-[rgba(255,255,255,0.06)] rounded-lg p-10 bg-[rgba(255,255,255,0.015)] transition-all duration-300 overflow-hidden hover:border-[rgba(59,130,246,0.2)] hover:-translate-y-1 hover:bg-[rgba(59,130,246,0.03)]"
1403
+ data-jp-item-id={pillar.id ?? `legacy-${idx}`}
1404
+ data-jp-item-field="pillars"
1405
+ >
1406
+ <div className="absolute top-0 left-0 right-0 h-[3px] bg-gradient-to-r from-[var(--local-primary)] to-[#22d3ee] opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
1407
+ <div className={cn(
1408
+ 'w-12 h-12 rounded-xl flex items-center justify-center mb-6 text-xl font-bold',
1409
+ iconVariantStyles[pillar.iconVariant]
1410
+ )}>
1411
+ {pillar.icon && isIconName(pillar.icon) ? (
1412
+ <Icon name={pillar.icon} size={24} className="shrink-0" />
1413
+ ) : pillar.icon ? (
1414
+ <span>{pillar.icon}</span>
1415
+ ) : null}
1416
+ </div>
1417
+ <h3 className="text-xl font-bold text-[var(--local-text)] mb-3">
1418
+ {pillar.title}
1419
+ </h3>
1420
+ <p className="text-[0.95rem] text-[var(--local-text-muted)] leading-relaxed">
1421
+ {pillar.description}
1422
+ </p>
1423
+ <span className={cn(
1424
+ 'inline-block text-[0.7rem] font-semibold uppercase tracking-wide px-3 py-1 rounded mt-4',
1425
+ tagVariantStyles[pillar.tagVariant]
1426
+ )}>
1427
+ {pillar.tag}
1428
+ </span>
1429
+ </div>
1430
+ ))}
1431
+ </div>
1432
+ </div>
1433
+ </section>
1434
+ );
1435
+ };
1281
1436
 
1282
1437
  END_OF_FILE_CONTENT
1283
1438
  echo "Creating src/components/pillars-grid/index.ts..."
1284
1439
  cat << 'END_OF_FILE_CONTENT' > "src/components/pillars-grid/index.ts"
1285
- export * from './View';
1286
- export * from './schema';
1287
- export * from './types';
1440
+ export * from './View';
1441
+ export * from './schema';
1442
+ export * from './types';
1288
1443
 
1289
1444
  END_OF_FILE_CONTENT
1290
1445
  echo "Creating src/components/pillars-grid/schema.ts..."
1291
1446
  cat << 'END_OF_FILE_CONTENT' > "src/components/pillars-grid/schema.ts"
1292
- import { z } from 'zod';
1293
- import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
1294
-
1295
- export const PillarIconVariantSchema = z.enum(['split', 'registry', 'federation']);
1296
- export const PillarTagVariantSchema = z.enum(['core', 'pattern', 'enterprise']);
1297
-
1298
- const PillarCardSchema = BaseArrayItem.extend({
1299
- icon: z.string().describe('ui:icon-picker'),
1300
- iconVariant: PillarIconVariantSchema.describe('ui:select'),
1301
- title: z.string().describe('ui:text'),
1302
- description: z.string().describe('ui:textarea'),
1303
- tag: z.string().describe('ui:text'),
1304
- tagVariant: PillarTagVariantSchema.describe('ui:select'),
1305
- });
1306
-
1307
- export const PillarsGridSchema = BaseSectionData.extend({
1308
- label: z.string().optional().describe('ui:text'),
1309
- title: z.string().describe('ui:text'),
1310
- description: z.string().optional().describe('ui:textarea'),
1311
- pillars: z.array(PillarCardSchema).describe('ui:list'),
1312
- });
1447
+ import { z } from 'zod';
1448
+ import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
1449
+
1450
+ export const PillarIconVariantSchema = z.enum(['split', 'registry', 'federation']);
1451
+ export const PillarTagVariantSchema = z.enum(['core', 'pattern', 'enterprise']);
1452
+
1453
+ const PillarCardSchema = BaseArrayItem.extend({
1454
+ icon: z.string().describe('ui:icon-picker'),
1455
+ iconVariant: PillarIconVariantSchema.describe('ui:select'),
1456
+ title: z.string().describe('ui:text'),
1457
+ description: z.string().describe('ui:textarea'),
1458
+ tag: z.string().describe('ui:text'),
1459
+ tagVariant: PillarTagVariantSchema.describe('ui:select'),
1460
+ });
1461
+
1462
+ export const PillarsGridSchema = BaseSectionData.extend({
1463
+ label: z.string().optional().describe('ui:text'),
1464
+ title: z.string().describe('ui:text'),
1465
+ description: z.string().optional().describe('ui:textarea'),
1466
+ pillars: z.array(PillarCardSchema).describe('ui:list'),
1467
+ });
1313
1468
 
1314
1469
  END_OF_FILE_CONTENT
1315
1470
  echo "Creating src/components/pillars-grid/types.ts..."
1316
1471
  cat << 'END_OF_FILE_CONTENT' > "src/components/pillars-grid/types.ts"
1317
- import { z } from 'zod';
1318
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
1319
- import { PillarsGridSchema, PillarIconVariantSchema, PillarTagVariantSchema } from './schema';
1320
-
1321
- export type PillarsGridData = z.infer<typeof PillarsGridSchema>;
1322
- export type PillarsGridSettings = z.infer<typeof BaseSectionSettingsSchema>;
1323
- export type PillarIconVariant = z.infer<typeof PillarIconVariantSchema>;
1324
- export type PillarTagVariant = z.infer<typeof PillarTagVariantSchema>;
1472
+ import { z } from 'zod';
1473
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
1474
+ import { PillarsGridSchema, PillarIconVariantSchema, PillarTagVariantSchema } from './schema';
1475
+
1476
+ export type PillarsGridData = z.infer<typeof PillarsGridSchema>;
1477
+ export type PillarsGridSettings = z.infer<typeof BaseSectionSettingsSchema>;
1478
+ export type PillarIconVariant = z.infer<typeof PillarIconVariantSchema>;
1479
+ export type PillarTagVariant = z.infer<typeof PillarTagVariantSchema>;
1325
1480
 
1326
1481
  END_OF_FILE_CONTENT
1327
1482
  mkdir -p "src/components/problem-statement"
1328
1483
  echo "Creating src/components/problem-statement/View.tsx..."
1329
1484
  cat << 'END_OF_FILE_CONTENT' > "src/components/problem-statement/View.tsx"
1330
- import React from 'react';
1331
- import { cn } from '@/lib/utils';
1332
- import type { ProblemStatementData, ProblemStatementSettings, SiloBlockVariant } from './types';
1333
-
1334
- const variantStyles: Record<SiloBlockVariant, string> = {
1335
- red: 'bg-[rgba(239,68,68,0.08)] border-[rgba(239,68,68,0.3)] text-[#f87171]',
1336
- amber: 'bg-[rgba(245,158,11,0.08)] border-[rgba(245,158,11,0.3)] text-[#fbbf24]',
1337
- green: 'bg-[rgba(34,197,94,0.08)] border-[rgba(34,197,94,0.3)] text-[#4ade80]',
1338
- blue: 'bg-[rgba(59,130,246,0.08)] border-[rgba(59,130,246,0.3)] text-[#60a5fa]',
1339
- };
1340
-
1341
- export const ProblemStatement: React.FC<{ data: ProblemStatementData; settings?: ProblemStatementSettings }> = ({ data }) => {
1342
- return (
1343
- <section
1344
- style={{
1345
- '--local-bg': 'var(--background)',
1346
- '--local-surface': 'var(--card)',
1347
- '--local-text': 'var(--foreground)',
1348
- '--local-text-muted': 'var(--muted-foreground)',
1349
- '--local-border': 'var(--border)',
1350
- } as React.CSSProperties}
1351
- className="jp-problem relative z-0 py-28 bg-gradient-to-b from-[var(--local-bg)] to-[var(--local-surface)]"
1352
- >
1353
- <div className="max-w-[1200px] mx-auto px-8">
1354
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
1355
- <div className="relative h-[360px] border border-[rgba(255,255,255,0.06)] rounded-lg bg-[rgba(255,255,255,0.02)] overflow-hidden flex items-center justify-center">
1356
- <div className="text-center p-8">
1357
- {data.siloGroups.map((group, gIdx) => (
1358
- <div
1359
- key={gIdx}
1360
- className="mb-4"
1361
- data-jp-item-id={(group as { id?: string }).id ?? `legacy-${gIdx}`}
1362
- data-jp-item-field="siloGroups"
1363
- >
1364
- <div className="flex flex-wrap justify-center gap-1.5">
1365
- {group.blocks.map((block, bIdx) => (
1366
- <span
1367
- key={(block as { id?: string }).id ?? bIdx}
1368
- className={cn(
1369
- 'inline-block px-4 py-2 rounded-lg text-[0.8rem] font-semibold border',
1370
- variantStyles[block.variant]
1371
- )}
1372
- data-jp-item-id={(block as { id?: string }).id ?? `legacy-${bIdx}`}
1373
- data-jp-item-field="blocks"
1374
- >
1375
- {block.label}
1376
- </span>
1377
- ))}
1378
- </div>
1379
- <span className="text-[0.7rem] text-[var(--local-text-muted)] uppercase tracking-widest mt-2 block opacity-60">
1380
- {group.label}
1381
- </span>
1382
- </div>
1383
- ))}
1384
- </div>
1385
- </div>
1386
- <div>
1387
- <h3 className="text-2xl font-bold text-[var(--local-text)] mb-4" data-jp-field="title">
1388
- {data.title}
1389
- </h3>
1390
- {data.paragraphs.map((p, idx) => (
1391
- <p
1392
- key={idx}
1393
- className="text-[var(--local-text-muted)] mb-5 text-[1.05rem] leading-relaxed"
1394
- data-jp-item-id={(p as { id?: string }).id ?? `legacy-${idx}`}
1395
- data-jp-item-field="paragraphs"
1396
- >
1397
- {p.isBold ? <strong className="text-[var(--local-text)]">{p.text}</strong> : p.text}
1398
- </p>
1399
- ))}
1400
- </div>
1401
- </div>
1402
- </div>
1403
- </section>
1404
- );
1405
- };
1485
+ import React from 'react';
1486
+ import { cn } from '@/lib/utils';
1487
+ import type { ProblemStatementData, ProblemStatementSettings, SiloBlockVariant } from './types';
1488
+
1489
+ const variantStyles: Record<SiloBlockVariant, string> = {
1490
+ red: 'bg-[rgba(239,68,68,0.08)] border-[rgba(239,68,68,0.3)] text-[#f87171]',
1491
+ amber: 'bg-[rgba(245,158,11,0.08)] border-[rgba(245,158,11,0.3)] text-[#fbbf24]',
1492
+ green: 'bg-[rgba(34,197,94,0.08)] border-[rgba(34,197,94,0.3)] text-[#4ade80]',
1493
+ blue: 'bg-[rgba(59,130,246,0.08)] border-[rgba(59,130,246,0.3)] text-[#60a5fa]',
1494
+ };
1495
+
1496
+ export const ProblemStatement: React.FC<{ data: ProblemStatementData; settings?: ProblemStatementSettings }> = ({ data }) => {
1497
+ return (
1498
+ <section
1499
+ style={{
1500
+ '--local-bg': 'var(--background)',
1501
+ '--local-surface': 'var(--card)',
1502
+ '--local-text': 'var(--foreground)',
1503
+ '--local-text-muted': 'var(--muted-foreground)',
1504
+ '--local-border': 'var(--border)',
1505
+ } as React.CSSProperties}
1506
+ className="jp-problem relative z-0 py-28 bg-gradient-to-b from-[var(--local-bg)] to-[var(--local-surface)]"
1507
+ >
1508
+ <div className="max-w-[1200px] mx-auto px-8">
1509
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
1510
+ <div className="relative h-[360px] border border-[rgba(255,255,255,0.06)] rounded-lg bg-[rgba(255,255,255,0.02)] overflow-hidden flex items-center justify-center">
1511
+ <div className="text-center p-8">
1512
+ {data.siloGroups.map((group, gIdx) => (
1513
+ <div
1514
+ key={gIdx}
1515
+ className="mb-4"
1516
+ data-jp-item-id={(group as { id?: string }).id ?? `legacy-${gIdx}`}
1517
+ data-jp-item-field="siloGroups"
1518
+ >
1519
+ <div className="flex flex-wrap justify-center gap-1.5">
1520
+ {group.blocks.map((block, bIdx) => (
1521
+ <span
1522
+ key={(block as { id?: string }).id ?? bIdx}
1523
+ className={cn(
1524
+ 'inline-block px-4 py-2 rounded-lg text-[0.8rem] font-semibold border',
1525
+ variantStyles[block.variant]
1526
+ )}
1527
+ data-jp-item-id={(block as { id?: string }).id ?? `legacy-${bIdx}`}
1528
+ data-jp-item-field="blocks"
1529
+ >
1530
+ {block.label}
1531
+ </span>
1532
+ ))}
1533
+ </div>
1534
+ <span className="text-[0.7rem] text-[var(--local-text-muted)] uppercase tracking-widest mt-2 block opacity-60">
1535
+ {group.label}
1536
+ </span>
1537
+ </div>
1538
+ ))}
1539
+ </div>
1540
+ </div>
1541
+ <div>
1542
+ <h3 className="text-2xl font-bold text-[var(--local-text)] mb-4" data-jp-field="title">
1543
+ {data.title}
1544
+ </h3>
1545
+ {data.paragraphs.map((p, idx) => (
1546
+ <p
1547
+ key={idx}
1548
+ className="text-[var(--local-text-muted)] mb-5 text-[1.05rem] leading-relaxed"
1549
+ data-jp-item-id={(p as { id?: string }).id ?? `legacy-${idx}`}
1550
+ data-jp-item-field="paragraphs"
1551
+ >
1552
+ {p.isBold ? <strong className="text-[var(--local-text)]">{p.text}</strong> : p.text}
1553
+ </p>
1554
+ ))}
1555
+ </div>
1556
+ </div>
1557
+ </div>
1558
+ </section>
1559
+ );
1560
+ };
1406
1561
 
1407
1562
  END_OF_FILE_CONTENT
1408
1563
  echo "Creating src/components/problem-statement/index.ts..."
1409
1564
  cat << 'END_OF_FILE_CONTENT' > "src/components/problem-statement/index.ts"
1410
- export * from './View';
1411
- export * from './schema';
1412
- export * from './types';
1565
+ export * from './View';
1566
+ export * from './schema';
1567
+ export * from './types';
1413
1568
 
1414
1569
  END_OF_FILE_CONTENT
1415
1570
  echo "Creating src/components/problem-statement/schema.ts..."
1416
1571
  cat << 'END_OF_FILE_CONTENT' > "src/components/problem-statement/schema.ts"
1417
- import { z } from 'zod';
1418
- import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
1419
-
1420
- export const SiloBlockVariantSchema = z.enum(['red', 'amber', 'green', 'blue']);
1421
- const SiloBlockSchema = BaseArrayItem.extend({
1422
- label: z.string().describe('ui:text'),
1423
- variant: SiloBlockVariantSchema.describe('ui:select'),
1424
- });
1425
-
1426
- const SiloGroupSchema = BaseArrayItem.extend({
1427
- blocks: z.array(SiloBlockSchema).describe('ui:list'),
1428
- label: z.string().describe('ui:text'),
1429
- });
1430
-
1431
- const ProblemParagraphSchema = BaseArrayItem.extend({
1432
- text: z.string().describe('ui:textarea'),
1433
- isBold: z.boolean().default(false).describe('ui:checkbox'),
1434
- });
1435
-
1436
- export const ProblemStatementSchema = BaseSectionData.extend({
1437
- siloGroups: z.array(SiloGroupSchema).describe('ui:list'),
1438
- title: z.string().describe('ui:text'),
1439
- paragraphs: z.array(ProblemParagraphSchema).describe('ui:list'),
1440
- highlight: z.string().optional().describe('ui:text'),
1441
- });
1572
+ import { z } from 'zod';
1573
+ import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
1574
+
1575
+ export const SiloBlockVariantSchema = z.enum(['red', 'amber', 'green', 'blue']);
1576
+ const SiloBlockSchema = BaseArrayItem.extend({
1577
+ label: z.string().describe('ui:text'),
1578
+ variant: SiloBlockVariantSchema.describe('ui:select'),
1579
+ });
1580
+
1581
+ const SiloGroupSchema = BaseArrayItem.extend({
1582
+ blocks: z.array(SiloBlockSchema).describe('ui:list'),
1583
+ label: z.string().describe('ui:text'),
1584
+ });
1585
+
1586
+ const ProblemParagraphSchema = BaseArrayItem.extend({
1587
+ text: z.string().describe('ui:textarea'),
1588
+ isBold: z.boolean().default(false).describe('ui:checkbox'),
1589
+ });
1590
+
1591
+ export const ProblemStatementSchema = BaseSectionData.extend({
1592
+ siloGroups: z.array(SiloGroupSchema).describe('ui:list'),
1593
+ title: z.string().describe('ui:text'),
1594
+ paragraphs: z.array(ProblemParagraphSchema).describe('ui:list'),
1595
+ highlight: z.string().optional().describe('ui:text'),
1596
+ });
1442
1597
 
1443
1598
  END_OF_FILE_CONTENT
1444
1599
  echo "Creating src/components/problem-statement/types.ts..."
1445
1600
  cat << 'END_OF_FILE_CONTENT' > "src/components/problem-statement/types.ts"
1446
- import { z } from 'zod';
1447
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
1448
- import { ProblemStatementSchema, SiloBlockVariantSchema } from './schema';
1449
-
1450
- export type ProblemStatementData = z.infer<typeof ProblemStatementSchema>;
1451
- export type ProblemStatementSettings = z.infer<typeof BaseSectionSettingsSchema>;
1452
- export type SiloBlockVariant = z.infer<typeof SiloBlockVariantSchema>;
1601
+ import { z } from 'zod';
1602
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
1603
+ import { ProblemStatementSchema, SiloBlockVariantSchema } from './schema';
1604
+
1605
+ export type ProblemStatementData = z.infer<typeof ProblemStatementSchema>;
1606
+ export type ProblemStatementSettings = z.infer<typeof BaseSectionSettingsSchema>;
1607
+ export type SiloBlockVariant = z.infer<typeof SiloBlockVariantSchema>;
1453
1608
 
1454
1609
  END_OF_FILE_CONTENT
1455
1610
  mkdir -p "src/components/product-triad"
1456
1611
  echo "Creating src/components/product-triad/View.tsx..."
1457
1612
  cat << 'END_OF_FILE_CONTENT' > "src/components/product-triad/View.tsx"
1458
- import React from 'react';
1459
- import { cn } from '@/lib/utils';
1460
- import type { ProductTriadData, ProductTriadSettings } from './types';
1461
-
1462
- export const ProductTriad: React.FC<{ data: ProductTriadData; settings?: ProductTriadSettings }> = ({ data }) => {
1463
- return (
1464
- <section
1465
- style={{
1466
- '--local-bg': 'var(--background)',
1467
- '--local-text': 'var(--foreground)',
1468
- '--local-text-muted': 'var(--muted-foreground)',
1469
- '--local-primary': 'var(--primary)',
1470
- '--local-accent': 'var(--color-accent, #60a5fa)',
1471
- '--local-border': 'var(--border)',
1472
- } as React.CSSProperties}
1473
- className="relative z-0 py-28 bg-[var(--local-bg)]"
1474
- >
1475
- <div className="max-w-[1200px] mx-auto px-8">
1476
- <div className="text-center">
1477
- {data.label && (
1478
- <div className="jp-section-label inline-flex items-center gap-2 text-[0.72rem] font-bold uppercase tracking-[0.12em] text-[var(--local-accent)] mb-4" data-jp-field="label">
1479
- <span className="w-5 h-px bg-[var(--local-primary)]" />
1480
- {data.label}
1481
- </div>
1482
- )}
1483
- <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
1484
- {data.title}
1485
- </h2>
1486
- {data.description && (
1487
- <p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed" data-jp-field="description">
1488
- {data.description}
1489
- </p>
1490
- )}
1491
- </div>
1492
- <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mt-14">
1493
- {data.products.map((product, idx) => (
1494
- <div
1495
- key={product.id ?? idx}
1496
- className={cn(
1497
- 'relative border rounded-lg p-10 transition-all duration-300 hover:-translate-y-1',
1498
- product.featured
1499
- ? 'border-[rgba(59,130,246,0.3)] bg-gradient-to-b from-[rgba(59,130,246,0.06)] to-[rgba(59,130,246,0.01)] hover:border-[rgba(59,130,246,0.4)]'
1500
- : 'border-[rgba(255,255,255,0.06)] bg-[rgba(255,255,255,0.015)] hover:border-[rgba(59,130,246,0.2)]'
1501
- )}
1502
- data-jp-item-id={product.id ?? `legacy-${idx}`}
1503
- data-jp-item-field="products"
1504
- >
1505
- {product.featured && (
1506
- <div className="absolute -top-3 left-1/2 -translate-x-1/2 bg-[var(--local-primary)] text-white text-[0.7rem] font-bold px-4 py-1 rounded-full uppercase tracking-wide">
1507
- Most Popular
1508
- </div>
1509
- )}
1510
- <div className="text-[0.75rem] font-bold uppercase tracking-[0.1em] text-[var(--local-accent)] mb-2">
1511
- {product.tier}
1512
- </div>
1513
- <div className="text-2xl font-extrabold text-[var(--local-text)] mb-2">
1514
- {product.name}
1515
- </div>
1516
- <div className="font-display text-[2.2rem] font-extrabold text-[var(--local-text)] mb-1">
1517
- {product.price}
1518
- {product.priceSuffix && (
1519
- <span className="text-[0.9rem] font-normal text-[var(--local-text-muted)]">
1520
- {product.priceSuffix}
1521
- </span>
1522
- )}
1523
- </div>
1524
- <div className="text-[0.85rem] text-[var(--local-text-muted)] mb-6 pb-6 border-b border-[rgba(255,255,255,0.06)]">
1525
- {product.delivery}
1526
- </div>
1527
- <ul className="mb-8 space-y-0">
1528
- {product.features.map((feature, fIdx) => (
1529
- <li
1530
- key={fIdx}
1531
- className="text-[0.9rem] text-[#cbd5e1] py-1.5 pl-6 relative before:content-['✓'] before:absolute before:left-0 before:text-[var(--local-accent)] before:font-bold before:text-[0.8rem]"
1532
- >
1533
- {feature.text}
1534
- </li>
1535
- ))}
1536
- </ul>
1537
- {product.ctaLabel && product.ctaHref && (
1538
- <a
1539
- href={product.ctaHref}
1540
- className={cn(
1541
- 'block text-center py-3 rounded-[5px] no-underline font-semibold text-[0.95rem] transition-all duration-200',
1542
- product.ctaVariant === 'primary'
1543
- ? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-px'
1544
- : 'bg-[rgba(255,255,255,0.05)] text-[#e2e8f0] border border-[rgba(255,255,255,0.1)] hover:bg-[rgba(255,255,255,0.08)] hover:border-[rgba(255,255,255,0.2)]'
1545
- )}
1546
- >
1547
- {product.ctaLabel}
1548
- </a>
1549
- )}
1550
- </div>
1551
- ))}
1552
- </div>
1553
- </div>
1554
- </section>
1555
- );
1556
- };
1613
+ import React from 'react';
1614
+ import { cn } from '@/lib/utils';
1615
+ import type { ProductTriadData, ProductTriadSettings } from './types';
1616
+
1617
+ export const ProductTriad: React.FC<{ data: ProductTriadData; settings?: ProductTriadSettings }> = ({ data }) => {
1618
+ return (
1619
+ <section
1620
+ style={{
1621
+ '--local-bg': 'var(--background)',
1622
+ '--local-text': 'var(--foreground)',
1623
+ '--local-text-muted': 'var(--muted-foreground)',
1624
+ '--local-primary': 'var(--primary)',
1625
+ '--local-accent': 'var(--color-accent, #60a5fa)',
1626
+ '--local-border': 'var(--border)',
1627
+ } as React.CSSProperties}
1628
+ className="relative z-0 py-28 bg-[var(--local-bg)]"
1629
+ >
1630
+ <div className="max-w-[1200px] mx-auto px-8">
1631
+ <div className="text-center">
1632
+ {data.label && (
1633
+ <div className="jp-section-label inline-flex items-center gap-2 text-[0.72rem] font-bold uppercase tracking-[0.12em] text-[var(--local-accent)] mb-4" data-jp-field="label">
1634
+ <span className="w-5 h-px bg-[var(--local-primary)]" />
1635
+ {data.label}
1636
+ </div>
1637
+ )}
1638
+ <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
1639
+ {data.title}
1640
+ </h2>
1641
+ {data.description && (
1642
+ <p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed" data-jp-field="description">
1643
+ {data.description}
1644
+ </p>
1645
+ )}
1646
+ </div>
1647
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mt-14">
1648
+ {data.products.map((product, idx) => (
1649
+ <div
1650
+ key={product.id ?? idx}
1651
+ className={cn(
1652
+ 'relative border rounded-lg p-10 transition-all duration-300 hover:-translate-y-1',
1653
+ product.featured
1654
+ ? 'border-[rgba(59,130,246,0.3)] bg-gradient-to-b from-[rgba(59,130,246,0.06)] to-[rgba(59,130,246,0.01)] hover:border-[rgba(59,130,246,0.4)]'
1655
+ : 'border-[rgba(255,255,255,0.06)] bg-[rgba(255,255,255,0.015)] hover:border-[rgba(59,130,246,0.2)]'
1656
+ )}
1657
+ data-jp-item-id={product.id ?? `legacy-${idx}`}
1658
+ data-jp-item-field="products"
1659
+ >
1660
+ {product.featured && (
1661
+ <div className="absolute -top-3 left-1/2 -translate-x-1/2 bg-[var(--local-primary)] text-white text-[0.7rem] font-bold px-4 py-1 rounded-full uppercase tracking-wide">
1662
+ Most Popular
1663
+ </div>
1664
+ )}
1665
+ <div className="text-[0.75rem] font-bold uppercase tracking-[0.1em] text-[var(--local-accent)] mb-2">
1666
+ {product.tier}
1667
+ </div>
1668
+ <div className="text-2xl font-extrabold text-[var(--local-text)] mb-2">
1669
+ {product.name}
1670
+ </div>
1671
+ <div className="font-display text-[2.2rem] font-extrabold text-[var(--local-text)] mb-1">
1672
+ {product.price}
1673
+ {product.priceSuffix && (
1674
+ <span className="text-[0.9rem] font-normal text-[var(--local-text-muted)]">
1675
+ {product.priceSuffix}
1676
+ </span>
1677
+ )}
1678
+ </div>
1679
+ <div className="text-[0.85rem] text-[var(--local-text-muted)] mb-6 pb-6 border-b border-[rgba(255,255,255,0.06)]">
1680
+ {product.delivery}
1681
+ </div>
1682
+ <ul className="mb-8 space-y-0">
1683
+ {product.features.map((feature, fIdx) => (
1684
+ <li
1685
+ key={fIdx}
1686
+ className="text-[0.9rem] text-[#cbd5e1] py-1.5 pl-6 relative before:content-['✓'] before:absolute before:left-0 before:text-[var(--local-accent)] before:font-bold before:text-[0.8rem]"
1687
+ >
1688
+ {feature.text}
1689
+ </li>
1690
+ ))}
1691
+ </ul>
1692
+ {product.ctaLabel && product.ctaHref && (
1693
+ <a
1694
+ href={product.ctaHref}
1695
+ className={cn(
1696
+ 'block text-center py-3 rounded-[5px] no-underline font-semibold text-[0.95rem] transition-all duration-200',
1697
+ product.ctaVariant === 'primary'
1698
+ ? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-px'
1699
+ : 'bg-[rgba(255,255,255,0.05)] text-[#e2e8f0] border border-[rgba(255,255,255,0.1)] hover:bg-[rgba(255,255,255,0.08)] hover:border-[rgba(255,255,255,0.2)]'
1700
+ )}
1701
+ >
1702
+ {product.ctaLabel}
1703
+ </a>
1704
+ )}
1705
+ </div>
1706
+ ))}
1707
+ </div>
1708
+ </div>
1709
+ </section>
1710
+ );
1711
+ };
1557
1712
 
1558
1713
  END_OF_FILE_CONTENT
1559
1714
  echo "Creating src/components/product-triad/index.ts..."
1560
1715
  cat << 'END_OF_FILE_CONTENT' > "src/components/product-triad/index.ts"
1561
- export * from './View';
1562
- export * from './schema';
1563
- export * from './types';
1716
+ export * from './View';
1717
+ export * from './schema';
1718
+ export * from './types';
1564
1719
 
1565
1720
  END_OF_FILE_CONTENT
1566
1721
  echo "Creating src/components/product-triad/schema.ts..."
1567
1722
  cat << 'END_OF_FILE_CONTENT' > "src/components/product-triad/schema.ts"
1568
- import { z } from 'zod';
1569
- import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
1570
-
1571
- const ProductFeatureSchema = z.object({
1572
- text: z.string().describe('ui:text'),
1573
- });
1574
-
1575
- const ProductCardSchema = BaseArrayItem.extend({
1576
- tier: z.string().describe('ui:text'),
1577
- name: z.string().describe('ui:text'),
1578
- price: z.string().describe('ui:text'),
1579
- priceSuffix: z.string().optional().describe('ui:text'),
1580
- delivery: z.string().describe('ui:text'),
1581
- features: z.array(ProductFeatureSchema).describe('ui:list'),
1582
- featured: z.boolean().default(false).describe('ui:checkbox'),
1583
- ctaLabel: z.string().optional().describe('ui:text'),
1584
- ctaHref: z.string().optional().describe('ui:text'),
1585
- ctaVariant: z.enum(['primary', 'secondary']).default('secondary').describe('ui:select'),
1586
- });
1587
-
1588
- export const ProductTriadSchema = BaseSectionData.extend({
1589
- label: z.string().optional().describe('ui:text'),
1590
- title: z.string().describe('ui:text'),
1591
- description: z.string().optional().describe('ui:textarea'),
1592
- products: z.array(ProductCardSchema).describe('ui:list'),
1593
- });
1723
+ import { z } from 'zod';
1724
+ import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
1725
+
1726
+ const ProductFeatureSchema = z.object({
1727
+ text: z.string().describe('ui:text'),
1728
+ });
1729
+
1730
+ const ProductCardSchema = BaseArrayItem.extend({
1731
+ tier: z.string().describe('ui:text'),
1732
+ name: z.string().describe('ui:text'),
1733
+ price: z.string().describe('ui:text'),
1734
+ priceSuffix: z.string().optional().describe('ui:text'),
1735
+ delivery: z.string().describe('ui:text'),
1736
+ features: z.array(ProductFeatureSchema).describe('ui:list'),
1737
+ featured: z.boolean().default(false).describe('ui:checkbox'),
1738
+ ctaLabel: z.string().optional().describe('ui:text'),
1739
+ ctaHref: z.string().optional().describe('ui:text'),
1740
+ ctaVariant: z.enum(['primary', 'secondary']).default('secondary').describe('ui:select'),
1741
+ });
1742
+
1743
+ export const ProductTriadSchema = BaseSectionData.extend({
1744
+ label: z.string().optional().describe('ui:text'),
1745
+ title: z.string().describe('ui:text'),
1746
+ description: z.string().optional().describe('ui:textarea'),
1747
+ products: z.array(ProductCardSchema).describe('ui:list'),
1748
+ });
1594
1749
 
1595
1750
  END_OF_FILE_CONTENT
1596
1751
  echo "Creating src/components/product-triad/types.ts..."
1597
1752
  cat << 'END_OF_FILE_CONTENT' > "src/components/product-triad/types.ts"
1598
- import { z } from 'zod';
1599
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
1600
- import { ProductTriadSchema } from './schema';
1601
-
1602
- export type ProductTriadData = z.infer<typeof ProductTriadSchema>;
1603
- export type ProductTriadSettings = z.infer<typeof BaseSectionSettingsSchema>;
1753
+ import { z } from 'zod';
1754
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
1755
+ import { ProductTriadSchema } from './schema';
1756
+
1757
+ export type ProductTriadData = z.infer<typeof ProductTriadSchema>;
1758
+ export type ProductTriadSettings = z.infer<typeof BaseSectionSettingsSchema>;
1604
1759
 
1605
1760
  END_OF_FILE_CONTENT
1606
1761
  mkdir -p "src/components/ui"
@@ -2178,85 +2333,85 @@ END_OF_FILE_CONTENT
2178
2333
  # SKIP: src/data/config/menu.json:Zone.Identifier è un file binario e non può essere convertito in testo.
2179
2334
  echo "Creating src/data/config/site.json..."
2180
2335
  cat << 'END_OF_FILE_CONTENT' > "src/data/config/site.json"
2181
- {
2182
- "identity": {
2183
- "title": "JsonPages",
2184
- "logoUrl": "/logo.svg"
2185
- },
2186
- "pages": [
2187
- {
2188
- "slug": "home",
2189
- "label": "Home"
2190
- },
2191
- {
2192
- "slug": "architecture",
2193
- "label": "Architecture"
2194
- },
2195
- {
2196
- "slug": "usage",
2197
- "label": "Usage"
2198
- }
2199
- ],
2200
- "header": {
2201
- "id": "global-header",
2202
- "type": "header",
2203
- "data": {
2204
- "logoText": "Json",
2205
- "logoHighlight": "Pages",
2206
- "logoIconText": "{ }",
2207
- "links": [
2208
- {
2209
- "label": "Architecture",
2210
- "href": "#architecture"
2211
- },
2212
- {
2213
- "label": "Products",
2214
- "href": "#products"
2215
- },
2216
- {
2217
- "label": "PA Ready",
2218
- "href": "#pa-ready"
2219
- },
2220
- {
2221
- "label": "Philosophy",
2222
- "href": "#philosophy"
2223
- }
2224
- ]
2225
- },
2226
- "settings": {
2227
- "sticky": true
2228
- }
2229
- },
2230
- "footer": {
2231
- "id": "global-footer",
2232
- "type": "footer",
2233
- "data": {
2234
- "brandText": "Json",
2235
- "brandHighlight": "Pages",
2236
- "copyright": "© 2026 JsonPages Ecosystem. Architected by Guido Filippo Serio.",
2237
- "links": [
2238
- {
2239
- "label": "Documentation",
2240
- "href": "#"
2241
- },
2242
- {
2243
- "label": "API Reference",
2244
- "href": "#"
2245
- },
2246
- {
2247
- "label": "Changelog",
2248
- "href": "#"
2249
- },
2250
- {
2251
- "label": "Privacy",
2252
- "href": "#"
2253
- }
2254
- ]
2255
- },
2256
- "settings": {
2257
- "showLogo": true
2258
- }
2259
- }
2336
+ {
2337
+ "identity": {
2338
+ "title": "JsonPages",
2339
+ "logoUrl": "/logo.svg"
2340
+ },
2341
+ "pages": [
2342
+ {
2343
+ "slug": "home",
2344
+ "label": "Home"
2345
+ },
2346
+ {
2347
+ "slug": "architecture",
2348
+ "label": "Architecture"
2349
+ },
2350
+ {
2351
+ "slug": "usage",
2352
+ "label": "Usage"
2353
+ }
2354
+ ],
2355
+ "header": {
2356
+ "id": "global-header",
2357
+ "type": "header",
2358
+ "data": {
2359
+ "logoText": "Json",
2360
+ "logoHighlight": "Pages",
2361
+ "logoIconText": "{ }",
2362
+ "links": [
2363
+ {
2364
+ "label": "Architecture",
2365
+ "href": "#architecture"
2366
+ },
2367
+ {
2368
+ "label": "Products",
2369
+ "href": "#products"
2370
+ },
2371
+ {
2372
+ "label": "PA Ready",
2373
+ "href": "#pa-ready"
2374
+ },
2375
+ {
2376
+ "label": "Philosophy",
2377
+ "href": "#philosophy"
2378
+ }
2379
+ ]
2380
+ },
2381
+ "settings": {
2382
+ "sticky": true
2383
+ }
2384
+ },
2385
+ "footer": {
2386
+ "id": "global-footer",
2387
+ "type": "footer",
2388
+ "data": {
2389
+ "brandText": "Json",
2390
+ "brandHighlight": "Pages",
2391
+ "copyright": "© 2026 JsonPages Ecosystem. Architected by Guido Filippo Serio.",
2392
+ "links": [
2393
+ {
2394
+ "label": "Documentation",
2395
+ "href": "#"
2396
+ },
2397
+ {
2398
+ "label": "API Reference",
2399
+ "href": "#"
2400
+ },
2401
+ {
2402
+ "label": "Changelog",
2403
+ "href": "#"
2404
+ },
2405
+ {
2406
+ "label": "Privacy",
2407
+ "href": "#"
2408
+ }
2409
+ ]
2410
+ },
2411
+ "settings": {
2412
+ "showLogo": true
2413
+ }
2414
+ }
2260
2415
  }
2261
2416
  END_OF_FILE_CONTENT
2262
2417
  # SKIP: src/data/config/site.json:Zone.Identifier è un file binario e non può essere convertito in testo.
@@ -2340,6 +2495,18 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
2340
2495
  },
2341
2496
  "settings": {}
2342
2497
  },
2498
+ {
2499
+ "id": "image-break-1",
2500
+ "type": "image-break",
2501
+ "data": {
2502
+ "image": {
2503
+ "url": "https://images.unsplash.com/photo-1464226184884-fa280b87c399?w=800",
2504
+ "alt": "Verdure fresche dall'orto"
2505
+ },
2506
+ "caption": "Il nostro orto a pochi passi dalla cucina — raccolto ogni mattina all'alba"
2507
+ },
2508
+ "settings": {}
2509
+ },
2343
2510
  {
2344
2511
  "id": "problem-statement",
2345
2512
  "type": "problem-statement",
@@ -2934,6 +3101,7 @@ import { ProductTriad } from '@/components/product-triad';
2934
3101
  import { PaSection } from '@/components/pa-section';
2935
3102
  import { Philosophy } from '@/components/philosophy';
2936
3103
  import { CtaBanner } from '@/components/cta-banner';
3104
+ import { ImageBreak } from '@/components/image-break';
2937
3105
 
2938
3106
  import type { SectionType } from '@jsonpages/core';
2939
3107
  import type { SectionComponentPropsMap } from '@/types';
@@ -2953,6 +3121,7 @@ export const ComponentRegistry: {
2953
3121
  'pa-section': PaSection,
2954
3122
  'philosophy': Philosophy,
2955
3123
  'cta-banner': CtaBanner,
3124
+ 'image-break': ImageBreak,
2956
3125
  };
2957
3126
 
2958
3127
  END_OF_FILE_CONTENT
@@ -3034,6 +3203,7 @@ const addableSectionTypes = [
3034
3203
  'pa-section',
3035
3204
  'philosophy',
3036
3205
  'cta-banner',
3206
+ 'image-break',
3037
3207
  ] as const;
3038
3208
 
3039
3209
  const sectionTypeLabels: Record<string, string> = {
@@ -3047,6 +3217,7 @@ const sectionTypeLabels: Record<string, string> = {
3047
3217
  'pa-section': 'PA Section',
3048
3218
  'philosophy': 'Philosophy',
3049
3219
  'cta-banner': 'CTA Banner',
3220
+ 'image-break': 'Image Break',
3050
3221
  };
3051
3222
 
3052
3223
  function getDefaultSectionData(sectionType: string): Record<string, unknown> {
@@ -3071,6 +3242,8 @@ function getDefaultSectionData(sectionType: string): Record<string, unknown> {
3071
3242
  return { title: 'Philosophy', quote: 'Your quote here.' };
3072
3243
  case 'cta-banner':
3073
3244
  return { title: 'Call to Action', description: '' };
3245
+ case 'image-break':
3246
+ return { image: { url: '', alt: '' }, caption: '' };
3074
3247
  default:
3075
3248
  return {};
3076
3249
  }
@@ -3085,33 +3258,33 @@ export const addSectionConfig: AddSectionConfig = {
3085
3258
  END_OF_FILE_CONTENT
3086
3259
  echo "Creating src/lib/base-schemas.ts..."
3087
3260
  cat << 'END_OF_FILE_CONTENT' > "src/lib/base-schemas.ts"
3088
- import { z } from 'zod';
3089
-
3090
- /**
3091
- * Base schemas shared by section capsules (CIP governance).
3092
- * Capsules extend these for consistent anchorId, array items, and settings.
3093
- */
3094
- export const BaseSectionData = z.object({
3095
- anchorId: z.string().optional().describe('ui:text'),
3096
- });
3097
-
3098
- export const BaseArrayItem = z.object({
3099
- id: z.string().optional(),
3100
- });
3101
-
3102
- export const BaseSectionSettingsSchema = z.object({
3103
- paddingTop: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),
3104
- paddingBottom: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),
3105
- theme: z.enum(['dark', 'light', 'accent']).default('dark').describe('ui:select'),
3106
- container: z.enum(['boxed', 'fluid']).default('boxed').describe('ui:select'),
3107
- });
3108
-
3109
- export const CtaSchema = z.object({
3110
- id: z.string().optional(),
3111
- label: z.string().describe('ui:text'),
3112
- href: z.string().describe('ui:text'),
3113
- variant: z.enum(['primary', 'secondary']).default('primary').describe('ui:select'),
3114
- });
3261
+ import { z } from 'zod';
3262
+
3263
+ /**
3264
+ * Base schemas shared by section capsules (CIP governance).
3265
+ * Capsules extend these for consistent anchorId, array items, and settings.
3266
+ */
3267
+ export const BaseSectionData = z.object({
3268
+ anchorId: z.string().optional().describe('ui:text'),
3269
+ });
3270
+
3271
+ export const BaseArrayItem = z.object({
3272
+ id: z.string().optional(),
3273
+ });
3274
+
3275
+ export const BaseSectionSettingsSchema = z.object({
3276
+ paddingTop: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),
3277
+ paddingBottom: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),
3278
+ theme: z.enum(['dark', 'light', 'accent']).default('dark').describe('ui:select'),
3279
+ container: z.enum(['boxed', 'fluid']).default('boxed').describe('ui:select'),
3280
+ });
3281
+
3282
+ export const CtaSchema = z.object({
3283
+ id: z.string().optional(),
3284
+ label: z.string().describe('ui:text'),
3285
+ href: z.string().describe('ui:text'),
3286
+ variant: z.enum(['primary', 'secondary']).default('primary').describe('ui:select'),
3287
+ });
3115
3288
 
3116
3289
  END_OF_FILE_CONTENT
3117
3290
  echo "Creating src/lib/schemas.ts..."
@@ -3139,6 +3312,7 @@ import { ProductTriadSchema } from '@/components/product-triad';
3139
3312
  import { PaSectionSchema } from '@/components/pa-section';
3140
3313
  import { PhilosophySchema } from '@/components/philosophy';
3141
3314
  import { CtaBannerSchema } from '@/components/cta-banner';
3315
+ import { ImageBreakSchema } from '@/components/image-break';
3142
3316
 
3143
3317
  export const SECTION_SCHEMAS = {
3144
3318
  'header': HeaderSchema,
@@ -3153,6 +3327,7 @@ export const SECTION_SCHEMAS = {
3153
3327
  'pa-section': PaSectionSchema,
3154
3328
  'philosophy': PhilosophySchema,
3155
3329
  'cta-banner': CtaBannerSchema,
3330
+ 'image-break': ImageBreakSchema,
3156
3331
  } as const;
3157
3332
 
3158
3333
  export type SectionType = keyof typeof SECTION_SCHEMAS;
@@ -3202,6 +3377,7 @@ import type { ProductTriadData, ProductTriadSettings } from '@/components/produc
3202
3377
  import type { PaSectionData, PaSectionSettings } from '@/components/pa-section';
3203
3378
  import type { PhilosophyData, PhilosophySettings } from '@/components/philosophy';
3204
3379
  import type { CtaBannerData, CtaBannerSettings } from '@/components/cta-banner';
3380
+ import type { ImageBreakData, ImageBreakSettings } from '@/components/image-break';
3205
3381
 
3206
3382
  export type SectionComponentPropsMap = {
3207
3383
  'header': { data: HeaderData; settings?: HeaderSettings; menu: MenuItem[] };
@@ -3216,6 +3392,7 @@ export type SectionComponentPropsMap = {
3216
3392
  'pa-section': { data: PaSectionData; settings?: PaSectionSettings };
3217
3393
  'philosophy': { data: PhilosophyData; settings?: PhilosophySettings };
3218
3394
  'cta-banner': { data: CtaBannerData; settings?: CtaBannerSettings };
3395
+ 'image-break': { data: ImageBreakData; settings?: ImageBreakSettings };
3219
3396
  };
3220
3397
 
3221
3398
  declare module '@jsonpages/core' {
@@ -3232,6 +3409,7 @@ declare module '@jsonpages/core' {
3232
3409
  'pa-section': PaSectionData;
3233
3410
  'philosophy': PhilosophyData;
3234
3411
  'cta-banner': CtaBannerData;
3412
+ 'image-break': ImageBreakData;
3235
3413
  }
3236
3414
 
3237
3415
  export interface SectionSettingsRegistry {
@@ -3247,6 +3425,7 @@ declare module '@jsonpages/core' {
3247
3425
  'pa-section': PaSectionSettings;
3248
3426
  'philosophy': PhilosophySettings;
3249
3427
  'cta-banner': CtaBannerSettings;
3428
+ 'image-break': ImageBreakSettings;
3250
3429
  }
3251
3430
  }
3252
3431