@jsonpages/cli 3.0.6 → 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.
- package/package.json +24 -29
- package/src/index.js +5 -6
- package/tsconfig.json +13 -0
- package/assets/src_tenant_alpha.sh +0 -3267
- package/src/projection.js +0 -76
|
@@ -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
|
-
“{renderQuote()}”
|
|
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
|