@jsonpages/cli 3.0.32 → 3.0.34
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 +2134 -882
- package/package.json +1 -1
- package/src/index.js +111 -13
|
@@ -27,121 +27,98 @@ END_OF_FILE_CONTENT
|
|
|
27
27
|
mkdir -p "src"
|
|
28
28
|
echo "Creating src/App.tsx..."
|
|
29
29
|
cat << 'END_OF_FILE_CONTENT' > "src/App.tsx"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
|
|
40
|
-
import
|
|
41
|
-
import
|
|
42
|
-
import
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
import
|
|
46
|
-
import
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const res = await fetch('/api/upload-asset', {
|
|
121
|
-
method: 'POST',
|
|
122
|
-
headers: { 'Content-Type': 'application/json' },
|
|
123
|
-
body: JSON.stringify({
|
|
124
|
-
filename: file.name,
|
|
125
|
-
mimeType: file.type || undefined,
|
|
126
|
-
data: base64,
|
|
127
|
-
}),
|
|
128
|
-
});
|
|
129
|
-
const body = (await res.json().catch(() => ({}))) as { url?: string; error?: string };
|
|
130
|
-
if (!res.ok) {
|
|
131
|
-
throw new Error(body.error || `Upload failed: ${res.status}`);
|
|
132
|
-
}
|
|
133
|
-
if (typeof body.url !== 'string') {
|
|
134
|
-
throw new Error('Invalid server response: missing url');
|
|
135
|
-
}
|
|
136
|
-
return body.url;
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
return <JsonPagesEngine config={config} />;
|
|
142
|
-
}
|
|
143
|
-
|
|
30
|
+
import { useState, useEffect } from 'react';
|
|
31
|
+
import { JsonPagesEngine } from '@jsonpages/core';
|
|
32
|
+
import type { LibraryImageEntry } from '@jsonpages/core';
|
|
33
|
+
import { ComponentRegistry } from '@/lib/ComponentRegistry';
|
|
34
|
+
import { SECTION_SCHEMAS } from '@/lib/schemas';
|
|
35
|
+
import { addSectionConfig } from '@/lib/addSectionConfig';
|
|
36
|
+
import { getHydratedData } from '@/lib/draftStorage';
|
|
37
|
+
import type { JsonPagesConfig, ProjectState } from '@jsonpages/core';
|
|
38
|
+
import type { SiteConfig, ThemeConfig, MenuConfig } from '@/types';
|
|
39
|
+
|
|
40
|
+
import siteData from '@/data/config/site.json';
|
|
41
|
+
import themeData from '@/data/config/theme.json';
|
|
42
|
+
import menuData from '@/data/config/menu.json';
|
|
43
|
+
import { getFilePages } from '@/lib/getFilePages';
|
|
44
|
+
|
|
45
|
+
import fontsCss from './fonts.css?inline';
|
|
46
|
+
import tenantCss from './index.css?inline';
|
|
47
|
+
|
|
48
|
+
const themeConfig = themeData as unknown as ThemeConfig;
|
|
49
|
+
const menuConfig = menuData as unknown as MenuConfig;
|
|
50
|
+
const TENANT_ID = 'alpha';
|
|
51
|
+
|
|
52
|
+
const filePages = getFilePages();
|
|
53
|
+
const fileSiteConfig = siteData as unknown as SiteConfig;
|
|
54
|
+
const MAX_UPLOAD_SIZE_BYTES = 5 * 1024 * 1024;
|
|
55
|
+
|
|
56
|
+
function getInitialData() {
|
|
57
|
+
return getHydratedData(TENANT_ID, filePages, fileSiteConfig);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function App() {
|
|
61
|
+
const [{ pages, siteConfig }] = useState(getInitialData);
|
|
62
|
+
const [assetsManifest, setAssetsManifest] = useState<LibraryImageEntry[]>([]);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
fetch('/api/list-assets')
|
|
66
|
+
.then((r) => (r.ok ? r.json() : []))
|
|
67
|
+
.then((list: LibraryImageEntry[]) => setAssetsManifest(Array.isArray(list) ? list : []))
|
|
68
|
+
.catch(() => setAssetsManifest([]));
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
const config: JsonPagesConfig = {
|
|
72
|
+
tenantId: TENANT_ID,
|
|
73
|
+
registry: ComponentRegistry as JsonPagesConfig['registry'],
|
|
74
|
+
schemas: SECTION_SCHEMAS as unknown as JsonPagesConfig['schemas'],
|
|
75
|
+
pages,
|
|
76
|
+
siteConfig,
|
|
77
|
+
themeConfig,
|
|
78
|
+
menuConfig,
|
|
79
|
+
themeCss: { tenant: fontsCss + '\n' + tenantCss },
|
|
80
|
+
addSection: addSectionConfig,
|
|
81
|
+
persistence: {
|
|
82
|
+
async saveToFile(state: ProjectState, slug: string): Promise<void> {
|
|
83
|
+
const res = await fetch('/api/save-to-file', {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: { 'Content-Type': 'application/json' },
|
|
86
|
+
body: JSON.stringify({ projectState: state, slug }),
|
|
87
|
+
});
|
|
88
|
+
const body = (await res.json().catch(() => ({}))) as { error?: string };
|
|
89
|
+
if (!res.ok) throw new Error(body.error ?? `Save to file failed: ${res.status}`);
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
assets: {
|
|
93
|
+
assetsBaseUrl: '/assets',
|
|
94
|
+
assetsManifest,
|
|
95
|
+
async onAssetUpload(file: File): Promise<string> {
|
|
96
|
+
if (!file.type.startsWith('image/')) throw new Error('Invalid file type.');
|
|
97
|
+
if (file.size > MAX_UPLOAD_SIZE_BYTES) throw new Error(`File too large. Max ${MAX_UPLOAD_SIZE_BYTES / 1024 / 1024}MB.`);
|
|
98
|
+
const base64 = await new Promise<string>((resolve, reject) => {
|
|
99
|
+
const reader = new FileReader();
|
|
100
|
+
reader.onload = () => resolve((reader.result as string).split(',')[1] ?? '');
|
|
101
|
+
reader.onerror = () => reject(reader.error);
|
|
102
|
+
reader.readAsDataURL(file);
|
|
103
|
+
});
|
|
104
|
+
const res = await fetch('/api/upload-asset', {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: { 'Content-Type': 'application/json' },
|
|
107
|
+
body: JSON.stringify({ filename: file.name, mimeType: file.type || undefined, data: base64 }),
|
|
108
|
+
});
|
|
109
|
+
const body = (await res.json().catch(() => ({}))) as { url?: string; error?: string };
|
|
110
|
+
if (!res.ok) throw new Error(body.error || `Upload failed: ${res.status}`);
|
|
111
|
+
if (typeof body.url !== 'string') throw new Error('Invalid server response: missing url');
|
|
112
|
+
return body.url;
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return <JsonPagesEngine config={config} />;
|
|
118
|
+
}
|
|
119
|
+
|
|
144
120
|
export default App;
|
|
121
|
+
|
|
145
122
|
END_OF_FILE_CONTENT
|
|
146
123
|
mkdir -p "src/components"
|
|
147
124
|
echo "Creating src/components/NotFound.tsx..."
|
|
@@ -338,6 +315,373 @@ export type ArchLayersSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
|
338
315
|
export type ArchLayerLevel = z.infer<typeof ArchLayerLevelSchema>;
|
|
339
316
|
export type SyntaxTokenType = z.infer<typeof SyntaxTokenTypeSchema>;
|
|
340
317
|
|
|
318
|
+
END_OF_FILE_CONTENT
|
|
319
|
+
mkdir -p "src/components/cli-section"
|
|
320
|
+
echo "Creating src/components/cli-section/View.tsx..."
|
|
321
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/cli-section/View.tsx"
|
|
322
|
+
import React from 'react';
|
|
323
|
+
import type { CliSectionData, CliSectionSettings } from './types';
|
|
324
|
+
|
|
325
|
+
export const CliSection: React.FC<{ data: CliSectionData; settings?: CliSectionSettings }> = ({ data }) => {
|
|
326
|
+
return (
|
|
327
|
+
<section
|
|
328
|
+
style={{
|
|
329
|
+
'--local-bg': 'var(--card)',
|
|
330
|
+
'--local-text': 'var(--foreground)',
|
|
331
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
332
|
+
'--local-primary': 'var(--primary)',
|
|
333
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
334
|
+
'--local-border': 'var(--border)',
|
|
335
|
+
} as React.CSSProperties}
|
|
336
|
+
className="relative z-0 py-28 bg-[var(--local-bg)]"
|
|
337
|
+
>
|
|
338
|
+
<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" />
|
|
339
|
+
<div className="max-w-[1200px] mx-auto px-8 grid grid-cols-2 gap-24 items-center">
|
|
340
|
+
|
|
341
|
+
{/* LEFT */}
|
|
342
|
+
<div>
|
|
343
|
+
{data.label && (
|
|
344
|
+
<div className="jp-section-label inline-flex items-center gap-2 text-[0.68rem] font-mono font-bold uppercase tracking-[0.14em] text-[var(--local-accent)] mb-5" data-jp-field="label">
|
|
345
|
+
<span className="w-6 h-px bg-[var(--local-primary)]" />
|
|
346
|
+
{data.label}
|
|
347
|
+
</div>
|
|
348
|
+
)}
|
|
349
|
+
<h2
|
|
350
|
+
className="font-display text-[clamp(2rem,4.5vw,3.8rem)] font-black text-[var(--local-text)] leading-[1.05] tracking-tight mb-5"
|
|
351
|
+
data-jp-field="title"
|
|
352
|
+
>
|
|
353
|
+
{data.title}
|
|
354
|
+
</h2>
|
|
355
|
+
{data.description && (
|
|
356
|
+
<p className="text-[1.05rem] text-[var(--local-text-muted)] leading-[1.8] mb-8" data-jp-field="description">
|
|
357
|
+
{data.description}
|
|
358
|
+
</p>
|
|
359
|
+
)}
|
|
360
|
+
{data.steps && data.steps.length > 0 && (
|
|
361
|
+
<div className="flex flex-col">
|
|
362
|
+
{data.steps.map((step, idx) => (
|
|
363
|
+
<div
|
|
364
|
+
key={step.id ?? idx}
|
|
365
|
+
className="grid grid-cols-[32px_1fr] gap-4 py-6 border-b border-[rgba(255,255,255,0.06)] last:border-b-0 items-start"
|
|
366
|
+
data-jp-item-id={step.id ?? `legacy-${idx}`}
|
|
367
|
+
data-jp-item-field="steps"
|
|
368
|
+
>
|
|
369
|
+
<div className="font-display text-[1.25rem] font-black text-[#334155] leading-none mt-0.5">{step.num}</div>
|
|
370
|
+
<div>
|
|
371
|
+
<div className="font-display font-bold text-[1rem] text-[var(--local-text)] mb-1">{step.title}</div>
|
|
372
|
+
<p className="text-[0.85rem] text-[var(--local-text-muted)] leading-[1.6]">{step.description}</p>
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
))}
|
|
376
|
+
</div>
|
|
377
|
+
)}
|
|
378
|
+
</div>
|
|
379
|
+
|
|
380
|
+
{/* RIGHT — terminal */}
|
|
381
|
+
<div className="rounded-[12px] overflow-hidden border border-[rgba(255,255,255,0.10)] shadow-[0_30px_60px_rgba(0,0,0,0.5)]">
|
|
382
|
+
<div className="bg-[#0d1828] px-4 py-2.5 flex items-center gap-1.5 border-b border-[rgba(255,255,255,0.05)]">
|
|
383
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#ef4444]" />
|
|
384
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#f59e0b]" />
|
|
385
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#22c55e]" />
|
|
386
|
+
<span className="mx-auto font-mono text-[0.60rem] text-[rgba(255,255,255,0.25)]">Terminal</span>
|
|
387
|
+
</div>
|
|
388
|
+
<div className="bg-[#020508] px-7 py-6 font-mono text-[0.78rem] leading-[2.1] overflow-x-auto">
|
|
389
|
+
<div><span className="text-[#5c6370] italic"># Step 1 — install CLI globally</span></div>
|
|
390
|
+
<div><span className="text-[#3b82f6]">$</span> <span className="text-white">npm install -g </span><span className="text-[#98c379]">@jsonpages/cli@latest</span></div>
|
|
391
|
+
<div><span className="text-[#334155]">added 1 package in 2.3s</span></div>
|
|
392
|
+
<div><span className="text-[#22c55e]">✓ @jsonpages/cli@1.2.0 installed</span></div>
|
|
393
|
+
<div> </div>
|
|
394
|
+
<div><span className="text-[#5c6370] italic"># Step 2 — scaffold a new tenant</span></div>
|
|
395
|
+
<div><span className="text-[#3b82f6]">$</span> <span className="text-[#98c379]">npx @jsonpages/cli@latest</span> <span className="text-white">new my-tenant</span></div>
|
|
396
|
+
<div><span className="text-[#22c55e]"> ✓ src/components/hero/</span></div>
|
|
397
|
+
<div><span className="text-[#22c55e]"> ✓ src/lib/schemas.ts</span></div>
|
|
398
|
+
<div><span className="text-[#22c55e]"> ✓ src/lib/ComponentRegistry.tsx</span></div>
|
|
399
|
+
<div><span className="text-[#22c55e]"> ✓ src/data/pages/home.json</span></div>
|
|
400
|
+
<div><span className="text-[#22c55e]"> ✓ Done in 1.8s</span></div>
|
|
401
|
+
<div> </div>
|
|
402
|
+
<div><span className="text-[#5c6370] italic"># Step 3 — start Studio</span></div>
|
|
403
|
+
<div><span className="text-[#3b82f6]">$</span> <span className="text-white">cd my-tenant && npm run dev</span></div>
|
|
404
|
+
<div><span className="text-[#22c55e]"> ➜ Studio ready at </span><span className="text-[#60a5fa]">http://localhost:5173</span><span className="inline-block w-2 h-[1em] bg-[#3b82f6] ml-1 align-text-bottom animate-pulse" /></div>
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
407
|
+
|
|
408
|
+
</div>
|
|
409
|
+
</section>
|
|
410
|
+
);
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
END_OF_FILE_CONTENT
|
|
414
|
+
echo "Creating src/components/cli-section/index.ts..."
|
|
415
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/cli-section/index.ts"
|
|
416
|
+
export * from './View';
|
|
417
|
+
export * from './schema';
|
|
418
|
+
export * from './types';
|
|
419
|
+
|
|
420
|
+
END_OF_FILE_CONTENT
|
|
421
|
+
echo "Creating src/components/cli-section/schema.ts..."
|
|
422
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/cli-section/schema.ts"
|
|
423
|
+
import { z } from 'zod';
|
|
424
|
+
import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
|
|
425
|
+
|
|
426
|
+
const CliStepSchema = BaseArrayItem.extend({
|
|
427
|
+
num: z.string().describe('ui:text'),
|
|
428
|
+
title: z.string().describe('ui:text'),
|
|
429
|
+
description: z.string().describe('ui:textarea'),
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
export const CliSectionSchema = BaseSectionData.extend({
|
|
433
|
+
label: z.string().optional().describe('ui:text'),
|
|
434
|
+
title: z.string().describe('ui:text'),
|
|
435
|
+
description: z.string().optional().describe('ui:textarea'),
|
|
436
|
+
steps: z.array(CliStepSchema).optional().describe('ui:list'),
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
END_OF_FILE_CONTENT
|
|
440
|
+
echo "Creating src/components/cli-section/types.ts..."
|
|
441
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/cli-section/types.ts"
|
|
442
|
+
import { z } from 'zod';
|
|
443
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
444
|
+
import { CliSectionSchema } from './schema';
|
|
445
|
+
|
|
446
|
+
export type CliSectionData = z.infer<typeof CliSectionSchema>;
|
|
447
|
+
export type CliSectionSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
448
|
+
|
|
449
|
+
END_OF_FILE_CONTENT
|
|
450
|
+
mkdir -p "src/components/cms-ice"
|
|
451
|
+
echo "Creating src/components/cms-ice/View.tsx..."
|
|
452
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/cms-ice/View.tsx"
|
|
453
|
+
import React from 'react';
|
|
454
|
+
import type { CmsIceData, CmsIceSettings } from './types';
|
|
455
|
+
|
|
456
|
+
export const CmsIce: React.FC<{ data: CmsIceData; settings?: CmsIceSettings }> = ({ data }) => {
|
|
457
|
+
return (
|
|
458
|
+
<section
|
|
459
|
+
style={{
|
|
460
|
+
'--local-bg': 'var(--background)',
|
|
461
|
+
'--local-text': 'var(--foreground)',
|
|
462
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
463
|
+
'--local-primary': 'var(--primary)',
|
|
464
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
465
|
+
'--local-cyan': 'var(--color-secondary, #22d3ee)',
|
|
466
|
+
'--local-border': 'var(--border)',
|
|
467
|
+
'--local-surface': 'var(--card)',
|
|
468
|
+
} as React.CSSProperties}
|
|
469
|
+
className="relative z-0 py-28 bg-[var(--local-bg)]"
|
|
470
|
+
>
|
|
471
|
+
<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" />
|
|
472
|
+
<div className="max-w-[1200px] mx-auto px-8">
|
|
473
|
+
|
|
474
|
+
{/* Header */}
|
|
475
|
+
<div className="grid grid-cols-2 gap-16 items-end mb-16">
|
|
476
|
+
<div>
|
|
477
|
+
{data.label && (
|
|
478
|
+
<div className="jp-section-label inline-flex items-center gap-2 text-[0.68rem] font-mono font-bold uppercase tracking-[0.14em] text-[var(--local-accent)] mb-5" data-jp-field="label">
|
|
479
|
+
<span className="w-6 h-px bg-[var(--local-primary)]" />
|
|
480
|
+
{data.label}
|
|
481
|
+
</div>
|
|
482
|
+
)}
|
|
483
|
+
<h2
|
|
484
|
+
className="font-display text-[clamp(2rem,4.5vw,3.8rem)] font-black text-[var(--local-text)] leading-[1.05] tracking-tight"
|
|
485
|
+
data-jp-field="title"
|
|
486
|
+
>
|
|
487
|
+
{data.title}
|
|
488
|
+
</h2>
|
|
489
|
+
</div>
|
|
490
|
+
{data.description && (
|
|
491
|
+
<p
|
|
492
|
+
className="text-[1.05rem] text-[var(--local-text-muted)] leading-[1.8] pb-1"
|
|
493
|
+
data-jp-field="description"
|
|
494
|
+
>
|
|
495
|
+
{data.description}
|
|
496
|
+
</p>
|
|
497
|
+
)}
|
|
498
|
+
</div>
|
|
499
|
+
|
|
500
|
+
{/* ICE Mockup — product demo, decorative */}
|
|
501
|
+
<div className="rounded-[16px] overflow-hidden border border-[rgba(255,255,255,0.10)] shadow-[0_0_0_1px_rgba(255,255,255,0.03),0_60px_120px_rgba(0,0,0,0.7),0_0_80px_rgba(59,130,246,0.08)] mb-16">
|
|
502
|
+
{/* Browser bar */}
|
|
503
|
+
<div className="bg-[#0d1828] px-4 py-2.5 flex items-center gap-1.5 border-b border-[rgba(255,255,255,0.05)]">
|
|
504
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#ef4444]" />
|
|
505
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#f59e0b]" />
|
|
506
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#22c55e]" />
|
|
507
|
+
<span className="mx-auto font-mono text-[0.62rem] text-[rgba(255,255,255,0.20)] bg-[rgba(255,255,255,0.04)] px-8 py-0.5 rounded">localhost:5173/admin — JsonPages Studio</span>
|
|
508
|
+
</div>
|
|
509
|
+
{/* Split */}
|
|
510
|
+
<div className="grid grid-cols-[1fr_300px] h-[520px] bg-[#060d1b]">
|
|
511
|
+
{/* Stage */}
|
|
512
|
+
<div className="flex flex-col overflow-hidden">
|
|
513
|
+
{/* Tenant nav sim */}
|
|
514
|
+
<div className="bg-[rgba(6,13,27,0.96)] px-6 py-3 flex items-center justify-between border-b border-[rgba(255,255,255,0.05)] flex-shrink-0">
|
|
515
|
+
<div className="flex items-center gap-2 font-display font-bold text-[0.9rem] text-white">
|
|
516
|
+
<div className="w-5 h-5 bg-gradient-to-br from-[#3b82f6] to-[#22d3ee] rounded flex items-center justify-center font-mono text-[0.45rem] text-white font-bold">{'{}'}</div>
|
|
517
|
+
Json<span className="text-[#60a5fa]">Pages</span>
|
|
518
|
+
</div>
|
|
519
|
+
<div className="flex gap-6 text-[0.68rem] text-[#475569] font-sans">
|
|
520
|
+
<span>Architecture</span><span>CMS</span><span>Versioning</span><span>Developer</span>
|
|
521
|
+
</div>
|
|
522
|
+
</div>
|
|
523
|
+
{/* Hero section — selected */}
|
|
524
|
+
<div className="flex-1 relative p-8 flex flex-col justify-center bg-gradient-to-br from-[#04090f] to-[#071320] outline outline-2 outline-[#3b82f6] -outline-offset-2">
|
|
525
|
+
<span className="absolute top-2.5 right-2.5 font-mono text-[0.5rem] font-bold uppercase tracking-widest bg-[#3b82f6] text-white px-2 py-0.5">HERO | LOCAL</span>
|
|
526
|
+
<div className="font-display font-black text-[2.4rem] leading-none text-white mb-0.5">The Sovereign Shell.</div>
|
|
527
|
+
<div className="font-display font-black text-[2.4rem] leading-none bg-gradient-to-r from-[#60a5fa] to-[#22d3ee] bg-clip-text text-transparent mb-4">Zero Runtime Overhead.</div>
|
|
528
|
+
<p className="text-[0.75rem] text-[#475569] leading-[1.65] max-w-[360px] mb-5">The @jsonpages/core package is a headless, schema-driven runtime. It handles routing, hydration, and the admin interface.</p>
|
|
529
|
+
<div className="flex gap-2">
|
|
530
|
+
<span className="text-[0.65rem] font-semibold bg-[#3b82f6] text-white px-3.5 py-1.5 rounded-[5px]">Read the Docs</span>
|
|
531
|
+
<span className="text-[0.65rem] border border-[rgba(255,255,255,0.15)] text-[#94a3b8] px-3.5 py-1.5 rounded-[5px]">View on NPM</span>
|
|
532
|
+
</div>
|
|
533
|
+
</div>
|
|
534
|
+
{/* Next section visible but dimmed */}
|
|
535
|
+
<div className="flex-shrink-0 px-6 py-4 bg-[#0a1628] border-t border-[rgba(255,255,255,0.05)] flex gap-3 opacity-40">
|
|
536
|
+
{['The Form Factory', 'The Tenant Protocol', 'The Core Engine'].map((t) => (
|
|
537
|
+
<div key={t} className="flex-1 bg-[rgba(255,255,255,0.02)] border border-[rgba(255,255,255,0.05)] rounded p-2.5">
|
|
538
|
+
<div className="w-3.5 h-3.5 rounded bg-[rgba(59,130,246,0.15)] mb-1.5" />
|
|
539
|
+
<div className="font-display font-bold text-[0.58rem] text-[#94a3b8]">{t}</div>
|
|
540
|
+
</div>
|
|
541
|
+
))}
|
|
542
|
+
</div>
|
|
543
|
+
</div>
|
|
544
|
+
{/* Inspector */}
|
|
545
|
+
<div className="bg-[#08121f] border-l border-[rgba(255,255,255,0.06)] flex flex-col overflow-hidden">
|
|
546
|
+
<div className="px-4 py-3 border-b border-[rgba(255,255,255,0.05)] flex items-start justify-between">
|
|
547
|
+
<div>
|
|
548
|
+
<div className="font-display font-bold text-[0.88rem] text-white">Inspector</div>
|
|
549
|
+
<div className="flex items-center gap-1.5 mt-0.5 font-mono text-[0.56rem] text-[#3b82f6]">
|
|
550
|
+
<span className="font-bold">■ HERO</span>
|
|
551
|
+
<span className="text-[#1e3a5f]">|</span>
|
|
552
|
+
<span className="text-[#334155]">LOCAL</span>
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
<span className="font-mono text-[0.58rem] text-[#3b82f6]">+ Add section</span>
|
|
556
|
+
</div>
|
|
557
|
+
{/* Layers */}
|
|
558
|
+
<div className="border-b border-[rgba(255,255,255,0.05)]">
|
|
559
|
+
<div className="px-4 py-1.5 font-mono text-[0.54rem] uppercase tracking-widest text-[#1e3a5f] flex justify-between">
|
|
560
|
+
<span>Page Layers</span><span className="text-[#334155]">(8)</span>
|
|
561
|
+
</div>
|
|
562
|
+
{[
|
|
563
|
+
{ type: 'HERO', label: 'The Sovereign Shell.', active: true, opacity: '' },
|
|
564
|
+
{ type: 'SOC', label: 'Separation of Concerns', active: false, opacity: 'opacity-55' },
|
|
565
|
+
{ type: 'GIT', label: 'Your content is code.', active: false, opacity: 'opacity-45' },
|
|
566
|
+
{ type: 'DEVEX', label: 'App.tsx is incredibly thin.', active: false, opacity: 'opacity-35' },
|
|
567
|
+
].map(({ type, label, active, opacity }) => (
|
|
568
|
+
<div key={type} className={`flex items-center gap-2 px-4 py-1.5 ${active ? 'bg-[rgba(59,130,246,0.08)]' : ''} ${opacity}`}>
|
|
569
|
+
<span className="text-[#1e3a5f] text-[0.58rem]">⠿</span>
|
|
570
|
+
<span className={`font-mono text-[0.52rem] uppercase tracking-wide w-10 flex-shrink-0 ${active ? 'text-[#3b82f6]' : 'text-[#1e3a5f]'}`}>{type}</span>
|
|
571
|
+
<span className={`font-sans text-[0.65rem] flex-1 truncate ${active ? 'text-[#e2e8f0] font-semibold' : 'text-[#475569]'}`}>{label}</span>
|
|
572
|
+
<span className={`w-1.5 h-1.5 rounded-full ${active ? 'bg-[#22c55e]' : 'bg-[#334155] opacity-40'}`} />
|
|
573
|
+
</div>
|
|
574
|
+
))}
|
|
575
|
+
</div>
|
|
576
|
+
{/* Context fields */}
|
|
577
|
+
<div className="flex-1 px-4 py-3 flex flex-col gap-3 overflow-y-auto">
|
|
578
|
+
{[
|
|
579
|
+
{ label: 'Title', val: 'The Sovereign Shell.', active: true },
|
|
580
|
+
{ label: 'Subtitle', val: 'Zero Runtime Overhead.', active: false },
|
|
581
|
+
{ label: 'Badge', val: 'Architecture v1.2', active: false },
|
|
582
|
+
].map(({ label, val, active }) => (
|
|
583
|
+
<div key={label}>
|
|
584
|
+
<div className="font-mono text-[0.52rem] uppercase tracking-widest text-[#334155] mb-1">{label}</div>
|
|
585
|
+
<div className={`rounded px-2.5 py-1.5 font-mono text-[0.60rem] truncate ${active
|
|
586
|
+
? 'bg-[rgba(59,130,246,0.05)] border border-[rgba(59,130,246,0.45)] text-[#e2e8f0]'
|
|
587
|
+
: 'bg-[rgba(255,255,255,0.03)] border border-[rgba(255,255,255,0.07)] text-[#94a3b8]'}`}
|
|
588
|
+
>{val}</div>
|
|
589
|
+
</div>
|
|
590
|
+
))}
|
|
591
|
+
<div>
|
|
592
|
+
<div className="font-mono text-[0.52rem] uppercase tracking-widest text-[#334155] mb-1 flex justify-between">
|
|
593
|
+
<span>CTAs (2)</span><span className="text-[#3b82f6]">+ Add Item</span>
|
|
594
|
+
</div>
|
|
595
|
+
<div className="border border-[rgba(255,255,255,0.05)] rounded overflow-hidden">
|
|
596
|
+
{[{ lbl: 'Read the Docs', tag: 'primary' }, { lbl: 'View on NPM', tag: 'secondary' }].map(({ lbl, tag }) => (
|
|
597
|
+
<div key={lbl} className="flex items-center gap-1.5 px-2.5 py-1.5 border-b border-[rgba(255,255,255,0.04)] last:border-b-0">
|
|
598
|
+
<span className="text-[#3b82f6] text-[0.52rem]">▸</span>
|
|
599
|
+
<span className="font-sans text-[0.60rem] text-[#475569] flex-1">{lbl}</span>
|
|
600
|
+
<span className="font-mono text-[0.48rem] px-1 py-0.5 rounded bg-[rgba(59,130,246,0.08)] text-[#60a5fa]">{tag}</span>
|
|
601
|
+
</div>
|
|
602
|
+
))}
|
|
603
|
+
</div>
|
|
604
|
+
</div>
|
|
605
|
+
</div>
|
|
606
|
+
{/* Bottom bar */}
|
|
607
|
+
<div className="px-4 py-2.5 border-t border-[rgba(255,255,255,0.05)] bg-[#060e1c] flex items-center gap-2">
|
|
608
|
+
<span className="w-1.5 h-1.5 rounded-full bg-[#22c55e]" />
|
|
609
|
+
<span className="font-mono text-[0.52rem] text-[#475569]">All Changes Saved</span>
|
|
610
|
+
<div className="flex items-center gap-1.5 ml-1 font-mono text-[0.52rem] text-[#334155]">
|
|
611
|
+
<div className="w-5 h-2.5 bg-[#3b82f6] rounded-full relative flex-shrink-0">
|
|
612
|
+
<div className="absolute top-[1.5px] right-[1.5px] w-[9px] h-[9px] bg-white rounded-full" />
|
|
613
|
+
</div>
|
|
614
|
+
Autosave
|
|
615
|
+
</div>
|
|
616
|
+
<div className="ml-auto flex gap-1.5">
|
|
617
|
+
<span className="font-mono text-[0.50rem] px-1.5 py-0.5 rounded border border-[rgba(59,130,246,0.3)] bg-[rgba(59,130,246,0.12)] text-[#60a5fa] flex items-center gap-1">⬡ HTML</span>
|
|
618
|
+
<span className="font-mono text-[0.50rem] px-1.5 py-0.5 rounded border border-[rgba(255,255,255,0.08)] bg-[rgba(255,255,255,0.03)] text-[#94a3b8] opacity-50">{'{}'} JSON</span>
|
|
619
|
+
</div>
|
|
620
|
+
</div>
|
|
621
|
+
</div>
|
|
622
|
+
</div>
|
|
623
|
+
</div>
|
|
624
|
+
|
|
625
|
+
{/* Callouts */}
|
|
626
|
+
{data.callouts && data.callouts.length > 0 && (
|
|
627
|
+
<div className="grid grid-cols-3 gap-6">
|
|
628
|
+
{data.callouts.map((c, idx) => (
|
|
629
|
+
<div
|
|
630
|
+
key={c.id ?? idx}
|
|
631
|
+
className="border border-[rgba(255,255,255,0.06)] rounded-[12px] p-8 bg-[rgba(255,255,255,0.015)] hover:border-[rgba(59,130,246,0.2)] hover:bg-[rgba(59,130,246,0.03)] transition-all duration-200"
|
|
632
|
+
data-jp-item-id={c.id ?? `legacy-${idx}`}
|
|
633
|
+
data-jp-item-field="callouts"
|
|
634
|
+
>
|
|
635
|
+
<div className="w-9 h-9 rounded-[8px] bg-[rgba(59,130,246,0.10)] flex items-center justify-center text-[1.1rem] mb-4">
|
|
636
|
+
{c.icon}
|
|
637
|
+
</div>
|
|
638
|
+
<h4 className="font-display font-bold text-[1.05rem] text-[var(--local-text)] mb-2">{c.title}</h4>
|
|
639
|
+
<p className="text-[0.88rem] text-[var(--local-text-muted)] leading-[1.7]">{c.description}</p>
|
|
640
|
+
</div>
|
|
641
|
+
))}
|
|
642
|
+
</div>
|
|
643
|
+
)}
|
|
644
|
+
</div>
|
|
645
|
+
</section>
|
|
646
|
+
);
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
END_OF_FILE_CONTENT
|
|
650
|
+
echo "Creating src/components/cms-ice/index.ts..."
|
|
651
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/cms-ice/index.ts"
|
|
652
|
+
export * from './View';
|
|
653
|
+
export * from './schema';
|
|
654
|
+
export * from './types';
|
|
655
|
+
|
|
656
|
+
END_OF_FILE_CONTENT
|
|
657
|
+
echo "Creating src/components/cms-ice/schema.ts..."
|
|
658
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/cms-ice/schema.ts"
|
|
659
|
+
import { z } from 'zod';
|
|
660
|
+
import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
|
|
661
|
+
|
|
662
|
+
const CalloutSchema = BaseArrayItem.extend({
|
|
663
|
+
icon: z.string().describe('ui:text'),
|
|
664
|
+
title: z.string().describe('ui:text'),
|
|
665
|
+
description: z.string().describe('ui:textarea'),
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
export const CmsIceSchema = BaseSectionData.extend({
|
|
669
|
+
label: z.string().optional().describe('ui:text'),
|
|
670
|
+
title: z.string().describe('ui:text'),
|
|
671
|
+
description: z.string().optional().describe('ui:textarea'),
|
|
672
|
+
callouts: z.array(CalloutSchema).optional().describe('ui:list'),
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
END_OF_FILE_CONTENT
|
|
676
|
+
echo "Creating src/components/cms-ice/types.ts..."
|
|
677
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/cms-ice/types.ts"
|
|
678
|
+
import { z } from 'zod';
|
|
679
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
680
|
+
import { CmsIceSchema } from './schema';
|
|
681
|
+
|
|
682
|
+
export type CmsIceData = z.infer<typeof CmsIceSchema>;
|
|
683
|
+
export type CmsIceSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
684
|
+
|
|
341
685
|
END_OF_FILE_CONTENT
|
|
342
686
|
mkdir -p "src/components/code-block"
|
|
343
687
|
echo "Creating src/components/code-block/View.tsx..."
|
|
@@ -441,65 +785,73 @@ END_OF_FILE_CONTENT
|
|
|
441
785
|
mkdir -p "src/components/cta-banner"
|
|
442
786
|
echo "Creating src/components/cta-banner/View.tsx..."
|
|
443
787
|
cat << 'END_OF_FILE_CONTENT' > "src/components/cta-banner/View.tsx"
|
|
444
|
-
import React from 'react';
|
|
445
|
-
import { cn } from '@/lib/utils';
|
|
446
|
-
import type { CtaBannerData, CtaBannerSettings } from './types';
|
|
447
|
-
|
|
448
|
-
export const CtaBanner: React.FC<{ data: CtaBannerData; settings?: CtaBannerSettings }> = ({ data }) => {
|
|
449
|
-
return (
|
|
450
|
-
<section
|
|
451
|
-
style={{
|
|
452
|
-
'--local-bg':
|
|
453
|
-
'--local-text':
|
|
454
|
-
'--local-text-muted': 'var(--muted-foreground)',
|
|
455
|
-
'--local-primary':
|
|
456
|
-
'--local-accent':
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
<div className="
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
788
|
+
import React from 'react';
|
|
789
|
+
import { cn } from '@/lib/utils';
|
|
790
|
+
import type { CtaBannerData, CtaBannerSettings } from './types';
|
|
791
|
+
|
|
792
|
+
export const CtaBanner: React.FC<{ data: CtaBannerData; settings?: CtaBannerSettings }> = ({ data }) => {
|
|
793
|
+
return (
|
|
794
|
+
<section
|
|
795
|
+
style={{
|
|
796
|
+
'--local-bg': 'var(--card)',
|
|
797
|
+
'--local-text': 'var(--foreground)',
|
|
798
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
799
|
+
'--local-primary': 'var(--primary)',
|
|
800
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
801
|
+
'--local-cyan': 'var(--color-secondary, #22d3ee)',
|
|
802
|
+
} as React.CSSProperties}
|
|
803
|
+
className="relative py-28 bg-[var(--local-bg)] overflow-hidden text-center"
|
|
804
|
+
>
|
|
805
|
+
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[48px] h-[2px] bg-gradient-to-r from-[var(--local-primary)] to-[var(--local-cyan)]" />
|
|
806
|
+
<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.06)_0%,transparent_60%)] pointer-events-none" />
|
|
807
|
+
<div className="relative max-w-[960px] mx-auto px-8">
|
|
808
|
+
<h2
|
|
809
|
+
className="font-display text-[clamp(3rem,7vw,6.5rem)] font-black text-[var(--local-text)] leading-[1.0] tracking-tight mb-6"
|
|
810
|
+
data-jp-field="title"
|
|
811
|
+
>
|
|
812
|
+
{data.title}
|
|
813
|
+
</h2>
|
|
814
|
+
{data.description && (
|
|
815
|
+
<p
|
|
816
|
+
className="text-[1.15rem] text-[var(--local-text-muted)] max-w-[560px] mx-auto leading-[1.75] mb-10"
|
|
817
|
+
data-jp-field="description"
|
|
818
|
+
>
|
|
819
|
+
{data.description}
|
|
820
|
+
</p>
|
|
821
|
+
)}
|
|
822
|
+
{data.cliCommand && (
|
|
823
|
+
<div
|
|
824
|
+
className="inline-flex items-center gap-4 bg-[rgba(0,0,0,0.5)] border border-[rgba(255,255,255,0.12)] rounded-[12px] px-6 py-4 font-mono text-[0.88rem] text-[var(--local-text-muted)] mb-10"
|
|
825
|
+
data-jp-field="cliCommand"
|
|
826
|
+
>
|
|
827
|
+
<span className="text-[var(--local-primary)]">$</span>
|
|
828
|
+
<span className="text-white">{data.cliCommand}</span>
|
|
829
|
+
</div>
|
|
830
|
+
)}
|
|
831
|
+
{data.ctas && data.ctas.length > 0 && (
|
|
832
|
+
<div className="flex gap-4 justify-center flex-wrap">
|
|
833
|
+
{data.ctas.map((cta, idx) => (
|
|
834
|
+
<a
|
|
835
|
+
key={cta.id ?? idx}
|
|
836
|
+
href={cta.href}
|
|
837
|
+
data-jp-item-id={cta.id ?? `legacy-${idx}`}
|
|
838
|
+
data-jp-item-field="ctas"
|
|
839
|
+
className={cn(
|
|
840
|
+
'inline-flex items-center gap-2 px-9 py-3.5 rounded-[7px] font-semibold text-[1rem] transition-all duration-200 no-underline',
|
|
841
|
+
cta.variant === 'primary'
|
|
842
|
+
? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-0.5 shadow-[0_0_32px_rgba(59,130,246,0.25)]'
|
|
843
|
+
: '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)]'
|
|
844
|
+
)}
|
|
845
|
+
>
|
|
846
|
+
{cta.label}
|
|
847
|
+
</a>
|
|
848
|
+
))}
|
|
849
|
+
</div>
|
|
850
|
+
)}
|
|
851
|
+
</div>
|
|
852
|
+
</section>
|
|
853
|
+
);
|
|
854
|
+
};
|
|
503
855
|
|
|
504
856
|
END_OF_FILE_CONTENT
|
|
505
857
|
echo "Creating src/components/cta-banner/index.ts..."
|
|
@@ -511,25 +863,534 @@ export * from './types';
|
|
|
511
863
|
END_OF_FILE_CONTENT
|
|
512
864
|
echo "Creating src/components/cta-banner/schema.ts..."
|
|
513
865
|
cat << 'END_OF_FILE_CONTENT' > "src/components/cta-banner/schema.ts"
|
|
514
|
-
import { z } from 'zod';
|
|
515
|
-
import { BaseSectionData, CtaSchema } from '@/lib/base-schemas';
|
|
516
|
-
|
|
517
|
-
export const CtaBannerSchema = BaseSectionData.extend({
|
|
518
|
-
label:
|
|
519
|
-
title:
|
|
520
|
-
description: z.string().optional().describe('ui:textarea'),
|
|
521
|
-
|
|
522
|
-
|
|
866
|
+
import { z } from 'zod';
|
|
867
|
+
import { BaseSectionData, CtaSchema } from '@/lib/base-schemas';
|
|
868
|
+
|
|
869
|
+
export const CtaBannerSchema = BaseSectionData.extend({
|
|
870
|
+
label: z.string().optional().describe('ui:text'),
|
|
871
|
+
title: z.string().describe('ui:text'),
|
|
872
|
+
description: z.string().optional().describe('ui:textarea'),
|
|
873
|
+
cliCommand: z.string().optional().describe('ui:text'),
|
|
874
|
+
ctas: z.array(CtaSchema).optional().describe('ui:list'),
|
|
875
|
+
});
|
|
523
876
|
|
|
524
877
|
END_OF_FILE_CONTENT
|
|
525
878
|
echo "Creating src/components/cta-banner/types.ts..."
|
|
526
879
|
cat << 'END_OF_FILE_CONTENT' > "src/components/cta-banner/types.ts"
|
|
527
|
-
import { z } from 'zod';
|
|
528
|
-
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
529
|
-
import { CtaBannerSchema } from './schema';
|
|
530
|
-
|
|
531
|
-
export type CtaBannerData
|
|
532
|
-
export type CtaBannerSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
880
|
+
import { z } from 'zod';
|
|
881
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
882
|
+
import { CtaBannerSchema } from './schema';
|
|
883
|
+
|
|
884
|
+
export type CtaBannerData = z.infer<typeof CtaBannerSchema>;
|
|
885
|
+
export type CtaBannerSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
886
|
+
|
|
887
|
+
END_OF_FILE_CONTENT
|
|
888
|
+
mkdir -p "src/components/devex"
|
|
889
|
+
echo "Creating src/components/devex/View.tsx..."
|
|
890
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/devex/View.tsx"
|
|
891
|
+
import React from 'react';
|
|
892
|
+
import type { DevexData, DevexSettings } from './types';
|
|
893
|
+
|
|
894
|
+
// The App.tsx snippet is canonical product content — not editable copy.
|
|
895
|
+
const APP_TSX_LINES = [
|
|
896
|
+
{ t: 'cm', c: '// Your App.tsx is incredibly thin.' },
|
|
897
|
+
{ t: 'pl', c: '' },
|
|
898
|
+
{ t: 'kw', c: "import { JsonPagesEngine } from '@jsonpages/core';" },
|
|
899
|
+
{ t: 'kw', c: "import { config } from './my-config';" },
|
|
900
|
+
{ t: 'pl', c: '' },
|
|
901
|
+
{ t: 'kw', c: 'export default function App() {' },
|
|
902
|
+
{ t: 'cm', c: ' // The Engine takes over from here' },
|
|
903
|
+
{ t: 'fn', c: ' return <JsonPagesEngine config={config} />;' },
|
|
904
|
+
{ t: 'pl', c: '}' },
|
|
905
|
+
] as const;
|
|
906
|
+
|
|
907
|
+
const tokenClass: Record<string, string> = {
|
|
908
|
+
cm: 'text-[#5c6370] italic',
|
|
909
|
+
kw: 'text-[#c678dd]',
|
|
910
|
+
fn: 'text-[#61afef]',
|
|
911
|
+
pl: 'text-[#cbd5e1]',
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
export const Devex: React.FC<{ data: DevexData; settings?: DevexSettings }> = ({ data }) => {
|
|
915
|
+
return (
|
|
916
|
+
<section
|
|
917
|
+
style={{
|
|
918
|
+
'--local-bg': 'var(--background)',
|
|
919
|
+
'--local-text': 'var(--foreground)',
|
|
920
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
921
|
+
'--local-primary': 'var(--primary)',
|
|
922
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
923
|
+
'--local-border': 'var(--border)',
|
|
924
|
+
} as React.CSSProperties}
|
|
925
|
+
className="relative z-0 py-28 bg-[var(--local-bg)]"
|
|
926
|
+
>
|
|
927
|
+
<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" />
|
|
928
|
+
<div className="max-w-[1200px] mx-auto px-8 grid grid-cols-2 gap-24 items-center">
|
|
929
|
+
|
|
930
|
+
{/* LEFT */}
|
|
931
|
+
<div>
|
|
932
|
+
{data.label && (
|
|
933
|
+
<div className="jp-section-label inline-flex items-center gap-2 text-[0.68rem] font-mono font-bold uppercase tracking-[0.14em] text-[var(--local-accent)] mb-5" data-jp-field="label">
|
|
934
|
+
<span className="w-6 h-px bg-[var(--local-primary)]" />
|
|
935
|
+
{data.label}
|
|
936
|
+
</div>
|
|
937
|
+
)}
|
|
938
|
+
<h2
|
|
939
|
+
className="font-display text-[clamp(2rem,4.5vw,3.8rem)] font-black text-[var(--local-text)] leading-[1.05] tracking-tight mb-5"
|
|
940
|
+
data-jp-field="title"
|
|
941
|
+
>
|
|
942
|
+
{data.title}
|
|
943
|
+
</h2>
|
|
944
|
+
{data.description && (
|
|
945
|
+
<p className="text-[1.05rem] text-[var(--local-text-muted)] leading-[1.75] mb-8" data-jp-field="description">
|
|
946
|
+
{data.description}
|
|
947
|
+
</p>
|
|
948
|
+
)}
|
|
949
|
+
{data.features && data.features.length > 0 && (
|
|
950
|
+
<ul className="flex flex-col">
|
|
951
|
+
{data.features.map((f, idx) => (
|
|
952
|
+
<li
|
|
953
|
+
key={f.id ?? idx}
|
|
954
|
+
className="flex items-center gap-3.5 text-[0.9rem] text-[var(--local-text-muted)] py-3.5 border-b border-[rgba(255,255,255,0.06)] last:border-b-0 hover:text-[var(--local-text)] hover:pl-1.5 transition-all"
|
|
955
|
+
data-jp-item-id={f.id ?? `legacy-${idx}`}
|
|
956
|
+
data-jp-item-field="features"
|
|
957
|
+
>
|
|
958
|
+
<svg className="w-4 h-4 text-[#22c55e] flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
959
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
960
|
+
</svg>
|
|
961
|
+
{f.text}
|
|
962
|
+
</li>
|
|
963
|
+
))}
|
|
964
|
+
</ul>
|
|
965
|
+
)}
|
|
966
|
+
</div>
|
|
967
|
+
|
|
968
|
+
{/* RIGHT — code window */}
|
|
969
|
+
<div className="rounded-[12px] overflow-hidden border border-[rgba(255,255,255,0.10)] shadow-[0_30px_60px_rgba(0,0,0,0.5),0_0_40px_rgba(59,130,246,0.06)]">
|
|
970
|
+
<div className="bg-[#0d1828] px-4 py-2.5 flex items-center gap-1.5 border-b border-[rgba(255,255,255,0.05)]">
|
|
971
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#ef4444]" />
|
|
972
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#f59e0b]" />
|
|
973
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#22c55e]" />
|
|
974
|
+
<span className="ml-auto font-mono text-[0.62rem] text-[rgba(255,255,255,0.25)]">src/App.tsx</span>
|
|
975
|
+
</div>
|
|
976
|
+
<div className="bg-[#030609] px-8 py-7 font-mono text-[0.80rem] leading-[2] overflow-x-auto">
|
|
977
|
+
{APP_TSX_LINES.map((ln, i) => (
|
|
978
|
+
<div key={i}>
|
|
979
|
+
<span className={tokenClass[ln.t]}>{ln.c || '\u00A0'}</span>
|
|
980
|
+
</div>
|
|
981
|
+
))}
|
|
982
|
+
</div>
|
|
983
|
+
</div>
|
|
984
|
+
|
|
985
|
+
</div>
|
|
986
|
+
</section>
|
|
987
|
+
);
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
END_OF_FILE_CONTENT
|
|
991
|
+
echo "Creating src/components/devex/index.ts..."
|
|
992
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/devex/index.ts"
|
|
993
|
+
export * from './View';
|
|
994
|
+
export * from './schema';
|
|
995
|
+
export * from './types';
|
|
996
|
+
|
|
997
|
+
END_OF_FILE_CONTENT
|
|
998
|
+
echo "Creating src/components/devex/schema.ts..."
|
|
999
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/devex/schema.ts"
|
|
1000
|
+
import { z } from 'zod';
|
|
1001
|
+
import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
|
|
1002
|
+
|
|
1003
|
+
const FeatureSchema = BaseArrayItem.extend({
|
|
1004
|
+
text: z.string().describe('ui:text'),
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
export const DevexSchema = BaseSectionData.extend({
|
|
1008
|
+
label: z.string().optional().describe('ui:text'),
|
|
1009
|
+
title: z.string().describe('ui:text'),
|
|
1010
|
+
description: z.string().optional().describe('ui:textarea'),
|
|
1011
|
+
features: z.array(FeatureSchema).optional().describe('ui:list'),
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
END_OF_FILE_CONTENT
|
|
1015
|
+
echo "Creating src/components/devex/types.ts..."
|
|
1016
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/devex/types.ts"
|
|
1017
|
+
import { z } from 'zod';
|
|
1018
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
1019
|
+
import { DevexSchema } from './schema';
|
|
1020
|
+
|
|
1021
|
+
export type DevexData = z.infer<typeof DevexSchema>;
|
|
1022
|
+
export type DevexSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
1023
|
+
|
|
1024
|
+
END_OF_FILE_CONTENT
|
|
1025
|
+
mkdir -p "src/components/docs-layout"
|
|
1026
|
+
echo "Creating src/components/docs-layout/View.tsx..."
|
|
1027
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/docs-layout/View.tsx"
|
|
1028
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
1029
|
+
import type { DocsLayoutData, DocsLayoutSettings } from './types';
|
|
1030
|
+
|
|
1031
|
+
type Block = DocsLayoutData['groups'][0]['sections'][0]['blocks'][0];
|
|
1032
|
+
|
|
1033
|
+
/* ── inline renderer: **bold** and `code` ─────────────────── */
|
|
1034
|
+
function renderInline(text: string): React.ReactNode {
|
|
1035
|
+
return text.split(/(\*\*[^*]+\*\*|`[^`]+`)/g).map((part, i) => {
|
|
1036
|
+
if (part.startsWith('**') && part.endsWith('**')) return <strong key={i}>{part.slice(2, -2)}</strong>;
|
|
1037
|
+
if (part.startsWith('`') && part.endsWith('`')) return <code key={i}>{part.slice(1, -1)}</code>;
|
|
1038
|
+
return part;
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/* ── block renderer ───────────────────────────────────────── */
|
|
1043
|
+
function DocBlock({ block, idx }: { block: Block; idx: number }) {
|
|
1044
|
+
const inlineCls = '[&_strong]:text-[var(--local-text)] [&_strong]:font-semibold [&_code]:font-mono [&_code]:text-[0.84em] [&_code]:bg-[rgba(255,255,255,0.07)] [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded-[3px] [&_code]:text-[var(--local-accent)]';
|
|
1045
|
+
|
|
1046
|
+
switch (block.type) {
|
|
1047
|
+
case 'heading':
|
|
1048
|
+
return <h4 key={idx} className="font-display font-bold text-[1.05rem] text-[var(--local-text)] mt-8 mb-3 tracking-tight">{block.content}</h4>;
|
|
1049
|
+
|
|
1050
|
+
case 'paragraph':
|
|
1051
|
+
return <p key={idx} className={`text-[0.93rem] text-[var(--local-text-muted)] leading-[1.9] mb-5 ${inlineCls}`}>{renderInline(block.content)}</p>;
|
|
1052
|
+
|
|
1053
|
+
case 'code':
|
|
1054
|
+
return (
|
|
1055
|
+
<div key={idx} className="mb-6 rounded-[10px] overflow-hidden border border-[rgba(255,255,255,0.08)] shadow-[0_8px_32px_rgba(0,0,0,0.4)]">
|
|
1056
|
+
{block.codeFilename && (
|
|
1057
|
+
<div className="bg-[#0d1828] px-4 py-2.5 flex items-center gap-2 border-b border-[rgba(255,255,255,0.05)]">
|
|
1058
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#ef4444]" />
|
|
1059
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#f59e0b]" />
|
|
1060
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#22c55e]" />
|
|
1061
|
+
<span className="ml-3 font-mono text-[0.65rem] text-[rgba(255,255,255,0.28)]">{block.codeFilename}</span>
|
|
1062
|
+
</div>
|
|
1063
|
+
)}
|
|
1064
|
+
<pre className="bg-[#020508] px-6 py-5 font-mono text-[0.78rem] leading-[1.9] overflow-x-auto text-[#cbd5e1] m-0 whitespace-pre">
|
|
1065
|
+
<code>{block.content}</code>
|
|
1066
|
+
</pre>
|
|
1067
|
+
</div>
|
|
1068
|
+
);
|
|
1069
|
+
|
|
1070
|
+
case 'list':
|
|
1071
|
+
return (
|
|
1072
|
+
<ul key={idx} className="mb-5 flex flex-col gap-2.5">
|
|
1073
|
+
{(block.items ?? []).map((item, i) => (
|
|
1074
|
+
<li key={item.id ?? i} className={`flex items-start gap-3 text-[0.93rem] text-[var(--local-text-muted)] leading-[1.8] ${inlineCls}`}>
|
|
1075
|
+
<span className="font-mono text-[var(--local-primary)] text-[0.72rem] flex-shrink-0 mt-[5px]">→</span>
|
|
1076
|
+
<span>{renderInline(item.text)}</span>
|
|
1077
|
+
</li>
|
|
1078
|
+
))}
|
|
1079
|
+
</ul>
|
|
1080
|
+
);
|
|
1081
|
+
|
|
1082
|
+
case 'table':
|
|
1083
|
+
return (
|
|
1084
|
+
<div key={idx} className="mb-6 overflow-hidden rounded-[10px] border border-[rgba(255,255,255,0.06)]">
|
|
1085
|
+
<table className="w-full text-[0.88rem]">
|
|
1086
|
+
<thead>
|
|
1087
|
+
<tr className="bg-[rgba(59,130,246,0.06)] border-b border-[rgba(255,255,255,0.06)]">
|
|
1088
|
+
<th className="px-5 py-3 text-left font-mono text-[0.66rem] uppercase tracking-widest text-[var(--local-accent)] w-[200px]">Cosa</th>
|
|
1089
|
+
<th className="px-5 py-3 text-left font-mono text-[0.66rem] uppercase tracking-widest text-[var(--local-accent)]">Azione</th>
|
|
1090
|
+
</tr>
|
|
1091
|
+
</thead>
|
|
1092
|
+
<tbody>
|
|
1093
|
+
{(block.rows ?? []).map((row, i) => (
|
|
1094
|
+
<tr key={row.id ?? i} className="border-b border-[rgba(255,255,255,0.04)] last:border-0 hover:bg-[rgba(59,130,246,0.025)] transition-colors">
|
|
1095
|
+
<td className={`px-5 py-4 text-[var(--local-text)] font-semibold align-top text-[0.88rem] ${inlineCls}`}>
|
|
1096
|
+
{renderInline(row.col1)}
|
|
1097
|
+
</td>
|
|
1098
|
+
<td className={`px-5 py-4 text-[var(--local-text-muted)] align-top leading-[1.75] text-[0.88rem] ${inlineCls}`}>
|
|
1099
|
+
{renderInline(row.col2)}
|
|
1100
|
+
</td>
|
|
1101
|
+
</tr>
|
|
1102
|
+
))}
|
|
1103
|
+
</tbody>
|
|
1104
|
+
</table>
|
|
1105
|
+
</div>
|
|
1106
|
+
);
|
|
1107
|
+
|
|
1108
|
+
case 'callout':
|
|
1109
|
+
return (
|
|
1110
|
+
<div key={idx} className={`mb-5 rounded-[10px] border border-[rgba(59,130,246,0.22)] bg-[rgba(59,130,246,0.05)] px-5 py-4 ${inlineCls}`}>
|
|
1111
|
+
<div className="flex items-start gap-3">
|
|
1112
|
+
<span className="text-[var(--local-accent)] font-mono text-[0.9rem] flex-shrink-0 mt-0.5 leading-none">ℹ</span>
|
|
1113
|
+
<p className="text-[0.90rem] text-[var(--local-text-muted)] leading-[1.8] m-0">{renderInline(block.content)}</p>
|
|
1114
|
+
</div>
|
|
1115
|
+
</div>
|
|
1116
|
+
);
|
|
1117
|
+
|
|
1118
|
+
case 'note':
|
|
1119
|
+
return (
|
|
1120
|
+
<div key={idx} className={`mb-5 rounded-[10px] border border-[rgba(239,68,68,0.20)] bg-[rgba(239,68,68,0.04)] px-5 py-4 ${inlineCls}`}>
|
|
1121
|
+
<div className="flex items-start gap-3">
|
|
1122
|
+
<span className="text-[#f87171] font-mono text-[0.9rem] flex-shrink-0 mt-0.5 leading-none">⚠</span>
|
|
1123
|
+
<p className="text-[0.90rem] text-[var(--local-text-muted)] leading-[1.8] m-0">{renderInline(block.content)}</p>
|
|
1124
|
+
</div>
|
|
1125
|
+
</div>
|
|
1126
|
+
);
|
|
1127
|
+
|
|
1128
|
+
default: return null;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
/* ── main component ───────────────────────────────────────── */
|
|
1133
|
+
export const DocsLayout: React.FC<{ data: DocsLayoutData; settings?: DocsLayoutSettings }> = ({ data }) => {
|
|
1134
|
+
const [activeAnchor, setActiveAnchor] = useState<string>('');
|
|
1135
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
1136
|
+
|
|
1137
|
+
// flat list of all anchors in order
|
|
1138
|
+
const allAnchors = data.groups.flatMap((g) => [
|
|
1139
|
+
{ anchor: g.anchor, level: 'group' as const, label: g.label },
|
|
1140
|
+
...(g.sections ?? []).map((s) => ({ anchor: s.anchor, level: 'section' as const, label: s.title, parent: g.anchor })),
|
|
1141
|
+
]);
|
|
1142
|
+
|
|
1143
|
+
useEffect(() => {
|
|
1144
|
+
const targets = allAnchors
|
|
1145
|
+
.map((a) => document.getElementById(a.anchor))
|
|
1146
|
+
.filter(Boolean) as HTMLElement[];
|
|
1147
|
+
if (!targets.length) return;
|
|
1148
|
+
|
|
1149
|
+
const observer = new IntersectionObserver(
|
|
1150
|
+
(entries) => {
|
|
1151
|
+
const visible = entries.filter((e) => e.isIntersecting);
|
|
1152
|
+
if (!visible.length) return;
|
|
1153
|
+
const top = visible.reduce((a, b) =>
|
|
1154
|
+
a.boundingClientRect.top < b.boundingClientRect.top ? a : b
|
|
1155
|
+
);
|
|
1156
|
+
setActiveAnchor((top.target as HTMLElement).id);
|
|
1157
|
+
},
|
|
1158
|
+
{ rootMargin: '-64px 0px -55% 0px', threshold: 0 }
|
|
1159
|
+
);
|
|
1160
|
+
targets.forEach((t) => observer.observe(t));
|
|
1161
|
+
return () => observer.disconnect();
|
|
1162
|
+
}, [data.groups]);
|
|
1163
|
+
|
|
1164
|
+
const scrollTo = (anchor: string) => (e: React.MouseEvent) => {
|
|
1165
|
+
e.preventDefault();
|
|
1166
|
+
document.getElementById(anchor)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
return (
|
|
1170
|
+
<section
|
|
1171
|
+
style={{
|
|
1172
|
+
'--local-bg': 'var(--background)',
|
|
1173
|
+
'--local-text': 'var(--foreground)',
|
|
1174
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
1175
|
+
'--local-primary': 'var(--primary)',
|
|
1176
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
1177
|
+
'--local-cyan': 'var(--color-secondary, #22d3ee)',
|
|
1178
|
+
'--local-border': 'var(--border)',
|
|
1179
|
+
'--local-surface': 'var(--card)',
|
|
1180
|
+
} as React.CSSProperties}
|
|
1181
|
+
className="relative z-0 min-h-screen bg-[var(--local-bg)]"
|
|
1182
|
+
>
|
|
1183
|
+
<div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.15)] to-transparent" />
|
|
1184
|
+
|
|
1185
|
+
{/* ── Page hero ──────────────────────────────────── */}
|
|
1186
|
+
<div className="max-w-[1200px] mx-auto px-8 pt-28 pb-12">
|
|
1187
|
+
<div
|
|
1188
|
+
className="inline-flex items-center gap-2 bg-[rgba(59,130,246,0.08)] border border-[rgba(59,130,246,0.18)] px-3.5 py-1 rounded-full font-mono text-[0.66rem] font-semibold text-[var(--local-accent)] mb-5 tracking-widest uppercase"
|
|
1189
|
+
data-jp-field="version"
|
|
1190
|
+
>
|
|
1191
|
+
<span className="w-1.5 h-1.5 rounded-full bg-[#22c55e]" />
|
|
1192
|
+
{data.version ?? 'v1.2'}
|
|
1193
|
+
</div>
|
|
1194
|
+
<h1
|
|
1195
|
+
className="font-display font-black text-[clamp(2.2rem,4vw,3.5rem)] text-[var(--local-text)] leading-[1.05] tracking-tight mb-4"
|
|
1196
|
+
data-jp-field="pageTitle"
|
|
1197
|
+
>
|
|
1198
|
+
{data.pageTitle}
|
|
1199
|
+
</h1>
|
|
1200
|
+
{data.pageSubtitle && (
|
|
1201
|
+
<p
|
|
1202
|
+
className="text-[1.02rem] text-[var(--local-text-muted)] max-w-[680px] leading-[1.85]"
|
|
1203
|
+
data-jp-field="pageSubtitle"
|
|
1204
|
+
>
|
|
1205
|
+
{data.pageSubtitle}
|
|
1206
|
+
</p>
|
|
1207
|
+
)}
|
|
1208
|
+
<div className="mt-8 h-px bg-gradient-to-r from-[rgba(59,130,246,0.3)] via-[rgba(59,130,246,0.06)] to-transparent" />
|
|
1209
|
+
</div>
|
|
1210
|
+
|
|
1211
|
+
{/* ── Sidebar + Content ──────────────────────────── */}
|
|
1212
|
+
<div className="max-w-[1200px] mx-auto px-8 pb-40 flex gap-14 items-start">
|
|
1213
|
+
|
|
1214
|
+
{/* SIDEBAR */}
|
|
1215
|
+
<aside className="w-[216px] flex-shrink-0 sticky top-[80px] self-start">
|
|
1216
|
+
<nav className="flex flex-col">
|
|
1217
|
+
{data.groups.map((group) => {
|
|
1218
|
+
const groupActive = activeAnchor === group.anchor ||
|
|
1219
|
+
(group.sections ?? []).some((s) => s.anchor === activeAnchor);
|
|
1220
|
+
return (
|
|
1221
|
+
<div key={group.anchor} className="mb-0.5">
|
|
1222
|
+
{/* Group */}
|
|
1223
|
+
<a
|
|
1224
|
+
href={`#${group.anchor}`}
|
|
1225
|
+
onClick={scrollTo(group.anchor)}
|
|
1226
|
+
className={`
|
|
1227
|
+
flex items-center gap-2 px-3 py-2 rounded-[6px] font-display font-bold text-[0.80rem]
|
|
1228
|
+
transition-all duration-150 no-underline
|
|
1229
|
+
${groupActive
|
|
1230
|
+
? 'text-[var(--local-text)] bg-[rgba(255,255,255,0.04)]'
|
|
1231
|
+
: 'text-[var(--local-text-muted)] hover:text-[var(--local-text)] hover:bg-[rgba(255,255,255,0.03)]'}
|
|
1232
|
+
${activeAnchor === group.anchor ? 'border-l-2 border-[var(--local-primary)] pl-[10px] text-[var(--local-accent)]' : ''}
|
|
1233
|
+
`}
|
|
1234
|
+
>
|
|
1235
|
+
{group.label}
|
|
1236
|
+
</a>
|
|
1237
|
+
{/* Sub-sections */}
|
|
1238
|
+
<div className={`overflow-hidden transition-all duration-200 ${groupActive ? 'max-h-96' : 'max-h-0'}`}>
|
|
1239
|
+
{(group.sections ?? []).map((s) => (
|
|
1240
|
+
<a
|
|
1241
|
+
key={s.anchor}
|
|
1242
|
+
href={`#${s.anchor}`}
|
|
1243
|
+
onClick={scrollTo(s.anchor)}
|
|
1244
|
+
className={`
|
|
1245
|
+
flex items-center gap-2.5 pl-[22px] pr-3 py-1.5 rounded-[5px]
|
|
1246
|
+
font-sans text-[0.76rem] transition-all duration-120 no-underline ml-0.5
|
|
1247
|
+
${activeAnchor === s.anchor
|
|
1248
|
+
? 'text-[var(--local-accent)] font-semibold bg-[rgba(59,130,246,0.07)]'
|
|
1249
|
+
: 'text-[var(--local-text-muted)] hover:text-[var(--local-text)] hover:bg-[rgba(255,255,255,0.025)]'}
|
|
1250
|
+
`}
|
|
1251
|
+
>
|
|
1252
|
+
<span className={`w-[5px] h-[5px] rounded-full flex-shrink-0 transition-colors ${activeAnchor === s.anchor ? 'bg-[var(--local-accent)]' : 'bg-[rgba(255,255,255,0.12)]'}`} />
|
|
1253
|
+
{s.title}
|
|
1254
|
+
</a>
|
|
1255
|
+
))}
|
|
1256
|
+
</div>
|
|
1257
|
+
</div>
|
|
1258
|
+
);
|
|
1259
|
+
})}
|
|
1260
|
+
</nav>
|
|
1261
|
+
|
|
1262
|
+
{/* Divider + back to top */}
|
|
1263
|
+
<div className="mt-6 pt-5 border-t border-[rgba(255,255,255,0.05)]">
|
|
1264
|
+
<button
|
|
1265
|
+
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
|
|
1266
|
+
className="flex items-center gap-2 font-mono text-[0.62rem] uppercase tracking-widest text-[var(--local-text-muted)] hover:text-[var(--local-accent)] transition-colors px-3"
|
|
1267
|
+
>
|
|
1268
|
+
↑ Back to top
|
|
1269
|
+
</button>
|
|
1270
|
+
</div>
|
|
1271
|
+
</aside>
|
|
1272
|
+
|
|
1273
|
+
{/* CONTENT */}
|
|
1274
|
+
<div ref={contentRef} className="flex-1 min-w-0 pt-2">
|
|
1275
|
+
{data.groups.map((group, gi) => (
|
|
1276
|
+
<div key={group.anchor} className={gi > 0 ? 'mt-20' : ''}>
|
|
1277
|
+
|
|
1278
|
+
{/* Group header */}
|
|
1279
|
+
<div id={group.anchor} className="scroll-mt-[88px] flex items-center gap-3 mb-2">
|
|
1280
|
+
<div className="w-7 h-7 rounded-[7px] bg-[rgba(59,130,246,0.12)] border border-[rgba(59,130,246,0.18)] flex items-center justify-center flex-shrink-0">
|
|
1281
|
+
<span className="font-mono text-[var(--local-accent)] text-[0.66rem] font-bold">{gi + 1}</span>
|
|
1282
|
+
</div>
|
|
1283
|
+
<h2
|
|
1284
|
+
className="font-display font-black text-[1.55rem] text-[var(--local-text)] tracking-tight"
|
|
1285
|
+
data-jp-item-id={group.id ?? `g-${gi}`}
|
|
1286
|
+
data-jp-item-field="groups"
|
|
1287
|
+
>
|
|
1288
|
+
{group.label}
|
|
1289
|
+
</h2>
|
|
1290
|
+
</div>
|
|
1291
|
+
<div className="h-px bg-[rgba(255,255,255,0.05)] mb-10 ml-10" />
|
|
1292
|
+
|
|
1293
|
+
{/* Sections */}
|
|
1294
|
+
{(group.sections ?? []).map((section, si) => (
|
|
1295
|
+
<div
|
|
1296
|
+
key={section.anchor}
|
|
1297
|
+
id={section.anchor}
|
|
1298
|
+
className="scroll-mt-[88px] mb-14"
|
|
1299
|
+
data-jp-item-id={section.id ?? `s-${gi}-${si}`}
|
|
1300
|
+
data-jp-item-field="sections"
|
|
1301
|
+
>
|
|
1302
|
+
{/* Section title */}
|
|
1303
|
+
<div className="flex items-center gap-2.5 mb-6">
|
|
1304
|
+
{section.tag && (
|
|
1305
|
+
<span className="font-mono text-[0.60rem] uppercase tracking-widest text-[var(--local-accent)] bg-[rgba(59,130,246,0.08)] border border-[rgba(59,130,246,0.15)] px-2 py-0.5 rounded-[4px] flex-shrink-0">
|
|
1306
|
+
{section.tag}
|
|
1307
|
+
</span>
|
|
1308
|
+
)}
|
|
1309
|
+
<h3
|
|
1310
|
+
className="font-display font-bold text-[1.2rem] text-[var(--local-text)] leading-tight tracking-tight"
|
|
1311
|
+
data-jp-field="title"
|
|
1312
|
+
>
|
|
1313
|
+
{section.title}
|
|
1314
|
+
</h3>
|
|
1315
|
+
</div>
|
|
1316
|
+
|
|
1317
|
+
{/* Blocks */}
|
|
1318
|
+
{(section.blocks ?? []).map((block, bi) => (
|
|
1319
|
+
<DocBlock key={block.id ?? bi} block={block} idx={bi} />
|
|
1320
|
+
))}
|
|
1321
|
+
|
|
1322
|
+
{/* Section divider */}
|
|
1323
|
+
{si < (group.sections ?? []).length - 1 && (
|
|
1324
|
+
<div className="mt-12 h-px bg-[rgba(255,255,255,0.035)]" />
|
|
1325
|
+
)}
|
|
1326
|
+
</div>
|
|
1327
|
+
))}
|
|
1328
|
+
</div>
|
|
1329
|
+
))}
|
|
1330
|
+
|
|
1331
|
+
{/* EOF marker */}
|
|
1332
|
+
<div className="mt-16 flex items-center gap-4 opacity-30">
|
|
1333
|
+
<div className="flex-1 h-px bg-[rgba(255,255,255,0.08)]" />
|
|
1334
|
+
<span className="font-mono text-[0.60rem] uppercase tracking-widest text-[var(--local-text-muted)]">End of document</span>
|
|
1335
|
+
<div className="flex-1 h-px bg-[rgba(255,255,255,0.08)]" />
|
|
1336
|
+
</div>
|
|
1337
|
+
</div>
|
|
1338
|
+
|
|
1339
|
+
</div>
|
|
1340
|
+
</section>
|
|
1341
|
+
);
|
|
1342
|
+
};
|
|
1343
|
+
|
|
1344
|
+
END_OF_FILE_CONTENT
|
|
1345
|
+
echo "Creating src/components/docs-layout/index.ts..."
|
|
1346
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/docs-layout/index.ts"
|
|
1347
|
+
export * from './View';
|
|
1348
|
+
export * from './schema';
|
|
1349
|
+
export * from './types';
|
|
1350
|
+
|
|
1351
|
+
END_OF_FILE_CONTENT
|
|
1352
|
+
echo "Creating src/components/docs-layout/schema.ts..."
|
|
1353
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/docs-layout/schema.ts"
|
|
1354
|
+
import { z } from 'zod';
|
|
1355
|
+
import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
|
|
1356
|
+
|
|
1357
|
+
const DocBlockSchema = BaseArrayItem.extend({
|
|
1358
|
+
type: z.enum(['paragraph', 'code', 'list', 'table', 'callout', 'note', 'heading']).default('paragraph'),
|
|
1359
|
+
content: z.string().describe('ui:textarea'),
|
|
1360
|
+
codeFilename: z.string().optional().describe('ui:text'),
|
|
1361
|
+
items: z.array(z.object({ id: z.string().optional(), text: z.string() })).optional().describe('ui:list'),
|
|
1362
|
+
rows: z.array(z.object({ id: z.string().optional(), col1: z.string(), col2: z.string() })).optional().describe('ui:list'),
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
const DocSectionSchema = BaseArrayItem.extend({
|
|
1366
|
+
anchor: z.string().describe('ui:text'),
|
|
1367
|
+
title: z.string().describe('ui:text'),
|
|
1368
|
+
tag: z.string().optional().describe('ui:text'),
|
|
1369
|
+
blocks: z.array(DocBlockSchema).describe('ui:list'),
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
const DocGroupSchema = BaseArrayItem.extend({
|
|
1373
|
+
anchor: z.string().describe('ui:text'),
|
|
1374
|
+
label: z.string().describe('ui:text'),
|
|
1375
|
+
sections: z.array(DocSectionSchema).describe('ui:list'),
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
export const DocsLayoutSchema = BaseSectionData.extend({
|
|
1379
|
+
pageTitle: z.string().describe('ui:text'),
|
|
1380
|
+
pageSubtitle: z.string().optional().describe('ui:textarea'),
|
|
1381
|
+
version: z.string().optional().describe('ui:text'),
|
|
1382
|
+
groups: z.array(DocGroupSchema).describe('ui:list'),
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
END_OF_FILE_CONTENT
|
|
1386
|
+
echo "Creating src/components/docs-layout/types.ts..."
|
|
1387
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/docs-layout/types.ts"
|
|
1388
|
+
import { z } from 'zod';
|
|
1389
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
1390
|
+
import { DocsLayoutSchema } from './schema';
|
|
1391
|
+
|
|
1392
|
+
export type DocsLayoutData = z.infer<typeof DocsLayoutSchema>;
|
|
1393
|
+
export type DocsLayoutSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
533
1394
|
|
|
534
1395
|
END_OF_FILE_CONTENT
|
|
535
1396
|
mkdir -p "src/components/feature-grid"
|
|
@@ -725,6 +1586,163 @@ import { FooterSchema, FooterSettingsSchema } from './schema';
|
|
|
725
1586
|
export type FooterData = z.infer<typeof FooterSchema>;
|
|
726
1587
|
export type FooterSettings = z.infer<typeof FooterSettingsSchema>;
|
|
727
1588
|
|
|
1589
|
+
END_OF_FILE_CONTENT
|
|
1590
|
+
mkdir -p "src/components/git-section"
|
|
1591
|
+
echo "Creating src/components/git-section/View.tsx..."
|
|
1592
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/git-section/View.tsx"
|
|
1593
|
+
import React from 'react';
|
|
1594
|
+
import type { GitSectionData, GitSectionSettings } from './types';
|
|
1595
|
+
|
|
1596
|
+
export const GitSection: React.FC<{ data: GitSectionData; settings?: GitSectionSettings }> = ({ data }) => {
|
|
1597
|
+
return (
|
|
1598
|
+
<section
|
|
1599
|
+
style={{
|
|
1600
|
+
'--local-bg': 'var(--card)',
|
|
1601
|
+
'--local-text': 'var(--foreground)',
|
|
1602
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
1603
|
+
'--local-primary': 'var(--primary)',
|
|
1604
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
1605
|
+
'--local-cyan': 'var(--color-secondary, #22d3ee)',
|
|
1606
|
+
'--local-border': 'var(--border)',
|
|
1607
|
+
} as React.CSSProperties}
|
|
1608
|
+
className="relative z-0 py-28 bg-[var(--local-bg)]"
|
|
1609
|
+
>
|
|
1610
|
+
<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" />
|
|
1611
|
+
<div className="max-w-[1200px] mx-auto px-8 grid grid-cols-2 gap-24 items-center">
|
|
1612
|
+
|
|
1613
|
+
{/* LEFT — copy */}
|
|
1614
|
+
<div>
|
|
1615
|
+
{data.label && (
|
|
1616
|
+
<div className="jp-section-label inline-flex items-center gap-2 text-[0.68rem] font-mono font-bold uppercase tracking-[0.14em] text-[var(--local-accent)] mb-5" data-jp-field="label">
|
|
1617
|
+
<span className="w-6 h-px bg-[var(--local-primary)]" />
|
|
1618
|
+
{data.label}
|
|
1619
|
+
</div>
|
|
1620
|
+
)}
|
|
1621
|
+
<h2 className="font-display text-[clamp(2rem,4.5vw,3.8rem)] font-black text-[var(--local-text)] leading-[1.05] tracking-tight mb-4" data-jp-field="title">
|
|
1622
|
+
{data.title}
|
|
1623
|
+
{data.titleHighlight && (
|
|
1624
|
+
<>
|
|
1625
|
+
<br />
|
|
1626
|
+
<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">
|
|
1627
|
+
{data.titleHighlight}
|
|
1628
|
+
</em>
|
|
1629
|
+
</>
|
|
1630
|
+
)}
|
|
1631
|
+
</h2>
|
|
1632
|
+
{data.description && (
|
|
1633
|
+
<p className="text-[1.05rem] text-[var(--local-text-muted)] leading-[1.8] mb-8" data-jp-field="description">
|
|
1634
|
+
{data.description}
|
|
1635
|
+
</p>
|
|
1636
|
+
)}
|
|
1637
|
+
{data.points && data.points.length > 0 && (
|
|
1638
|
+
<ul className="flex flex-col">
|
|
1639
|
+
{data.points.map((p, idx) => (
|
|
1640
|
+
<li
|
|
1641
|
+
key={p.id ?? idx}
|
|
1642
|
+
className="flex items-start gap-3.5 text-[0.9rem] text-[var(--local-text-muted)] py-3.5 border-b border-[rgba(255,255,255,0.06)] last:border-b-0 hover:text-[var(--local-text)] transition-colors leading-[1.5]"
|
|
1643
|
+
data-jp-item-id={p.id ?? `legacy-${idx}`}
|
|
1644
|
+
data-jp-item-field="points"
|
|
1645
|
+
>
|
|
1646
|
+
<span className="font-mono text-[var(--local-primary)] text-[0.75rem] flex-shrink-0 mt-0.5">→</span>
|
|
1647
|
+
{p.text}
|
|
1648
|
+
</li>
|
|
1649
|
+
))}
|
|
1650
|
+
</ul>
|
|
1651
|
+
)}
|
|
1652
|
+
</div>
|
|
1653
|
+
|
|
1654
|
+
{/* RIGHT — git diff panel (decorative, content-driven commits) */}
|
|
1655
|
+
<div className="rounded-[12px] overflow-hidden border border-[rgba(255,255,255,0.10)] shadow-[0_30px_60px_rgba(0,0,0,0.5)]">
|
|
1656
|
+
<div className="bg-[#0d1828] px-4 py-2.5 flex items-center gap-1.5 border-b border-[rgba(255,255,255,0.05)]">
|
|
1657
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#ef4444]" />
|
|
1658
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#f59e0b]" />
|
|
1659
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#22c55e]" />
|
|
1660
|
+
<span className="ml-auto font-mono text-[0.62rem] text-[rgba(255,255,255,0.25)]">src/data/pages/home.json</span>
|
|
1661
|
+
</div>
|
|
1662
|
+
{/* Tabs */}
|
|
1663
|
+
<div className="bg-[#0a1624] border-b border-[rgba(255,255,255,0.05)] flex">
|
|
1664
|
+
<div className="font-mono text-[0.60rem] px-4 py-2 text-white border-b-2 border-[#3b82f6]">Changes</div>
|
|
1665
|
+
<div className="font-mono text-[0.60rem] px-4 py-2 text-[#475569]">History</div>
|
|
1666
|
+
<div className="font-mono text-[0.60rem] px-4 py-2 text-[#475569]">Blame</div>
|
|
1667
|
+
</div>
|
|
1668
|
+
{/* Diff */}
|
|
1669
|
+
<div className="bg-[#030609] px-4 py-4 font-mono text-[0.73rem] leading-[1.9] overflow-x-auto">
|
|
1670
|
+
{[
|
|
1671
|
+
{ t: 'ctx', g: '12', s: ' ', c: ' "type": "hero",' },
|
|
1672
|
+
{ t: 'ctx', g: '13', s: ' ', c: ' "data": {' },
|
|
1673
|
+
{ t: 'del', g: '14', s: '-', c: ' "title": "Local Authoring.hh",' },
|
|
1674
|
+
{ t: 'add', g: '14', s: '+', c: ' "title": "The Sovereign Shell.",' },
|
|
1675
|
+
{ t: 'del', g: '15', s: '-', c: ' "titleHighlight": "Global Governance.",' },
|
|
1676
|
+
{ t: 'add', g: '15', s: '+', c: ' "titleHighlight": "Zero Runtime Overhead.",' },
|
|
1677
|
+
{ t: 'ctx', g: '16', s: ' ', c: ' "badge": "Architecture v1.2",' },
|
|
1678
|
+
{ t: 'ctx', g: '17', s: ' ', c: ' }' },
|
|
1679
|
+
].map((ln, i) => (
|
|
1680
|
+
<div key={i} className={`flex gap-3 px-1 rounded-[2px] ${
|
|
1681
|
+
ln.t === 'add' ? 'bg-[rgba(34,197,94,0.07)]' :
|
|
1682
|
+
ln.t === 'del' ? 'bg-[rgba(239,68,68,0.07)]' :
|
|
1683
|
+
'opacity-45'}`}
|
|
1684
|
+
>
|
|
1685
|
+
<span className="text-[#334155] min-w-[18px] text-right select-none">{ln.g}</span>
|
|
1686
|
+
<span className={`min-w-[12px] ${ln.t === 'add' ? 'text-[#22c55e]' : ln.t === 'del' ? 'text-[#ef4444]' : 'text-[#334155]'}`}>{ln.s}</span>
|
|
1687
|
+
<span className={`whitespace-pre ${ln.t === 'add' ? 'text-[#86efac]' : ln.t === 'del' ? 'text-[#fca5a5]' : 'text-[#cbd5e1]'}`}>{ln.c}</span>
|
|
1688
|
+
</div>
|
|
1689
|
+
))}
|
|
1690
|
+
</div>
|
|
1691
|
+
{/* Commits */}
|
|
1692
|
+
<div className="bg-[#050d1c] border-t border-[rgba(255,255,255,0.05)] px-4 py-3 flex flex-col gap-2.5">
|
|
1693
|
+
{[
|
|
1694
|
+
{ hash: 'a3f9c12', msg: 'feat(home): update hero headline copy', time: '2m ago', op: 1 },
|
|
1695
|
+
{ hash: '8b21e04', msg: 'content(home): add 3 metrics to hero', time: '1h ago', op: 0.6 },
|
|
1696
|
+
{ hash: 'cc70a91', msg: 'feat(home): initial page structure', time: '2d ago', op: 0.4 },
|
|
1697
|
+
].map(({ hash, msg, time, op }) => (
|
|
1698
|
+
<div key={hash} className="flex items-center gap-3" style={{ opacity: op }}>
|
|
1699
|
+
<span className="font-mono text-[0.58rem] text-[#3b82f6] min-w-[52px]">{hash}</span>
|
|
1700
|
+
<span className="font-sans text-[0.70rem] text-[#475569] flex-1 truncate">{msg}</span>
|
|
1701
|
+
<span className="font-mono text-[0.56rem] text-[#334155]">{time}</span>
|
|
1702
|
+
</div>
|
|
1703
|
+
))}
|
|
1704
|
+
</div>
|
|
1705
|
+
</div>
|
|
1706
|
+
</div>
|
|
1707
|
+
</section>
|
|
1708
|
+
);
|
|
1709
|
+
};
|
|
1710
|
+
|
|
1711
|
+
END_OF_FILE_CONTENT
|
|
1712
|
+
echo "Creating src/components/git-section/index.ts..."
|
|
1713
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/git-section/index.ts"
|
|
1714
|
+
export * from './View';
|
|
1715
|
+
export * from './schema';
|
|
1716
|
+
export * from './types';
|
|
1717
|
+
|
|
1718
|
+
END_OF_FILE_CONTENT
|
|
1719
|
+
echo "Creating src/components/git-section/schema.ts..."
|
|
1720
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/git-section/schema.ts"
|
|
1721
|
+
import { z } from 'zod';
|
|
1722
|
+
import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
|
|
1723
|
+
|
|
1724
|
+
const PointSchema = BaseArrayItem.extend({
|
|
1725
|
+
text: z.string().describe('ui:text'),
|
|
1726
|
+
});
|
|
1727
|
+
|
|
1728
|
+
export const GitSectionSchema = BaseSectionData.extend({
|
|
1729
|
+
label: z.string().optional().describe('ui:text'),
|
|
1730
|
+
title: z.string().describe('ui:text'),
|
|
1731
|
+
titleHighlight: z.string().optional().describe('ui:text'),
|
|
1732
|
+
description: z.string().optional().describe('ui:textarea'),
|
|
1733
|
+
points: z.array(PointSchema).optional().describe('ui:list'),
|
|
1734
|
+
});
|
|
1735
|
+
|
|
1736
|
+
END_OF_FILE_CONTENT
|
|
1737
|
+
echo "Creating src/components/git-section/types.ts..."
|
|
1738
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/git-section/types.ts"
|
|
1739
|
+
import { z } from 'zod';
|
|
1740
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
1741
|
+
import { GitSectionSchema } from './schema';
|
|
1742
|
+
|
|
1743
|
+
export type GitSectionData = z.infer<typeof GitSectionSchema>;
|
|
1744
|
+
export type GitSectionSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
1745
|
+
|
|
728
1746
|
END_OF_FILE_CONTENT
|
|
729
1747
|
mkdir -p "src/components/header"
|
|
730
1748
|
echo "Creating src/components/header/View.tsx..."
|
|
@@ -898,97 +1916,220 @@ END_OF_FILE_CONTENT
|
|
|
898
1916
|
mkdir -p "src/components/hero"
|
|
899
1917
|
echo "Creating src/components/hero/View.tsx..."
|
|
900
1918
|
cat << 'END_OF_FILE_CONTENT' > "src/components/hero/View.tsx"
|
|
901
|
-
import React from 'react';
|
|
902
|
-
import { cn } from '@/lib/utils';
|
|
903
|
-
import type { HeroData, HeroSettings } from './types';
|
|
904
|
-
|
|
905
|
-
export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ data }) => {
|
|
906
|
-
return (
|
|
907
|
-
<section
|
|
908
|
-
style={{
|
|
909
|
-
'--local-bg':
|
|
910
|
-
'--local-text':
|
|
911
|
-
'--local-text-muted':
|
|
912
|
-
'--local-primary':
|
|
913
|
-
'--local-accent':
|
|
914
|
-
'--local-cyan':
|
|
915
|
-
'--local-border':
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
<div className="
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
{
|
|
982
|
-
</
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
}
|
|
1919
|
+
import React from 'react';
|
|
1920
|
+
import { cn } from '@/lib/utils';
|
|
1921
|
+
import type { HeroData, HeroSettings } from './types';
|
|
1922
|
+
|
|
1923
|
+
export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ data }) => {
|
|
1924
|
+
return (
|
|
1925
|
+
<section
|
|
1926
|
+
style={{
|
|
1927
|
+
'--local-bg': 'var(--background)',
|
|
1928
|
+
'--local-text': 'var(--foreground)',
|
|
1929
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
1930
|
+
'--local-primary': 'var(--primary)',
|
|
1931
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
1932
|
+
'--local-cyan': 'var(--color-secondary, #22d3ee)',
|
|
1933
|
+
'--local-border': 'var(--border)',
|
|
1934
|
+
'--local-surface': 'var(--card)',
|
|
1935
|
+
} as React.CSSProperties}
|
|
1936
|
+
className="jp-hero relative min-h-screen flex items-center overflow-hidden pt-24 pb-0 bg-[var(--local-bg)]"
|
|
1937
|
+
>
|
|
1938
|
+
{/* Background glows */}
|
|
1939
|
+
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[1100px] h-[650px] bg-[radial-gradient(ellipse_at_50%_0%,rgba(59,130,246,0.13),transparent_65%)] pointer-events-none" />
|
|
1940
|
+
<div className="absolute inset-0 bg-[image:linear-gradient(rgba(59,130,246,0.04)_1px,transparent_1px),linear-gradient(90deg,rgba(59,130,246,0.04)_1px,transparent_1px)] bg-[size:80px_80px] [mask-image:radial-gradient(ellipse_at_50%_0%,black_25%,transparent_75%)] pointer-events-none" />
|
|
1941
|
+
|
|
1942
|
+
<div className="relative z-0 max-w-[1200px] mx-auto px-8 w-full">
|
|
1943
|
+
<div className="grid grid-cols-2 gap-16 items-center pb-20">
|
|
1944
|
+
|
|
1945
|
+
{/* LEFT — copy */}
|
|
1946
|
+
<div>
|
|
1947
|
+
{data.badge && (
|
|
1948
|
+
<div
|
|
1949
|
+
className="inline-flex items-center gap-2 bg-[rgba(59,130,246,0.10)] border border-[rgba(59,130,246,0.20)] px-4 py-1.5 rounded-full text-[0.70rem] font-mono font-semibold text-[var(--local-accent)] mb-8 tracking-widest uppercase jp-animate-in"
|
|
1950
|
+
data-jp-field="badge"
|
|
1951
|
+
>
|
|
1952
|
+
<span className="w-1.5 h-1.5 rounded-full bg-[#22c55e] jp-pulse-dot" />
|
|
1953
|
+
{data.badge}
|
|
1954
|
+
</div>
|
|
1955
|
+
)}
|
|
1956
|
+
|
|
1957
|
+
<h1
|
|
1958
|
+
className="font-display font-black text-[clamp(3rem,6vw,5.5rem)] text-[var(--local-text)] leading-[1.0] tracking-tight mb-6 jp-animate-in jp-d1"
|
|
1959
|
+
data-jp-field="title"
|
|
1960
|
+
>
|
|
1961
|
+
{data.title}
|
|
1962
|
+
{data.titleHighlight && (
|
|
1963
|
+
<>
|
|
1964
|
+
<br />
|
|
1965
|
+
<em
|
|
1966
|
+
className="not-italic bg-gradient-to-br from-[var(--local-accent)] to-[var(--local-cyan)] bg-clip-text text-transparent"
|
|
1967
|
+
data-jp-field="titleHighlight"
|
|
1968
|
+
>
|
|
1969
|
+
{data.titleHighlight}
|
|
1970
|
+
</em>
|
|
1971
|
+
</>
|
|
1972
|
+
)}
|
|
1973
|
+
</h1>
|
|
1974
|
+
|
|
1975
|
+
{data.description && (
|
|
1976
|
+
<p
|
|
1977
|
+
className="text-[1.05rem] text-[var(--local-text-muted)] max-w-[500px] leading-[1.75] mb-10 jp-animate-in jp-d2"
|
|
1978
|
+
data-jp-field="description"
|
|
1979
|
+
>
|
|
1980
|
+
{data.description}
|
|
1981
|
+
</p>
|
|
1982
|
+
)}
|
|
1983
|
+
|
|
1984
|
+
{data.ctas && data.ctas.length > 0 && (
|
|
1985
|
+
<div className="flex gap-4 flex-wrap jp-animate-in jp-d3">
|
|
1986
|
+
{data.ctas.map((cta, idx) => (
|
|
1987
|
+
<a
|
|
1988
|
+
key={cta.id ?? idx}
|
|
1989
|
+
href={cta.href}
|
|
1990
|
+
data-jp-item-id={cta.id ?? `legacy-${idx}`}
|
|
1991
|
+
data-jp-item-field="ctas"
|
|
1992
|
+
className={cn(
|
|
1993
|
+
'inline-flex items-center gap-2 px-7 py-3 rounded-[7px] font-semibold text-[0.95rem] transition-all duration-200 no-underline',
|
|
1994
|
+
cta.variant === 'primary'
|
|
1995
|
+
? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-0.5 shadow-[0_0_24px_rgba(59,130,246,0.25)]'
|
|
1996
|
+
: '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)]'
|
|
1997
|
+
)}
|
|
1998
|
+
>
|
|
1999
|
+
{cta.label}
|
|
2000
|
+
</a>
|
|
2001
|
+
))}
|
|
2002
|
+
</div>
|
|
2003
|
+
)}
|
|
2004
|
+
|
|
2005
|
+
{data.metrics && data.metrics.length > 0 && (
|
|
2006
|
+
<div className="flex gap-10 mt-14 pt-10 border-t border-[rgba(255,255,255,0.06)] flex-wrap jp-animate-in jp-d4">
|
|
2007
|
+
{data.metrics.map((metric, idx) => (
|
|
2008
|
+
<div
|
|
2009
|
+
key={(metric as { id?: string }).id ?? idx}
|
|
2010
|
+
data-jp-item-id={(metric as { id?: string }).id ?? `legacy-${idx}`}
|
|
2011
|
+
data-jp-item-field="metrics"
|
|
2012
|
+
>
|
|
2013
|
+
<div className="font-display text-[2.2rem] font-black text-[var(--local-text)] leading-none">
|
|
2014
|
+
{metric.val}
|
|
2015
|
+
</div>
|
|
2016
|
+
<div className="text-[0.72rem] font-mono uppercase tracking-[0.1em] text-[var(--local-text-muted)] mt-1 opacity-70">
|
|
2017
|
+
{metric.label}
|
|
2018
|
+
</div>
|
|
2019
|
+
</div>
|
|
2020
|
+
))}
|
|
2021
|
+
</div>
|
|
2022
|
+
)}
|
|
2023
|
+
</div>
|
|
2024
|
+
|
|
2025
|
+
{/* RIGHT — ICE mini-mockup */}
|
|
2026
|
+
<div className="jp-animate-in jp-d2 rounded-[12px] overflow-hidden border border-[rgba(255,255,255,0.10)] shadow-[0_0_0_1px_rgba(255,255,255,0.04),0_40px_80px_rgba(0,0,0,0.6),0_0_60px_rgba(59,130,246,0.08)]">
|
|
2027
|
+
{/* Browser bar */}
|
|
2028
|
+
<div className="bg-[#0f1923] px-3 py-2.5 flex items-center gap-1.5 border-b border-[rgba(255,255,255,0.05)]">
|
|
2029
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#ef4444]" />
|
|
2030
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#f59e0b]" />
|
|
2031
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#22c55e]" />
|
|
2032
|
+
<span className="mx-auto font-mono text-[0.60rem] text-[rgba(255,255,255,0.20)] bg-[rgba(255,255,255,0.04)] px-3 py-0.5 rounded">localhost:5173 · Studio</span>
|
|
2033
|
+
</div>
|
|
2034
|
+
{/* Split: canvas + inspector */}
|
|
2035
|
+
<div className="grid grid-cols-[1fr_260px] h-[360px] bg-[#060d1b]">
|
|
2036
|
+
{/* Canvas */}
|
|
2037
|
+
<div className="relative bg-gradient-to-br from-[#04090f] to-[#07112a] p-8 flex flex-col justify-center">
|
|
2038
|
+
<span className="absolute top-2 right-2 font-mono text-[0.48rem] font-bold tracking-widest uppercase bg-[#3b82f6] text-white px-1.5 py-0.5">HERO | LOCAL</span>
|
|
2039
|
+
<div className="absolute inset-0 border-2 border-[#3b82f6] pointer-events-none" />
|
|
2040
|
+
<div className="inline-flex items-center gap-1.5 bg-[rgba(255,255,255,0.07)] border border-[rgba(255,255,255,0.10)] rounded-full px-2.5 py-1 font-mono text-[0.52rem] text-[#94a3b8] mb-3 w-fit">
|
|
2041
|
+
<span className="w-1 h-1 rounded-full bg-[#22c55e]" />
|
|
2042
|
+
{data.badge ?? 'Architecture v1.2'}
|
|
2043
|
+
</div>
|
|
2044
|
+
<div className="font-display font-black text-[1.5rem] leading-none text-white mb-0.5">
|
|
2045
|
+
{data.title}
|
|
2046
|
+
</div>
|
|
2047
|
+
{data.titleHighlight && (
|
|
2048
|
+
<div className="font-display font-black text-[1.5rem] leading-none bg-gradient-to-r from-[#60a5fa] to-[#22d3ee] bg-clip-text text-transparent mb-3">
|
|
2049
|
+
{data.titleHighlight}
|
|
2050
|
+
</div>
|
|
2051
|
+
)}
|
|
2052
|
+
<p className="text-[0.65rem] text-[#475569] leading-[1.6] max-w-[220px] mb-3">
|
|
2053
|
+
{data.description?.slice(0, 100)}…
|
|
2054
|
+
</p>
|
|
2055
|
+
<div className="flex gap-1.5">
|
|
2056
|
+
<span className="text-[0.58rem] font-semibold bg-[#3b82f6] text-white px-2.5 py-1 rounded">Read the Docs</span>
|
|
2057
|
+
<span className="text-[0.58rem] border border-[rgba(255,255,255,0.15)] text-[#94a3b8] px-2.5 py-1 rounded">View on NPM</span>
|
|
2058
|
+
</div>
|
|
2059
|
+
<div className="flex gap-4 mt-3 pt-3 border-t border-[rgba(255,255,255,0.05)]">
|
|
2060
|
+
{(data.metrics ?? []).map((m, i) => (
|
|
2061
|
+
<div key={i}>
|
|
2062
|
+
<div className="font-display font-black text-[1rem] text-white leading-none">{m.val}</div>
|
|
2063
|
+
<div className="font-mono text-[0.44rem] uppercase tracking-widest text-[#334155] mt-0.5">{m.label}</div>
|
|
2064
|
+
</div>
|
|
2065
|
+
))}
|
|
2066
|
+
</div>
|
|
2067
|
+
</div>
|
|
2068
|
+
{/* Inspector */}
|
|
2069
|
+
<div className="bg-[#08121f] border-l border-[rgba(255,255,255,0.06)] flex flex-col">
|
|
2070
|
+
<div className="px-3.5 py-2.5 border-b border-[rgba(255,255,255,0.05)] flex items-start justify-between">
|
|
2071
|
+
<div>
|
|
2072
|
+
<div className="font-display font-bold text-[0.80rem] text-white">Inspector</div>
|
|
2073
|
+
<div className="flex items-center gap-1.5 mt-0.5 font-mono text-[0.54rem] text-[#3b82f6]">
|
|
2074
|
+
<span className="font-bold">■ HERO</span>
|
|
2075
|
+
<span className="text-[#1e3a5f]">|</span>
|
|
2076
|
+
<span className="text-[#334155]">LOCAL</span>
|
|
2077
|
+
</div>
|
|
2078
|
+
</div>
|
|
2079
|
+
<span className="font-mono text-[0.55rem] text-[#3b82f6]">+ Add section</span>
|
|
2080
|
+
</div>
|
|
2081
|
+
{/* Layers */}
|
|
2082
|
+
<div className="border-b border-[rgba(255,255,255,0.05)]">
|
|
2083
|
+
<div className="px-3.5 py-1.5 font-mono text-[0.50rem] uppercase tracking-widest text-[#1e3a5f] flex justify-between">
|
|
2084
|
+
<span>Page Layers</span><span className="text-[#334155]">(8)</span>
|
|
2085
|
+
</div>
|
|
2086
|
+
<div className="flex items-center gap-2 px-3.5 py-1.5 bg-[rgba(59,130,246,0.08)]">
|
|
2087
|
+
<span className="font-mono text-[0.50rem] uppercase tracking-wide text-[#3b82f6] w-9">HERO</span>
|
|
2088
|
+
<span className="font-sans text-[0.60rem] text-[#e2e8f0] font-semibold flex-1 truncate">{data.title}</span>
|
|
2089
|
+
<span className="w-1.5 h-1.5 rounded-full bg-[#22c55e]" />
|
|
2090
|
+
</div>
|
|
2091
|
+
<div className="flex items-center gap-2 px-3.5 py-1.5 opacity-50">
|
|
2092
|
+
<span className="font-mono text-[0.50rem] uppercase tracking-wide text-[#1e3a5f] w-9">SOC</span>
|
|
2093
|
+
<span className="font-sans text-[0.60rem] text-[#334155] flex-1 truncate">Separation of Concerns</span>
|
|
2094
|
+
</div>
|
|
2095
|
+
<div className="flex items-center gap-2 px-3.5 py-1.5 opacity-35">
|
|
2096
|
+
<span className="font-mono text-[0.50rem] uppercase tracking-wide text-[#1e3a5f] w-9">CMS</span>
|
|
2097
|
+
<span className="font-sans text-[0.60rem] text-[#334155] flex-1 truncate">In-Context Editing</span>
|
|
2098
|
+
</div>
|
|
2099
|
+
</div>
|
|
2100
|
+
{/* Fields */}
|
|
2101
|
+
<div className="flex-1 px-3.5 py-3 flex flex-col gap-2.5 overflow-hidden">
|
|
2102
|
+
<div>
|
|
2103
|
+
<div className="font-mono text-[0.50rem] uppercase tracking-widest text-[#334155] mb-1">Title</div>
|
|
2104
|
+
<div className="bg-[rgba(59,130,246,0.05)] border border-[rgba(59,130,246,0.45)] rounded px-2 py-1.5 font-mono text-[0.58rem] text-[#e2e8f0] truncate">{data.title}</div>
|
|
2105
|
+
</div>
|
|
2106
|
+
<div>
|
|
2107
|
+
<div className="font-mono text-[0.50rem] uppercase tracking-widest text-[#334155] mb-1">Subtitle</div>
|
|
2108
|
+
<div className="bg-[rgba(255,255,255,0.03)] border border-[rgba(255,255,255,0.07)] rounded px-2 py-1.5 font-mono text-[0.58rem] text-[#94a3b8] truncate">{data.titleHighlight}</div>
|
|
2109
|
+
</div>
|
|
2110
|
+
<div>
|
|
2111
|
+
<div className="font-mono text-[0.50rem] uppercase tracking-widest text-[#334155] mb-1">Badge</div>
|
|
2112
|
+
<div className="bg-[rgba(255,255,255,0.03)] border border-[rgba(255,255,255,0.07)] rounded px-2 py-1.5 font-mono text-[0.58rem] text-[#94a3b8] truncate">{data.badge}</div>
|
|
2113
|
+
</div>
|
|
2114
|
+
</div>
|
|
2115
|
+
{/* Bottom bar */}
|
|
2116
|
+
<div className="px-3.5 py-2 border-t border-[rgba(255,255,255,0.05)] bg-[#060e1c] flex items-center gap-2">
|
|
2117
|
+
<span className="w-1.5 h-1.5 rounded-full bg-[#22c55e]" />
|
|
2118
|
+
<span className="font-mono text-[0.50rem] text-[#475569]">All Changes Saved</span>
|
|
2119
|
+
<div className="ml-auto flex gap-1.5">
|
|
2120
|
+
<span className="font-mono text-[0.48rem] px-1.5 py-0.5 rounded border border-[rgba(59,130,246,0.3)] bg-[rgba(59,130,246,0.12)] text-[#60a5fa]">⬡ HTML</span>
|
|
2121
|
+
<span className="font-mono text-[0.48rem] px-1.5 py-0.5 rounded border border-[rgba(255,255,255,0.08)] bg-[rgba(255,255,255,0.03)] text-[#94a3b8] opacity-50">{ } JSON</span>
|
|
2122
|
+
</div>
|
|
2123
|
+
</div>
|
|
2124
|
+
</div>
|
|
2125
|
+
</div>
|
|
2126
|
+
</div>
|
|
2127
|
+
|
|
2128
|
+
</div>
|
|
2129
|
+
</div>
|
|
2130
|
+
</section>
|
|
2131
|
+
);
|
|
2132
|
+
};
|
|
992
2133
|
|
|
993
2134
|
END_OF_FILE_CONTENT
|
|
994
2135
|
echo "Creating src/components/hero/index.ts..."
|
|
@@ -2307,46 +3448,24 @@ echo "Creating src/data/config/menu.json..."
|
|
|
2307
3448
|
cat << 'END_OF_FILE_CONTENT' > "src/data/config/menu.json"
|
|
2308
3449
|
{
|
|
2309
3450
|
"main": [
|
|
2310
|
-
{
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
}
|
|
2314
|
-
{
|
|
2315
|
-
"label": "Products",
|
|
2316
|
-
"href": "#products"
|
|
2317
|
-
},
|
|
2318
|
-
{
|
|
2319
|
-
"label": "PA Ready",
|
|
2320
|
-
"href": "#pa-ready"
|
|
2321
|
-
},
|
|
2322
|
-
{
|
|
2323
|
-
"label": "Philosophy",
|
|
2324
|
-
"href": "#philosophy"
|
|
2325
|
-
}
|
|
3451
|
+
{ "label": "Architecture", "href": "#architecture" },
|
|
3452
|
+
{ "label": "CMS", "href": "#cms" },
|
|
3453
|
+
{ "label": "Versioning", "href": "#git" },
|
|
3454
|
+
{ "label": "Developer", "href": "#devex" }
|
|
2326
3455
|
]
|
|
2327
3456
|
}
|
|
3457
|
+
|
|
2328
3458
|
END_OF_FILE_CONTENT
|
|
2329
3459
|
# SKIP: src/data/config/menu.json:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
2330
3460
|
echo "Creating src/data/config/site.json..."
|
|
2331
3461
|
cat << 'END_OF_FILE_CONTENT' > "src/data/config/site.json"
|
|
2332
3462
|
{
|
|
2333
|
-
"identity": {
|
|
2334
|
-
"title": "JsonPages",
|
|
2335
|
-
"logoUrl": "/logo.svg"
|
|
2336
|
-
},
|
|
3463
|
+
"identity": { "title": "JsonPages", "logoUrl": "/logo.svg" },
|
|
2337
3464
|
"pages": [
|
|
2338
|
-
{
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
}
|
|
2342
|
-
{
|
|
2343
|
-
"slug": "architecture",
|
|
2344
|
-
"label": "Architecture"
|
|
2345
|
-
},
|
|
2346
|
-
{
|
|
2347
|
-
"slug": "usage",
|
|
2348
|
-
"label": "Usage"
|
|
2349
|
-
}
|
|
3465
|
+
{ "slug": "home", "label": "Home" },
|
|
3466
|
+
{ "slug": "docs", "label": "Docs" },
|
|
3467
|
+
{ "slug": "architecture", "label": "Architecture" },
|
|
3468
|
+
{ "slug": "usage", "label": "Usage" }
|
|
2350
3469
|
],
|
|
2351
3470
|
"header": {
|
|
2352
3471
|
"id": "global-header",
|
|
@@ -2356,27 +3475,14 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/config/site.json"
|
|
|
2356
3475
|
"logoHighlight": "Pages",
|
|
2357
3476
|
"logoIconText": "{ }",
|
|
2358
3477
|
"links": [
|
|
2359
|
-
{
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
},
|
|
2363
|
-
{
|
|
2364
|
-
"label": "Products",
|
|
2365
|
-
"href": "#products"
|
|
2366
|
-
},
|
|
2367
|
-
{
|
|
2368
|
-
"label": "PA Ready",
|
|
2369
|
-
"href": "#pa-ready"
|
|
2370
|
-
},
|
|
2371
|
-
{
|
|
2372
|
-
"label": "Philosophy",
|
|
2373
|
-
"href": "#philosophy"
|
|
2374
|
-
}
|
|
3478
|
+
{ "label": "Architecture", "href": "#architecture" },
|
|
3479
|
+
{ "label": "CMS", "href": "#cms" },
|
|
3480
|
+
{ "label": "Versioning", "href": "#git" },
|
|
3481
|
+
{ "label": "Developer", "href": "#devex" },
|
|
3482
|
+
{ "label": "Docs", "href": "/docs" }
|
|
2375
3483
|
]
|
|
2376
3484
|
},
|
|
2377
|
-
"settings": {
|
|
2378
|
-
"sticky": true
|
|
2379
|
-
}
|
|
3485
|
+
"settings": { "sticky": true }
|
|
2380
3486
|
},
|
|
2381
3487
|
"footer": {
|
|
2382
3488
|
"id": "global-footer",
|
|
@@ -2384,31 +3490,18 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/config/site.json"
|
|
|
2384
3490
|
"data": {
|
|
2385
3491
|
"brandText": "Json",
|
|
2386
3492
|
"brandHighlight": "Pages",
|
|
2387
|
-
"copyright": "© 2026 JsonPages
|
|
3493
|
+
"copyright": "© 2026 JsonPages. Source-available · Free to use.",
|
|
2388
3494
|
"links": [
|
|
2389
|
-
{
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
}
|
|
2393
|
-
{
|
|
2394
|
-
"label": "API Reference",
|
|
2395
|
-
"href": "#"
|
|
2396
|
-
},
|
|
2397
|
-
{
|
|
2398
|
-
"label": "Changelog",
|
|
2399
|
-
"href": "#"
|
|
2400
|
-
},
|
|
2401
|
-
{
|
|
2402
|
-
"label": "Privacy",
|
|
2403
|
-
"href": "#"
|
|
2404
|
-
}
|
|
3495
|
+
{ "label": "Docs", "href": "/docs" },
|
|
3496
|
+
{ "label": "NPM", "href": "#" },
|
|
3497
|
+
{ "label": "GitHub", "href": "#" },
|
|
3498
|
+
{ "label": "Changelog", "href": "#" }
|
|
2405
3499
|
]
|
|
2406
3500
|
},
|
|
2407
|
-
"settings": {
|
|
2408
|
-
"showLogo": true
|
|
2409
|
-
}
|
|
3501
|
+
"settings": { "showLogo": true }
|
|
2410
3502
|
}
|
|
2411
3503
|
}
|
|
3504
|
+
|
|
2412
3505
|
END_OF_FILE_CONTENT
|
|
2413
3506
|
# SKIP: src/data/config/site.json:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
2414
3507
|
echo "Creating src/data/config/theme.json..."
|
|
@@ -2417,21 +3510,21 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/config/theme.json"
|
|
|
2417
3510
|
"name": "JsonPages Landing",
|
|
2418
3511
|
"tokens": {
|
|
2419
3512
|
"colors": {
|
|
2420
|
-
"primary":
|
|
2421
|
-
"secondary":
|
|
2422
|
-
"accent":
|
|
2423
|
-
"background":
|
|
2424
|
-
"surface":
|
|
2425
|
-
"surfaceAlt":
|
|
2426
|
-
"text":
|
|
2427
|
-
"textMuted":
|
|
2428
|
-
"border":
|
|
3513
|
+
"primary": "#3b82f6",
|
|
3514
|
+
"secondary": "#22d3ee",
|
|
3515
|
+
"accent": "#60a5fa",
|
|
3516
|
+
"background": "#060d1b",
|
|
3517
|
+
"surface": "#0b1529",
|
|
3518
|
+
"surfaceAlt": "#101e38",
|
|
3519
|
+
"text": "#e2e8f0",
|
|
3520
|
+
"textMuted": "#94a3b8",
|
|
3521
|
+
"border": "#162a4d"
|
|
2429
3522
|
},
|
|
2430
3523
|
"typography": {
|
|
2431
3524
|
"fontFamily": {
|
|
2432
3525
|
"primary": "'Instrument Sans', system-ui, sans-serif",
|
|
2433
|
-
"mono":
|
|
2434
|
-
"display": "'
|
|
3526
|
+
"mono": "'JetBrains Mono', monospace",
|
|
3527
|
+
"display": "'Bricolage Grotesque', system-ui, sans-serif"
|
|
2435
3528
|
}
|
|
2436
3529
|
},
|
|
2437
3530
|
"borderRadius": {
|
|
@@ -2441,411 +3534,509 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/config/theme.json"
|
|
|
2441
3534
|
}
|
|
2442
3535
|
}
|
|
2443
3536
|
}
|
|
3537
|
+
|
|
2444
3538
|
END_OF_FILE_CONTENT
|
|
2445
3539
|
# SKIP: src/data/config/theme.json:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
2446
3540
|
mkdir -p "src/data/pages"
|
|
2447
|
-
echo "Creating src/data/pages/
|
|
2448
|
-
cat << 'END_OF_FILE_CONTENT' > "src/data/pages/
|
|
3541
|
+
echo "Creating src/data/pages/docs.json..."
|
|
3542
|
+
cat << 'END_OF_FILE_CONTENT' > "src/data/pages/docs.json"
|
|
2449
3543
|
{
|
|
2450
|
-
"id": "
|
|
2451
|
-
"slug": "
|
|
3544
|
+
"id": "docs-page",
|
|
3545
|
+
"slug": "docs",
|
|
2452
3546
|
"meta": {
|
|
2453
|
-
"title": "JsonPages —
|
|
2454
|
-
"description": "
|
|
3547
|
+
"title": "JsonPages Docs — Onboarding Governance",
|
|
3548
|
+
"description": "Percorso Governance completo: capsule, IDAC, registry, schema, overlay e checklist per integrare il CMS con governance piena."
|
|
2455
3549
|
},
|
|
2456
3550
|
"sections": [
|
|
2457
3551
|
{
|
|
2458
|
-
"id": "
|
|
2459
|
-
"type": "
|
|
2460
|
-
"data": {
|
|
2461
|
-
"badge": "V1.3",
|
|
2462
|
-
"title": "Local Authoring.",
|
|
2463
|
-
"titleHighlight": "Global Governance.",
|
|
2464
|
-
"description": "JsonPages is a high-fidelity Site Factory that solves the scalability bottleneck of traditional CMS systems. Distribute, govern, and render UI components across hundreds of independent sites — without technical drift.",
|
|
2465
|
-
"ctas": [
|
|
2466
|
-
{
|
|
2467
|
-
"label": "Explore Products →",
|
|
2468
|
-
"href": "#products",
|
|
2469
|
-
"variant": "primary"
|
|
2470
|
-
},
|
|
2471
|
-
{
|
|
2472
|
-
"label": "Read the Spec",
|
|
2473
|
-
"href": "#architecture",
|
|
2474
|
-
"variant": "secondary"
|
|
2475
|
-
}
|
|
2476
|
-
],
|
|
2477
|
-
"metrics": [
|
|
2478
|
-
{
|
|
2479
|
-
"val": "100+",
|
|
2480
|
-
"label": "Pages per tenant"
|
|
2481
|
-
},
|
|
2482
|
-
{
|
|
2483
|
-
"val": "0ms",
|
|
2484
|
-
"label": "Runtime overhead"
|
|
2485
|
-
},
|
|
2486
|
-
{
|
|
2487
|
-
"val": "∞",
|
|
2488
|
-
"label": "Scalable blocks"
|
|
2489
|
-
}
|
|
2490
|
-
]
|
|
2491
|
-
},
|
|
2492
|
-
"settings": {}
|
|
2493
|
-
},
|
|
2494
|
-
{
|
|
2495
|
-
"id": "problem-statement",
|
|
2496
|
-
"type": "problem-statement",
|
|
3552
|
+
"id": "docs-main",
|
|
3553
|
+
"type": "docs-layout",
|
|
2497
3554
|
"data": {
|
|
2498
|
-
"
|
|
3555
|
+
"pageTitle": "Onboarding — Percorso Governance",
|
|
3556
|
+
"pageSubtitle": "Per chi vuole il CMS (Studio, ICE, Form Factory): authoring in-app, tipizzazione forte, governance dei contenuti e dei componenti. Riferimento spec: JSONPAGES Architecture Specifications v1.2 + Appendix A.",
|
|
3557
|
+
"version": "Spec v1.2",
|
|
3558
|
+
"groups": [
|
|
2499
3559
|
{
|
|
2500
|
-
"
|
|
3560
|
+
"id": "g-1",
|
|
3561
|
+
"anchor": "governance",
|
|
3562
|
+
"label": "1. Governance",
|
|
3563
|
+
"sections": [
|
|
2501
3564
|
{
|
|
2502
|
-
"
|
|
2503
|
-
"
|
|
3565
|
+
"id": "s-1-1",
|
|
3566
|
+
"anchor": "cosa-implica",
|
|
3567
|
+
"title": "Cosa implica \"governance\"",
|
|
3568
|
+
"blocks": [
|
|
3569
|
+
{
|
|
3570
|
+
"id": "b-1-1",
|
|
3571
|
+
"type": "list",
|
|
3572
|
+
"content": "",
|
|
3573
|
+
"items": [
|
|
3574
|
+
{ "id": "i-1", "text": "**Tipi:** Ogni section type è dichiarato in `SectionDataRegistry` / `SectionSettingsRegistry` (module augmentation) e in `SectionComponentPropsMap`. Registry e config sono tipizzati." },
|
|
3575
|
+
{ "id": "i-2", "text": "**Schema:** Ogni section type ha uno schema Zod (data, e opzionalmente settings) usato dal Form Factory per generare l'editor nell'Inspector. Gli schema sono aggregati in `SECTION_SCHEMAS`." },
|
|
3576
|
+
{ "id": "i-3", "text": "**Studio/ICE:** L'editor (Inspector) si aggancia al DOM tramite `data-jp-field` e `data-jp-item-id` / `data-jp-item-field`. L'overlay di selezione in iframe richiede che il tenant fornisca il CSS (TOCC)." },
|
|
3577
|
+
{ "id": "i-4", "text": "**Add Section:** Il tenant espone **AddSectionConfig** (tipi addabili, label, default data) così in Studio l'utente può aggiungere section dalla libreria." },
|
|
3578
|
+
{ "id": "i-5", "text": "**Design tokens:** Le View usano variabili CSS (`--local-*`) e nessuna utility nuda (CIP) per coerenza e compatibilità con tema e overlay." }
|
|
3579
|
+
]
|
|
3580
|
+
},
|
|
3581
|
+
{
|
|
3582
|
+
"id": "b-1-2",
|
|
3583
|
+
"type": "callout",
|
|
3584
|
+
"content": "**Perché servono (in sintesi):** Tipi e schema permettono al Core e al Form Factory di operare senza conoscere i dettagli del Tenant; IDAC permette all'Inspector di legare click in Stage e riga attiva nella sidebar; TOCC rende visibile l'overlay; AddSectionConfig definisce la libreria Aggiungi sezione; token e z-index evitano conflitti con l'UI di editing."
|
|
3585
|
+
}
|
|
3586
|
+
]
|
|
2504
3587
|
},
|
|
2505
3588
|
{
|
|
2506
|
-
"
|
|
2507
|
-
"
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
"
|
|
2511
|
-
|
|
3589
|
+
"id": "s-1-2",
|
|
3590
|
+
"anchor": "tipizzazione",
|
|
3591
|
+
"title": "Valore della tipizzazione: governance e CMS UX",
|
|
3592
|
+
"tag": "§1.1",
|
|
3593
|
+
"blocks": [
|
|
3594
|
+
{
|
|
3595
|
+
"id": "b-2-1",
|
|
3596
|
+
"type": "paragraph",
|
|
3597
|
+
"content": "La tipizzazione (tipi TypeScript + schema Zod) serve a **due livelli**: governance (sviluppatore/architettura) e **UX del CMS** (autore che usa Studio). Spesso si menziona solo il primo."
|
|
3598
|
+
},
|
|
3599
|
+
{
|
|
3600
|
+
"id": "b-2-2",
|
|
3601
|
+
"type": "paragraph",
|
|
3602
|
+
"content": "**Governance:** registry tipizzato, SectionComponentPropsMap, forma di SiteConfig/PageConfig, audit, code-generation → coerenza tra tenant, niente drift, refactor sicuro, tooling basato su spec."
|
|
3603
|
+
},
|
|
3604
|
+
{
|
|
3605
|
+
"id": "b-2-3",
|
|
3606
|
+
"type": "paragraph",
|
|
3607
|
+
"content": "**CMS UX:** lo schema Zod guida il **Form Factory** (quali widget per ogni campo: text, textarea, select, list, icon-picker, **image-picker**); `data-jp-field` e `data-jp-item-id/field` legano click in Stage e form nell'Inspector; **AddSectionConfig** dà tipi addabili, label e default."
|
|
3608
|
+
},
|
|
3609
|
+
{
|
|
3610
|
+
"id": "b-2-4",
|
|
3611
|
+
"type": "callout",
|
|
3612
|
+
"content": "Per la governance la tipizzazione garantisce **contratti**; per la CMS UX definisce l'**esperienza di editing** (controlli, label, default, binding). Va specificato entrambi."
|
|
3613
|
+
}
|
|
3614
|
+
]
|
|
2512
3615
|
}
|
|
2513
|
-
]
|
|
2514
|
-
"label": "Silo #1"
|
|
3616
|
+
]
|
|
2515
3617
|
},
|
|
2516
3618
|
{
|
|
2517
|
-
"
|
|
3619
|
+
"id": "g-2",
|
|
3620
|
+
"anchor": "struttura",
|
|
3621
|
+
"label": "2. Struttura progetto",
|
|
3622
|
+
"sections": [
|
|
2518
3623
|
{
|
|
2519
|
-
"
|
|
2520
|
-
"
|
|
2521
|
-
|
|
3624
|
+
"id": "s-2-1",
|
|
3625
|
+
"anchor": "file-layout",
|
|
3626
|
+
"title": "File e cartelle (completa)",
|
|
3627
|
+
"blocks": [
|
|
3628
|
+
{
|
|
3629
|
+
"id": "b-3-1",
|
|
3630
|
+
"type": "list",
|
|
3631
|
+
"content": "",
|
|
3632
|
+
"items": [
|
|
3633
|
+
{ "id": "f-1", "text": "`src/data/config/site.json` — SiteConfig (identity, pages[], header block, footer block)." },
|
|
3634
|
+
{ "id": "f-2", "text": "`src/data/config/menu.json` — MenuConfig (`main: MenuItem[]`)." },
|
|
3635
|
+
{ "id": "f-3", "text": "`src/data/config/theme.json` — ThemeConfig (tokens)." },
|
|
3636
|
+
{ "id": "f-4", "text": "`src/data/pages/<slug>.json` — PageConfig (slug, meta, sections[])." },
|
|
3637
|
+
{ "id": "f-5", "text": "`src/components/<sectionType>/` — **Capsula piena:** View.tsx, schema.ts, types.ts, index.ts." },
|
|
3638
|
+
{ "id": "f-6", "text": "`src/lib/base-schemas.ts` — BaseSectionData, BaseArrayItem, BaseSectionSettingsSchema." },
|
|
3639
|
+
{ "id": "f-7", "text": "`src/lib/schemas.ts` — SECTION_SCHEMAS (aggregato degli schema data per tipo) + export SectionType." },
|
|
3640
|
+
{ "id": "f-8", "text": "`src/lib/ComponentRegistry.tsx` — Registry tipizzato: `{ [K in SectionType]: React.FC<SectionComponentPropsMap[K]> }`." },
|
|
3641
|
+
{ "id": "f-9", "text": "`src/lib/addSectionConfig.ts` — AddSectionConfig (addableSectionTypes, sectionTypeLabels, getDefaultSectionData)." },
|
|
3642
|
+
{ "id": "f-10", "text": "`src/types.ts` — SectionComponentPropsMap, PageConfig, SiteConfig, MenuConfig, ThemeConfig; **module augmentation** per SectionDataRegistry e SectionSettingsRegistry; re-export da `@jsonpages/core`." },
|
|
3643
|
+
{ "id": "f-11", "text": "`src/App.tsx` — Bootstrap: config (tenantId, registry, schemas, pages, siteConfig, themeConfig, menuConfig, themeCss, addSection); `<JsonPagesEngine config={config} />`." },
|
|
3644
|
+
{ "id": "f-12", "text": "**CSS globale** — Include i selettori TOCC per overlay (hover/selected/type label)." }
|
|
3645
|
+
]
|
|
3646
|
+
}
|
|
3647
|
+
]
|
|
3648
|
+
}
|
|
3649
|
+
]
|
|
3650
|
+
},
|
|
3651
|
+
{
|
|
3652
|
+
"id": "g-3",
|
|
3653
|
+
"anchor": "componenti",
|
|
3654
|
+
"label": "3. Componenti",
|
|
3655
|
+
"sections": [
|
|
2522
3656
|
{
|
|
2523
|
-
"
|
|
2524
|
-
"
|
|
3657
|
+
"id": "s-3-1",
|
|
3658
|
+
"anchor": "capsule-idac",
|
|
3659
|
+
"title": "Capsule + IDAC + token",
|
|
3660
|
+
"blocks": [
|
|
3661
|
+
{
|
|
3662
|
+
"id": "b-4-1",
|
|
3663
|
+
"type": "list",
|
|
3664
|
+
"content": "",
|
|
3665
|
+
"items": [
|
|
3666
|
+
{ "id": "c-1", "text": "**Capsula:** Ogni section type ha View, schema (Zod), types (inferiti), index. Lo schema **data** estende BaseSectionData; gli item degli array estendono BaseArrayItem." },
|
|
3667
|
+
{ "id": "c-2", "text": "**View:** Riceve `data` e `settings` (e `menu` per header). Non importa Zod. Usa **solo** variabili CSS per colori/raggi (`bg-[var(--local-bg)]`), sezione root con `z-index` ≤ 1." },
|
|
3668
|
+
{ "id": "c-3", "text": "**IDAC (ICE):** Su ogni campo scalare editabile: `data-jp-field=\"<fieldKey>\"`. Su ogni item di array: `data-jp-item-id=\"<stableId>\"` e `data-jp-item-field=\"<arrayKey>\"`." },
|
|
3669
|
+
{ "id": "c-4", "text": "**Schema:** Usa il vocabolario UI (ECIP): `.describe('ui:text')`, `ui:textarea`, `ui:select`, `ui:number`, `ui:list`, `ui:icon-picker`, `ui:image-picker`. Array di oggetti editabili: ogni oggetto con `id` (BaseArrayItem)." }
|
|
3670
|
+
]
|
|
3671
|
+
},
|
|
3672
|
+
{
|
|
3673
|
+
"id": "b-4-2",
|
|
3674
|
+
"type": "callout",
|
|
3675
|
+
"content": "**Perché servono:** `data-jp-field` e `data-jp-item-*` servono perché lo Stage è in un iframe e il Core deve sapere quale campo/item corrisponde al click senza conoscere il DOM del Tenant. Senza IDAC, click sul canvas non si riflette nella sidebar. Vedi spec §6 (IDAC), §5 (ECIP), §4 (CIP)."
|
|
3676
|
+
}
|
|
3677
|
+
]
|
|
2525
3678
|
},
|
|
2526
3679
|
{
|
|
2527
|
-
"
|
|
2528
|
-
"
|
|
3680
|
+
"id": "s-3-2",
|
|
3681
|
+
"anchor": "image-picker",
|
|
3682
|
+
"title": "Image Picker: uso corretto nello schema",
|
|
3683
|
+
"tag": "§3.1",
|
|
3684
|
+
"blocks": [
|
|
3685
|
+
{
|
|
3686
|
+
"id": "b-5-1",
|
|
3687
|
+
"type": "paragraph",
|
|
3688
|
+
"content": "Per i **campi immagine** il Form Factory espone il widget **Image Picker** solo se lo schema è modellato correttamente."
|
|
3689
|
+
},
|
|
3690
|
+
{
|
|
3691
|
+
"id": "b-5-2",
|
|
3692
|
+
"type": "heading",
|
|
3693
|
+
"content": "Regola"
|
|
3694
|
+
},
|
|
3695
|
+
{
|
|
3696
|
+
"id": "b-5-3",
|
|
3697
|
+
"type": "list",
|
|
3698
|
+
"content": "",
|
|
3699
|
+
"items": [
|
|
3700
|
+
{ "id": "r-1", "text": "Il campo immagine non è una stringa (`z.string()`), ma un **oggetto** con almeno `url` e, opzionalmente, `alt`." },
|
|
3701
|
+
{ "id": "r-2", "text": "Lo **schema di questo oggetto** va marcato con **`.describe('ui:image-picker')`**. Il Form Factory riconosce `ui:image-picker` solo su **ZodObject**, non su campi stringa." }
|
|
3702
|
+
]
|
|
3703
|
+
},
|
|
3704
|
+
{
|
|
3705
|
+
"id": "b-5-4",
|
|
3706
|
+
"type": "heading",
|
|
3707
|
+
"content": "Esempio (capsula image-break)"
|
|
3708
|
+
},
|
|
3709
|
+
{
|
|
3710
|
+
"id": "b-5-5",
|
|
3711
|
+
"type": "code",
|
|
3712
|
+
"codeFilename": "src/components/image-break/schema.ts",
|
|
3713
|
+
"content": "import { z } from 'zod';\nimport { BaseSectionData } from '@/lib/base-schemas';\n\nconst ImageSelectionSchema = z\n .object({\n url: z.string(),\n alt: z.string().optional(),\n })\n .describe('ui:image-picker');\n\nexport const ImageBreakSchema = BaseSectionData.extend({\n label: z.string().optional().describe('ui:text'),\n image: ImageSelectionSchema.default({ url: '', alt: '' }),\n caption: z.string().optional().describe('ui:textarea'),\n});"
|
|
3714
|
+
},
|
|
3715
|
+
{
|
|
3716
|
+
"id": "b-5-6",
|
|
3717
|
+
"type": "paragraph",
|
|
3718
|
+
"content": "In **View.tsx**: usa `resolveAssetUrl(data.image.url, tenantId)` per il `src` dell'immagine. Aggiungi `data-jp-field=\"image\"` sul nodo corrispondente nel DOM."
|
|
3719
|
+
},
|
|
3720
|
+
{
|
|
3721
|
+
"id": "b-5-7",
|
|
3722
|
+
"type": "note",
|
|
3723
|
+
"content": "**Cosa evitare:** Non usare `.describe('ui:image-picker')` su un campo stringa: il widget Image Picker si aspetta un oggetto `{ url, alt? }`. Non dimenticare `data-jp-field=\"image\"` nel DOM, altrimenti il binding Inspector ↔ Stage non funziona."
|
|
3724
|
+
}
|
|
3725
|
+
]
|
|
2529
3726
|
}
|
|
2530
|
-
]
|
|
2531
|
-
"label": "Silo #2"
|
|
3727
|
+
]
|
|
2532
3728
|
},
|
|
2533
3729
|
{
|
|
2534
|
-
"
|
|
3730
|
+
"id": "g-4",
|
|
3731
|
+
"anchor": "dati",
|
|
3732
|
+
"label": "4. Dati",
|
|
3733
|
+
"sections": [
|
|
2535
3734
|
{
|
|
2536
|
-
"
|
|
2537
|
-
"
|
|
2538
|
-
|
|
3735
|
+
"id": "s-4-1",
|
|
3736
|
+
"anchor": "forma-dati",
|
|
3737
|
+
"title": "Forma e responsabilità",
|
|
3738
|
+
"blocks": [
|
|
3739
|
+
{
|
|
3740
|
+
"id": "b-6-1",
|
|
3741
|
+
"type": "list",
|
|
3742
|
+
"content": "",
|
|
3743
|
+
"items": [
|
|
3744
|
+
{ "id": "d-1", "text": "`site.json` / `menu.json` / `theme.json` / `pages/*.json` — Forma esatta come in Appendix A. Sono la source of truth quando l'utente salva da Studio." },
|
|
3745
|
+
{ "id": "d-2", "text": "**Studio** aggiorna il Working Draft; il sync con l'iframe e il Bake usano la stessa struttura. I dati passati a JsonPagesEngine devono essere compatibili con ciò che l'editor modifica." }
|
|
3746
|
+
]
|
|
3747
|
+
},
|
|
3748
|
+
{
|
|
3749
|
+
"id": "b-6-2",
|
|
3750
|
+
"type": "callout",
|
|
3751
|
+
"content": "Se i dati arrivano da un CMS esterno tocca a te sincronizzare. In ogni caso la **forma** delle pagine (sections con id, type, data, settings) resta quella della spec."
|
|
3752
|
+
}
|
|
3753
|
+
]
|
|
3754
|
+
}
|
|
3755
|
+
]
|
|
3756
|
+
},
|
|
3757
|
+
{
|
|
3758
|
+
"id": "g-5",
|
|
3759
|
+
"anchor": "registry",
|
|
3760
|
+
"label": "5. Registry & Types",
|
|
3761
|
+
"sections": [
|
|
2539
3762
|
{
|
|
2540
|
-
"
|
|
2541
|
-
"
|
|
2542
|
-
|
|
3763
|
+
"id": "s-5-1",
|
|
3764
|
+
"anchor": "registry-detail",
|
|
3765
|
+
"title": "Registry, schemas, types, addSection",
|
|
3766
|
+
"blocks": [
|
|
3767
|
+
{
|
|
3768
|
+
"id": "b-7-1",
|
|
3769
|
+
"type": "list",
|
|
3770
|
+
"content": "",
|
|
3771
|
+
"items": [
|
|
3772
|
+
{ "id": "re-1", "text": "**types.ts:** Unico punto di **module augmentation** e definizione di SectionComponentPropsMap, PageConfig, SiteConfig, MenuConfig, ThemeConfig. Header: `{ data, settings?, menu: MenuItem[] }`; tutti gli altri: `{ data, settings? }`." },
|
|
3773
|
+
{ "id": "re-2", "text": "**ComponentRegistry:** Ogni chiave di SectionType ha il componente corrispondente; tipo: `{ [K in SectionType]: React.FC<SectionComponentPropsMap[K]> }`." },
|
|
3774
|
+
{ "id": "re-3", "text": "**SECTION_SCHEMAS:** Ogni chiave di SectionType ha lo **schema Zod della data** (stesso ordine del registry). Base schemas re-exportati da base-schemas.ts." },
|
|
3775
|
+
{ "id": "re-4", "text": "**addSectionConfig:** addableSectionTypes (solo i tipi che l'utente può aggiungere dalla libreria), sectionTypeLabels, getDefaultSectionData(type) che restituisce `data` valido per quello schema." }
|
|
3776
|
+
]
|
|
3777
|
+
},
|
|
3778
|
+
{
|
|
3779
|
+
"id": "b-7-2",
|
|
3780
|
+
"type": "callout",
|
|
3781
|
+
"content": "Un solo punto di augmentation (types.ts) e un solo SECTION_SCHEMAS evita duplicazioni. AddSectionConfig è l'unica fonte di verità per quali section si possono aggiungere e con quali default. Vedi spec §9 (ASC), Appendix A.2–A.3."
|
|
3782
|
+
}
|
|
3783
|
+
]
|
|
3784
|
+
}
|
|
3785
|
+
]
|
|
3786
|
+
},
|
|
3787
|
+
{
|
|
3788
|
+
"id": "g-6",
|
|
3789
|
+
"anchor": "overlay",
|
|
3790
|
+
"label": "6. Overlay CSS (TOCC)",
|
|
3791
|
+
"sections": [
|
|
2543
3792
|
{
|
|
2544
|
-
"
|
|
2545
|
-
"
|
|
3793
|
+
"id": "s-6-1",
|
|
3794
|
+
"anchor": "tocc-detail",
|
|
3795
|
+
"title": "Selettori TOCC richiesti",
|
|
3796
|
+
"blocks": [
|
|
3797
|
+
{
|
|
3798
|
+
"id": "b-8-1",
|
|
3799
|
+
"type": "paragraph",
|
|
3800
|
+
"content": "Il Core inietta il markup dell'overlay (wrapper con `data-section-id`, sibling con `data-jp-section-overlay`). Il **tenant** deve fornire il CSS nel proprio `index.css` per renderlo visibile."
|
|
3801
|
+
},
|
|
3802
|
+
{
|
|
3803
|
+
"id": "b-8-2",
|
|
3804
|
+
"type": "list",
|
|
3805
|
+
"content": "",
|
|
3806
|
+
"items": [
|
|
3807
|
+
{ "id": "t-1", "text": "`[data-jp-section-overlay]` copra la section, `pointer-events: none`, z-index alto (es. 9999)." },
|
|
3808
|
+
{ "id": "t-2", "text": "Hover e selected siano visibili (bordo tratteggiato / pieno, eventuale tint)." },
|
|
3809
|
+
{ "id": "t-3", "text": "Il type label (`[data-jp-section-overlay] > .jp-section-type-label`) sia posizionato e visibile su hover/selected." }
|
|
3810
|
+
]
|
|
3811
|
+
},
|
|
3812
|
+
{
|
|
3813
|
+
"id": "b-8-3",
|
|
3814
|
+
"type": "code",
|
|
3815
|
+
"codeFilename": "src/index.css — TOCC selectors",
|
|
3816
|
+
"content": "/* ── TOCC — Tenant Overlay CSS Contract ── */\n[data-jp-section-overlay] {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 9999;\n border: 1.5px dashed transparent;\n transition: border-color 0.15s, background 0.15s;\n}\n\n[data-jp-section-wrapper]:hover > [data-jp-section-overlay] {\n border-color: rgba(59, 130, 246, 0.5);\n background: rgba(59, 130, 246, 0.03);\n}\n\n[data-jp-section-wrapper].jp-selected > [data-jp-section-overlay] {\n border-color: #3b82f6;\n border-style: solid;\n background: rgba(59, 130, 246, 0.05);\n}\n\n[data-jp-section-overlay] > .jp-section-type-label {\n position: absolute;\n top: 6px;\n right: 8px;\n font-family: 'JetBrains Mono', monospace;\n font-size: 0.55rem;\n font-weight: 700;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n background: #3b82f6;\n color: white;\n padding: 2px 6px;\n opacity: 0;\n transition: opacity 0.15s;\n}\n\n[data-jp-section-wrapper]:hover > [data-jp-section-overlay] > .jp-section-type-label,\n[data-jp-section-wrapper].jp-selected > [data-jp-section-overlay] > .jp-section-type-label {\n opacity: 1;\n}"
|
|
3817
|
+
},
|
|
3818
|
+
{
|
|
3819
|
+
"id": "b-8-4",
|
|
3820
|
+
"type": "callout",
|
|
3821
|
+
"content": "**Perché servono (TOCC):** L'iframe dello Stage carica solo il CSS del Tenant; il Core inietta il markup dell'overlay ma non gli stili. Senza i selettori TOCC, bordo hover/selected e type label non sono visibili. Vedi spec §7 (TOCC)."
|
|
3822
|
+
}
|
|
3823
|
+
]
|
|
2546
3824
|
}
|
|
2547
|
-
]
|
|
2548
|
-
|
|
2549
|
-
}
|
|
2550
|
-
],
|
|
2551
|
-
"title": "Traditional CMS systems create closed silos.",
|
|
2552
|
-
"paragraphs": [
|
|
3825
|
+
]
|
|
3826
|
+
},
|
|
2553
3827
|
{
|
|
2554
|
-
"
|
|
2555
|
-
"
|
|
3828
|
+
"id": "g-7",
|
|
3829
|
+
"anchor": "checklist",
|
|
3830
|
+
"label": "7. Checklist",
|
|
3831
|
+
"sections": [
|
|
3832
|
+
{
|
|
3833
|
+
"id": "s-7-1",
|
|
3834
|
+
"anchor": "checklist-table",
|
|
3835
|
+
"title": "Checklist rapida — sviluppo grafico e dati (con CMS)",
|
|
3836
|
+
"blocks": [
|
|
3837
|
+
{
|
|
3838
|
+
"id": "b-9-1",
|
|
3839
|
+
"type": "table",
|
|
3840
|
+
"content": "",
|
|
3841
|
+
"rows": [
|
|
3842
|
+
{ "id": "r-1", "col1": "**Layout / grafico**", "col2": "View con variabili `--local-*`, z-index ≤ 1, nessuna utility naked." },
|
|
3843
|
+
{ "id": "r-2", "col1": "**Dati (forma)**", "col2": "SiteConfig, MenuConfig, ThemeConfig, PageConfig come in Appendix A; JSON in `data/config` e `data/pages`." },
|
|
3844
|
+
{ "id": "r-3", "col1": "**Capsule**", "col2": "View + schema (con `ui:*`) + types + index; data schema estende BaseSectionData; array item con id." },
|
|
3845
|
+
{ "id": "r-4", "col1": "**IDAC**", "col2": "`data-jp-field` su campi scalari editabili; `data-jp-item-id` e `data-jp-item-field` su item di array." },
|
|
3846
|
+
{ "id": "r-5", "col1": "**Image Picker**", "col2": "Campo immagine = oggetto `{ url, alt? }` con sub-schema `.describe('ui:image-picker')`; `resolveAssetUrl` + `data-jp-field` in View." },
|
|
3847
|
+
{ "id": "r-6", "col1": "**types.ts**", "col2": "SectionComponentPropsMap (header con menu), augmentation, PageConfig, SiteConfig, MenuConfig, ThemeConfig." },
|
|
3848
|
+
{ "id": "r-7", "col1": "**Registry**", "col2": "Tutti i tipi mappati al componente; tipo registry come in Appendix A." },
|
|
3849
|
+
{ "id": "r-8", "col1": "**SECTION_SCHEMAS**", "col2": "Un entry per tipo (schema data); re-export base schemas." },
|
|
3850
|
+
{ "id": "r-9", "col1": "**addSectionConfig**", "col2": "addableSectionTypes, sectionTypeLabels, getDefaultSectionData." },
|
|
3851
|
+
{ "id": "r-10", "col1": "**Config**", "col2": "tenantId, registry, schemas, pages, siteConfig, themeConfig, menuConfig, themeCss, addSection." },
|
|
3852
|
+
{ "id": "r-11", "col1": "**TOCC**", "col2": "CSS overlay per `[data-jp-section-overlay]`, hover, selected, type label." }
|
|
3853
|
+
]
|
|
3854
|
+
}
|
|
3855
|
+
]
|
|
3856
|
+
}
|
|
3857
|
+
]
|
|
2556
3858
|
},
|
|
2557
3859
|
{
|
|
2558
|
-
"
|
|
2559
|
-
"
|
|
3860
|
+
"id": "g-8",
|
|
3861
|
+
"anchor": "riferimenti",
|
|
3862
|
+
"label": "8. Riferimenti spec",
|
|
3863
|
+
"sections": [
|
|
3864
|
+
{
|
|
3865
|
+
"id": "s-8-1",
|
|
3866
|
+
"anchor": "spec-refs",
|
|
3867
|
+
"title": "Documenti di riferimento",
|
|
3868
|
+
"blocks": [
|
|
3869
|
+
{
|
|
3870
|
+
"id": "b-10-1",
|
|
3871
|
+
"type": "list",
|
|
3872
|
+
"content": "",
|
|
3873
|
+
"items": [
|
|
3874
|
+
{ "id": "sp-1", "text": "**Architettura e ICE:** §1–§10 (MTRP, JSP, TBP, CIP, ECIP, IDAC, TOCC, BSDS, ASC, JEB)." },
|
|
3875
|
+
{ "id": "sp-2", "text": "**Tipi e code-generation:** Appendix A (Core types, Tenant types, Schema contract, File paths, Integration checklist)." },
|
|
3876
|
+
{ "id": "sp-3", "text": "**Admin:** JAP (Studio topology, Working Draft, Bake, overlay, Green Build)." }
|
|
3877
|
+
]
|
|
3878
|
+
},
|
|
3879
|
+
{
|
|
3880
|
+
"id": "b-10-2",
|
|
3881
|
+
"type": "callout",
|
|
3882
|
+
"content": "Usando questo percorso hai **governance** piena: tipi, schema, editor, Add Section e overlay allineati alle spec v1.2. Per le versioni con tutti i Perché servono usa il file **JSONPAGES_Specs_v1.2_completo.md**."
|
|
3883
|
+
}
|
|
3884
|
+
]
|
|
3885
|
+
}
|
|
3886
|
+
]
|
|
2560
3887
|
}
|
|
2561
|
-
]
|
|
2562
|
-
"anchorId": "aa"
|
|
3888
|
+
]
|
|
2563
3889
|
},
|
|
2564
3890
|
"settings": {}
|
|
2565
|
-
}
|
|
3891
|
+
}
|
|
3892
|
+
]
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3895
|
+
END_OF_FILE_CONTENT
|
|
3896
|
+
echo "Creating src/data/pages/home.json..."
|
|
3897
|
+
cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
|
|
3898
|
+
{
|
|
3899
|
+
"id": "home-page",
|
|
3900
|
+
"slug": "home",
|
|
3901
|
+
"meta": {
|
|
3902
|
+
"title": "JsonPages — The Sovereign Shell. Zero Runtime Overhead.",
|
|
3903
|
+
"description": "The @jsonpages/core package is a headless, schema-driven runtime. It handles routing, hydration, and the admin interface, leaving your Tenant code pure and framework-agnostic."
|
|
3904
|
+
},
|
|
3905
|
+
"sections": [
|
|
2566
3906
|
{
|
|
2567
|
-
"id": "
|
|
2568
|
-
"type": "
|
|
3907
|
+
"id": "hero-main",
|
|
3908
|
+
"type": "hero",
|
|
2569
3909
|
"data": {
|
|
2570
|
-
"
|
|
2571
|
-
"
|
|
2572
|
-
"
|
|
2573
|
-
"description": "
|
|
2574
|
-
"
|
|
2575
|
-
{
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
},
|
|
2583
|
-
{
|
|
2584
|
-
"icon": "sparkles",
|
|
2585
|
-
"iconVariant": "registry",
|
|
2586
|
-
"title": "The Semantic Brain. 🧠",
|
|
2587
|
-
"description": "We reject hardcoded Union Types. The Kernel defines an open registry where independent modules inject their DNA via Zod schemas. This provides the AI with a precise, type-safe vocabulary to build brand-compliant interfaces. Because the registry is the Single Source of Truth, the AI knows exactly which components exist and how to configure them without ever touching the core code.",
|
|
2588
|
-
"tag": "The Vocabulary",
|
|
2589
|
-
"tagVariant": "pattern"
|
|
2590
|
-
},
|
|
2591
|
-
{
|
|
2592
|
-
"icon": "zap",
|
|
2593
|
-
"iconVariant": "federation",
|
|
2594
|
-
"title": "The 5-Minute Tenant. 🤖",
|
|
2595
|
-
"description": "AI doesn't guess; it follows the Protocol. Because our ecosystem is governed by 5 deterministic standards, agents like Claude can project a complex, multi-page, brand-compliant tenant in under 5 minutes. The result is 100% Type-Safe JSON that perfectly matches your schemas, delivering a production-ready site with zero technical drift and an instant polymorphic CMS.",
|
|
2596
|
-
"tag": "The Multiplier",
|
|
2597
|
-
"tagVariant": "enterprise"
|
|
2598
|
-
}
|
|
3910
|
+
"badge": "Architecture v1.2",
|
|
3911
|
+
"title": "The Sovereign Shell.",
|
|
3912
|
+
"titleHighlight": "Zero Runtime Overhead.",
|
|
3913
|
+
"description": "The @jsonpages/core package is a headless, schema-driven runtime. It handles routing, hydration, and the admin interface, leaving your Tenant code pure and framework-agnostic.",
|
|
3914
|
+
"ctas": [
|
|
3915
|
+
{ "id": "cta-1", "label": "Read the Docs", "href": "#devex", "variant": "primary" },
|
|
3916
|
+
{ "id": "cta-2", "label": "View on NPM", "href": "#", "variant": "secondary" }
|
|
3917
|
+
],
|
|
3918
|
+
"metrics": [
|
|
3919
|
+
{ "id": "m-1", "val": "3KB", "label": "Core Overhead" },
|
|
3920
|
+
{ "id": "m-2", "val": "100%", "label": "Type Safety" },
|
|
3921
|
+
{ "id": "m-3", "val": "MIT", "label": "License" }
|
|
2599
3922
|
]
|
|
2600
3923
|
},
|
|
2601
3924
|
"settings": {}
|
|
2602
3925
|
},
|
|
2603
3926
|
{
|
|
2604
|
-
"id": "
|
|
3927
|
+
"id": "soc-section",
|
|
2605
3928
|
"type": "arch-layers",
|
|
2606
3929
|
"data": {
|
|
2607
|
-
"anchorId": "
|
|
2608
|
-
"label": "
|
|
2609
|
-
"title": "
|
|
2610
|
-
"description": "
|
|
3930
|
+
"anchorId": "architecture",
|
|
3931
|
+
"label": "Architecture",
|
|
3932
|
+
"title": "Separation of Concerns",
|
|
3933
|
+
"description": "We enforce a strict boundary between the Engine (us) and the Tenant (you). This prevents vendor lock-in.",
|
|
2611
3934
|
"layers": [
|
|
2612
3935
|
{
|
|
2613
|
-
"
|
|
2614
|
-
"
|
|
2615
|
-
"
|
|
2616
|
-
"
|
|
3936
|
+
"id": "layer-3",
|
|
3937
|
+
"number": "3",
|
|
3938
|
+
"layerLevel": "l2",
|
|
3939
|
+
"title": "The Form Factory",
|
|
3940
|
+
"description": "The Admin UI is not hardcoded. It is generated at runtime by analyzing your Zod schemas. Change the schema, change the UI."
|
|
2617
3941
|
},
|
|
2618
3942
|
{
|
|
2619
|
-
"
|
|
3943
|
+
"id": "layer-2",
|
|
3944
|
+
"number": "2",
|
|
2620
3945
|
"layerLevel": "l1",
|
|
2621
|
-
"title": "The
|
|
2622
|
-
"description": "
|
|
3946
|
+
"title": "The Tenant Protocol (TBP)",
|
|
3947
|
+
"description": "Your components live here. They receive pure JSON data. They do not know they are being edited. They just render."
|
|
2623
3948
|
},
|
|
2624
3949
|
{
|
|
2625
|
-
"
|
|
2626
|
-
"
|
|
2627
|
-
"
|
|
2628
|
-
"
|
|
3950
|
+
"id": "layer-1",
|
|
3951
|
+
"number": "1",
|
|
3952
|
+
"layerLevel": "l0",
|
|
3953
|
+
"title": "The Core Engine (@jsonpages/core)",
|
|
3954
|
+
"description": "Handles the React rendering loop, routing, and the Studio iframe injection. It is completely agnostic of your design system."
|
|
2629
3955
|
}
|
|
2630
3956
|
],
|
|
2631
|
-
"
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
},
|
|
2645
|
-
{
|
|
2646
|
-
"content": " // Open Registry: modules inject here",
|
|
2647
|
-
"tokenType": "comment"
|
|
2648
|
-
},
|
|
2649
|
-
{
|
|
2650
|
-
"content": "}",
|
|
2651
|
-
"tokenType": "plain"
|
|
2652
|
-
},
|
|
2653
|
-
{
|
|
2654
|
-
"content": "",
|
|
2655
|
-
"tokenType": "plain"
|
|
2656
|
-
},
|
|
2657
|
-
{
|
|
2658
|
-
"content": "export interface BaseSection<K extends keyof SectionDataRegistry> {",
|
|
2659
|
-
"tokenType": "keyword"
|
|
2660
|
-
},
|
|
2661
|
-
{
|
|
2662
|
-
"content": " id: string;",
|
|
2663
|
-
"tokenType": "type"
|
|
2664
|
-
},
|
|
2665
|
-
{
|
|
2666
|
-
"content": " type: K;",
|
|
2667
|
-
"tokenType": "type"
|
|
2668
|
-
},
|
|
2669
|
-
{
|
|
2670
|
-
"content": " data: SectionDataRegistry[K];",
|
|
2671
|
-
"tokenType": "type"
|
|
2672
|
-
},
|
|
2673
|
-
{
|
|
2674
|
-
"content": " settings?: SectionSettings;",
|
|
2675
|
-
"tokenType": "type"
|
|
2676
|
-
},
|
|
2677
|
-
{
|
|
2678
|
-
"content": "}",
|
|
2679
|
-
"tokenType": "plain"
|
|
2680
|
-
},
|
|
2681
|
-
{
|
|
2682
|
-
"content": "",
|
|
2683
|
-
"tokenType": "plain"
|
|
2684
|
-
},
|
|
2685
|
-
{
|
|
2686
|
-
"content": "// Computed Union — built automatically from registry",
|
|
2687
|
-
"tokenType": "comment"
|
|
2688
|
-
},
|
|
3957
|
+
"codeLines": []
|
|
3958
|
+
},
|
|
3959
|
+
"settings": {}
|
|
3960
|
+
},
|
|
3961
|
+
{
|
|
3962
|
+
"id": "cms-ice-section",
|
|
3963
|
+
"type": "cms-ice",
|
|
3964
|
+
"data": {
|
|
3965
|
+
"anchorId": "cms",
|
|
3966
|
+
"label": "In-Context Editing",
|
|
3967
|
+
"title": "The Inspector that only shows what you need.",
|
|
3968
|
+
"description": "Traditional CMS sidebars dump every field at once. ICE binds to the section you click in the live canvas and shows only the fields for that block. Zero cognitive overhead. The Form Factory generates every input widget automatically from your Zod schema — change the schema, change the UI.",
|
|
3969
|
+
"callouts": [
|
|
2689
3970
|
{
|
|
2690
|
-
"
|
|
2691
|
-
"
|
|
3971
|
+
"id": "cl-1",
|
|
3972
|
+
"icon": "🎯",
|
|
3973
|
+
"title": "Context-bound Inspector",
|
|
3974
|
+
"description": "Click any section in the canvas. The Inspector shows exactly — and only — the fields for that block. No sidebar overload."
|
|
2692
3975
|
},
|
|
2693
3976
|
{
|
|
2694
|
-
"
|
|
2695
|
-
"
|
|
3977
|
+
"id": "cl-2",
|
|
3978
|
+
"icon": "⚡",
|
|
3979
|
+
"title": "Schema → UI, automatically",
|
|
3980
|
+
"description": "The Admin UI is not hardcoded. It is generated at runtime by analyzing your Zod schemas. Change the schema, change the UI."
|
|
2696
3981
|
},
|
|
2697
3982
|
{
|
|
2698
|
-
"
|
|
2699
|
-
"
|
|
3983
|
+
"id": "cl-3",
|
|
3984
|
+
"icon": "📤",
|
|
3985
|
+
"title": "Bake to Zero-Runtime HTML",
|
|
3986
|
+
"description": "Hit Bake. The engine exports pure HTML/CSS with no JavaScript runtime — CDN-ready, infinitely cacheable, fully portable."
|
|
2700
3987
|
}
|
|
2701
3988
|
]
|
|
2702
3989
|
},
|
|
2703
3990
|
"settings": {}
|
|
2704
3991
|
},
|
|
2705
3992
|
{
|
|
2706
|
-
"id": "
|
|
2707
|
-
"type": "
|
|
3993
|
+
"id": "git-section",
|
|
3994
|
+
"type": "git-section",
|
|
2708
3995
|
"data": {
|
|
2709
|
-
"anchorId": "
|
|
2710
|
-
"label": "
|
|
2711
|
-
"title": "
|
|
2712
|
-
"
|
|
2713
|
-
"
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
"features": [
|
|
2721
|
-
{
|
|
2722
|
-
"text": "Local Authoring SDK"
|
|
2723
|
-
},
|
|
2724
|
-
{
|
|
2725
|
-
"text": "Zero-Runtime HTML Bake"
|
|
2726
|
-
},
|
|
2727
|
-
{
|
|
2728
|
-
"text": "Pure HTML/CSS Export"
|
|
2729
|
-
},
|
|
2730
|
-
{
|
|
2731
|
-
"text": "CDN-Ready Delivery"
|
|
2732
|
-
},
|
|
2733
|
-
{
|
|
2734
|
-
"text": "Full Code Ownership"
|
|
2735
|
-
}
|
|
2736
|
-
],
|
|
2737
|
-
"featured": false
|
|
2738
|
-
},
|
|
2739
|
-
{
|
|
2740
|
-
"tier": "Professional",
|
|
2741
|
-
"name": "JsonPages",
|
|
2742
|
-
"price": "SaaS",
|
|
2743
|
-
"priceSuffix": "/month",
|
|
2744
|
-
"delivery": "Managed Cloud · Multi-Tenant",
|
|
2745
|
-
"features": [
|
|
2746
|
-
{
|
|
2747
|
-
"text": "Multi-Tenant Hosting"
|
|
2748
|
-
},
|
|
2749
|
-
{
|
|
2750
|
-
"text": "Premium Block Library"
|
|
2751
|
-
},
|
|
2752
|
-
{
|
|
2753
|
-
"text": "SchemaUI Components"
|
|
2754
|
-
},
|
|
2755
|
-
{
|
|
2756
|
-
"text": "In-Context Editing"
|
|
2757
|
-
},
|
|
2758
|
-
{
|
|
2759
|
-
"text": "AI-Native Protocol"
|
|
2760
|
-
},
|
|
2761
|
-
{
|
|
2762
|
-
"text": "Tailwind v4 & BI Engine"
|
|
2763
|
-
}
|
|
2764
|
-
],
|
|
2765
|
-
"featured": false
|
|
2766
|
-
},
|
|
2767
|
-
{
|
|
2768
|
-
"tier": "Enterprise",
|
|
2769
|
-
"name": "The Platform",
|
|
2770
|
-
"price": "Custom",
|
|
2771
|
-
"delivery": "Private Cloud · Enterprise Governance",
|
|
2772
|
-
"features": [
|
|
2773
|
-
{
|
|
2774
|
-
"text": "Private Federated Registry"
|
|
2775
|
-
},
|
|
2776
|
-
{
|
|
2777
|
-
"text": "Module Federation"
|
|
2778
|
-
},
|
|
2779
|
-
{
|
|
2780
|
-
"text": "Runtime Component Injection"
|
|
2781
|
-
},
|
|
2782
|
-
{
|
|
2783
|
-
"text": "Custom Brand Guidelines"
|
|
2784
|
-
},
|
|
2785
|
-
{
|
|
2786
|
-
"text": "Dedicated SLA & Support"
|
|
2787
|
-
},
|
|
2788
|
-
{
|
|
2789
|
-
"text": "SSO & RBAC"
|
|
2790
|
-
}
|
|
2791
|
-
],
|
|
2792
|
-
"featured": false
|
|
2793
|
-
}
|
|
3996
|
+
"anchorId": "git",
|
|
3997
|
+
"label": "Versioning",
|
|
3998
|
+
"title": "Your content is code.",
|
|
3999
|
+
"titleHighlight": "Branch it. Diff it. Roll it back.",
|
|
4000
|
+
"description": "Because every page is a plain JSON file, your content workflow is identical to your code workflow. No database exports, no CMS snapshots, no proprietary backups. Just git.",
|
|
4001
|
+
"points": [
|
|
4002
|
+
{ "id": "p-1", "text": "Content changes appear in pull requests like any code change" },
|
|
4003
|
+
{ "id": "p-2", "text": "Branch a page, preview it, merge when approved" },
|
|
4004
|
+
{ "id": "p-3", "text": "Full diff history on every field of every section" },
|
|
4005
|
+
{ "id": "p-4", "text": "Roll back any page to any commit in seconds" },
|
|
4006
|
+
{ "id": "p-5", "text": "CI/CD pipelines trigger on content changes, not just code" }
|
|
2794
4007
|
]
|
|
2795
4008
|
},
|
|
2796
4009
|
"settings": {}
|
|
2797
4010
|
},
|
|
2798
4011
|
{
|
|
2799
|
-
"id": "
|
|
2800
|
-
"type": "
|
|
4012
|
+
"id": "devex-section",
|
|
4013
|
+
"type": "devex",
|
|
2801
4014
|
"data": {
|
|
2802
|
-
"anchorId": "
|
|
2803
|
-
"label": "
|
|
2804
|
-
"title": "
|
|
2805
|
-
"
|
|
2806
|
-
"
|
|
2807
|
-
{
|
|
2808
|
-
|
|
2809
|
-
}
|
|
2810
|
-
|
|
2811
|
-
"text": "The same JSON ontology drives both the Tailwind-powered private sector and the Bootstrap Italia public sector pipeline. This is not flexibility — this is Business Continuity."
|
|
2812
|
-
}
|
|
2813
|
-
],
|
|
2814
|
-
"badges": [
|
|
2815
|
-
{
|
|
2816
|
-
"label": "PNRR Ready"
|
|
2817
|
-
},
|
|
2818
|
-
{
|
|
2819
|
-
"label": "WCAG 2.1 AA"
|
|
2820
|
-
},
|
|
2821
|
-
{
|
|
2822
|
-
"label": "AgID Compliant"
|
|
2823
|
-
}
|
|
2824
|
-
],
|
|
2825
|
-
"engines": [
|
|
2826
|
-
{
|
|
2827
|
-
"label": "Tailwind v4",
|
|
2828
|
-
"variant": "tailwind"
|
|
2829
|
-
},
|
|
2830
|
-
{
|
|
2831
|
-
"label": "Bootstrap Italia",
|
|
2832
|
-
"variant": "bootstrap"
|
|
2833
|
-
}
|
|
2834
|
-
],
|
|
2835
|
-
"codeSnippet": "{\n \"type\": \"hero\",\n \"data\": { \"title\": \"...\" },\n \"settings\": { \"engine\": \"auto\" }\n}"
|
|
4015
|
+
"anchorId": "devex",
|
|
4016
|
+
"label": "Developer Experience",
|
|
4017
|
+
"title": "Your App.tsx is incredibly thin.",
|
|
4018
|
+
"description": "You don't write routing logic. You don't write admin panels. You just import the Engine and pass your configuration.",
|
|
4019
|
+
"features": [
|
|
4020
|
+
{ "id": "f-1", "text": "Automatic Routing based on JSON files" },
|
|
4021
|
+
{ "id": "f-2", "text": "Hot Module Replacement (HMR)" },
|
|
4022
|
+
{ "id": "f-3", "text": "Full TypeScript support" }
|
|
4023
|
+
]
|
|
2836
4024
|
},
|
|
2837
4025
|
"settings": {}
|
|
2838
4026
|
},
|
|
2839
4027
|
{
|
|
2840
|
-
"id": "
|
|
2841
|
-
"type": "
|
|
4028
|
+
"id": "cli-section",
|
|
4029
|
+
"type": "cli-section",
|
|
2842
4030
|
"data": {
|
|
2843
|
-
"anchorId": "
|
|
2844
|
-
"label": "
|
|
2845
|
-
"title": "
|
|
2846
|
-
"
|
|
2847
|
-
"
|
|
2848
|
-
|
|
4031
|
+
"anchorId": "cli",
|
|
4032
|
+
"label": "CLI Tool",
|
|
4033
|
+
"title": "A new tenant in under 5 minutes.",
|
|
4034
|
+
"description": "Install the CLI once. From there, scaffolding a fully-typed, production-ready tenant — with all capsules, registry, schemas, and config wired up — is a single command.",
|
|
4035
|
+
"steps": [
|
|
4036
|
+
{ "id": "s-1", "num": "1", "title": "Install the CLI globally", "description": "One-time install. The CLI lives alongside your other dev tools." },
|
|
4037
|
+
{ "id": "s-2", "num": "2", "title": "Scaffold a new tenant", "description": "Generates the full TBP structure: capsules, registry, schemas, config, and data files — all typed and ready." },
|
|
4038
|
+
{ "id": "s-3", "num": "3", "title": "Start building", "description": "Run npm run dev and your tenant is live with Studio, HMR, and full CMS editing." }
|
|
4039
|
+
]
|
|
2849
4040
|
},
|
|
2850
4041
|
"settings": {}
|
|
2851
4042
|
},
|
|
@@ -2854,16 +4045,26 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
|
|
|
2854
4045
|
"type": "cta-banner",
|
|
2855
4046
|
"data": {
|
|
2856
4047
|
"anchorId": "start",
|
|
2857
|
-
"
|
|
2858
|
-
"
|
|
2859
|
-
"
|
|
4048
|
+
"title": "Trust the Architecture.",
|
|
4049
|
+
"description": "Stop building page builders. Start building Systems.",
|
|
4050
|
+
"cliCommand": "npx @jsonpages/cli@latest new tenant",
|
|
4051
|
+
"ctas": [
|
|
4052
|
+
{ "id": "cta-docs", "label": "Read the Docs", "href": "#devex", "variant": "primary" },
|
|
4053
|
+
{ "id": "cta-npm", "label": "View on NPM", "href": "#", "variant": "secondary" }
|
|
4054
|
+
]
|
|
2860
4055
|
},
|
|
2861
4056
|
"settings": {}
|
|
2862
4057
|
}
|
|
2863
4058
|
]
|
|
2864
4059
|
}
|
|
4060
|
+
|
|
2865
4061
|
END_OF_FILE_CONTENT
|
|
2866
4062
|
# SKIP: src/data/pages/home.json:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
4063
|
+
echo "Creating src/fonts.css..."
|
|
4064
|
+
cat << 'END_OF_FILE_CONTENT' > "src/fonts.css"
|
|
4065
|
+
@import url('https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,600;12..96,700;12..96,800&family=JetBrains+Mono:wght@400;500;600;700&family=Instrument+Sans:wght@400;500;600&display=swap');
|
|
4066
|
+
|
|
4067
|
+
END_OF_FILE_CONTENT
|
|
2867
4068
|
mkdir -p "src/hooks"
|
|
2868
4069
|
echo "Creating src/hooks/useDocumentMeta.ts..."
|
|
2869
4070
|
cat << 'END_OF_FILE_CONTENT' > "src/hooks/useDocumentMeta.ts"
|
|
@@ -3073,39 +4274,49 @@ mkdir -p "src/lib"
|
|
|
3073
4274
|
echo "Creating src/lib/ComponentRegistry.tsx..."
|
|
3074
4275
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/ComponentRegistry.tsx"
|
|
3075
4276
|
import React from 'react';
|
|
3076
|
-
import { Header }
|
|
3077
|
-
import { Footer }
|
|
3078
|
-
import { Hero }
|
|
3079
|
-
import { FeatureGrid }
|
|
3080
|
-
import { CodeBlock }
|
|
4277
|
+
import { Header } from '@/components/header';
|
|
4278
|
+
import { Footer } from '@/components/footer';
|
|
4279
|
+
import { Hero } from '@/components/hero';
|
|
4280
|
+
import { FeatureGrid } from '@/components/feature-grid';
|
|
4281
|
+
import { CodeBlock } from '@/components/code-block';
|
|
3081
4282
|
import { ProblemStatement } from '@/components/problem-statement';
|
|
3082
|
-
import { PillarsGrid }
|
|
3083
|
-
import { ArchLayers }
|
|
3084
|
-
import { ProductTriad }
|
|
3085
|
-
import { PaSection }
|
|
3086
|
-
import { Philosophy }
|
|
3087
|
-
import { CtaBanner }
|
|
3088
|
-
import { ImageBreak }
|
|
3089
|
-
|
|
3090
|
-
import
|
|
4283
|
+
import { PillarsGrid } from '@/components/pillars-grid';
|
|
4284
|
+
import { ArchLayers } from '@/components/arch-layers';
|
|
4285
|
+
import { ProductTriad } from '@/components/product-triad';
|
|
4286
|
+
import { PaSection } from '@/components/pa-section';
|
|
4287
|
+
import { Philosophy } from '@/components/philosophy';
|
|
4288
|
+
import { CtaBanner } from '@/components/cta-banner';
|
|
4289
|
+
import { ImageBreak } from '@/components/image-break';
|
|
4290
|
+
import { CmsIce } from '@/components/cms-ice';
|
|
4291
|
+
import { GitSection } from '@/components/git-section';
|
|
4292
|
+
import { Devex } from '@/components/devex';
|
|
4293
|
+
import { CliSection } from '@/components/cli-section';
|
|
4294
|
+
import { DocsLayout } from '@/components/docs-layout';
|
|
4295
|
+
|
|
4296
|
+
import type { SectionType } from '@jsonpages/core';
|
|
3091
4297
|
import type { SectionComponentPropsMap } from '@/types';
|
|
3092
4298
|
|
|
3093
4299
|
export const ComponentRegistry: {
|
|
3094
4300
|
[K in SectionType]: React.FC<SectionComponentPropsMap[K]>;
|
|
3095
4301
|
} = {
|
|
3096
|
-
'header':
|
|
3097
|
-
'footer':
|
|
3098
|
-
'hero':
|
|
3099
|
-
'feature-grid':
|
|
3100
|
-
'code-block':
|
|
4302
|
+
'header': Header,
|
|
4303
|
+
'footer': Footer,
|
|
4304
|
+
'hero': Hero,
|
|
4305
|
+
'feature-grid': FeatureGrid,
|
|
4306
|
+
'code-block': CodeBlock,
|
|
3101
4307
|
'problem-statement': ProblemStatement,
|
|
3102
|
-
'pillars-grid':
|
|
3103
|
-
'arch-layers':
|
|
3104
|
-
'product-triad':
|
|
3105
|
-
'pa-section':
|
|
3106
|
-
'philosophy':
|
|
3107
|
-
'cta-banner':
|
|
3108
|
-
'image-break':
|
|
4308
|
+
'pillars-grid': PillarsGrid,
|
|
4309
|
+
'arch-layers': ArchLayers,
|
|
4310
|
+
'product-triad': ProductTriad,
|
|
4311
|
+
'pa-section': PaSection,
|
|
4312
|
+
'philosophy': Philosophy,
|
|
4313
|
+
'cta-banner': CtaBanner,
|
|
4314
|
+
'image-break': ImageBreak,
|
|
4315
|
+
'cms-ice': CmsIce,
|
|
4316
|
+
'git-section': GitSection,
|
|
4317
|
+
'devex': Devex,
|
|
4318
|
+
'cli-section': CliSection,
|
|
4319
|
+
'docs-layout': DocsLayout,
|
|
3109
4320
|
};
|
|
3110
4321
|
|
|
3111
4322
|
END_OF_FILE_CONTENT
|
|
@@ -3169,67 +4380,53 @@ export const Icon: React.FC<IconProps> = ({ name, size = 20, className }) => {
|
|
|
3169
4380
|
END_OF_FILE_CONTENT
|
|
3170
4381
|
echo "Creating src/lib/addSectionConfig.ts..."
|
|
3171
4382
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/addSectionConfig.ts"
|
|
3172
|
-
/**
|
|
3173
|
-
* Add-section config for the ICE admin (tenant-alpha).
|
|
3174
|
-
* Provides default data and labels for each addable section type.
|
|
3175
|
-
* Core remains agnostic; this file is tenant-specific.
|
|
3176
|
-
*/
|
|
3177
4383
|
import type { AddSectionConfig } from '@jsonpages/core';
|
|
3178
4384
|
|
|
3179
4385
|
const addableSectionTypes = [
|
|
3180
|
-
'hero',
|
|
3181
|
-
'
|
|
3182
|
-
'
|
|
3183
|
-
'
|
|
3184
|
-
'pillars-grid',
|
|
3185
|
-
'arch-layers',
|
|
3186
|
-
'product-triad',
|
|
3187
|
-
'pa-section',
|
|
3188
|
-
'philosophy',
|
|
3189
|
-
'cta-banner',
|
|
3190
|
-
'image-break',
|
|
4386
|
+
'hero', 'feature-grid', 'code-block', 'problem-statement',
|
|
4387
|
+
'pillars-grid', 'arch-layers', 'product-triad', 'pa-section',
|
|
4388
|
+
'philosophy', 'cta-banner', 'image-break',
|
|
4389
|
+
'cms-ice', 'git-section', 'devex', 'cli-section', 'docs-layout',
|
|
3191
4390
|
] as const;
|
|
3192
4391
|
|
|
3193
4392
|
const sectionTypeLabels: Record<string, string> = {
|
|
3194
|
-
'hero':
|
|
3195
|
-
'feature-grid':
|
|
3196
|
-
'code-block':
|
|
4393
|
+
'hero': 'Hero',
|
|
4394
|
+
'feature-grid': 'Feature Grid',
|
|
4395
|
+
'code-block': 'Code Block',
|
|
3197
4396
|
'problem-statement': 'Problem Statement',
|
|
3198
|
-
'pillars-grid':
|
|
3199
|
-
'arch-layers':
|
|
3200
|
-
'product-triad':
|
|
3201
|
-
'pa-section':
|
|
3202
|
-
'philosophy':
|
|
3203
|
-
'cta-banner':
|
|
3204
|
-
'image-break':
|
|
4397
|
+
'pillars-grid': 'Pillars Grid',
|
|
4398
|
+
'arch-layers': 'Architecture Layers',
|
|
4399
|
+
'product-triad': 'Product Triad',
|
|
4400
|
+
'pa-section': 'PA Section',
|
|
4401
|
+
'philosophy': 'Philosophy',
|
|
4402
|
+
'cta-banner': 'CTA Banner',
|
|
4403
|
+
'image-break': 'Image Break',
|
|
4404
|
+
'cms-ice': 'CMS / In-Context Editing',
|
|
4405
|
+
'git-section': 'Git Versioning',
|
|
4406
|
+
'devex': 'Developer Experience',
|
|
4407
|
+
'cli-section': 'CLI Tool',
|
|
4408
|
+
'docs-layout': 'Documentation Layout',
|
|
3205
4409
|
};
|
|
3206
4410
|
|
|
3207
|
-
function getDefaultSectionData(
|
|
3208
|
-
switch (
|
|
3209
|
-
case 'hero':
|
|
3210
|
-
return {
|
|
3211
|
-
case '
|
|
3212
|
-
|
|
3213
|
-
case '
|
|
3214
|
-
|
|
3215
|
-
case '
|
|
3216
|
-
|
|
3217
|
-
case '
|
|
3218
|
-
|
|
3219
|
-
case '
|
|
3220
|
-
|
|
3221
|
-
case '
|
|
3222
|
-
|
|
3223
|
-
case '
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
return { title: 'Philosophy', quote: 'Your quote here.' };
|
|
3227
|
-
case 'cta-banner':
|
|
3228
|
-
return { title: 'Call to Action', description: '' };
|
|
3229
|
-
case 'image-break':
|
|
3230
|
-
return { image: { url: '', alt: '' }, caption: '' };
|
|
3231
|
-
default:
|
|
3232
|
-
return {};
|
|
4411
|
+
function getDefaultSectionData(type: string): Record<string, unknown> {
|
|
4412
|
+
switch (type) {
|
|
4413
|
+
case 'hero': return { title: 'New Hero', description: '' };
|
|
4414
|
+
case 'feature-grid': return { sectionTitle: 'Features', cards: [] };
|
|
4415
|
+
case 'code-block': return { lines: [] };
|
|
4416
|
+
case 'problem-statement': return { title: 'Problem Statement', siloGroups: [], paragraphs: [] };
|
|
4417
|
+
case 'pillars-grid': return { title: 'Pillars', pillars: [] };
|
|
4418
|
+
case 'arch-layers': return { title: 'Architecture', layers: [] };
|
|
4419
|
+
case 'product-triad': return { title: 'Products', products: [] };
|
|
4420
|
+
case 'pa-section': return { title: 'Section', subtitle: 'Subtitle', paragraphs: [{ text: '' }] };
|
|
4421
|
+
case 'philosophy': return { title: 'Philosophy', quote: 'Your quote here.' };
|
|
4422
|
+
case 'cta-banner': return { title: 'Call to Action', description: '', cliCommand: '' };
|
|
4423
|
+
case 'image-break': return { image: { url: '', alt: '' }, caption: '' };
|
|
4424
|
+
case 'cms-ice': return { title: 'In-Context Editing', description: '', callouts: [] };
|
|
4425
|
+
case 'git-section': return { title: 'Your content is code.', description: '', points: [] };
|
|
4426
|
+
case 'devex': return { title: 'Developer Experience', description: '', features: [] };
|
|
4427
|
+
case 'cli-section': return { title: 'CLI Tool', description: '', steps: [] };
|
|
4428
|
+
case 'docs-layout': return { pageTitle: 'Documentation', pageSubtitle: '', version: 'v1.0', groups: [] };
|
|
4429
|
+
default: return {};
|
|
3233
4430
|
}
|
|
3234
4431
|
}
|
|
3235
4432
|
|
|
@@ -3284,73 +4481,109 @@ export const CtaSchema = z.object({
|
|
|
3284
4481
|
END_OF_FILE_CONTENT
|
|
3285
4482
|
echo "Creating src/lib/draftStorage.ts..."
|
|
3286
4483
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/draftStorage.ts"
|
|
3287
|
-
/**
|
|
3288
|
-
* Tenant initial data — file-backed only (no localStorage).
|
|
3289
|
-
*/
|
|
3290
|
-
|
|
3291
|
-
import type { PageConfig, SiteConfig } from '@/types';
|
|
3292
|
-
|
|
3293
|
-
export interface HydratedData {
|
|
3294
|
-
pages: Record<string, PageConfig>;
|
|
3295
|
-
siteConfig: SiteConfig;
|
|
3296
|
-
}
|
|
3297
|
-
|
|
3298
|
-
/**
|
|
3299
|
-
* Return pages and siteConfig from file-backed data only.
|
|
3300
|
-
*/
|
|
3301
|
-
export function getHydratedData(
|
|
3302
|
-
_tenantId: string,
|
|
3303
|
-
filePages: Record<string, PageConfig>,
|
|
3304
|
-
fileSiteConfig: SiteConfig
|
|
3305
|
-
): HydratedData {
|
|
3306
|
-
return {
|
|
3307
|
-
pages: { ...filePages },
|
|
3308
|
-
siteConfig: fileSiteConfig,
|
|
3309
|
-
};
|
|
3310
|
-
}
|
|
4484
|
+
/**
|
|
4485
|
+
* Tenant initial data — file-backed only (no localStorage).
|
|
4486
|
+
*/
|
|
4487
|
+
|
|
4488
|
+
import type { PageConfig, SiteConfig } from '@/types';
|
|
4489
|
+
|
|
4490
|
+
export interface HydratedData {
|
|
4491
|
+
pages: Record<string, PageConfig>;
|
|
4492
|
+
siteConfig: SiteConfig;
|
|
4493
|
+
}
|
|
4494
|
+
|
|
4495
|
+
/**
|
|
4496
|
+
* Return pages and siteConfig from file-backed data only.
|
|
4497
|
+
*/
|
|
4498
|
+
export function getHydratedData(
|
|
4499
|
+
_tenantId: string,
|
|
4500
|
+
filePages: Record<string, PageConfig>,
|
|
4501
|
+
fileSiteConfig: SiteConfig
|
|
4502
|
+
): HydratedData {
|
|
4503
|
+
return {
|
|
4504
|
+
pages: { ...filePages },
|
|
4505
|
+
siteConfig: fileSiteConfig,
|
|
4506
|
+
};
|
|
4507
|
+
}
|
|
3311
4508
|
|
|
3312
4509
|
END_OF_FILE_CONTENT
|
|
3313
|
-
echo "Creating src/lib/
|
|
3314
|
-
cat << 'END_OF_FILE_CONTENT' > "src/lib/
|
|
4510
|
+
echo "Creating src/lib/getFilePages.ts..."
|
|
4511
|
+
cat << 'END_OF_FILE_CONTENT' > "src/lib/getFilePages.ts"
|
|
3315
4512
|
/**
|
|
3316
|
-
*
|
|
3317
|
-
*
|
|
4513
|
+
* Page registry from src/data/pages/*.json.
|
|
4514
|
+
* Add a .json file in that directory to add a page; no manual list in App.tsx.
|
|
3318
4515
|
*/
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
import
|
|
3329
|
-
|
|
3330
|
-
|
|
4516
|
+
import type { PageConfig } from '@/types';
|
|
4517
|
+
|
|
4518
|
+
function slugFromPath(filePath: string): string {
|
|
4519
|
+
const base = filePath.split('/').pop() ?? filePath;
|
|
4520
|
+
const name = base.replace(/\.json$/i, '').trim();
|
|
4521
|
+
return name || 'page';
|
|
4522
|
+
}
|
|
4523
|
+
|
|
4524
|
+
export function getFilePages(): Record<string, PageConfig> {
|
|
4525
|
+
const glob = import.meta.glob<{ default: unknown }>('@/data/pages/*.json', { eager: true });
|
|
4526
|
+
const bySlug = new Map<string, PageConfig>();
|
|
4527
|
+
for (const [path, mod] of Object.entries(glob)) {
|
|
4528
|
+
const slug = slugFromPath(path);
|
|
4529
|
+
const raw = mod?.default;
|
|
4530
|
+
if (raw == null || typeof raw !== 'object') continue;
|
|
4531
|
+
bySlug.set(slug, raw as PageConfig);
|
|
4532
|
+
}
|
|
4533
|
+
const slugs = Array.from(bySlug.keys()).sort((a, b) =>
|
|
4534
|
+
a === 'home' ? -1 : b === 'home' ? 1 : a.localeCompare(b)
|
|
4535
|
+
);
|
|
4536
|
+
const record: Record<string, PageConfig> = {};
|
|
4537
|
+
for (const slug of slugs) {
|
|
4538
|
+
const config = bySlug.get(slug);
|
|
4539
|
+
if (config) record[slug] = config;
|
|
4540
|
+
}
|
|
4541
|
+
return record;
|
|
4542
|
+
}
|
|
4543
|
+
|
|
4544
|
+
END_OF_FILE_CONTENT
|
|
4545
|
+
echo "Creating src/lib/schemas.ts..."
|
|
4546
|
+
cat << 'END_OF_FILE_CONTENT' > "src/lib/schemas.ts"
|
|
4547
|
+
export { BaseSectionData, BaseArrayItem, BaseSectionSettingsSchema, CtaSchema } from './base-schemas';
|
|
4548
|
+
|
|
4549
|
+
import { HeaderSchema } from '@/components/header';
|
|
4550
|
+
import { FooterSchema } from '@/components/footer';
|
|
4551
|
+
import { HeroSchema } from '@/components/hero';
|
|
4552
|
+
import { FeatureGridSchema } from '@/components/feature-grid';
|
|
4553
|
+
import { CodeBlockSchema } from '@/components/code-block';
|
|
3331
4554
|
import { ProblemStatementSchema } from '@/components/problem-statement';
|
|
3332
|
-
import { PillarsGridSchema }
|
|
3333
|
-
import { ArchLayersSchema }
|
|
3334
|
-
import { ProductTriadSchema }
|
|
3335
|
-
import { PaSectionSchema }
|
|
3336
|
-
import { PhilosophySchema }
|
|
3337
|
-
import { CtaBannerSchema }
|
|
3338
|
-
import { ImageBreakSchema }
|
|
4555
|
+
import { PillarsGridSchema } from '@/components/pillars-grid';
|
|
4556
|
+
import { ArchLayersSchema } from '@/components/arch-layers';
|
|
4557
|
+
import { ProductTriadSchema } from '@/components/product-triad';
|
|
4558
|
+
import { PaSectionSchema } from '@/components/pa-section';
|
|
4559
|
+
import { PhilosophySchema } from '@/components/philosophy';
|
|
4560
|
+
import { CtaBannerSchema } from '@/components/cta-banner';
|
|
4561
|
+
import { ImageBreakSchema } from '@/components/image-break';
|
|
4562
|
+
import { CmsIceSchema } from '@/components/cms-ice';
|
|
4563
|
+
import { GitSectionSchema } from '@/components/git-section';
|
|
4564
|
+
import { DevexSchema } from '@/components/devex';
|
|
4565
|
+
import { CliSectionSchema } from '@/components/cli-section';
|
|
4566
|
+
import { DocsLayoutSchema } from '@/components/docs-layout';
|
|
3339
4567
|
|
|
3340
4568
|
export const SECTION_SCHEMAS = {
|
|
3341
|
-
'header':
|
|
3342
|
-
'footer':
|
|
3343
|
-
'hero':
|
|
3344
|
-
'feature-grid':
|
|
3345
|
-
'code-block':
|
|
4569
|
+
'header': HeaderSchema,
|
|
4570
|
+
'footer': FooterSchema,
|
|
4571
|
+
'hero': HeroSchema,
|
|
4572
|
+
'feature-grid': FeatureGridSchema,
|
|
4573
|
+
'code-block': CodeBlockSchema,
|
|
3346
4574
|
'problem-statement': ProblemStatementSchema,
|
|
3347
|
-
'pillars-grid':
|
|
3348
|
-
'arch-layers':
|
|
3349
|
-
'product-triad':
|
|
3350
|
-
'pa-section':
|
|
3351
|
-
'philosophy':
|
|
3352
|
-
'cta-banner':
|
|
3353
|
-
'image-break':
|
|
4575
|
+
'pillars-grid': PillarsGridSchema,
|
|
4576
|
+
'arch-layers': ArchLayersSchema,
|
|
4577
|
+
'product-triad': ProductTriadSchema,
|
|
4578
|
+
'pa-section': PaSectionSchema,
|
|
4579
|
+
'philosophy': PhilosophySchema,
|
|
4580
|
+
'cta-banner': CtaBannerSchema,
|
|
4581
|
+
'image-break': ImageBreakSchema,
|
|
4582
|
+
'cms-ice': CmsIceSchema,
|
|
4583
|
+
'git-section': GitSectionSchema,
|
|
4584
|
+
'devex': DevexSchema,
|
|
4585
|
+
'cli-section': CliSectionSchema,
|
|
4586
|
+
'docs-layout': DocsLayoutSchema,
|
|
3354
4587
|
} as const;
|
|
3355
4588
|
|
|
3356
4589
|
export type SectionType = keyof typeof SECTION_SCHEMAS;
|
|
@@ -3389,67 +4622,86 @@ mkdir -p "src/server"
|
|
|
3389
4622
|
echo "Creating src/types.ts..."
|
|
3390
4623
|
cat << 'END_OF_FILE_CONTENT' > "src/types.ts"
|
|
3391
4624
|
import type { MenuItem } from '@jsonpages/core';
|
|
3392
|
-
import type { HeaderData,
|
|
3393
|
-
import type { FooterData,
|
|
3394
|
-
import type { HeroData,
|
|
3395
|
-
import type { FeatureGridData,
|
|
3396
|
-
import type { CodeBlockData,
|
|
4625
|
+
import type { HeaderData, HeaderSettings } from '@/components/header';
|
|
4626
|
+
import type { FooterData, FooterSettings } from '@/components/footer';
|
|
4627
|
+
import type { HeroData, HeroSettings } from '@/components/hero';
|
|
4628
|
+
import type { FeatureGridData, FeatureGridSettings } from '@/components/feature-grid';
|
|
4629
|
+
import type { CodeBlockData, CodeBlockSettings } from '@/components/code-block';
|
|
3397
4630
|
import type { ProblemStatementData, ProblemStatementSettings } from '@/components/problem-statement';
|
|
3398
|
-
import type { PillarsGridData,
|
|
3399
|
-
import type { ArchLayersData,
|
|
3400
|
-
import type { ProductTriadData,
|
|
3401
|
-
import type { PaSectionData,
|
|
3402
|
-
import type { PhilosophyData,
|
|
3403
|
-
import type { CtaBannerData,
|
|
3404
|
-
import type { ImageBreakData,
|
|
4631
|
+
import type { PillarsGridData, PillarsGridSettings } from '@/components/pillars-grid';
|
|
4632
|
+
import type { ArchLayersData, ArchLayersSettings } from '@/components/arch-layers';
|
|
4633
|
+
import type { ProductTriadData, ProductTriadSettings } from '@/components/product-triad';
|
|
4634
|
+
import type { PaSectionData, PaSectionSettings } from '@/components/pa-section';
|
|
4635
|
+
import type { PhilosophyData, PhilosophySettings } from '@/components/philosophy';
|
|
4636
|
+
import type { CtaBannerData, CtaBannerSettings } from '@/components/cta-banner';
|
|
4637
|
+
import type { ImageBreakData, ImageBreakSettings } from '@/components/image-break';
|
|
4638
|
+
import type { CmsIceData, CmsIceSettings } from '@/components/cms-ice';
|
|
4639
|
+
import type { GitSectionData, GitSectionSettings } from '@/components/git-section';
|
|
4640
|
+
import type { DevexData, DevexSettings } from '@/components/devex';
|
|
4641
|
+
import type { CliSectionData, CliSectionSettings } from '@/components/cli-section';
|
|
4642
|
+
import type { DocsLayoutData, DocsLayoutSettings } from '@/components/docs-layout';
|
|
3405
4643
|
|
|
3406
4644
|
export type SectionComponentPropsMap = {
|
|
3407
|
-
'header':
|
|
3408
|
-
'footer':
|
|
3409
|
-
'hero':
|
|
3410
|
-
'feature-grid':
|
|
3411
|
-
'code-block':
|
|
3412
|
-
'problem-statement': { data: ProblemStatementData;
|
|
3413
|
-
'pillars-grid':
|
|
3414
|
-
'arch-layers':
|
|
3415
|
-
'product-triad':
|
|
3416
|
-
'pa-section':
|
|
3417
|
-
'philosophy':
|
|
3418
|
-
'cta-banner':
|
|
3419
|
-
'image-break':
|
|
4645
|
+
'header': { data: HeaderData; settings?: HeaderSettings; menu: MenuItem[] };
|
|
4646
|
+
'footer': { data: FooterData; settings?: FooterSettings };
|
|
4647
|
+
'hero': { data: HeroData; settings?: HeroSettings };
|
|
4648
|
+
'feature-grid': { data: FeatureGridData; settings?: FeatureGridSettings };
|
|
4649
|
+
'code-block': { data: CodeBlockData; settings?: CodeBlockSettings };
|
|
4650
|
+
'problem-statement': { data: ProblemStatementData; settings?: ProblemStatementSettings };
|
|
4651
|
+
'pillars-grid': { data: PillarsGridData; settings?: PillarsGridSettings };
|
|
4652
|
+
'arch-layers': { data: ArchLayersData; settings?: ArchLayersSettings };
|
|
4653
|
+
'product-triad': { data: ProductTriadData; settings?: ProductTriadSettings };
|
|
4654
|
+
'pa-section': { data: PaSectionData; settings?: PaSectionSettings };
|
|
4655
|
+
'philosophy': { data: PhilosophyData; settings?: PhilosophySettings };
|
|
4656
|
+
'cta-banner': { data: CtaBannerData; settings?: CtaBannerSettings };
|
|
4657
|
+
'image-break': { data: ImageBreakData; settings?: ImageBreakSettings };
|
|
4658
|
+
'cms-ice': { data: CmsIceData; settings?: CmsIceSettings };
|
|
4659
|
+
'git-section': { data: GitSectionData; settings?: GitSectionSettings };
|
|
4660
|
+
'devex': { data: DevexData; settings?: DevexSettings };
|
|
4661
|
+
'cli-section': { data: CliSectionData; settings?: CliSectionSettings };
|
|
4662
|
+
'docs-layout': { data: DocsLayoutData; settings?: DocsLayoutSettings };
|
|
3420
4663
|
};
|
|
3421
4664
|
|
|
3422
4665
|
declare module '@jsonpages/core' {
|
|
3423
4666
|
export interface SectionDataRegistry {
|
|
3424
|
-
'header':
|
|
3425
|
-
'footer':
|
|
3426
|
-
'hero':
|
|
3427
|
-
'feature-grid':
|
|
3428
|
-
'code-block':
|
|
4667
|
+
'header': HeaderData;
|
|
4668
|
+
'footer': FooterData;
|
|
4669
|
+
'hero': HeroData;
|
|
4670
|
+
'feature-grid': FeatureGridData;
|
|
4671
|
+
'code-block': CodeBlockData;
|
|
3429
4672
|
'problem-statement': ProblemStatementData;
|
|
3430
|
-
'pillars-grid':
|
|
3431
|
-
'arch-layers':
|
|
3432
|
-
'product-triad':
|
|
3433
|
-
'pa-section':
|
|
3434
|
-
'philosophy':
|
|
3435
|
-
'cta-banner':
|
|
3436
|
-
'image-break':
|
|
4673
|
+
'pillars-grid': PillarsGridData;
|
|
4674
|
+
'arch-layers': ArchLayersData;
|
|
4675
|
+
'product-triad': ProductTriadData;
|
|
4676
|
+
'pa-section': PaSectionData;
|
|
4677
|
+
'philosophy': PhilosophyData;
|
|
4678
|
+
'cta-banner': CtaBannerData;
|
|
4679
|
+
'image-break': ImageBreakData;
|
|
4680
|
+
'cms-ice': CmsIceData;
|
|
4681
|
+
'git-section': GitSectionData;
|
|
4682
|
+
'devex': DevexData;
|
|
4683
|
+
'cli-section': CliSectionData;
|
|
4684
|
+
'docs-layout': DocsLayoutData;
|
|
3437
4685
|
}
|
|
3438
|
-
|
|
3439
4686
|
export interface SectionSettingsRegistry {
|
|
3440
|
-
'header':
|
|
3441
|
-
'footer':
|
|
3442
|
-
'hero':
|
|
3443
|
-
'feature-grid':
|
|
3444
|
-
'code-block':
|
|
4687
|
+
'header': HeaderSettings;
|
|
4688
|
+
'footer': FooterSettings;
|
|
4689
|
+
'hero': HeroSettings;
|
|
4690
|
+
'feature-grid': FeatureGridSettings;
|
|
4691
|
+
'code-block': CodeBlockSettings;
|
|
3445
4692
|
'problem-statement': ProblemStatementSettings;
|
|
3446
|
-
'pillars-grid':
|
|
3447
|
-
'arch-layers':
|
|
3448
|
-
'product-triad':
|
|
3449
|
-
'pa-section':
|
|
3450
|
-
'philosophy':
|
|
3451
|
-
'cta-banner':
|
|
3452
|
-
'image-break':
|
|
4693
|
+
'pillars-grid': PillarsGridSettings;
|
|
4694
|
+
'arch-layers': ArchLayersSettings;
|
|
4695
|
+
'product-triad': ProductTriadSettings;
|
|
4696
|
+
'pa-section': PaSectionSettings;
|
|
4697
|
+
'philosophy': PhilosophySettings;
|
|
4698
|
+
'cta-banner': CtaBannerSettings;
|
|
4699
|
+
'image-break': ImageBreakSettings;
|
|
4700
|
+
'cms-ice': CmsIceSettings;
|
|
4701
|
+
'git-section': GitSectionSettings;
|
|
4702
|
+
'devex': DevexSettings;
|
|
4703
|
+
'cli-section': CliSectionSettings;
|
|
4704
|
+
'docs-layout': DocsLayoutSettings;
|
|
3453
4705
|
}
|
|
3454
4706
|
}
|
|
3455
4707
|
|