@jsonpages/cli 3.0.5 → 3.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3267 +0,0 @@
1
- #!/bin/bash
2
- set -e # Termina se c'è un errore
3
-
4
- echo "Inizio ricostruzione progetto..."
5
-
6
- echo "Creating index.html..."
7
- cat << 'END_OF_FILE_CONTENT' > "index.html"
8
- <!DOCTYPE html>
9
- <html lang="en">
10
- <head>
11
- <meta charset="UTF-8" />
12
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
13
- <meta name="description" content="JsonPages - Global Authoring. Global Governance." />
14
- <link rel="preconnect" href="https://fonts.googleapis.com" />
15
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
16
- <link href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Playfair+Display:wght@700;800;900&display=swap" rel="stylesheet" />
17
- <title>JsonPages</title>
18
- </head>
19
- <body>
20
- <div id="root"></div>
21
- <script type="module" src="/src/main.tsx"></script>
22
- </body>
23
- </html>
24
-
25
-
26
- END_OF_FILE_CONTENT
27
- mkdir -p "src"
28
- echo "Creating src/App.tsx..."
29
- cat << 'END_OF_FILE_CONTENT' > "src/App.tsx"
30
- /**
31
- * Thin Entry Point (Tenant).
32
- */
33
- import { JsonPagesEngine } from '@jsonpages/core';
34
- import { ComponentRegistry } from '@/lib/ComponentRegistry';
35
- import { SECTION_SCHEMAS } from '@/lib/schemas';
36
- import { addSectionConfig } from '@/lib/addSectionConfig';
37
- import type { JsonPagesConfig } from '@jsonpages/core';
38
- import type { PageConfig, SiteConfig, ThemeConfig, MenuConfig } from '@/types';
39
-
40
- // Tenant data
41
- import siteData from '@/data/config/site.json';
42
- import themeData from '@/data/config/theme.json';
43
- import menuData from '@/data/config/menu.json';
44
- import homeData from '@/data/pages/home.json';
45
-
46
- // Tenant CSS
47
- import tenantCss from './index.css?inline';
48
-
49
- const siteConfig = siteData as unknown as SiteConfig;
50
- const themeConfig = themeData as unknown as ThemeConfig;
51
- const menuConfig = menuData as unknown as MenuConfig;
52
-
53
- const pages: Record<string, PageConfig> = {
54
- home: homeData as unknown as PageConfig,
55
- };
56
-
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
- function App() {
70
- return <JsonPagesEngine config={config} />;
71
- }
72
-
73
- export default App;
74
- END_OF_FILE_CONTENT
75
- mkdir -p "src/components"
76
- echo "Creating src/components/NotFound.tsx..."
77
- cat << 'END_OF_FILE_CONTENT' > "src/components/NotFound.tsx"
78
- import React from 'react';
79
- import { Icon } from '@/lib/IconResolver';
80
-
81
- export const NotFound: React.FC = () => {
82
- return (
83
- <div
84
- style={{
85
- '--local-bg': 'var(--color-background)',
86
- '--local-text': 'var(--color-text)',
87
- '--local-text-muted': 'var(--color-text-muted)',
88
- '--local-primary': 'var(--color-primary)',
89
- '--local-radius-md': 'var(--radius-md)',
90
- } as React.CSSProperties}
91
- className="min-h-screen flex flex-col items-center justify-center bg-[var(--local-bg)] px-6"
92
- >
93
- <h1 className="text-6xl font-bold text-[var(--local-text)] mb-4">404</h1>
94
- <p className="text-xl text-[var(--local-text-muted)] mb-8">Page not found</p>
95
- <a
96
- href="/"
97
- className="inline-flex items-center gap-2 px-6 py-3 rounded-[var(--local-radius-md)] bg-[var(--local-primary)] text-[var(--local-bg)] font-semibold text-sm hover:opacity-90 transition-opacity"
98
- >
99
- <span>Back to Home</span>
100
- <Icon name="arrow-right" size={16} />
101
- </a>
102
- </div>
103
- );
104
- };
105
-
106
-
107
-
108
-
109
-
110
- END_OF_FILE_CONTENT
111
- mkdir -p "src/components/arch-layers"
112
- echo "Creating src/components/arch-layers/View.tsx..."
113
- 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
- };
220
-
221
- END_OF_FILE_CONTENT
222
- echo "Creating src/components/arch-layers/index.ts..."
223
- cat << 'END_OF_FILE_CONTENT' > "src/components/arch-layers/index.ts"
224
- export * from './View';
225
- export * from './schema';
226
- export * from './types';
227
-
228
- END_OF_FILE_CONTENT
229
- echo "Creating src/components/arch-layers/schema.ts..."
230
- 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
- });
257
-
258
- END_OF_FILE_CONTENT
259
- echo "Creating src/components/arch-layers/types.ts..."
260
- 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>;
269
-
270
- END_OF_FILE_CONTENT
271
- mkdir -p "src/components/code-block"
272
- echo "Creating src/components/code-block/View.tsx..."
273
- 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
- };
331
-
332
- END_OF_FILE_CONTENT
333
- echo "Creating src/components/code-block/index.ts..."
334
- cat << 'END_OF_FILE_CONTENT' > "src/components/code-block/index.ts"
335
- export * from './View';
336
- export * from './schema';
337
- export * from './types';
338
-
339
- END_OF_FILE_CONTENT
340
- echo "Creating src/components/code-block/schema.ts..."
341
- 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
- });
358
-
359
- END_OF_FILE_CONTENT
360
- echo "Creating src/components/code-block/types.ts..."
361
- 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>;
368
-
369
- END_OF_FILE_CONTENT
370
- mkdir -p "src/components/cta-banner"
371
- echo "Creating src/components/cta-banner/View.tsx..."
372
- 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
- };
432
-
433
- END_OF_FILE_CONTENT
434
- echo "Creating src/components/cta-banner/index.ts..."
435
- cat << 'END_OF_FILE_CONTENT' > "src/components/cta-banner/index.ts"
436
- export * from './View';
437
- export * from './schema';
438
- export * from './types';
439
-
440
- END_OF_FILE_CONTENT
441
- echo "Creating src/components/cta-banner/schema.ts..."
442
- 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
- });
452
-
453
- END_OF_FILE_CONTENT
454
- echo "Creating src/components/cta-banner/types.ts..."
455
- 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>;
462
-
463
- END_OF_FILE_CONTENT
464
- mkdir -p "src/components/feature-grid"
465
- echo "Creating src/components/feature-grid/View.tsx..."
466
- 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
- };
530
-
531
- END_OF_FILE_CONTENT
532
- echo "Creating src/components/feature-grid/index.ts..."
533
- cat << 'END_OF_FILE_CONTENT' > "src/components/feature-grid/index.ts"
534
- export * from './View';
535
- export * from './schema';
536
- export * from './types';
537
-
538
- END_OF_FILE_CONTENT
539
- echo "Creating src/components/feature-grid/schema.ts..."
540
- 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
- });
560
-
561
- END_OF_FILE_CONTENT
562
- echo "Creating src/components/feature-grid/types.ts..."
563
- 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>;
570
-
571
- END_OF_FILE_CONTENT
572
- mkdir -p "src/components/footer"
573
- echo "Creating src/components/footer/View.tsx..."
574
- 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
- };
621
-
622
- END_OF_FILE_CONTENT
623
- echo "Creating src/components/footer/index.ts..."
624
- cat << 'END_OF_FILE_CONTENT' > "src/components/footer/index.ts"
625
- export * from './View';
626
- export * from './schema';
627
- export * from './types';
628
-
629
- END_OF_FILE_CONTENT
630
- echo "Creating src/components/footer/schema.ts..."
631
- 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
- });
647
-
648
- END_OF_FILE_CONTENT
649
- echo "Creating src/components/footer/types.ts..."
650
- 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>;
656
-
657
- END_OF_FILE_CONTENT
658
- mkdir -p "src/components/header"
659
- echo "Creating src/components/header/View.tsx..."
660
- 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
- };
773
-
774
- END_OF_FILE_CONTENT
775
- echo "Creating src/components/header/index.ts..."
776
- cat << 'END_OF_FILE_CONTENT' > "src/components/header/index.ts"
777
- export * from './View';
778
- export * from './schema';
779
- export * from './types';
780
- END_OF_FILE_CONTENT
781
- echo "Creating src/components/header/schema.ts..."
782
- cat << 'END_OF_FILE_CONTENT' > "src/components/header/schema.ts"
783
- import { z } from 'zod';
784
-
785
- /**
786
- * 📝 HEADER SCHEMA (Contract)
787
- * Definisce la struttura dati che l'Admin userà per generare la form.
788
- */
789
- export const HeaderSchema = z.object({
790
- logoText: z.string().describe('ui:text'),
791
- logoHighlight: z.string().optional().describe('ui:text'),
792
- logoIconText: z.string().optional().describe('ui:text'),
793
- links: z.array(z.object({
794
- label: z.string().describe('ui:text'),
795
- href: z.string().describe('ui:text'),
796
- isCta: z.boolean().default(false).describe('ui:checkbox'),
797
- })).describe('ui:list'),
798
- });
799
-
800
- /**
801
- * ⚙️ HEADER SETTINGS
802
- * Definisce i parametri tecnici (non di contenuto).
803
- */
804
- export const HeaderSettingsSchema = z.object({
805
- sticky: z.boolean().default(true).describe('ui:checkbox'),
806
- });
807
- END_OF_FILE_CONTENT
808
- echo "Creating src/components/header/types.ts..."
809
- cat << 'END_OF_FILE_CONTENT' > "src/components/header/types.ts"
810
- import { z } from 'zod';
811
- import { HeaderSchema, HeaderSettingsSchema } from './schema';
812
-
813
- /**
814
- * 🧩 HEADER DATA
815
- * Tipo inferito dallo schema Zod del contenuto.
816
- * Utilizzato dalla View per renderizzare logo e links.
817
- */
818
- export type HeaderData = z.infer<typeof HeaderSchema>;
819
-
820
- /**
821
- * ⚙️ HEADER SETTINGS
822
- * Tipo inferito dallo schema Zod dei settings.
823
- * Gestisce comportamenti tecnici come lo 'sticky'.
824
- */
825
- export type HeaderSettings = z.infer<typeof HeaderSettingsSchema>;
826
- END_OF_FILE_CONTENT
827
- mkdir -p "src/components/hero"
828
- echo "Creating src/components/hero/View.tsx..."
829
- cat << 'END_OF_FILE_CONTENT' > "src/components/hero/View.tsx"
830
- import React from 'react';
831
- import { cn } from '@/lib/utils';
832
- import type { HeroData, HeroSettings } from './types';
833
-
834
- export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ data }) => {
835
- return (
836
- <section
837
- style={{
838
- '--local-bg': 'var(--background)',
839
- '--local-text': 'var(--foreground)',
840
- '--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
- } as React.CSSProperties}
846
- className="jp-hero relative min-h-screen flex items-center overflow-hidden pt-24 bg-[var(--local-bg)]"
847
- >
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
- </>
871
- )}
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
- ))}
914
- </div>
915
- )}
916
- </div>
917
- </div>
918
- </section>
919
- );
920
- };
921
-
922
- 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';
928
-
929
- END_OF_FILE_CONTENT
930
- echo "Creating src/components/hero/schema.ts..."
931
- cat << 'END_OF_FILE_CONTENT' > "src/components/hero/schema.ts"
932
- import { z } from 'zod';
933
- import { BaseSectionData, CtaSchema } from '@/lib/base-schemas';
934
-
935
- const HeroMetricSchema = z.object({
936
- val: z.string().describe('ui:text'),
937
- label: z.string().describe('ui:text'),
938
- });
939
-
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'),
947
- });
948
-
949
- 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';
955
-
956
- export type HeroData = z.infer<typeof HeroSchema>;
957
- export type HeroSettings = z.infer<typeof BaseSectionSettingsSchema>;
958
-
959
- END_OF_FILE_CONTENT
960
- mkdir -p "src/components/image-test"
961
- mkdir -p "src/components/pa-section"
962
- echo "Creating src/components/pa-section/View.tsx..."
963
- 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
- };
1054
-
1055
- END_OF_FILE_CONTENT
1056
- echo "Creating src/components/pa-section/index.ts..."
1057
- cat << 'END_OF_FILE_CONTENT' > "src/components/pa-section/index.ts"
1058
- export * from './View';
1059
- export * from './schema';
1060
- export * from './types';
1061
-
1062
- END_OF_FILE_CONTENT
1063
- echo "Creating src/components/pa-section/schema.ts..."
1064
- 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
- });
1086
-
1087
- END_OF_FILE_CONTENT
1088
- echo "Creating src/components/pa-section/types.ts..."
1089
- 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>;
1096
-
1097
- END_OF_FILE_CONTENT
1098
- mkdir -p "src/components/philosophy"
1099
- echo "Creating src/components/philosophy/View.tsx..."
1100
- 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
- };
1161
-
1162
- END_OF_FILE_CONTENT
1163
- echo "Creating src/components/philosophy/index.ts..."
1164
- cat << 'END_OF_FILE_CONTENT' > "src/components/philosophy/index.ts"
1165
- export * from './View';
1166
- export * from './schema';
1167
- export * from './types';
1168
-
1169
- END_OF_FILE_CONTENT
1170
- echo "Creating src/components/philosophy/schema.ts..."
1171
- 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
- });
1182
-
1183
- END_OF_FILE_CONTENT
1184
- echo "Creating src/components/philosophy/types.ts..."
1185
- 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>;
1192
-
1193
- END_OF_FILE_CONTENT
1194
- mkdir -p "src/components/pillars-grid"
1195
- echo "Creating src/components/pillars-grid/View.tsx..."
1196
- 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
- };
1281
-
1282
- END_OF_FILE_CONTENT
1283
- echo "Creating src/components/pillars-grid/index.ts..."
1284
- cat << 'END_OF_FILE_CONTENT' > "src/components/pillars-grid/index.ts"
1285
- export * from './View';
1286
- export * from './schema';
1287
- export * from './types';
1288
-
1289
- END_OF_FILE_CONTENT
1290
- echo "Creating src/components/pillars-grid/schema.ts..."
1291
- 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
- });
1313
-
1314
- END_OF_FILE_CONTENT
1315
- echo "Creating src/components/pillars-grid/types.ts..."
1316
- 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>;
1325
-
1326
- END_OF_FILE_CONTENT
1327
- mkdir -p "src/components/problem-statement"
1328
- echo "Creating src/components/problem-statement/View.tsx..."
1329
- 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
- };
1406
-
1407
- END_OF_FILE_CONTENT
1408
- echo "Creating src/components/problem-statement/index.ts..."
1409
- cat << 'END_OF_FILE_CONTENT' > "src/components/problem-statement/index.ts"
1410
- export * from './View';
1411
- export * from './schema';
1412
- export * from './types';
1413
-
1414
- END_OF_FILE_CONTENT
1415
- echo "Creating src/components/problem-statement/schema.ts..."
1416
- 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
- });
1442
-
1443
- END_OF_FILE_CONTENT
1444
- echo "Creating src/components/problem-statement/types.ts..."
1445
- 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>;
1453
-
1454
- END_OF_FILE_CONTENT
1455
- mkdir -p "src/components/product-triad"
1456
- echo "Creating src/components/product-triad/View.tsx..."
1457
- 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
- };
1557
-
1558
- END_OF_FILE_CONTENT
1559
- echo "Creating src/components/product-triad/index.ts..."
1560
- cat << 'END_OF_FILE_CONTENT' > "src/components/product-triad/index.ts"
1561
- export * from './View';
1562
- export * from './schema';
1563
- export * from './types';
1564
-
1565
- END_OF_FILE_CONTENT
1566
- echo "Creating src/components/product-triad/schema.ts..."
1567
- 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
- });
1594
-
1595
- END_OF_FILE_CONTENT
1596
- echo "Creating src/components/product-triad/types.ts..."
1597
- 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>;
1604
-
1605
- END_OF_FILE_CONTENT
1606
- mkdir -p "src/components/ui"
1607
- echo "Creating src/components/ui/checkbox.tsx..."
1608
- cat << 'END_OF_FILE_CONTENT' > "src/components/ui/checkbox.tsx"
1609
- "use client"
1610
-
1611
- import * as React from "react"
1612
- import { Checkbox as CheckboxPrimitive } from "radix-ui"
1613
-
1614
- import { cn } from "@/lib/utils"
1615
- import { CheckIcon } from "lucide-react"
1616
-
1617
- function Checkbox({
1618
- className,
1619
- ...props
1620
- }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
1621
- return (
1622
- <CheckboxPrimitive.Root
1623
- data-slot="checkbox"
1624
- className={cn(
1625
- "border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-[4px] border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50",
1626
- className
1627
- )}
1628
- {...props}
1629
- >
1630
- <CheckboxPrimitive.Indicator
1631
- data-slot="checkbox-indicator"
1632
- className="[&>svg]:size-3.5 grid place-content-center text-current transition-none"
1633
- >
1634
- <CheckIcon
1635
- />
1636
- </CheckboxPrimitive.Indicator>
1637
- </CheckboxPrimitive.Root>
1638
- )
1639
- }
1640
-
1641
- export { Checkbox }
1642
-
1643
-
1644
-
1645
-
1646
-
1647
- END_OF_FILE_CONTENT
1648
- echo "Creating src/components/ui/input.tsx..."
1649
- cat << 'END_OF_FILE_CONTENT' > "src/components/ui/input.tsx"
1650
- import * as React from "react"
1651
-
1652
- import { cn } from "@/lib/utils"
1653
-
1654
- function Input({ className, type, ...props }: React.ComponentProps<"input">) {
1655
- return (
1656
- <input
1657
- type={type}
1658
- data-slot="input"
1659
- className={cn(
1660
- "dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
1661
- className
1662
- )}
1663
- {...props}
1664
- />
1665
- )
1666
- }
1667
-
1668
- export { Input }
1669
-
1670
-
1671
-
1672
-
1673
-
1674
- END_OF_FILE_CONTENT
1675
- echo "Creating src/components/ui/label.tsx..."
1676
- cat << 'END_OF_FILE_CONTENT' > "src/components/ui/label.tsx"
1677
- import * as React from "react"
1678
- import { Label as LabelPrimitive } from "radix-ui"
1679
-
1680
- import { cn } from "@/lib/utils"
1681
-
1682
- function Label({
1683
- className,
1684
- ...props
1685
- }: React.ComponentProps<typeof LabelPrimitive.Root>) {
1686
- return (
1687
- <LabelPrimitive.Root
1688
- data-slot="label"
1689
- className={cn(
1690
- "gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed",
1691
- className
1692
- )}
1693
- {...props}
1694
- />
1695
- )
1696
- }
1697
-
1698
- export { Label }
1699
-
1700
-
1701
-
1702
-
1703
-
1704
- END_OF_FILE_CONTENT
1705
- echo "Creating src/components/ui/select.tsx..."
1706
- cat << 'END_OF_FILE_CONTENT' > "src/components/ui/select.tsx"
1707
- import * as React from "react"
1708
- import { Select as SelectPrimitive } from "radix-ui"
1709
-
1710
- import { cn } from "@/lib/utils"
1711
- import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
1712
-
1713
- function Select({
1714
- ...props
1715
- }: React.ComponentProps<typeof SelectPrimitive.Root>) {
1716
- return <SelectPrimitive.Root data-slot="select" {...props} />
1717
- }
1718
-
1719
- function SelectGroup({
1720
- className,
1721
- ...props
1722
- }: React.ComponentProps<typeof SelectPrimitive.Group>) {
1723
- return (
1724
- <SelectPrimitive.Group
1725
- data-slot="select-group"
1726
- className={cn("scroll-my-1 p-1", className)}
1727
- {...props}
1728
- />
1729
- )
1730
- }
1731
-
1732
- function SelectValue({
1733
- ...props
1734
- }: React.ComponentProps<typeof SelectPrimitive.Value>) {
1735
- return <SelectPrimitive.Value data-slot="select-value" {...props} />
1736
- }
1737
-
1738
- function SelectTrigger({
1739
- className,
1740
- size = "default",
1741
- children,
1742
- ...props
1743
- }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
1744
- size?: "sm" | "default"
1745
- }) {
1746
- return (
1747
- <SelectPrimitive.Trigger
1748
- data-slot="select-trigger"
1749
- data-size={size}
1750
- className={cn(
1751
- "border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-lg border bg-transparent py-2 pr-2 pl-2.5 text-sm transition-colors select-none focus-visible:ring-3 aria-invalid:ring-3 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-full items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
1752
- className
1753
- )}
1754
- {...props}
1755
- >
1756
- {children}
1757
- <SelectPrimitive.Icon asChild>
1758
- <ChevronDownIcon className="text-muted-foreground size-4 pointer-events-none" />
1759
- </SelectPrimitive.Icon>
1760
- </SelectPrimitive.Trigger>
1761
- )
1762
- }
1763
-
1764
- function SelectContent({
1765
- className,
1766
- children,
1767
- position = "popper",
1768
- align = "center",
1769
- ...props
1770
- }: React.ComponentProps<typeof SelectPrimitive.Content>) {
1771
- return (
1772
- <SelectPrimitive.Portal>
1773
- <SelectPrimitive.Content
1774
- data-slot="select-content"
1775
- data-align-trigger={position === "item-aligned"}
1776
- className={cn(
1777
- "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-lg shadow-md ring-1 duration-100 relative z-[110] max-h-(--radix-select-content-available-height) origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto data-[align-trigger=true]:animate-none",
1778
- position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
1779
- className
1780
- )}
1781
- position={position}
1782
- align={align}
1783
- {...props}
1784
- >
1785
- <SelectScrollUpButton />
1786
- <SelectPrimitive.Viewport
1787
- data-position={position}
1788
- className={cn(
1789
- "p-1",
1790
- position === "popper" && "h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)"
1791
- )}
1792
- >
1793
- {children}
1794
- </SelectPrimitive.Viewport>
1795
- <SelectScrollDownButton />
1796
- </SelectPrimitive.Content>
1797
- </SelectPrimitive.Portal>
1798
- )
1799
- }
1800
-
1801
- function SelectLabel({
1802
- className,
1803
- ...props
1804
- }: React.ComponentProps<typeof SelectPrimitive.Label>) {
1805
- return (
1806
- <SelectPrimitive.Label
1807
- data-slot="select-label"
1808
- className={cn("text-muted-foreground px-1.5 py-1 text-xs", className)}
1809
- {...props}
1810
- />
1811
- )
1812
- }
1813
-
1814
- function SelectItem({
1815
- className,
1816
- children,
1817
- ...props
1818
- }: React.ComponentProps<typeof SelectPrimitive.Item>) {
1819
- return (
1820
- <SelectPrimitive.Item
1821
- data-slot="select-item"
1822
- className={cn(
1823
- "focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
1824
- className
1825
- )}
1826
- {...props}
1827
- >
1828
- <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
1829
- <SelectPrimitive.ItemIndicator>
1830
- <CheckIcon className="pointer-events-none" />
1831
- </SelectPrimitive.ItemIndicator>
1832
- </span>
1833
- <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
1834
- </SelectPrimitive.Item>
1835
- )
1836
- }
1837
-
1838
- function SelectSeparator({
1839
- className,
1840
- ...props
1841
- }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
1842
- return (
1843
- <SelectPrimitive.Separator
1844
- data-slot="select-separator"
1845
- className={cn("bg-border -mx-1 my-1 h-px pointer-events-none", className)}
1846
- {...props}
1847
- />
1848
- )
1849
- }
1850
-
1851
- function SelectScrollUpButton({
1852
- className,
1853
- ...props
1854
- }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
1855
- return (
1856
- <SelectPrimitive.ScrollUpButton
1857
- data-slot="select-scroll-up-button"
1858
- className={cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4", className)}
1859
- {...props}
1860
- >
1861
- <ChevronUpIcon />
1862
- </SelectPrimitive.ScrollUpButton>
1863
- )
1864
- }
1865
-
1866
- function SelectScrollDownButton({
1867
- className,
1868
- ...props
1869
- }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
1870
- return (
1871
- <SelectPrimitive.ScrollDownButton
1872
- data-slot="select-scroll-down-button"
1873
- className={cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4", className)}
1874
- {...props}
1875
- >
1876
- <ChevronDownIcon />
1877
- </SelectPrimitive.ScrollDownButton>
1878
- )
1879
- }
1880
-
1881
- export {
1882
- Select,
1883
- SelectContent,
1884
- SelectGroup,
1885
- SelectItem,
1886
- SelectLabel,
1887
- SelectScrollDownButton,
1888
- SelectScrollUpButton,
1889
- SelectSeparator,
1890
- SelectTrigger,
1891
- SelectValue,
1892
- }
1893
-
1894
-
1895
-
1896
-
1897
- END_OF_FILE_CONTENT
1898
- echo "Creating src/components/ui/select.txt..."
1899
- cat << 'END_OF_FILE_CONTENT' > "src/components/ui/select.txt"
1900
- import * as React from "react"
1901
- import { Select as SelectPrimitive } from "radix-ui"
1902
-
1903
- import { cn } from "@/lib/utils"
1904
- import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
1905
-
1906
- function Select({
1907
- ...props
1908
- }: React.ComponentProps<typeof SelectPrimitive.Root>) {
1909
- return <SelectPrimitive.Root data-slot="select" {...props} />
1910
- }
1911
-
1912
- function SelectGroup({
1913
- className,
1914
- ...props
1915
- }: React.ComponentProps<typeof SelectPrimitive.Group>) {
1916
- return (
1917
- <SelectPrimitive.Group
1918
- data-slot="select-group"
1919
- className={cn("scroll-my-1 p-1", className)}
1920
- {...props}
1921
- />
1922
- )
1923
- }
1924
-
1925
- function SelectValue({
1926
- ...props
1927
- }: React.ComponentProps<typeof SelectPrimitive.Value>) {
1928
- return <SelectPrimitive.Value data-slot="select-value" {...props} />
1929
- }
1930
-
1931
- function SelectTrigger({
1932
- className,
1933
- size = "default",
1934
- children,
1935
- ...props
1936
- }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
1937
- size?: "sm" | "default"
1938
- }) {
1939
- return (
1940
- <SelectPrimitive.Trigger
1941
- data-slot="select-trigger"
1942
- data-size={size}
1943
- className={cn(
1944
- "border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-lg border bg-transparent py-2 pr-2 pl-2.5 text-sm transition-colors select-none focus-visible:ring-3 aria-invalid:ring-3 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-full items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
1945
- className
1946
- )}
1947
- {...props}
1948
- >
1949
- {children}
1950
- <SelectPrimitive.Icon asChild>
1951
- <ChevronDownIcon className="text-muted-foreground size-4 pointer-events-none" />
1952
- </SelectPrimitive.Icon>
1953
- </SelectPrimitive.Trigger>
1954
- )
1955
- }
1956
-
1957
- function SelectContent({
1958
- className,
1959
- children,
1960
- position = "popper",
1961
- align = "center",
1962
- ...props
1963
- }: React.ComponentProps<typeof SelectPrimitive.Content>) {
1964
- return (
1965
- <SelectPrimitive.Portal>
1966
- <SelectPrimitive.Content
1967
- data-slot="select-content"
1968
- data-align-trigger={position === "item-aligned"}
1969
- className={cn(
1970
- "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-lg shadow-md ring-1 duration-100 relative z-[110] max-h-(--radix-select-content-available-height) origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto data-[align-trigger=true]:animate-none",
1971
- position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
1972
- className
1973
- )}
1974
- position={position}
1975
- align={align}
1976
- {...props}
1977
- >
1978
- <SelectScrollUpButton />
1979
- <SelectPrimitive.Viewport
1980
- data-position={position}
1981
- className={cn(
1982
- "p-1",
1983
- position === "popper" && "h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)"
1984
- )}
1985
- >
1986
- {children}
1987
- </SelectPrimitive.Viewport>
1988
- <SelectScrollDownButton />
1989
- </SelectPrimitive.Content>
1990
- </SelectPrimitive.Portal>
1991
- )
1992
- }
1993
-
1994
- function SelectLabel({
1995
- className,
1996
- ...props
1997
- }: React.ComponentProps<typeof SelectPrimitive.Label>) {
1998
- return (
1999
- <SelectPrimitive.Label
2000
- data-slot="select-label"
2001
- className={cn("text-muted-foreground px-1.5 py-1 text-xs", className)}
2002
- {...props}
2003
- />
2004
- )
2005
- }
2006
-
2007
- function SelectItem({
2008
- className,
2009
- children,
2010
- ...props
2011
- }: React.ComponentProps<typeof SelectPrimitive.Item>) {
2012
- return (
2013
- <SelectPrimitive.Item
2014
- data-slot="select-item"
2015
- className={cn(
2016
- "focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
2017
- className
2018
- )}
2019
- {...props}
2020
- >
2021
- <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
2022
- <SelectPrimitive.ItemIndicator>
2023
- <CheckIcon className="pointer-events-none" />
2024
- </SelectPrimitive.ItemIndicator>
2025
- </span>
2026
- <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
2027
- </SelectPrimitive.Item>
2028
- )
2029
- }
2030
-
2031
- function SelectSeparator({
2032
- className,
2033
- ...props
2034
- }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
2035
- return (
2036
- <SelectPrimitive.Separator
2037
- data-slot="select-separator"
2038
- className={cn("bg-border -mx-1 my-1 h-px pointer-events-none", className)}
2039
- {...props}
2040
- />
2041
- )
2042
- }
2043
-
2044
- function SelectScrollUpButton({
2045
- className,
2046
- ...props
2047
- }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
2048
- return (
2049
- <SelectPrimitive.ScrollUpButton
2050
- data-slot="select-scroll-up-button"
2051
- className={cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4", className)}
2052
- {...props}
2053
- >
2054
- <ChevronUpIcon />
2055
- </SelectPrimitive.ScrollUpButton>
2056
- )
2057
- }
2058
-
2059
- function SelectScrollDownButton({
2060
- className,
2061
- ...props
2062
- }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
2063
- return (
2064
- <SelectPrimitive.ScrollDownButton
2065
- data-slot="select-scroll-down-button"
2066
- className={cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4", className)}
2067
- {...props}
2068
- >
2069
- <ChevronDownIcon />
2070
- </SelectPrimitive.ScrollDownButton>
2071
- )
2072
- }
2073
-
2074
- export {
2075
- Select,
2076
- SelectContent,
2077
- SelectGroup,
2078
- SelectItem,
2079
- SelectLabel,
2080
- SelectScrollDownButton,
2081
- SelectScrollUpButton,
2082
- SelectSeparator,
2083
- SelectTrigger,
2084
- SelectValue,
2085
- }
2086
-
2087
-
2088
-
2089
-
2090
- END_OF_FILE_CONTENT
2091
- echo "Creating src/components/ui/separator.tsx..."
2092
- cat << 'END_OF_FILE_CONTENT' > "src/components/ui/separator.tsx"
2093
- "use client"
2094
-
2095
- import * as React from "react"
2096
- import { Separator as SeparatorPrimitive } from "radix-ui"
2097
-
2098
- import { cn } from "@/lib/utils"
2099
-
2100
- function Separator({
2101
- className,
2102
- orientation = "horizontal",
2103
- decorative = true,
2104
- ...props
2105
- }: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
2106
- return (
2107
- <SeparatorPrimitive.Root
2108
- data-slot="separator"
2109
- decorative={decorative}
2110
- orientation={orientation}
2111
- className={cn(
2112
- "bg-border shrink-0 data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
2113
- className
2114
- )}
2115
- {...props}
2116
- />
2117
- )
2118
- }
2119
-
2120
- export { Separator }
2121
-
2122
-
2123
-
2124
-
2125
-
2126
- END_OF_FILE_CONTENT
2127
- echo "Creating src/components/ui/textarea.tsx..."
2128
- cat << 'END_OF_FILE_CONTENT' > "src/components/ui/textarea.tsx"
2129
- import * as React from "react"
2130
-
2131
- import { cn } from "@/lib/utils"
2132
-
2133
- function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
2134
- return (
2135
- <textarea
2136
- data-slot="textarea"
2137
- className={cn(
2138
- "border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 rounded-lg border bg-transparent px-2.5 py-2 text-base transition-colors focus-visible:ring-3 aria-invalid:ring-3 md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
2139
- className
2140
- )}
2141
- {...props}
2142
- />
2143
- )
2144
- }
2145
-
2146
- export { Textarea }
2147
-
2148
-
2149
-
2150
-
2151
-
2152
- END_OF_FILE_CONTENT
2153
- mkdir -p "src/data"
2154
- mkdir -p "src/data/config"
2155
- echo "Creating src/data/config/menu.json..."
2156
- cat << 'END_OF_FILE_CONTENT' > "src/data/config/menu.json"
2157
- {
2158
- "main": [
2159
- {
2160
- "label": "Architecture",
2161
- "href": "#architecture"
2162
- },
2163
- {
2164
- "label": "Products",
2165
- "href": "#products"
2166
- },
2167
- {
2168
- "label": "PA Ready",
2169
- "href": "#pa-ready"
2170
- },
2171
- {
2172
- "label": "Philosophy",
2173
- "href": "#philosophy"
2174
- }
2175
- ]
2176
- }
2177
- END_OF_FILE_CONTENT
2178
- # SKIP: src/data/config/menu.json:Zone.Identifier è un file binario e non può essere convertito in testo.
2179
- echo "Creating src/data/config/site.json..."
2180
- 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
- }
2260
- }
2261
- END_OF_FILE_CONTENT
2262
- # SKIP: src/data/config/site.json:Zone.Identifier è un file binario e non può essere convertito in testo.
2263
- echo "Creating src/data/config/theme.json..."
2264
- cat << 'END_OF_FILE_CONTENT' > "src/data/config/theme.json"
2265
- {
2266
- "name": "JsonPages Landing",
2267
- "tokens": {
2268
- "colors": {
2269
- "primary": "#3b82f6",
2270
- "secondary": "#22d3ee",
2271
- "accent": "#60a5fa",
2272
- "background": "#060d1b",
2273
- "surface": "#0b1529",
2274
- "surfaceAlt": "#101e38",
2275
- "text": "#e2e8f0",
2276
- "textMuted": "#94a3b8",
2277
- "border": "#162a4d"
2278
- },
2279
- "typography": {
2280
- "fontFamily": {
2281
- "primary": "'Instrument Sans', system-ui, sans-serif",
2282
- "mono": "'JetBrains Mono', monospace",
2283
- "display": "'Playfair Display', Georgia, serif"
2284
- }
2285
- },
2286
- "borderRadius": {
2287
- "sm": "5px",
2288
- "md": "7px",
2289
- "lg": "8px"
2290
- }
2291
- }
2292
- }
2293
- END_OF_FILE_CONTENT
2294
- # SKIP: src/data/config/theme.json:Zone.Identifier è un file binario e non può essere convertito in testo.
2295
- mkdir -p "src/data/pages"
2296
- echo "Creating src/data/pages/home.json..."
2297
- cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
2298
- {
2299
- "id": "home-page",
2300
- "slug": "home",
2301
- "meta": {
2302
- "title": "JsonPages — Global Authoring. Global Governance.",
2303
- "description": "A high-fidelity Site Factory that solves the scalability bottleneck of traditional CMS systems."
2304
- },
2305
- "sections": [
2306
- {
2307
- "id": "hero-main",
2308
- "type": "hero",
2309
- "data": {
2310
- "badge": "Composable UI Engine · v1.0",
2311
- "title": "Local Authoring.",
2312
- "titleHighlight": "Global Governance.",
2313
- "description": "JsonPages is a high-fidelity Site Factory that solves the scalability bottleneck of traditional CMS systems. Distribute, govern, and render UI components across hundreds of independent sites — without technical drift.",
2314
- "ctas": [
2315
- {
2316
- "label": "Explore Products →",
2317
- "href": "#products",
2318
- "variant": "primary"
2319
- },
2320
- {
2321
- "label": "Read the Spec",
2322
- "href": "#architecture",
2323
- "variant": "secondary"
2324
- }
2325
- ],
2326
- "metrics": [
2327
- {
2328
- "val": "100+",
2329
- "label": "Pages per tenant"
2330
- },
2331
- {
2332
- "val": "0ms",
2333
- "label": "Runtime overhead"
2334
- },
2335
- {
2336
- "val": "∞",
2337
- "label": "Scalable blocks"
2338
- }
2339
- ]
2340
- },
2341
- "settings": {}
2342
- },
2343
- {
2344
- "id": "problem-statement",
2345
- "type": "problem-statement",
2346
- "data": {
2347
- "siloGroups": [
2348
- {
2349
- "blocks": [
2350
- {
2351
- "label": "Agency A Stack",
2352
- "variant": "red"
2353
- },
2354
- {
2355
- "label": "Custom CMS",
2356
- "variant": "red"
2357
- },
2358
- {
2359
- "label": "PHP Backend",
2360
- "variant": "red"
2361
- }
2362
- ],
2363
- "label": "Silo #1"
2364
- },
2365
- {
2366
- "blocks": [
2367
- {
2368
- "label": "Agency B Tools",
2369
- "variant": "amber"
2370
- },
2371
- {
2372
- "label": "WordPress",
2373
- "variant": "amber"
2374
- },
2375
- {
2376
- "label": "LAMP Stack",
2377
- "variant": "amber"
2378
- }
2379
- ],
2380
- "label": "Silo #2"
2381
- },
2382
- {
2383
- "blocks": [
2384
- {
2385
- "label": "Agency C Kit",
2386
- "variant": "green"
2387
- },
2388
- {
2389
- "label": "React App",
2390
- "variant": "green"
2391
- },
2392
- {
2393
- "label": "Node.js",
2394
- "variant": "green"
2395
- }
2396
- ],
2397
- "label": "Silo #3"
2398
- }
2399
- ],
2400
- "title": "Traditional CMS systems create closed silos.",
2401
- "paragraphs": [
2402
- {
2403
- "text": "When global brands delegate web projects to multiple agencies, each agency picks its own stack, its own conventions, its own deployment pipeline. The result is a portfolio of ungovernable sites with inconsistent quality and unpredictable maintenance costs.",
2404
- "isBold": false
2405
- },
2406
- {
2407
- "text": "JsonPages eliminates this by providing a centralized protocol to distribute, govern, and render UI components — ensuring every site, regardless of who builds it, conforms to a single typed ontology.",
2408
- "isBold": true
2409
- }
2410
- ],
2411
- "anchorId": "aa"
2412
- },
2413
- "settings": {}
2414
- },
2415
- {
2416
- "id": "architecture-pillars",
2417
- "type": "pillars-grid",
2418
- "data": {
2419
- "anchorId": "architecture",
2420
- "label": "Core Architecture",
2421
- "title": "Three Pillars of Composability",
2422
- "description": "A contract-first architecture designed to scale without core refactoring.",
2423
- "pillars": [
2424
- {
2425
- "icon": "box",
2426
- "iconVariant": "split",
2427
- "title": "The Machine-Readable Blueprint. 📐",
2428
- "description": "We enforce a strict file-system ontology that physically separates Global Governance from Local Content. By utilizing \"Dumb Components\" (CIP), we create a deterministic environment where the UI is a predictable function of data. This protocol allows AI agents to understand your site structure instantly, eliminating the \"guessing game\" of traditional web development.",
2429
- "tag": "The Foundation",
2430
- "tagVariant": "core"
2431
- },
2432
- {
2433
- "icon": "sparkles",
2434
- "iconVariant": "registry",
2435
- "title": "The Semantic Brain. 🧠",
2436
- "description": "We reject hardcoded Union Types. The Kernel defines an open registry where independent modules inject their DNA via Zod schemas. This provides the AI with a precise, type-safe vocabulary to build brand-compliant interfaces. Because the registry is the Single Source of Truth, the AI knows exactly which components exist and how to configure them without ever touching the core code.",
2437
- "tag": "The Vocabulary",
2438
- "tagVariant": "pattern"
2439
- },
2440
- {
2441
- "icon": "zap",
2442
- "iconVariant": "federation",
2443
- "title": "The 5-Minute Tenant. 🤖",
2444
- "description": "AI doesn't guess; it follows the Protocol. Because our ecosystem is governed by 5 deterministic standards, agents like Claude can project a complex, multi-page, brand-compliant tenant in under 5 minutes. The result is 100% Type-Safe JSON that perfectly matches your schemas, delivering a production-ready site with zero technical drift and an instant polymorphic CMS.",
2445
- "tag": "The Multiplier",
2446
- "tagVariant": "enterprise"
2447
- }
2448
- ]
2449
- },
2450
- "settings": {}
2451
- },
2452
- {
2453
- "id": "layered-architecture",
2454
- "type": "arch-layers",
2455
- "data": {
2456
- "anchorId": "layers",
2457
- "label": "Engineering Layers",
2458
- "title": "Strict Boundaries. Clean Contracts.",
2459
- "description": "Three architectural layers maintain the separation between engine and implementation.",
2460
- "layers": [
2461
- {
2462
- "number": "0",
2463
- "layerLevel": "l0",
2464
- "title": "The Immutable Kernel",
2465
- "description": "Defines the base structure of a Section — ID, Type, Payload, Settings — and exports the computed union. Zero knowledge of UI implementation or business logic."
2466
- },
2467
- {
2468
- "number": "1",
2469
- "layerLevel": "l1",
2470
- "title": "The Standard Library",
2471
- "description": "Fundamental components (Hero, Text, Grid) used across 90% of projects. Augments the Kernel's registry while remaining part of the internal ecosystem."
2472
- },
2473
- {
2474
- "number": "2",
2475
- "layerLevel": "l2",
2476
- "title": "User-Land Extensions",
2477
- "description": "The enabling layer. Third-party developers define specialized blocks — StockTickers, CRM Forms — in their own repositories. These extensions plug into the Kernel at compile-time."
2478
- }
2479
- ],
2480
- "codeFilename": "libs/shared-data/src/lib/core.ts",
2481
- "codeLines": [
2482
- {
2483
- "content": "// 🛡️ KERNEL — Single Source of Truth",
2484
- "tokenType": "comment"
2485
- },
2486
- {
2487
- "content": "",
2488
- "tokenType": "plain"
2489
- },
2490
- {
2491
- "content": "export interface SectionDataRegistry {",
2492
- "tokenType": "keyword"
2493
- },
2494
- {
2495
- "content": " // Open Registry: modules inject here",
2496
- "tokenType": "comment"
2497
- },
2498
- {
2499
- "content": "}",
2500
- "tokenType": "plain"
2501
- },
2502
- {
2503
- "content": "",
2504
- "tokenType": "plain"
2505
- },
2506
- {
2507
- "content": "export interface BaseSection<K extends keyof SectionDataRegistry> {",
2508
- "tokenType": "keyword"
2509
- },
2510
- {
2511
- "content": " id: string;",
2512
- "tokenType": "type"
2513
- },
2514
- {
2515
- "content": " type: K;",
2516
- "tokenType": "type"
2517
- },
2518
- {
2519
- "content": " data: SectionDataRegistry[K];",
2520
- "tokenType": "type"
2521
- },
2522
- {
2523
- "content": " settings?: SectionSettings;",
2524
- "tokenType": "type"
2525
- },
2526
- {
2527
- "content": "}",
2528
- "tokenType": "plain"
2529
- },
2530
- {
2531
- "content": "",
2532
- "tokenType": "plain"
2533
- },
2534
- {
2535
- "content": "// Computed Union — built automatically from registry",
2536
- "tokenType": "comment"
2537
- },
2538
- {
2539
- "content": "export type Section =",
2540
- "tokenType": "keyword"
2541
- },
2542
- {
2543
- "content": " { [K in keyof SectionDataRegistry]: BaseSection<K> }",
2544
- "tokenType": "type"
2545
- },
2546
- {
2547
- "content": " [keyof SectionDataRegistry];",
2548
- "tokenType": "operator"
2549
- }
2550
- ]
2551
- },
2552
- "settings": {}
2553
- },
2554
- {
2555
- "id": "product-triad",
2556
- "type": "product-triad",
2557
- "data": {
2558
- "anchorId": "products",
2559
- "label": "Product Triad",
2560
- "title": "One Protocol. Three Products.",
2561
- "description": "From solo developers to global enterprises — the same underlying ontology powers every tier.",
2562
- "products": [
2563
- {
2564
- "tier": "Starter",
2565
- "name": "JsonPage",
2566
- "price": "NPM",
2567
- "priceSuffix": "Local Install",
2568
- "delivery": "Static Export · Zero Runtime",
2569
- "features": [
2570
- {
2571
- "text": "Local Authoring SDK"
2572
- },
2573
- {
2574
- "text": "Zero-Runtime HTML Bake"
2575
- },
2576
- {
2577
- "text": "Pure HTML/CSS Export"
2578
- },
2579
- {
2580
- "text": "CDN-Ready Delivery"
2581
- },
2582
- {
2583
- "text": "Full Code Ownership"
2584
- }
2585
- ],
2586
- "featured": false
2587
- },
2588
- {
2589
- "tier": "Professional",
2590
- "name": "JsonPages",
2591
- "price": "SaaS",
2592
- "priceSuffix": "/month",
2593
- "delivery": "Managed Cloud · Multi-Tenant",
2594
- "features": [
2595
- {
2596
- "text": "Multi-Tenant Hosting"
2597
- },
2598
- {
2599
- "text": "Premium Block Library"
2600
- },
2601
- {
2602
- "text": "SchemaUI Components"
2603
- },
2604
- {
2605
- "text": "In-Context Editing"
2606
- },
2607
- {
2608
- "text": "AI-Native Protocol"
2609
- },
2610
- {
2611
- "text": "Tailwind v4 & BI Engine"
2612
- }
2613
- ],
2614
- "featured": false
2615
- },
2616
- {
2617
- "tier": "Enterprise",
2618
- "name": "The Platform",
2619
- "price": "Custom",
2620
- "delivery": "Private Cloud · Enterprise Governance",
2621
- "features": [
2622
- {
2623
- "text": "Private Federated Registry"
2624
- },
2625
- {
2626
- "text": "Module Federation"
2627
- },
2628
- {
2629
- "text": "Runtime Component Injection"
2630
- },
2631
- {
2632
- "text": "Custom Brand Guidelines"
2633
- },
2634
- {
2635
- "text": "Dedicated SLA & Support"
2636
- },
2637
- {
2638
- "text": "SSO & RBAC"
2639
- }
2640
- ],
2641
- "featured": false
2642
- }
2643
- ]
2644
- },
2645
- "settings": {}
2646
- },
2647
- {
2648
- "id": "pa-ready",
2649
- "type": "pa-section",
2650
- "data": {
2651
- "anchorId": "pa-ready",
2652
- "label": "Public Sector",
2653
- "title": "PA Ready — One Platform, Two Markets",
2654
- "subtitle": "The Only Builder with Native Bootstrap Italia.",
2655
- "paragraphs": [
2656
- {
2657
- "text": "By switching the rendering engine to Bootstrap Italia, System Integrators use the same high-efficiency workflow to deliver WCAG-compliant sites for Municipalities and Government bodies — without rewriting backend logic."
2658
- },
2659
- {
2660
- "text": "The same JSON ontology drives both the Tailwind-powered private sector and the Bootstrap Italia public sector pipeline. This is not flexibility — this is Business Continuity."
2661
- }
2662
- ],
2663
- "badges": [
2664
- {
2665
- "label": "PNRR Ready"
2666
- },
2667
- {
2668
- "label": "WCAG 2.1 AA"
2669
- },
2670
- {
2671
- "label": "AgID Compliant"
2672
- }
2673
- ],
2674
- "engines": [
2675
- {
2676
- "label": "Tailwind v4",
2677
- "variant": "tailwind"
2678
- },
2679
- {
2680
- "label": "Bootstrap Italia",
2681
- "variant": "bootstrap"
2682
- }
2683
- ],
2684
- "codeSnippet": "{\n \"type\": \"hero\",\n \"data\": { \"title\": \"...\" },\n \"settings\": { \"engine\": \"auto\" }\n}"
2685
- },
2686
- "settings": {}
2687
- },
2688
- {
2689
- "id": "philosophy",
2690
- "type": "philosophy",
2691
- "data": {
2692
- "anchorId": "philosophy",
2693
- "label": "Our Philosophy",
2694
- "title": "Governance Over Captivity",
2695
- "quote": "We sell the infrastructure to manage UI — not the right to access the code.",
2696
- "quoteHighlightWord": "manage",
2697
- "description": "Unlike closed-garden platforms, JsonPages provides an Eject Button by design. The Bake Engine exports your site as pure HTML/CSS with zero backend requirements. If you leave the SaaS, the shared ontology and SDK ensure you remain fully operational and independent. We earn your loyalty through value — not lock-in."
2698
- },
2699
- "settings": {}
2700
- },
2701
- {
2702
- "id": "cta-final",
2703
- "type": "cta-banner",
2704
- "data": {
2705
- "anchorId": "start",
2706
- "label": "Local Authoring Global Governance",
2707
- "title": "Ready to Govern at Scale?",
2708
- "description": "Whether you're building a single site or managing a portfolio of hundreds — JsonPages scales with your ambition."
2709
- },
2710
- "settings": {}
2711
- }
2712
- ]
2713
- }
2714
- END_OF_FILE_CONTENT
2715
- # SKIP: src/data/pages/home.json:Zone.Identifier è un file binario e non può essere convertito in testo.
2716
- mkdir -p "src/hooks"
2717
- echo "Creating src/hooks/useDocumentMeta.ts..."
2718
- cat << 'END_OF_FILE_CONTENT' > "src/hooks/useDocumentMeta.ts"
2719
- import { useEffect } from 'react';
2720
- import type { PageMeta } from '@/types';
2721
-
2722
- export const useDocumentMeta = (meta: PageMeta): void => {
2723
- useEffect(() => {
2724
- // Set document title
2725
- document.title = meta.title;
2726
-
2727
- // Set or update meta description
2728
- let metaDescription = document.querySelector('meta[name="description"]');
2729
- if (!metaDescription) {
2730
- metaDescription = document.createElement('meta');
2731
- metaDescription.setAttribute('name', 'description');
2732
- document.head.appendChild(metaDescription);
2733
- }
2734
- metaDescription.setAttribute('content', meta.description);
2735
- }, [meta.title, meta.description]);
2736
- };
2737
-
2738
-
2739
-
2740
-
2741
-
2742
- END_OF_FILE_CONTENT
2743
- echo "Creating src/index.css..."
2744
- cat << 'END_OF_FILE_CONTENT' > "src/index.css"
2745
- @import "tailwindcss";
2746
-
2747
- @source "./**/*.tsx";
2748
-
2749
- @theme {
2750
- /*
2751
- 🎯 MASTER MAPPING (V2.7 Landing)
2752
- */
2753
- --color-background: var(--background);
2754
- --color-foreground: var(--foreground);
2755
-
2756
- --color-card: var(--card);
2757
- --color-card-foreground: var(--card-foreground);
2758
-
2759
- --color-primary: var(--primary);
2760
- --color-primary-foreground: var(--primary-foreground);
2761
-
2762
- --color-secondary: var(--secondary);
2763
- --color-secondary-foreground: var(--secondary-foreground);
2764
-
2765
- --color-muted: var(--muted);
2766
- --color-muted-foreground: var(--muted-foreground);
2767
-
2768
- --color-accent: var(--accent);
2769
- --color-border: var(--border);
2770
-
2771
- --radius-lg: var(--radius);
2772
- --radius-md: calc(var(--radius) - 2px);
2773
- --radius-sm: calc(var(--radius) - 4px);
2774
-
2775
- --font-primary: var(--theme-font-primary);
2776
- --font-mono: var(--theme-font-mono);
2777
-
2778
- /*
2779
- 🔧 DISPLAY FONT — Forward-compatible workaround
2780
- theme-manager.ts does NOT inject --theme-font-display (Skeleton v2.7 gap).
2781
- The var() falls through to the hardcoded fallback today.
2782
- When Skeleton v2.8 wires display into theme-manager, the var() will resolve
2783
- automatically and the fallback becomes dead code.
2784
- */
2785
- --font-display: var(--theme-font-display, 'Playfair Display', Georgia, serif);
2786
- }
2787
-
2788
- /*
2789
- 🌍 TENANT BRAND TOKENS (JSP 1.5)
2790
- */
2791
- :root {
2792
- --background: var(--theme-background);
2793
- --foreground: var(--theme-text);
2794
- --card: var(--theme-surface);
2795
- --card-foreground: var(--theme-text);
2796
- --primary: var(--theme-primary);
2797
- --primary-foreground: oklch(0.98 0 0);
2798
- --secondary: var(--theme-secondary);
2799
- --secondary-foreground: var(--theme-text);
2800
- --muted: var(--theme-surface-alt);
2801
- --muted-foreground: var(--theme-text-muted);
2802
- --border: var(--theme-border);
2803
- --radius: 0.45rem;
2804
-
2805
- /*
2806
- 🔧 ACCENT CHAIN — Forward-compatible workaround
2807
- theme-manager.ts already injects --theme-accent on :root,
2808
- but the original index.css never bridged it into the semantic layer.
2809
- This closes the gap: --theme-accent → --accent → --color-accent.
2810
- Falls back to --theme-primary if accent is undefined.
2811
- */
2812
- --accent: var(--theme-accent, var(--theme-primary));
2813
- }
2814
-
2815
- @layer base {
2816
- * { border-color: var(--border); }
2817
- body {
2818
- background-color: var(--background);
2819
- color: var(--foreground);
2820
- font-family: var(--font-primary);
2821
- line-height: 1.7;
2822
- overflow-x: hidden;
2823
- @apply antialiased;
2824
- }
2825
- }
2826
-
2827
- /* ==========================================================================
2828
- FONT DISPLAY UTILITY
2829
- Maps .font-display class to the display font family (Playfair Display)
2830
- ========================================================================== */
2831
- .font-display {
2832
- font-family: var(--font-display, var(--font-primary));
2833
- }
2834
-
2835
- /* ==========================================================================
2836
- LANDING ANIMATIONS
2837
- ========================================================================== */
2838
- @keyframes jp-fadeUp {
2839
- from { opacity: 0; transform: translateY(30px); }
2840
- to { opacity: 1; transform: translateY(0); }
2841
- }
2842
-
2843
- @keyframes jp-pulseDot {
2844
- 0%, 100% { opacity: 1; }
2845
- 50% { opacity: 0.3; }
2846
- }
2847
-
2848
- .jp-animate-in {
2849
- opacity: 0;
2850
- animation: jp-fadeUp 0.7s ease forwards;
2851
- }
2852
- .jp-d1 { animation-delay: 0.1s; }
2853
- .jp-d2 { animation-delay: 0.2s; }
2854
- .jp-d3 { animation-delay: 0.3s; }
2855
- .jp-d4 { animation-delay: 0.4s; }
2856
- .jp-d5 { animation-delay: 0.5s; }
2857
-
2858
- .jp-pulse-dot {
2859
- animation: jp-pulseDot 2s ease infinite;
2860
- }
2861
-
2862
- /* ==========================================================================
2863
- SMOOTH SCROLL
2864
- ========================================================================== */
2865
- html {
2866
- scroll-behavior: smooth;
2867
- }
2868
-
2869
- /* ==========================================================================
2870
- ICE ADMIN — Section highlight in preview iframe
2871
- The preview iframe only receives tenant CSS; core's overlay classes
2872
- (z-[50], absolute, etc.) are not in this build. Define them here so
2873
- the section highlight is always visible in /admin.
2874
- ========================================================================== */
2875
- [data-jp-section-overlay] {
2876
- position: absolute;
2877
- inset: 0;
2878
- z-index: 9999;
2879
- pointer-events: none;
2880
- transition: border-color 0.2s, background-color 0.2s;
2881
- border: 2px solid transparent;
2882
- }
2883
-
2884
- [data-section-id]:hover [data-jp-section-overlay] {
2885
- border-color: rgba(96, 165, 250, 0.5);
2886
- border-style: dashed;
2887
- }
2888
-
2889
- [data-section-id][data-jp-selected] [data-jp-section-overlay] {
2890
- border-color: rgb(37, 99, 235);
2891
- border-style: solid;
2892
- background-color: rgba(59, 130, 246, 0.05);
2893
- }
2894
-
2895
- [data-jp-section-overlay] > div {
2896
- position: absolute;
2897
- top: 0;
2898
- right: 0;
2899
- padding: 0.25rem 0.5rem;
2900
- font-size: 9px;
2901
- font-weight: 800;
2902
- text-transform: uppercase;
2903
- letter-spacing: 0.1em;
2904
- background: rgb(37, 99, 235);
2905
- color: white;
2906
- transition: opacity 0.2s;
2907
- }
2908
-
2909
- [data-section-id]:hover [data-jp-section-overlay] > div,
2910
- [data-section-id][data-jp-selected] [data-jp-section-overlay] > div {
2911
- opacity: 1;
2912
- }
2913
-
2914
- [data-section-id] [data-jp-section-overlay] > div {
2915
- opacity: 0;
2916
- }
2917
-
2918
-
2919
-
2920
- END_OF_FILE_CONTENT
2921
- mkdir -p "src/lib"
2922
- echo "Creating src/lib/ComponentRegistry.tsx..."
2923
- cat << 'END_OF_FILE_CONTENT' > "src/lib/ComponentRegistry.tsx"
2924
- import React from 'react';
2925
- import { Header } from '@/components/header';
2926
- import { Footer } from '@/components/footer';
2927
- import { Hero } from '@/components/hero';
2928
- import { FeatureGrid } from '@/components/feature-grid';
2929
- import { CodeBlock } from '@/components/code-block';
2930
- import { ProblemStatement } from '@/components/problem-statement';
2931
- import { PillarsGrid } from '@/components/pillars-grid';
2932
- import { ArchLayers } from '@/components/arch-layers';
2933
- import { ProductTriad } from '@/components/product-triad';
2934
- import { PaSection } from '@/components/pa-section';
2935
- import { Philosophy } from '@/components/philosophy';
2936
- import { CtaBanner } from '@/components/cta-banner';
2937
-
2938
- import type { SectionType } from '@jsonpages/core';
2939
- import type { SectionComponentPropsMap } from '@/types';
2940
-
2941
- export const ComponentRegistry: {
2942
- [K in SectionType]: React.FC<SectionComponentPropsMap[K]>;
2943
- } = {
2944
- 'header': Header,
2945
- 'footer': Footer,
2946
- 'hero': Hero,
2947
- 'feature-grid': FeatureGrid,
2948
- 'code-block': CodeBlock,
2949
- 'problem-statement': ProblemStatement,
2950
- 'pillars-grid': PillarsGrid,
2951
- 'arch-layers': ArchLayers,
2952
- 'product-triad': ProductTriad,
2953
- 'pa-section': PaSection,
2954
- 'philosophy': Philosophy,
2955
- 'cta-banner': CtaBanner,
2956
- };
2957
-
2958
- END_OF_FILE_CONTENT
2959
- echo "Creating src/lib/IconResolver.tsx..."
2960
- cat << 'END_OF_FILE_CONTENT' > "src/lib/IconResolver.tsx"
2961
- import React from 'react';
2962
- import {
2963
- Layers,
2964
- Github,
2965
- ArrowRight,
2966
- Box,
2967
- Terminal,
2968
- ChevronRight,
2969
- Menu,
2970
- X,
2971
- Sparkles,
2972
- Zap,
2973
- type LucideIcon
2974
- } from 'lucide-react';
2975
-
2976
- const iconMap = {
2977
- 'layers': Layers,
2978
- 'github': Github,
2979
- 'arrow-right': ArrowRight,
2980
- 'box': Box,
2981
- 'terminal': Terminal,
2982
- 'chevron-right': ChevronRight,
2983
- 'menu': Menu,
2984
- 'x': X,
2985
- 'sparkles': Sparkles,
2986
- 'zap': Zap,
2987
- } as const satisfies Record<string, LucideIcon>;
2988
-
2989
- export type IconName = keyof typeof iconMap;
2990
-
2991
- export function isIconName(s: string): s is IconName {
2992
- return s in iconMap;
2993
- }
2994
-
2995
- interface IconProps {
2996
- name: string;
2997
- size?: number;
2998
- className?: string;
2999
- }
3000
-
3001
- export const Icon: React.FC<IconProps> = ({ name, size = 20, className }) => {
3002
- const IconComponent = isIconName(name) ? iconMap[name] : undefined;
3003
-
3004
- if (!IconComponent) {
3005
- if (import.meta.env.DEV) {
3006
- console.warn(`[IconResolver] Unknown icon: "${name}". Add it to iconMap.`);
3007
- }
3008
- return null;
3009
- }
3010
-
3011
- return <IconComponent size={size} className={className} />;
3012
- };
3013
-
3014
-
3015
-
3016
- END_OF_FILE_CONTENT
3017
- echo "Creating src/lib/addSectionConfig.ts..."
3018
- cat << 'END_OF_FILE_CONTENT' > "src/lib/addSectionConfig.ts"
3019
- /**
3020
- * Add-section config for the ICE admin (tenant-alpha).
3021
- * Provides default data and labels for each addable section type.
3022
- * Core remains agnostic; this file is tenant-specific.
3023
- */
3024
- import type { AddSectionConfig } from '@jsonpages/core';
3025
-
3026
- const addableSectionTypes = [
3027
- 'hero',
3028
- 'feature-grid',
3029
- 'code-block',
3030
- 'problem-statement',
3031
- 'pillars-grid',
3032
- 'arch-layers',
3033
- 'product-triad',
3034
- 'pa-section',
3035
- 'philosophy',
3036
- 'cta-banner',
3037
- ] as const;
3038
-
3039
- const sectionTypeLabels: Record<string, string> = {
3040
- 'hero': 'Hero',
3041
- 'feature-grid': 'Feature Grid',
3042
- 'code-block': 'Code Block',
3043
- 'problem-statement': 'Problem Statement',
3044
- 'pillars-grid': 'Pillars Grid',
3045
- 'arch-layers': 'Architecture Layers',
3046
- 'product-triad': 'Product Triad',
3047
- 'pa-section': 'PA Section',
3048
- 'philosophy': 'Philosophy',
3049
- 'cta-banner': 'CTA Banner',
3050
- };
3051
-
3052
- function getDefaultSectionData(sectionType: string): Record<string, unknown> {
3053
- switch (sectionType) {
3054
- case 'hero':
3055
- return { title: 'New Hero', description: '' };
3056
- case 'feature-grid':
3057
- return { sectionTitle: 'Features', cards: [] };
3058
- case 'code-block':
3059
- return { lines: [] };
3060
- case 'problem-statement':
3061
- return { title: 'Problem Statement', siloGroups: [], paragraphs: [] };
3062
- case 'pillars-grid':
3063
- return { title: 'Pillars', pillars: [] };
3064
- case 'arch-layers':
3065
- return { title: 'Architecture', layers: [] };
3066
- case 'product-triad':
3067
- return { title: 'Products', products: [] };
3068
- case 'pa-section':
3069
- return { title: 'Section', subtitle: 'Subtitle', paragraphs: [{ text: '' }] };
3070
- case 'philosophy':
3071
- return { title: 'Philosophy', quote: 'Your quote here.' };
3072
- case 'cta-banner':
3073
- return { title: 'Call to Action', description: '' };
3074
- default:
3075
- return {};
3076
- }
3077
- }
3078
-
3079
- export const addSectionConfig: AddSectionConfig = {
3080
- addableSectionTypes: [...addableSectionTypes],
3081
- sectionTypeLabels,
3082
- getDefaultSectionData,
3083
- };
3084
-
3085
- END_OF_FILE_CONTENT
3086
- echo "Creating src/lib/base-schemas.ts..."
3087
- 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
- });
3115
-
3116
- END_OF_FILE_CONTENT
3117
- echo "Creating src/lib/schemas.ts..."
3118
- cat << 'END_OF_FILE_CONTENT' > "src/lib/schemas.ts"
3119
- /**
3120
- * SECTION_SCHEMAS registry — SSOT for FormFactory.
3121
- * Re-exports base schemas and aggregates all section schemas from TBP capsules.
3122
- */
3123
- export {
3124
- BaseSectionData,
3125
- BaseArrayItem,
3126
- BaseSectionSettingsSchema,
3127
- CtaSchema,
3128
- } from './base-schemas';
3129
-
3130
- import { HeaderSchema } from '@/components/header';
3131
- import { FooterSchema } from '@/components/footer';
3132
- import { HeroSchema } from '@/components/hero';
3133
- import { FeatureGridSchema } from '@/components/feature-grid';
3134
- import { CodeBlockSchema } from '@/components/code-block';
3135
- import { ProblemStatementSchema } from '@/components/problem-statement';
3136
- import { PillarsGridSchema } from '@/components/pillars-grid';
3137
- import { ArchLayersSchema } from '@/components/arch-layers';
3138
- import { ProductTriadSchema } from '@/components/product-triad';
3139
- import { PaSectionSchema } from '@/components/pa-section';
3140
- import { PhilosophySchema } from '@/components/philosophy';
3141
- import { CtaBannerSchema } from '@/components/cta-banner';
3142
-
3143
- export const SECTION_SCHEMAS = {
3144
- 'header': HeaderSchema,
3145
- 'footer': FooterSchema,
3146
- 'hero': HeroSchema,
3147
- 'feature-grid': FeatureGridSchema,
3148
- 'code-block': CodeBlockSchema,
3149
- 'problem-statement': ProblemStatementSchema,
3150
- 'pillars-grid': PillarsGridSchema,
3151
- 'arch-layers': ArchLayersSchema,
3152
- 'product-triad': ProductTriadSchema,
3153
- 'pa-section': PaSectionSchema,
3154
- 'philosophy': PhilosophySchema,
3155
- 'cta-banner': CtaBannerSchema,
3156
- } as const;
3157
-
3158
- export type SectionType = keyof typeof SECTION_SCHEMAS;
3159
-
3160
- END_OF_FILE_CONTENT
3161
- echo "Creating src/lib/utils.ts..."
3162
- cat << 'END_OF_FILE_CONTENT' > "src/lib/utils.ts"
3163
- import { clsx, type ClassValue } from 'clsx';
3164
- import { twMerge } from 'tailwind-merge';
3165
-
3166
- export function cn(...inputs: ClassValue[]) {
3167
- return twMerge(clsx(inputs));
3168
- }
3169
-
3170
- END_OF_FILE_CONTENT
3171
- echo "Creating src/main.tsx..."
3172
- cat << 'END_OF_FILE_CONTENT' > "src/main.tsx"
3173
- import '@/types'; // TBP: load type augmentation from capsule-driven types
3174
- import React from 'react';
3175
- import ReactDOM from 'react-dom/client';
3176
- import App from './App';
3177
- // ... resto del file
3178
-
3179
- ReactDOM.createRoot(document.getElementById('root')!).render(
3180
- <React.StrictMode>
3181
- <App />
3182
- </React.StrictMode>
3183
- );
3184
-
3185
-
3186
-
3187
-
3188
- END_OF_FILE_CONTENT
3189
- # SKIP: src/registry-types.ts è un file binario e non può essere convertito in testo.
3190
- echo "Creating src/types.ts..."
3191
- cat << 'END_OF_FILE_CONTENT' > "src/types.ts"
3192
- import type { MenuItem } from '@jsonpages/core';
3193
- import type { HeaderData, HeaderSettings } from '@/components/header';
3194
- import type { FooterData, FooterSettings } from '@/components/footer';
3195
- import type { HeroData, HeroSettings } from '@/components/hero';
3196
- import type { FeatureGridData, FeatureGridSettings } from '@/components/feature-grid';
3197
- import type { CodeBlockData, CodeBlockSettings } from '@/components/code-block';
3198
- import type { ProblemStatementData, ProblemStatementSettings } from '@/components/problem-statement';
3199
- import type { PillarsGridData, PillarsGridSettings } from '@/components/pillars-grid';
3200
- import type { ArchLayersData, ArchLayersSettings } from '@/components/arch-layers';
3201
- import type { ProductTriadData, ProductTriadSettings } from '@/components/product-triad';
3202
- import type { PaSectionData, PaSectionSettings } from '@/components/pa-section';
3203
- import type { PhilosophyData, PhilosophySettings } from '@/components/philosophy';
3204
- import type { CtaBannerData, CtaBannerSettings } from '@/components/cta-banner';
3205
-
3206
- export type SectionComponentPropsMap = {
3207
- 'header': { data: HeaderData; settings?: HeaderSettings; menu: MenuItem[] };
3208
- 'footer': { data: FooterData; settings?: FooterSettings };
3209
- 'hero': { data: HeroData; settings?: HeroSettings };
3210
- 'feature-grid': { data: FeatureGridData; settings?: FeatureGridSettings };
3211
- 'code-block': { data: CodeBlockData; settings?: CodeBlockSettings };
3212
- 'problem-statement': { data: ProblemStatementData; settings?: ProblemStatementSettings };
3213
- 'pillars-grid': { data: PillarsGridData; settings?: PillarsGridSettings };
3214
- 'arch-layers': { data: ArchLayersData; settings?: ArchLayersSettings };
3215
- 'product-triad': { data: ProductTriadData; settings?: ProductTriadSettings };
3216
- 'pa-section': { data: PaSectionData; settings?: PaSectionSettings };
3217
- 'philosophy': { data: PhilosophyData; settings?: PhilosophySettings };
3218
- 'cta-banner': { data: CtaBannerData; settings?: CtaBannerSettings };
3219
- };
3220
-
3221
- declare module '@jsonpages/core' {
3222
- export interface SectionDataRegistry {
3223
- 'header': HeaderData;
3224
- 'footer': FooterData;
3225
- 'hero': HeroData;
3226
- 'feature-grid': FeatureGridData;
3227
- 'code-block': CodeBlockData;
3228
- 'problem-statement': ProblemStatementData;
3229
- 'pillars-grid': PillarsGridData;
3230
- 'arch-layers': ArchLayersData;
3231
- 'product-triad': ProductTriadData;
3232
- 'pa-section': PaSectionData;
3233
- 'philosophy': PhilosophyData;
3234
- 'cta-banner': CtaBannerData;
3235
- }
3236
-
3237
- export interface SectionSettingsRegistry {
3238
- 'header': HeaderSettings;
3239
- 'footer': FooterSettings;
3240
- 'hero': HeroSettings;
3241
- 'feature-grid': FeatureGridSettings;
3242
- 'code-block': CodeBlockSettings;
3243
- 'problem-statement': ProblemStatementSettings;
3244
- 'pillars-grid': PillarsGridSettings;
3245
- 'arch-layers': ArchLayersSettings;
3246
- 'product-triad': ProductTriadSettings;
3247
- 'pa-section': PaSectionSettings;
3248
- 'philosophy': PhilosophySettings;
3249
- 'cta-banner': CtaBannerSettings;
3250
- }
3251
- }
3252
-
3253
- export * from '@jsonpages/core';
3254
-
3255
- END_OF_FILE_CONTENT
3256
- echo "Creating src/vite-env.d.ts..."
3257
- cat << 'END_OF_FILE_CONTENT' > "src/vite-env.d.ts"
3258
- /// <reference types="vite/client" />
3259
-
3260
- declare module '*?inline' {
3261
- const content: string;
3262
- export default content;
3263
- }
3264
-
3265
-
3266
-
3267
- END_OF_FILE_CONTENT