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