@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.
@@ -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
- * Thin Entry Point (Tenant).
32
- * Data from getHydratedData (file-backed or draft); assets from public/assets/images.
33
- */
34
- import { useState, useEffect } from 'react';
35
- import { JsonPagesEngine } from '@jsonpages/core';
36
- import type { LibraryImageEntry } from '@jsonpages/core';
37
- import { ComponentRegistry } from '@/lib/ComponentRegistry';
38
- import { SECTION_SCHEMAS } from '@/lib/schemas';
39
- import { addSectionConfig } from '@/lib/addSectionConfig';
40
- import { getHydratedData } from '@/lib/draftStorage';
41
- import type { JsonPagesConfig, ProjectState } from '@jsonpages/core';
42
- import type { PageConfig, SiteConfig, ThemeConfig, MenuConfig } from '@/types';
43
-
44
- import siteData from '@/data/config/site.json';
45
- import themeData from '@/data/config/theme.json';
46
- import menuData from '@/data/config/menu.json';
47
- import homeData from '@/data/pages/home.json';
48
-
49
- import tenantCss from './index.css?inline';
50
-
51
- const themeConfig = themeData as unknown as ThemeConfig;
52
- const menuConfig = menuData as unknown as MenuConfig;
53
-
54
- const TENANT_ID = 'alpha';
55
- const filePages: Record<string, PageConfig> = {
56
- home: homeData as unknown as PageConfig,
57
- };
58
- const fileSiteConfig = siteData as unknown as SiteConfig;
59
-
60
- const MAX_UPLOAD_SIZE_BYTES = 5 * 1024 * 1024; // 5MB, must match server
61
-
62
- function getInitialData() {
63
- return getHydratedData(TENANT_ID, filePages, fileSiteConfig);
64
- }
65
-
66
- function App() {
67
- const [{ pages, siteConfig }] = useState(getInitialData);
68
- const [assetsManifest, setAssetsManifest] = useState<LibraryImageEntry[]>([]);
69
-
70
- useEffect(() => {
71
- fetch('/api/list-assets')
72
- .then((r) => (r.ok ? r.json() : []))
73
- .then((list: LibraryImageEntry[]) => setAssetsManifest(Array.isArray(list) ? list : []))
74
- .catch(() => setAssetsManifest([]));
75
- }, []);
76
-
77
- const config: JsonPagesConfig = {
78
- tenantId: TENANT_ID,
79
- registry: ComponentRegistry as JsonPagesConfig['registry'],
80
- schemas: SECTION_SCHEMAS as unknown as JsonPagesConfig['schemas'],
81
- pages,
82
- siteConfig,
83
- themeConfig,
84
- menuConfig,
85
- themeCss: { tenant: tenantCss },
86
- addSection: addSectionConfig,
87
- persistence: {
88
- async saveToFile(state: ProjectState, slug: string): Promise<void> {
89
- const res = await fetch('/api/save-to-file', {
90
- method: 'POST',
91
- headers: { 'Content-Type': 'application/json' },
92
- body: JSON.stringify({ projectState: state, slug }),
93
- });
94
- const body = (await res.json().catch(() => ({}))) as { error?: string };
95
- if (!res.ok) {
96
- throw new Error(body.error ?? `Save to file failed: ${res.status}`);
97
- }
98
- },
99
- },
100
- assets: {
101
- assetsBaseUrl: '/assets',
102
- assetsManifest,
103
- async onAssetUpload(file: File): Promise<string> {
104
- if (!file.type.startsWith('image/')) {
105
- throw new Error('Invalid file type. Allowed: JPEG, PNG, WebP, GIF, SVG, AVIF.');
106
- }
107
- if (file.size > MAX_UPLOAD_SIZE_BYTES) {
108
- throw new Error(`File too large. Maximum size is ${MAX_UPLOAD_SIZE_BYTES / 1024 / 1024}MB.`);
109
- }
110
- const base64 = await new Promise<string>((resolve, reject) => {
111
- const reader = new FileReader();
112
- reader.onload = () => {
113
- const dataUrl = reader.result as string;
114
- const b64 = dataUrl.includes(',') ? dataUrl.split(',')[1] : '';
115
- resolve(b64 || '');
116
- };
117
- reader.onerror = () => reject(reader.error);
118
- reader.readAsDataURL(file);
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>&nbsp;</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>&nbsp;</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': 'var(--background)',
453
- '--local-text': 'var(--foreground)',
454
- '--local-text-muted': 'var(--muted-foreground)',
455
- '--local-primary': 'var(--primary)',
456
- '--local-accent': 'var(--color-accent, #60a5fa)',
457
- } as React.CSSProperties}
458
- className="relative py-28 bg-[var(--local-bg)] overflow-hidden text-center"
459
- >
460
- <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[60vw] h-[60vw] bg-[radial-gradient(circle,rgba(59,130,246,0.08)_0%,transparent_60%)] pointer-events-none" />
461
- <div className="relative max-w-[1200px] mx-auto px-8">
462
- {data.label && (
463
- <div className="jp-section-label inline-flex items-center gap-2 text-[0.72rem] font-bold uppercase tracking-[0.12em] text-[var(--local-accent)] mb-4" data-jp-field="label">
464
- <span className="w-5 h-px bg-[var(--local-primary)]" />
465
- {data.label}
466
- </div>
467
- )}
468
- <h2
469
- className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-6"
470
- data-jp-field="title"
471
- >
472
- {data.title}
473
- </h2>
474
- {data.description && (
475
- <p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed mb-10" data-jp-field="description">
476
- {data.description}
477
- </p>
478
- )}
479
- {data.ctas && data.ctas.length > 0 && (
480
- <div className="flex gap-4 justify-center flex-wrap">
481
- {data.ctas.map((cta, idx) => (
482
- <a
483
- key={cta.id ?? idx}
484
- href={cta.href}
485
- data-jp-item-id={cta.id ?? `legacy-${idx}`}
486
- data-jp-item-field="ctas"
487
- className={cn(
488
- 'inline-flex items-center gap-2 px-8 py-3.5 rounded-[5px] font-semibold text-base transition-all duration-200 no-underline',
489
- cta.variant === 'primary'
490
- ? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-0.5 hover:shadow-[0_8px_30px_rgba(59,130,246,0.3)]'
491
- : '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)]'
492
- )}
493
- >
494
- {cta.label}
495
- </a>
496
- ))}
497
- </div>
498
- )}
499
- </div>
500
- </section>
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: z.string().optional().describe('ui:text'),
519
- title: z.string().describe('ui:text'),
520
- description: z.string().optional().describe('ui:textarea'),
521
- ctas: z.array(CtaSchema).optional().describe('ui:list'),
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 = z.infer<typeof CtaBannerSchema>;
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': 'var(--background)',
910
- '--local-text': 'var(--foreground)',
911
- '--local-text-muted': 'var(--muted-foreground)',
912
- '--local-primary': 'var(--primary)',
913
- '--local-accent': 'var(--color-accent, #60a5fa)',
914
- '--local-cyan': 'var(--color-secondary, #22d3ee)',
915
- '--local-border': 'var(--border)',
916
- } as React.CSSProperties}
917
- className="jp-hero relative min-h-screen flex items-center overflow-hidden pt-24 bg-[var(--local-bg)]"
918
- >
919
- <div className="absolute -top-[40%] -right-[20%] w-[70vw] h-[70vw] rounded-full bg-[radial-gradient(circle,rgba(59,130,246,0.06)_0%,transparent_70%)] pointer-events-none" />
920
- <div className="absolute -bottom-[10%] -left-[10%] w-[50vw] h-[50vw] rounded-full bg-[radial-gradient(circle,rgba(34,211,238,0.03)_0%,transparent_60%)] pointer-events-none" />
921
-
922
- <div className="relative max-w-[1200px] mx-auto px-8 w-full">
923
- <div className="max-w-[820px]">
924
- {data.badge && (
925
- <div className="inline-flex items-center gap-2 bg-[rgba(59,130,246,0.08)] border border-[rgba(59,130,246,0.2)] px-4 py-1.5 rounded-full text-[0.78rem] font-semibold text-[var(--local-accent)] mb-8 tracking-wide jp-animate-in" data-jp-field="badge">
926
- <span className="w-1.5 h-1.5 rounded-full bg-[var(--local-accent)] jp-pulse-dot" />
927
- {data.badge}
928
- </div>
929
- )}
930
- <h1
931
- className="font-display text-[clamp(2.8rem,6vw,4.8rem)] font-black text-[var(--local-text)] leading-[1.08] tracking-tight mb-6 jp-animate-in jp-d1"
932
- data-jp-field="title"
933
- >
934
- {data.title}
935
- {data.titleHighlight && (
936
- <>
937
- <br />
938
- <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">
939
- {data.titleHighlight}
940
- </em>
941
- </>
942
- )}
943
- </h1>
944
- {data.description && (
945
- <p className="text-xl text-[var(--local-text-muted)] max-w-[600px] leading-relaxed mb-10 jp-animate-in jp-d2" data-jp-field="description">
946
- {data.description}
947
- </p>
948
- )}
949
- {data.ctas && data.ctas.length > 0 && (
950
- <div className="flex gap-4 flex-wrap jp-animate-in jp-d3">
951
- {data.ctas.map((cta, idx) => (
952
- <a
953
- key={cta.id ?? idx}
954
- href={cta.href}
955
- data-jp-item-id={cta.id ?? `legacy-${idx}`}
956
- data-jp-item-field="ctas"
957
- className={cn(
958
- 'inline-flex items-center gap-2 px-8 py-3.5 rounded-[5px] font-semibold text-base transition-all duration-200 no-underline',
959
- cta.variant === 'primary'
960
- ? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-0.5 hover:shadow-[0_8px_30px_rgba(59,130,246,0.3)]'
961
- : '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)]'
962
- )}
963
- >
964
- {cta.label}
965
- </a>
966
- ))}
967
- </div>
968
- )}
969
- {data.metrics && data.metrics.length > 0 && (
970
- <div className="flex gap-12 mt-16 pt-12 border-t border-[rgba(255,255,255,0.06)] flex-wrap jp-animate-in jp-d4">
971
- {data.metrics.map((metric, idx) => (
972
- <div
973
- key={(metric as { id?: string }).id ?? idx}
974
- data-jp-item-id={(metric as { id?: string }).id ?? `legacy-${idx}`}
975
- data-jp-item-field="metrics"
976
- >
977
- <div className="text-[2rem] font-bold text-[var(--local-text)] font-display">
978
- {metric.val}
979
- </div>
980
- <div className="text-[0.82rem] text-[var(--muted-foreground)] mt-0.5 opacity-70">
981
- {metric.label}
982
- </div>
983
- </div>
984
- ))}
985
- </div>
986
- )}
987
- </div>
988
- </div>
989
- </section>
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
- "label": "Architecture",
2312
- "href": "#architecture"
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
- "slug": "home",
2340
- "label": "Home"
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
- "label": "Architecture",
2361
- "href": "#architecture"
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 Ecosystem. Architected by Guido Filippo Serio.",
3493
+ "copyright": "© 2026 JsonPages. Source-available · Free to use.",
2388
3494
  "links": [
2389
- {
2390
- "label": "Documentation",
2391
- "href": "#"
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": "#3b82f6",
2421
- "secondary": "#22d3ee",
2422
- "accent": "#60a5fa",
2423
- "background": "#060d1b",
2424
- "surface": "#0b1529",
2425
- "surfaceAlt": "#101e38",
2426
- "text": "#e2e8f0",
2427
- "textMuted": "#94a3b8",
2428
- "border": "#162a4d"
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": "'JetBrains Mono', monospace",
2434
- "display": "'Playfair Display', Georgia, serif"
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/home.json..."
2448
- cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
3541
+ echo "Creating src/data/pages/docs.json..."
3542
+ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/docs.json"
2449
3543
  {
2450
- "id": "home-page",
2451
- "slug": "home",
3544
+ "id": "docs-page",
3545
+ "slug": "docs",
2452
3546
  "meta": {
2453
- "title": "JsonPages — Global Authoring. Global Governance.",
2454
- "description": "A high-fidelity Site Factory that solves the scalability bottleneck of traditional CMS systems."
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": "hero-main",
2459
- "type": "hero",
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
- "siloGroups": [
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
- "blocks": [
3560
+ "id": "g-1",
3561
+ "anchor": "governance",
3562
+ "label": "1. Governance",
3563
+ "sections": [
2501
3564
  {
2502
- "label": "Agency A Stack",
2503
- "variant": "red"
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
- "label": "Custom CMS",
2507
- "variant": "red"
2508
- },
2509
- {
2510
- "label": "PHP Backend",
2511
- "variant": "red"
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
- "blocks": [
3619
+ "id": "g-2",
3620
+ "anchor": "struttura",
3621
+ "label": "2. Struttura progetto",
3622
+ "sections": [
2518
3623
  {
2519
- "label": "Agency B Tools",
2520
- "variant": "amber"
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
- "label": "WordPress",
2524
- "variant": "amber"
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
- "label": "LAMP Stack",
2528
- "variant": "amber"
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
- "blocks": [
3730
+ "id": "g-4",
3731
+ "anchor": "dati",
3732
+ "label": "4. Dati",
3733
+ "sections": [
2535
3734
  {
2536
- "label": "Agency C Kit",
2537
- "variant": "green"
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
- "label": "React App",
2541
- "variant": "green"
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
- "label": "Node.js",
2545
- "variant": "green"
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
- "label": "Silo #3"
2549
- }
2550
- ],
2551
- "title": "Traditional CMS systems create closed silos.",
2552
- "paragraphs": [
3825
+ ]
3826
+ },
2553
3827
  {
2554
- "text": "When global brands delegate web projects to multiple agencies, each agency picks its own stack, its own conventions, its own deployment pipeline. The result is a portfolio of ungovernable sites with inconsistent quality and unpredictable maintenance costs.",
2555
- "isBold": false
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
- "text": "JsonPages eliminates this by providing a centralized protocol to distribute, govern, and render UI components — ensuring every site, regardless of who builds it, conforms to a single typed ontology.",
2559
- "isBold": true
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": "architecture-pillars",
2568
- "type": "pillars-grid",
3907
+ "id": "hero-main",
3908
+ "type": "hero",
2569
3909
  "data": {
2570
- "anchorId": "architecture",
2571
- "label": "Core Architecture",
2572
- "title": "Three Pillars of Composability",
2573
- "description": "A contract-first architecture designed to scale without core refactoring.",
2574
- "pillars": [
2575
- {
2576
- "icon": "box",
2577
- "iconVariant": "split",
2578
- "title": "The Machine-Readable Blueprint. 📐",
2579
- "description": "We enforce a strict file-system ontology that physically separates Global Governance from Local Content. By utilizing \"Dumb Components\" (CIP), we create a deterministic environment where the UI is a predictable function of data. This protocol allows AI agents to understand your site structure instantly, eliminating the \"guessing game\" of traditional web development.",
2580
- "tag": "The Foundation",
2581
- "tagVariant": "core"
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": "layered-architecture",
3927
+ "id": "soc-section",
2605
3928
  "type": "arch-layers",
2606
3929
  "data": {
2607
- "anchorId": "layers",
2608
- "label": "Engineering Layers",
2609
- "title": "Strict Boundaries. Clean Contracts.",
2610
- "description": "Three architectural layers maintain the separation between engine and implementation.",
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
- "number": "0",
2614
- "layerLevel": "l0",
2615
- "title": "The Immutable Kernel",
2616
- "description": "Defines the base structure of a Section — ID, Type, Payload, Settings — and exports the computed union. Zero knowledge of UI implementation or business logic."
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
- "number": "1",
3943
+ "id": "layer-2",
3944
+ "number": "2",
2620
3945
  "layerLevel": "l1",
2621
- "title": "The Standard Library",
2622
- "description": "Fundamental components (Hero, Text, Grid) used across 90% of projects. Augments the Kernel's registry while remaining part of the internal ecosystem."
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
- "number": "2",
2626
- "layerLevel": "l2",
2627
- "title": "User-Land Extensions",
2628
- "description": "The enabling layer. Third-party developers define specialized blocks — StockTickers, CRM Forms — in their own repositories. These extensions plug into the Kernel at compile-time."
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
- "codeFilename": "libs/shared-data/src/lib/core.ts",
2632
- "codeLines": [
2633
- {
2634
- "content": "// 🛡️ KERNEL — Single Source of Truth",
2635
- "tokenType": "comment"
2636
- },
2637
- {
2638
- "content": "",
2639
- "tokenType": "plain"
2640
- },
2641
- {
2642
- "content": "export interface SectionDataRegistry {",
2643
- "tokenType": "keyword"
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
- "content": "export type Section =",
2691
- "tokenType": "keyword"
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
- "content": " { [K in keyof SectionDataRegistry]: BaseSection<K> }",
2695
- "tokenType": "type"
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
- "content": " [keyof SectionDataRegistry];",
2699
- "tokenType": "operator"
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": "product-triad",
2707
- "type": "product-triad",
3993
+ "id": "git-section",
3994
+ "type": "git-section",
2708
3995
  "data": {
2709
- "anchorId": "products",
2710
- "label": "Product Triad",
2711
- "title": "One Protocol. Three Products.",
2712
- "description": "From solo developers to global enterprises — the same underlying ontology powers every tier.",
2713
- "products": [
2714
- {
2715
- "tier": "Starter",
2716
- "name": "JsonPage",
2717
- "price": "NPM",
2718
- "priceSuffix": "Local Install",
2719
- "delivery": "Static Export · Zero Runtime",
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": "pa-ready",
2800
- "type": "pa-section",
4012
+ "id": "devex-section",
4013
+ "type": "devex",
2801
4014
  "data": {
2802
- "anchorId": "pa-ready",
2803
- "label": "Public Sector",
2804
- "title": "PA Ready — One Platform, Two Markets",
2805
- "subtitle": "The Only Builder with Native Bootstrap Italia.",
2806
- "paragraphs": [
2807
- {
2808
- "text": "By switching the rendering engine to Bootstrap Italia, System Integrators use the same high-efficiency workflow to deliver WCAG-compliant sites for Municipalities and Government bodies — without rewriting backend logic."
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": "philosophy",
2841
- "type": "philosophy",
4028
+ "id": "cli-section",
4029
+ "type": "cli-section",
2842
4030
  "data": {
2843
- "anchorId": "philosophy",
2844
- "label": "Our Philosophy",
2845
- "title": "Governance Over Captivity",
2846
- "quote": "We sell the infrastructure to manage UI — not the right to access the code.",
2847
- "quoteHighlightWord": "manage",
2848
- "description": "Unlike closed-garden platforms, JsonPages provides an Eject Button by design. The Bake Engine exports your site as pure HTML/CSS with zero backend requirements. If you leave the SaaS, the shared ontology and SDK ensure you remain fully operational and independent. We earn your loyalty through value — not lock-in."
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
- "label": "Local Authoring Global Governance",
2858
- "title": "Ready to Govern at Scale?",
2859
- "description": "Whether you're building a single site or managing a portfolio of hundreds — JsonPages scales with your ambition."
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 } from '@/components/header';
3077
- import { Footer } from '@/components/footer';
3078
- import { Hero } from '@/components/hero';
3079
- import { FeatureGrid } from '@/components/feature-grid';
3080
- import { CodeBlock } from '@/components/code-block';
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 } from '@/components/pillars-grid';
3083
- import { ArchLayers } from '@/components/arch-layers';
3084
- import { ProductTriad } from '@/components/product-triad';
3085
- import { PaSection } from '@/components/pa-section';
3086
- import { Philosophy } from '@/components/philosophy';
3087
- import { CtaBanner } from '@/components/cta-banner';
3088
- import { ImageBreak } from '@/components/image-break';
3089
-
3090
- import type { SectionType } from '@jsonpages/core';
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': Header,
3097
- 'footer': Footer,
3098
- 'hero': Hero,
3099
- 'feature-grid': FeatureGrid,
3100
- 'code-block': CodeBlock,
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': PillarsGrid,
3103
- 'arch-layers': ArchLayers,
3104
- 'product-triad': ProductTriad,
3105
- 'pa-section': PaSection,
3106
- 'philosophy': Philosophy,
3107
- 'cta-banner': CtaBanner,
3108
- 'image-break': ImageBreak,
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
- 'feature-grid',
3182
- 'code-block',
3183
- 'problem-statement',
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': 'Hero',
3195
- 'feature-grid': 'Feature Grid',
3196
- 'code-block': 'Code Block',
4393
+ 'hero': 'Hero',
4394
+ 'feature-grid': 'Feature Grid',
4395
+ 'code-block': 'Code Block',
3197
4396
  'problem-statement': 'Problem Statement',
3198
- 'pillars-grid': 'Pillars Grid',
3199
- 'arch-layers': 'Architecture Layers',
3200
- 'product-triad': 'Product Triad',
3201
- 'pa-section': 'PA Section',
3202
- 'philosophy': 'Philosophy',
3203
- 'cta-banner': 'CTA Banner',
3204
- 'image-break': '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(sectionType: string): Record<string, unknown> {
3208
- switch (sectionType) {
3209
- case 'hero':
3210
- return { title: 'New Hero', description: '' };
3211
- case 'feature-grid':
3212
- return { sectionTitle: 'Features', cards: [] };
3213
- case 'code-block':
3214
- return { lines: [] };
3215
- case 'problem-statement':
3216
- return { title: 'Problem Statement', siloGroups: [], paragraphs: [] };
3217
- case 'pillars-grid':
3218
- return { title: 'Pillars', pillars: [] };
3219
- case 'arch-layers':
3220
- return { title: 'Architecture', layers: [] };
3221
- case 'product-triad':
3222
- return { title: 'Products', products: [] };
3223
- case 'pa-section':
3224
- return { title: 'Section', subtitle: 'Subtitle', paragraphs: [{ text: '' }] };
3225
- case 'philosophy':
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/schemas.ts..."
3314
- cat << 'END_OF_FILE_CONTENT' > "src/lib/schemas.ts"
4510
+ echo "Creating src/lib/getFilePages.ts..."
4511
+ cat << 'END_OF_FILE_CONTENT' > "src/lib/getFilePages.ts"
3315
4512
  /**
3316
- * SECTION_SCHEMAS registry — SSOT for FormFactory.
3317
- * Re-exports base schemas and aggregates all section schemas from TBP capsules.
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
- export {
3320
- BaseSectionData,
3321
- BaseArrayItem,
3322
- BaseSectionSettingsSchema,
3323
- CtaSchema,
3324
- } from './base-schemas';
3325
-
3326
- import { HeaderSchema } from '@/components/header';
3327
- import { FooterSchema } from '@/components/footer';
3328
- import { HeroSchema } from '@/components/hero';
3329
- import { FeatureGridSchema } from '@/components/feature-grid';
3330
- import { CodeBlockSchema } from '@/components/code-block';
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 } from '@/components/pillars-grid';
3333
- import { ArchLayersSchema } from '@/components/arch-layers';
3334
- import { ProductTriadSchema } from '@/components/product-triad';
3335
- import { PaSectionSchema } from '@/components/pa-section';
3336
- import { PhilosophySchema } from '@/components/philosophy';
3337
- import { CtaBannerSchema } from '@/components/cta-banner';
3338
- import { ImageBreakSchema } from '@/components/image-break';
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': HeaderSchema,
3342
- 'footer': FooterSchema,
3343
- 'hero': HeroSchema,
3344
- 'feature-grid': FeatureGridSchema,
3345
- 'code-block': CodeBlockSchema,
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': PillarsGridSchema,
3348
- 'arch-layers': ArchLayersSchema,
3349
- 'product-triad': ProductTriadSchema,
3350
- 'pa-section': PaSectionSchema,
3351
- 'philosophy': PhilosophySchema,
3352
- 'cta-banner': CtaBannerSchema,
3353
- 'image-break': ImageBreakSchema,
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, HeaderSettings } from '@/components/header';
3393
- import type { FooterData, FooterSettings } from '@/components/footer';
3394
- import type { HeroData, HeroSettings } from '@/components/hero';
3395
- import type { FeatureGridData, FeatureGridSettings } from '@/components/feature-grid';
3396
- import type { CodeBlockData, CodeBlockSettings } from '@/components/code-block';
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, PillarsGridSettings } from '@/components/pillars-grid';
3399
- import type { ArchLayersData, ArchLayersSettings } from '@/components/arch-layers';
3400
- import type { ProductTriadData, ProductTriadSettings } from '@/components/product-triad';
3401
- import type { PaSectionData, PaSectionSettings } from '@/components/pa-section';
3402
- import type { PhilosophyData, PhilosophySettings } from '@/components/philosophy';
3403
- import type { CtaBannerData, CtaBannerSettings } from '@/components/cta-banner';
3404
- import type { ImageBreakData, ImageBreakSettings } from '@/components/image-break';
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': { data: HeaderData; settings?: HeaderSettings; menu: MenuItem[] };
3408
- 'footer': { data: FooterData; settings?: FooterSettings };
3409
- 'hero': { data: HeroData; settings?: HeroSettings };
3410
- 'feature-grid': { data: FeatureGridData; settings?: FeatureGridSettings };
3411
- 'code-block': { data: CodeBlockData; settings?: CodeBlockSettings };
3412
- 'problem-statement': { data: ProblemStatementData; settings?: ProblemStatementSettings };
3413
- 'pillars-grid': { data: PillarsGridData; settings?: PillarsGridSettings };
3414
- 'arch-layers': { data: ArchLayersData; settings?: ArchLayersSettings };
3415
- 'product-triad': { data: ProductTriadData; settings?: ProductTriadSettings };
3416
- 'pa-section': { data: PaSectionData; settings?: PaSectionSettings };
3417
- 'philosophy': { data: PhilosophyData; settings?: PhilosophySettings };
3418
- 'cta-banner': { data: CtaBannerData; settings?: CtaBannerSettings };
3419
- 'image-break': { data: ImageBreakData; settings?: ImageBreakSettings };
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': HeaderData;
3425
- 'footer': FooterData;
3426
- 'hero': HeroData;
3427
- 'feature-grid': FeatureGridData;
3428
- 'code-block': CodeBlockData;
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': PillarsGridData;
3431
- 'arch-layers': ArchLayersData;
3432
- 'product-triad': ProductTriadData;
3433
- 'pa-section': PaSectionData;
3434
- 'philosophy': PhilosophyData;
3435
- 'cta-banner': CtaBannerData;
3436
- 'image-break': ImageBreakData;
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': HeaderSettings;
3441
- 'footer': FooterSettings;
3442
- 'hero': HeroSettings;
3443
- 'feature-grid': FeatureGridSettings;
3444
- 'code-block': CodeBlockSettings;
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': PillarsGridSettings;
3447
- 'arch-layers': ArchLayersSettings;
3448
- 'product-triad': ProductTriadSettings;
3449
- 'pa-section': PaSectionSettings;
3450
- 'philosophy': PhilosophySettings;
3451
- 'cta-banner': CtaBannerSettings;
3452
- 'image-break': ImageBreakSettings;
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