@jsonpages/cli 3.0.29 → 3.0.32

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,124 +27,120 @@ 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
- * Persistence: Export JSON (download) + optional Salva su repo (POST /api/persist → git push).
34
- */
35
- import { useState, useEffect } from 'react';
36
- import { JsonPagesEngine } from '@jsonpages/core';
37
- import type { LibraryImageEntry } from '@jsonpages/core';
38
- import { ComponentRegistry } from '@/lib/ComponentRegistry';
39
- import { SECTION_SCHEMAS } from '@/lib/schemas';
40
- import { addSectionConfig } from '@/lib/addSectionConfig';
41
- import { getHydratedData } from '@/lib/draftStorage';
42
- import type { JsonPagesConfig } from '@jsonpages/core';
43
- import type { PageConfig, SiteConfig, ThemeConfig, MenuConfig } from '@/types';
44
- import type { ProjectState } from '@jsonpages/core';
45
-
46
- import siteData from '@/data/config/site.json';
47
- import themeData from '@/data/config/theme.json';
48
- import menuData from '@/data/config/menu.json';
49
- import homeData from '@/data/pages/home.json';
50
-
51
- import tenantCss from './index.css?inline';
52
-
53
- const themeConfig = themeData as unknown as ThemeConfig;
54
- const menuConfig = menuData as unknown as MenuConfig;
55
-
56
- const TENANT_ID = 'alpha';
57
- const filePages: Record<string, PageConfig> = {
58
- home: homeData as unknown as PageConfig,
59
- };
60
- const fileSiteConfig = siteData as unknown as SiteConfig;
61
-
62
- const MAX_UPLOAD_SIZE_BYTES = 5 * 1024 * 1024; // 5MB, must match server
63
-
64
- function getInitialData() {
65
- return getHydratedData(TENANT_ID, filePages, fileSiteConfig);
66
- }
67
-
68
- function App() {
69
- const [{ pages, siteConfig }] = useState(getInitialData);
70
- const [assetsManifest, setAssetsManifest] = useState<LibraryImageEntry[]>([]);
71
-
72
- useEffect(() => {
73
- fetch('/api/list-assets')
74
- .then((r) => (r.ok ? r.json() : []))
75
- .then((list: LibraryImageEntry[]) => setAssetsManifest(Array.isArray(list) ? list : []))
76
- .catch(() => setAssetsManifest([]));
77
- }, []);
78
-
79
- const config: JsonPagesConfig = {
80
- tenantId: TENANT_ID,
81
- registry: ComponentRegistry as JsonPagesConfig['registry'],
82
- schemas: SECTION_SCHEMAS as unknown as JsonPagesConfig['schemas'],
83
- pages,
84
- siteConfig,
85
- themeConfig,
86
- menuConfig,
87
- themeCss: { tenant: tenantCss },
88
- addSection: addSectionConfig,
89
- persistence: {
90
- async saveToRepo(state: ProjectState, slug: string) {
91
- const res = await fetch('/api/persist', {
92
- method: 'POST',
93
- headers: { 'Content-Type': 'application/json' },
94
- body: JSON.stringify({ projectState: state, slug }),
95
- });
96
- const body = (await res.json().catch(() => ({}))) as { error?: string; message?: string };
97
- if (!res.ok) {
98
- const msg = body.error ?? `Save failed: ${res.status}`;
99
- alert(msg);
100
- throw new Error(msg);
101
- }
102
- },
103
- },
104
- assets: {
105
- assetsBaseUrl: '/assets',
106
- assetsManifest,
107
- async onAssetUpload(file: File): Promise<string> {
108
- if (!file.type.startsWith('image/')) {
109
- throw new Error('Invalid file type. Allowed: JPEG, PNG, WebP, GIF, SVG, AVIF.');
110
- }
111
- if (file.size > MAX_UPLOAD_SIZE_BYTES) {
112
- throw new Error(`File too large. Maximum size is ${MAX_UPLOAD_SIZE_BYTES / 1024 / 1024}MB.`);
113
- }
114
- const base64 = await new Promise<string>((resolve, reject) => {
115
- const reader = new FileReader();
116
- reader.onload = () => {
117
- const dataUrl = reader.result as string;
118
- const b64 = dataUrl.includes(',') ? dataUrl.split(',')[1] : '';
119
- resolve(b64 || '');
120
- };
121
- reader.onerror = () => reject(reader.error);
122
- reader.readAsDataURL(file);
123
- });
124
- const res = await fetch('/api/upload-asset', {
125
- method: 'POST',
126
- headers: { 'Content-Type': 'application/json' },
127
- body: JSON.stringify({
128
- filename: file.name,
129
- mimeType: file.type || undefined,
130
- data: base64,
131
- }),
132
- });
133
- const body = (await res.json().catch(() => ({}))) as { url?: string; error?: string };
134
- if (!res.ok) {
135
- throw new Error(body.error || `Upload failed: ${res.status}`);
136
- }
137
- if (typeof body.url !== 'string') {
138
- throw new Error('Invalid server response: missing url');
139
- }
140
- return body.url;
141
- },
142
- },
143
- };
144
-
145
- return <JsonPagesEngine config={config} />;
146
- }
147
-
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
+
148
144
  export default App;
149
145
  END_OF_FILE_CONTENT
150
146
  mkdir -p "src/components"
@@ -2333,85 +2329,85 @@ END_OF_FILE_CONTENT
2333
2329
  # SKIP: src/data/config/menu.json:Zone.Identifier è un file binario e non può essere convertito in testo.
2334
2330
  echo "Creating src/data/config/site.json..."
2335
2331
  cat << 'END_OF_FILE_CONTENT' > "src/data/config/site.json"
2336
- {
2337
- "identity": {
2338
- "title": "JsonPages",
2339
- "logoUrl": "/logo.svg"
2340
- },
2341
- "pages": [
2342
- {
2343
- "slug": "home",
2344
- "label": "Home"
2345
- },
2346
- {
2347
- "slug": "architecture",
2348
- "label": "Architecture"
2349
- },
2350
- {
2351
- "slug": "usage",
2352
- "label": "Usage"
2353
- }
2354
- ],
2355
- "header": {
2356
- "id": "global-header",
2357
- "type": "header",
2358
- "data": {
2359
- "logoText": "Json",
2360
- "logoHighlight": "Pages",
2361
- "logoIconText": "{ }",
2362
- "links": [
2363
- {
2364
- "label": "Architecture",
2365
- "href": "#architecture"
2366
- },
2367
- {
2368
- "label": "Products",
2369
- "href": "#products"
2370
- },
2371
- {
2372
- "label": "PA Ready",
2373
- "href": "#pa-ready"
2374
- },
2375
- {
2376
- "label": "Philosophy",
2377
- "href": "#philosophy"
2378
- }
2379
- ]
2380
- },
2381
- "settings": {
2382
- "sticky": true
2383
- }
2384
- },
2385
- "footer": {
2386
- "id": "global-footer",
2387
- "type": "footer",
2388
- "data": {
2389
- "brandText": "Json",
2390
- "brandHighlight": "Pages",
2391
- "copyright": "© 2026 JsonPages Ecosystem. Architected by Guido Filippo Serio.",
2392
- "links": [
2393
- {
2394
- "label": "Documentation",
2395
- "href": "#"
2396
- },
2397
- {
2398
- "label": "API Reference",
2399
- "href": "#"
2400
- },
2401
- {
2402
- "label": "Changelog",
2403
- "href": "#"
2404
- },
2405
- {
2406
- "label": "Privacy",
2407
- "href": "#"
2408
- }
2409
- ]
2410
- },
2411
- "settings": {
2412
- "showLogo": true
2413
- }
2414
- }
2332
+ {
2333
+ "identity": {
2334
+ "title": "JsonPages",
2335
+ "logoUrl": "/logo.svg"
2336
+ },
2337
+ "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
+ }
2350
+ ],
2351
+ "header": {
2352
+ "id": "global-header",
2353
+ "type": "header",
2354
+ "data": {
2355
+ "logoText": "Json",
2356
+ "logoHighlight": "Pages",
2357
+ "logoIconText": "{ }",
2358
+ "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
+ }
2375
+ ]
2376
+ },
2377
+ "settings": {
2378
+ "sticky": true
2379
+ }
2380
+ },
2381
+ "footer": {
2382
+ "id": "global-footer",
2383
+ "type": "footer",
2384
+ "data": {
2385
+ "brandText": "Json",
2386
+ "brandHighlight": "Pages",
2387
+ "copyright": "© 2026 JsonPages Ecosystem. Architected by Guido Filippo Serio.",
2388
+ "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
+ }
2405
+ ]
2406
+ },
2407
+ "settings": {
2408
+ "showLogo": true
2409
+ }
2410
+ }
2415
2411
  }
2416
2412
  END_OF_FILE_CONTENT
2417
2413
  # SKIP: src/data/config/site.json:Zone.Identifier è un file binario e non può essere convertito in testo.
@@ -2450,422 +2446,421 @@ END_OF_FILE_CONTENT
2450
2446
  mkdir -p "src/data/pages"
2451
2447
  echo "Creating src/data/pages/home.json..."
2452
2448
  cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
2453
- {
2454
- "id": "home-page",
2455
- "slug": "home",
2456
- "meta": {
2457
- "title": "JsonPages — Global Authoring. Global Governance.",
2458
- "description": "A high-fidelity Site Factory that solves the scalability bottleneck of traditional CMS systems."
2459
- },
2460
- "sections": [
2461
- {
2462
- "id": "hero-main",
2463
- "type": "hero",
2464
- "data": {
2465
- "badge": "Composable UI Engine · v1.0",
2466
- "title": "Local Authoring.",
2467
- "titleHighlight": "Global Governance.",
2468
- "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.",
2469
- "ctas": [
2470
- {
2471
- "label": "Explore Products →",
2472
- "href": "#products",
2473
- "variant": "primary"
2474
- },
2475
- {
2476
- "label": "Read the Spec",
2477
- "href": "#architecture",
2478
- "variant": "secondary"
2479
- }
2480
- ],
2481
- "metrics": [
2482
- {
2483
- "val": "100+",
2484
- "label": "Pages per tenant"
2485
- },
2486
- {
2487
- "val": "0ms",
2488
- "label": "Runtime overhead"
2489
- },
2490
- {
2491
- "val": "∞",
2492
- "label": "Scalable blocks"
2493
- }
2494
- ]
2495
- },
2496
- "settings": {}
2497
- },
2498
-
2499
- {
2500
- "id": "problem-statement",
2501
- "type": "problem-statement",
2502
- "data": {
2503
- "siloGroups": [
2504
- {
2505
- "blocks": [
2506
- {
2507
- "label": "Agency A Stack",
2508
- "variant": "red"
2509
- },
2510
- {
2511
- "label": "Custom CMS",
2512
- "variant": "red"
2513
- },
2514
- {
2515
- "label": "PHP Backend",
2516
- "variant": "red"
2517
- }
2518
- ],
2519
- "label": "Silo #1"
2520
- },
2521
- {
2522
- "blocks": [
2523
- {
2524
- "label": "Agency B Tools",
2525
- "variant": "amber"
2526
- },
2527
- {
2528
- "label": "WordPress",
2529
- "variant": "amber"
2530
- },
2531
- {
2532
- "label": "LAMP Stack",
2533
- "variant": "amber"
2534
- }
2535
- ],
2536
- "label": "Silo #2"
2537
- },
2538
- {
2539
- "blocks": [
2540
- {
2541
- "label": "Agency C Kit",
2542
- "variant": "green"
2543
- },
2544
- {
2545
- "label": "React App",
2546
- "variant": "green"
2547
- },
2548
- {
2549
- "label": "Node.js",
2550
- "variant": "green"
2551
- }
2552
- ],
2553
- "label": "Silo #3"
2554
- }
2555
- ],
2556
- "title": "Traditional CMS systems create closed silos.",
2557
- "paragraphs": [
2558
- {
2559
- "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.",
2560
- "isBold": false
2561
- },
2562
- {
2563
- "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.",
2564
- "isBold": true
2565
- }
2566
- ],
2567
- "anchorId": "aa"
2568
- },
2569
- "settings": {}
2570
- },
2571
- {
2572
- "id": "architecture-pillars",
2573
- "type": "pillars-grid",
2574
- "data": {
2575
- "anchorId": "architecture",
2576
- "label": "Core Architecture",
2577
- "title": "Three Pillars of Composability",
2578
- "description": "A contract-first architecture designed to scale without core refactoring.",
2579
- "pillars": [
2580
- {
2581
- "icon": "box",
2582
- "iconVariant": "split",
2583
- "title": "The Machine-Readable Blueprint. 📐",
2584
- "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.",
2585
- "tag": "The Foundation",
2586
- "tagVariant": "core"
2587
- },
2588
- {
2589
- "icon": "sparkles",
2590
- "iconVariant": "registry",
2591
- "title": "The Semantic Brain. 🧠",
2592
- "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.",
2593
- "tag": "The Vocabulary",
2594
- "tagVariant": "pattern"
2595
- },
2596
- {
2597
- "icon": "zap",
2598
- "iconVariant": "federation",
2599
- "title": "The 5-Minute Tenant. 🤖",
2600
- "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.",
2601
- "tag": "The Multiplier",
2602
- "tagVariant": "enterprise"
2603
- }
2604
- ]
2605
- },
2606
- "settings": {}
2607
- },
2608
- {
2609
- "id": "layered-architecture",
2610
- "type": "arch-layers",
2611
- "data": {
2612
- "anchorId": "layers",
2613
- "label": "Engineering Layers",
2614
- "title": "Strict Boundaries. Clean Contracts.",
2615
- "description": "Three architectural layers maintain the separation between engine and implementation.",
2616
- "layers": [
2617
- {
2618
- "number": "0",
2619
- "layerLevel": "l0",
2620
- "title": "The Immutable Kernel",
2621
- "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."
2622
- },
2623
- {
2624
- "number": "1",
2625
- "layerLevel": "l1",
2626
- "title": "The Standard Library",
2627
- "description": "Fundamental components (Hero, Text, Grid) used across 90% of projects. Augments the Kernel's registry while remaining part of the internal ecosystem."
2628
- },
2629
- {
2630
- "number": "2",
2631
- "layerLevel": "l2",
2632
- "title": "User-Land Extensions",
2633
- "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."
2634
- }
2635
- ],
2636
- "codeFilename": "libs/shared-data/src/lib/core.ts",
2637
- "codeLines": [
2638
- {
2639
- "content": "// 🛡️ KERNEL — Single Source of Truth",
2640
- "tokenType": "comment"
2641
- },
2642
- {
2643
- "content": "",
2644
- "tokenType": "plain"
2645
- },
2646
- {
2647
- "content": "export interface SectionDataRegistry {",
2648
- "tokenType": "keyword"
2649
- },
2650
- {
2651
- "content": " // Open Registry: modules inject here",
2652
- "tokenType": "comment"
2653
- },
2654
- {
2655
- "content": "}",
2656
- "tokenType": "plain"
2657
- },
2658
- {
2659
- "content": "",
2660
- "tokenType": "plain"
2661
- },
2662
- {
2663
- "content": "export interface BaseSection<K extends keyof SectionDataRegistry> {",
2664
- "tokenType": "keyword"
2665
- },
2666
- {
2667
- "content": " id: string;",
2668
- "tokenType": "type"
2669
- },
2670
- {
2671
- "content": " type: K;",
2672
- "tokenType": "type"
2673
- },
2674
- {
2675
- "content": " data: SectionDataRegistry[K];",
2676
- "tokenType": "type"
2677
- },
2678
- {
2679
- "content": " settings?: SectionSettings;",
2680
- "tokenType": "type"
2681
- },
2682
- {
2683
- "content": "}",
2684
- "tokenType": "plain"
2685
- },
2686
- {
2687
- "content": "",
2688
- "tokenType": "plain"
2689
- },
2690
- {
2691
- "content": "// Computed Union — built automatically from registry",
2692
- "tokenType": "comment"
2693
- },
2694
- {
2695
- "content": "export type Section =",
2696
- "tokenType": "keyword"
2697
- },
2698
- {
2699
- "content": " { [K in keyof SectionDataRegistry]: BaseSection<K> }",
2700
- "tokenType": "type"
2701
- },
2702
- {
2703
- "content": " [keyof SectionDataRegistry];",
2704
- "tokenType": "operator"
2705
- }
2706
- ]
2707
- },
2708
- "settings": {}
2709
- },
2710
- {
2711
- "id": "product-triad",
2712
- "type": "product-triad",
2713
- "data": {
2714
- "anchorId": "products",
2715
- "label": "Product Triad",
2716
- "title": "One Protocol. Three Products.",
2717
- "description": "From solo developers to global enterprises — the same underlying ontology powers every tier.",
2718
- "products": [
2719
- {
2720
- "tier": "Starter",
2721
- "name": "JsonPage",
2722
- "price": "NPM",
2723
- "priceSuffix": "Local Install",
2724
- "delivery": "Static Export · Zero Runtime",
2725
- "features": [
2726
- {
2727
- "text": "Local Authoring SDK"
2728
- },
2729
- {
2730
- "text": "Zero-Runtime HTML Bake"
2731
- },
2732
- {
2733
- "text": "Pure HTML/CSS Export"
2734
- },
2735
- {
2736
- "text": "CDN-Ready Delivery"
2737
- },
2738
- {
2739
- "text": "Full Code Ownership"
2740
- }
2741
- ],
2742
- "featured": false
2743
- },
2744
- {
2745
- "tier": "Professional",
2746
- "name": "JsonPages",
2747
- "price": "SaaS",
2748
- "priceSuffix": "/month",
2749
- "delivery": "Managed Cloud · Multi-Tenant",
2750
- "features": [
2751
- {
2752
- "text": "Multi-Tenant Hosting"
2753
- },
2754
- {
2755
- "text": "Premium Block Library"
2756
- },
2757
- {
2758
- "text": "SchemaUI Components"
2759
- },
2760
- {
2761
- "text": "In-Context Editing"
2762
- },
2763
- {
2764
- "text": "AI-Native Protocol"
2765
- },
2766
- {
2767
- "text": "Tailwind v4 & BI Engine"
2768
- }
2769
- ],
2770
- "featured": false
2771
- },
2772
- {
2773
- "tier": "Enterprise",
2774
- "name": "The Platform",
2775
- "price": "Custom",
2776
- "delivery": "Private Cloud · Enterprise Governance",
2777
- "features": [
2778
- {
2779
- "text": "Private Federated Registry"
2780
- },
2781
- {
2782
- "text": "Module Federation"
2783
- },
2784
- {
2785
- "text": "Runtime Component Injection"
2786
- },
2787
- {
2788
- "text": "Custom Brand Guidelines"
2789
- },
2790
- {
2791
- "text": "Dedicated SLA & Support"
2792
- },
2793
- {
2794
- "text": "SSO & RBAC"
2795
- }
2796
- ],
2797
- "featured": false
2798
- }
2799
- ]
2800
- },
2801
- "settings": {}
2802
- },
2803
- {
2804
- "id": "pa-ready",
2805
- "type": "pa-section",
2806
- "data": {
2807
- "anchorId": "pa-ready",
2808
- "label": "Public Sector",
2809
- "title": "PA Ready — One Platform, Two Markets",
2810
- "subtitle": "The Only Builder with Native Bootstrap Italia.",
2811
- "paragraphs": [
2812
- {
2813
- "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."
2814
- },
2815
- {
2816
- "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."
2817
- }
2818
- ],
2819
- "badges": [
2820
- {
2821
- "label": "PNRR Ready"
2822
- },
2823
- {
2824
- "label": "WCAG 2.1 AA"
2825
- },
2826
- {
2827
- "label": "AgID Compliant"
2828
- }
2829
- ],
2830
- "engines": [
2831
- {
2832
- "label": "Tailwind v4",
2833
- "variant": "tailwind"
2834
- },
2835
- {
2836
- "label": "Bootstrap Italia",
2837
- "variant": "bootstrap"
2838
- }
2839
- ],
2840
- "codeSnippet": "{\n \"type\": \"hero\",\n \"data\": { \"title\": \"...\" },\n \"settings\": { \"engine\": \"auto\" }\n}"
2841
- },
2842
- "settings": {}
2843
- },
2844
- {
2845
- "id": "philosophy",
2846
- "type": "philosophy",
2847
- "data": {
2848
- "anchorId": "philosophy",
2849
- "label": "Our Philosophy",
2850
- "title": "Governance Over Captivity",
2851
- "quote": "We sell the infrastructure to manage UI — not the right to access the code.",
2852
- "quoteHighlightWord": "manage",
2853
- "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."
2854
- },
2855
- "settings": {}
2856
- },
2857
- {
2858
- "id": "cta-final",
2859
- "type": "cta-banner",
2860
- "data": {
2861
- "anchorId": "start",
2862
- "label": "Local Authoring Global Governance",
2863
- "title": "Ready to Govern at Scale?",
2864
- "description": "Whether you're building a single site or managing a portfolio of hundreds — JsonPages scales with your ambition."
2865
- },
2866
- "settings": {}
2867
- }
2868
- ]
2449
+ {
2450
+ "id": "home-page",
2451
+ "slug": "home",
2452
+ "meta": {
2453
+ "title": "JsonPages — Global Authoring. Global Governance.",
2454
+ "description": "A high-fidelity Site Factory that solves the scalability bottleneck of traditional CMS systems."
2455
+ },
2456
+ "sections": [
2457
+ {
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",
2497
+ "data": {
2498
+ "siloGroups": [
2499
+ {
2500
+ "blocks": [
2501
+ {
2502
+ "label": "Agency A Stack",
2503
+ "variant": "red"
2504
+ },
2505
+ {
2506
+ "label": "Custom CMS",
2507
+ "variant": "red"
2508
+ },
2509
+ {
2510
+ "label": "PHP Backend",
2511
+ "variant": "red"
2512
+ }
2513
+ ],
2514
+ "label": "Silo #1"
2515
+ },
2516
+ {
2517
+ "blocks": [
2518
+ {
2519
+ "label": "Agency B Tools",
2520
+ "variant": "amber"
2521
+ },
2522
+ {
2523
+ "label": "WordPress",
2524
+ "variant": "amber"
2525
+ },
2526
+ {
2527
+ "label": "LAMP Stack",
2528
+ "variant": "amber"
2529
+ }
2530
+ ],
2531
+ "label": "Silo #2"
2532
+ },
2533
+ {
2534
+ "blocks": [
2535
+ {
2536
+ "label": "Agency C Kit",
2537
+ "variant": "green"
2538
+ },
2539
+ {
2540
+ "label": "React App",
2541
+ "variant": "green"
2542
+ },
2543
+ {
2544
+ "label": "Node.js",
2545
+ "variant": "green"
2546
+ }
2547
+ ],
2548
+ "label": "Silo #3"
2549
+ }
2550
+ ],
2551
+ "title": "Traditional CMS systems create closed silos.",
2552
+ "paragraphs": [
2553
+ {
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
2556
+ },
2557
+ {
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
2560
+ }
2561
+ ],
2562
+ "anchorId": "aa"
2563
+ },
2564
+ "settings": {}
2565
+ },
2566
+ {
2567
+ "id": "architecture-pillars",
2568
+ "type": "pillars-grid",
2569
+ "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
+ }
2599
+ ]
2600
+ },
2601
+ "settings": {}
2602
+ },
2603
+ {
2604
+ "id": "layered-architecture",
2605
+ "type": "arch-layers",
2606
+ "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.",
2611
+ "layers": [
2612
+ {
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."
2617
+ },
2618
+ {
2619
+ "number": "1",
2620
+ "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."
2623
+ },
2624
+ {
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."
2629
+ }
2630
+ ],
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
+ },
2689
+ {
2690
+ "content": "export type Section =",
2691
+ "tokenType": "keyword"
2692
+ },
2693
+ {
2694
+ "content": " { [K in keyof SectionDataRegistry]: BaseSection<K> }",
2695
+ "tokenType": "type"
2696
+ },
2697
+ {
2698
+ "content": " [keyof SectionDataRegistry];",
2699
+ "tokenType": "operator"
2700
+ }
2701
+ ]
2702
+ },
2703
+ "settings": {}
2704
+ },
2705
+ {
2706
+ "id": "product-triad",
2707
+ "type": "product-triad",
2708
+ "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
+ }
2794
+ ]
2795
+ },
2796
+ "settings": {}
2797
+ },
2798
+ {
2799
+ "id": "pa-ready",
2800
+ "type": "pa-section",
2801
+ "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}"
2836
+ },
2837
+ "settings": {}
2838
+ },
2839
+ {
2840
+ "id": "philosophy",
2841
+ "type": "philosophy",
2842
+ "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."
2849
+ },
2850
+ "settings": {}
2851
+ },
2852
+ {
2853
+ "id": "cta-final",
2854
+ "type": "cta-banner",
2855
+ "data": {
2856
+ "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."
2860
+ },
2861
+ "settings": {}
2862
+ }
2863
+ ]
2869
2864
  }
2870
2865
  END_OF_FILE_CONTENT
2871
2866
  # SKIP: src/data/pages/home.json:Zone.Identifier è un file binario e non può essere convertito in testo.
@@ -2898,180 +2893,180 @@ export const useDocumentMeta = (meta: PageMeta): void => {
2898
2893
  END_OF_FILE_CONTENT
2899
2894
  echo "Creating src/index.css..."
2900
2895
  cat << 'END_OF_FILE_CONTENT' > "src/index.css"
2901
- @import "tailwindcss";
2902
-
2903
- @source "./**/*.tsx";
2904
-
2905
- @theme {
2906
- /*
2907
- 🎯 MASTER MAPPING (V2.7 Landing)
2908
- */
2909
- --color-background: var(--background);
2910
- --color-foreground: var(--foreground);
2911
-
2912
- --color-card: var(--card);
2913
- --color-card-foreground: var(--card-foreground);
2914
-
2915
- --color-primary: var(--primary);
2916
- --color-primary-foreground: var(--primary-foreground);
2917
-
2918
- --color-secondary: var(--secondary);
2919
- --color-secondary-foreground: var(--secondary-foreground);
2920
-
2921
- --color-muted: var(--muted);
2922
- --color-muted-foreground: var(--muted-foreground);
2923
-
2924
- --color-accent: var(--accent);
2925
- --color-border: var(--border);
2926
-
2927
- --radius-lg: var(--radius);
2928
- --radius-md: calc(var(--radius) - 2px);
2929
- --radius-sm: calc(var(--radius) - 4px);
2930
-
2931
- --font-primary: var(--theme-font-primary);
2932
- --font-mono: var(--theme-font-mono);
2933
-
2934
- /*
2935
- 🔧 DISPLAY FONT — Forward-compatible workaround
2936
- theme-manager.ts does NOT inject --theme-font-display (Skeleton v2.7 gap).
2937
- The var() falls through to the hardcoded fallback today.
2938
- When Skeleton v2.8 wires display into theme-manager, the var() will resolve
2939
- automatically and the fallback becomes dead code.
2940
- */
2941
- --font-display: var(--theme-font-display, 'Playfair Display', Georgia, serif);
2942
- }
2943
-
2944
- /*
2945
- 🌍 TENANT BRAND TOKENS (JSP 1.5)
2946
- */
2947
- :root {
2948
- --background: var(--theme-background);
2949
- --foreground: var(--theme-text);
2950
- --card: var(--theme-surface);
2951
- --card-foreground: var(--theme-text);
2952
- --primary: var(--theme-primary);
2953
- --primary-foreground: oklch(0.98 0 0);
2954
- --secondary: var(--theme-secondary);
2955
- --secondary-foreground: var(--theme-text);
2956
- --muted: var(--theme-surface-alt);
2957
- --muted-foreground: var(--theme-text-muted);
2958
- --border: var(--theme-border);
2959
- --radius: 0.45rem;
2960
-
2961
- /*
2962
- 🔧 ACCENT CHAIN — Forward-compatible workaround
2963
- theme-manager.ts already injects --theme-accent on :root,
2964
- but the original index.css never bridged it into the semantic layer.
2965
- This closes the gap: --theme-accent → --accent → --color-accent.
2966
- Falls back to --theme-primary if accent is undefined.
2967
- */
2968
- --accent: var(--theme-accent, var(--theme-primary));
2969
- }
2970
-
2971
- @layer base {
2972
- * { border-color: var(--border); }
2973
- body {
2974
- background-color: var(--background);
2975
- color: var(--foreground);
2976
- font-family: var(--font-primary);
2977
- line-height: 1.7;
2978
- overflow-x: hidden;
2979
- @apply antialiased;
2980
- }
2981
- }
2982
-
2983
- /* ==========================================================================
2984
- FONT DISPLAY UTILITY
2985
- Maps .font-display class to the display font family (Playfair Display)
2986
- ========================================================================== */
2987
- .font-display {
2988
- font-family: var(--font-display, var(--font-primary));
2989
- }
2990
-
2991
- /* ==========================================================================
2992
- LANDING ANIMATIONS
2993
- ========================================================================== */
2994
- @keyframes jp-fadeUp {
2995
- from { opacity: 0; transform: translateY(30px); }
2996
- to { opacity: 1; transform: translateY(0); }
2997
- }
2998
-
2999
- @keyframes jp-pulseDot {
3000
- 0%, 100% { opacity: 1; }
3001
- 50% { opacity: 0.3; }
3002
- }
3003
-
3004
- .jp-animate-in {
3005
- opacity: 0;
3006
- animation: jp-fadeUp 0.7s ease forwards;
3007
- }
3008
- .jp-d1 { animation-delay: 0.1s; }
3009
- .jp-d2 { animation-delay: 0.2s; }
3010
- .jp-d3 { animation-delay: 0.3s; }
3011
- .jp-d4 { animation-delay: 0.4s; }
3012
- .jp-d5 { animation-delay: 0.5s; }
3013
-
3014
- .jp-pulse-dot {
3015
- animation: jp-pulseDot 2s ease infinite;
3016
- }
3017
-
3018
- /* ==========================================================================
3019
- SMOOTH SCROLL
3020
- ========================================================================== */
3021
- html {
3022
- scroll-behavior: smooth;
3023
- }
3024
-
3025
- /* ==========================================================================
3026
- ICE ADMIN — Section highlight in preview iframe
3027
- The preview iframe only receives tenant CSS; core's overlay classes
3028
- (z-[50], absolute, etc.) are not in this build. Define them here so
3029
- the section highlight is always visible in /admin.
3030
- ========================================================================== */
3031
- [data-jp-section-overlay] {
3032
- position: absolute;
3033
- inset: 0;
3034
- z-index: 9999;
3035
- pointer-events: none;
3036
- transition: border-color 0.2s, background-color 0.2s;
3037
- border: 2px solid transparent;
3038
- }
3039
-
3040
- [data-section-id]:hover [data-jp-section-overlay] {
3041
- border-color: rgba(96, 165, 250, 0.5);
3042
- border-style: dashed;
3043
- }
3044
-
3045
- [data-section-id][data-jp-selected] [data-jp-section-overlay] {
3046
- border-color: rgb(37, 99, 235);
3047
- border-style: solid;
3048
- background-color: rgba(59, 130, 246, 0.05);
3049
- }
3050
-
3051
- [data-jp-section-overlay] > div {
3052
- position: absolute;
3053
- top: 0;
3054
- right: 0;
3055
- padding: 0.25rem 0.5rem;
3056
- font-size: 9px;
3057
- font-weight: 800;
3058
- text-transform: uppercase;
3059
- letter-spacing: 0.1em;
3060
- background: rgb(37, 99, 235);
3061
- color: white;
3062
- transition: opacity 0.2s;
3063
- }
3064
-
3065
- [data-section-id]:hover [data-jp-section-overlay] > div,
3066
- [data-section-id][data-jp-selected] [data-jp-section-overlay] > div {
3067
- opacity: 1;
3068
- }
3069
-
3070
- [data-section-id] [data-jp-section-overlay] > div {
3071
- opacity: 0;
3072
- }
3073
-
3074
-
2896
+ @import "tailwindcss";
2897
+
2898
+ @source "./**/*.tsx";
2899
+
2900
+ @theme {
2901
+ /*
2902
+ 🎯 MASTER MAPPING (V2.7 Landing)
2903
+ */
2904
+ --color-background: var(--background);
2905
+ --color-foreground: var(--foreground);
2906
+
2907
+ --color-card: var(--card);
2908
+ --color-card-foreground: var(--card-foreground);
2909
+
2910
+ --color-primary: var(--primary);
2911
+ --color-primary-foreground: var(--primary-foreground);
2912
+
2913
+ --color-secondary: var(--secondary);
2914
+ --color-secondary-foreground: var(--secondary-foreground);
2915
+
2916
+ --color-muted: var(--muted);
2917
+ --color-muted-foreground: var(--muted-foreground);
2918
+
2919
+ --color-accent: var(--accent);
2920
+ --color-border: var(--border);
2921
+
2922
+ --radius-lg: var(--radius);
2923
+ --radius-md: calc(var(--radius) - 2px);
2924
+ --radius-sm: calc(var(--radius) - 4px);
2925
+
2926
+ --font-primary: var(--theme-font-primary);
2927
+ --font-mono: var(--theme-font-mono);
2928
+
2929
+ /*
2930
+ 🔧 DISPLAY FONT — Forward-compatible workaround
2931
+ theme-manager.ts does NOT inject --theme-font-display (Skeleton v2.7 gap).
2932
+ The var() falls through to the hardcoded fallback today.
2933
+ When Skeleton v2.8 wires display into theme-manager, the var() will resolve
2934
+ automatically and the fallback becomes dead code.
2935
+ */
2936
+ --font-display: var(--theme-font-display, 'Playfair Display', Georgia, serif);
2937
+ }
2938
+
2939
+ /*
2940
+ 🌍 TENANT BRAND TOKENS (JSP 1.5)
2941
+ */
2942
+ :root {
2943
+ --background: var(--theme-background);
2944
+ --foreground: var(--theme-text);
2945
+ --card: var(--theme-surface);
2946
+ --card-foreground: var(--theme-text);
2947
+ --primary: var(--theme-primary);
2948
+ --primary-foreground: oklch(0.98 0 0);
2949
+ --secondary: var(--theme-secondary);
2950
+ --secondary-foreground: var(--theme-text);
2951
+ --muted: var(--theme-surface-alt);
2952
+ --muted-foreground: var(--theme-text-muted);
2953
+ --border: var(--theme-border);
2954
+ --radius: 0.45rem;
2955
+
2956
+ /*
2957
+ 🔧 ACCENT CHAIN — Forward-compatible workaround
2958
+ theme-manager.ts already injects --theme-accent on :root,
2959
+ but the original index.css never bridged it into the semantic layer.
2960
+ This closes the gap: --theme-accent → --accent → --color-accent.
2961
+ Falls back to --theme-primary if accent is undefined.
2962
+ */
2963
+ --accent: var(--theme-accent, var(--theme-primary));
2964
+ }
2965
+
2966
+ @layer base {
2967
+ * { border-color: var(--border); }
2968
+ body {
2969
+ background-color: var(--background);
2970
+ color: var(--foreground);
2971
+ font-family: var(--font-primary);
2972
+ line-height: 1.7;
2973
+ overflow-x: hidden;
2974
+ @apply antialiased;
2975
+ }
2976
+ }
2977
+
2978
+ /* ==========================================================================
2979
+ FONT DISPLAY UTILITY
2980
+ Maps .font-display class to the display font family (Playfair Display)
2981
+ ========================================================================== */
2982
+ .font-display {
2983
+ font-family: var(--font-display, var(--font-primary));
2984
+ }
2985
+
2986
+ /* ==========================================================================
2987
+ LANDING ANIMATIONS
2988
+ ========================================================================== */
2989
+ @keyframes jp-fadeUp {
2990
+ from { opacity: 0; transform: translateY(30px); }
2991
+ to { opacity: 1; transform: translateY(0); }
2992
+ }
2993
+
2994
+ @keyframes jp-pulseDot {
2995
+ 0%, 100% { opacity: 1; }
2996
+ 50% { opacity: 0.3; }
2997
+ }
2998
+
2999
+ .jp-animate-in {
3000
+ opacity: 0;
3001
+ animation: jp-fadeUp 0.7s ease forwards;
3002
+ }
3003
+ .jp-d1 { animation-delay: 0.1s; }
3004
+ .jp-d2 { animation-delay: 0.2s; }
3005
+ .jp-d3 { animation-delay: 0.3s; }
3006
+ .jp-d4 { animation-delay: 0.4s; }
3007
+ .jp-d5 { animation-delay: 0.5s; }
3008
+
3009
+ .jp-pulse-dot {
3010
+ animation: jp-pulseDot 2s ease infinite;
3011
+ }
3012
+
3013
+ /* ==========================================================================
3014
+ SMOOTH SCROLL
3015
+ ========================================================================== */
3016
+ html {
3017
+ scroll-behavior: smooth;
3018
+ }
3019
+
3020
+ /* ==========================================================================
3021
+ ICE ADMIN — Section highlight in preview iframe
3022
+ The preview iframe only receives tenant CSS; core's overlay classes
3023
+ (z-[50], absolute, etc.) are not in this build. Define them here so
3024
+ the section highlight is always visible in /admin.
3025
+ ========================================================================== */
3026
+ [data-jp-section-overlay] {
3027
+ position: absolute;
3028
+ inset: 0;
3029
+ z-index: 9999;
3030
+ pointer-events: none;
3031
+ transition: border-color 0.2s, background-color 0.2s;
3032
+ border: 2px solid transparent;
3033
+ }
3034
+
3035
+ [data-section-id]:hover [data-jp-section-overlay] {
3036
+ border-color: rgba(96, 165, 250, 0.5);
3037
+ border-style: dashed;
3038
+ }
3039
+
3040
+ [data-section-id][data-jp-selected] [data-jp-section-overlay] {
3041
+ border-color: rgb(37, 99, 235);
3042
+ border-style: solid;
3043
+ background-color: rgba(59, 130, 246, 0.05);
3044
+ }
3045
+
3046
+ [data-jp-section-overlay] > div {
3047
+ position: absolute;
3048
+ top: 0;
3049
+ right: 0;
3050
+ padding: 0.25rem 0.5rem;
3051
+ font-size: 9px;
3052
+ font-weight: 800;
3053
+ text-transform: uppercase;
3054
+ letter-spacing: 0.1em;
3055
+ background: rgb(37, 99, 235);
3056
+ color: white;
3057
+ transition: opacity 0.2s;
3058
+ }
3059
+
3060
+ [data-section-id]:hover [data-jp-section-overlay] > div,
3061
+ [data-section-id][data-jp-selected] [data-jp-section-overlay] > div {
3062
+ opacity: 1;
3063
+ }
3064
+
3065
+ [data-section-id] [data-jp-section-overlay] > div {
3066
+ opacity: 0;
3067
+ }
3068
+
3069
+
3075
3070
 
3076
3071
  END_OF_FILE_CONTENT
3077
3072
  mkdir -p "src/lib"
@@ -3290,66 +3285,28 @@ END_OF_FILE_CONTENT
3290
3285
  echo "Creating src/lib/draftStorage.ts..."
3291
3286
  cat << 'END_OF_FILE_CONTENT' > "src/lib/draftStorage.ts"
3292
3287
  /**
3293
- * Tenant draft storage — same key and shape as @jsonpages/core autosave.
3294
- * Load from localStorage when present, otherwise use file-backed data.
3288
+ * Tenant initial data — file-backed only (no localStorage).
3295
3289
  */
3296
3290
 
3297
3291
  import type { PageConfig, SiteConfig } from '@/types';
3298
3292
 
3299
- const DRAFT_KEY_PREFIX = 'jsonpages-draft';
3300
-
3301
- export function getDraftStorageKey(tenantId: string, slug: string): string {
3302
- return `${DRAFT_KEY_PREFIX}-${tenantId}-${slug}`;
3303
- }
3304
-
3305
- export interface StoredDraft {
3306
- page?: PageConfig;
3307
- site?: SiteConfig;
3308
- }
3309
-
3310
- export function readDraftFromStorage(
3311
- tenantId: string,
3312
- slug: string
3313
- ): StoredDraft | null {
3314
- if (typeof window === 'undefined') return null;
3315
- try {
3316
- const key = getDraftStorageKey(tenantId, slug);
3317
- const raw = localStorage.getItem(key);
3318
- if (!raw) return null;
3319
- return JSON.parse(raw) as StoredDraft;
3320
- } catch {
3321
- return null;
3322
- }
3323
- }
3324
-
3325
3293
  export interface HydratedData {
3326
3294
  pages: Record<string, PageConfig>;
3327
3295
  siteConfig: SiteConfig;
3328
3296
  }
3329
3297
 
3330
3298
  /**
3331
- * Build pages and siteConfig from localStorage when a draft exists for a slug,
3332
- * otherwise use file-backed data. Uses the same storage key as Core autosave.
3299
+ * Return pages and siteConfig from file-backed data only.
3333
3300
  */
3334
3301
  export function getHydratedData(
3335
- tenantId: string,
3302
+ _tenantId: string,
3336
3303
  filePages: Record<string, PageConfig>,
3337
3304
  fileSiteConfig: SiteConfig
3338
3305
  ): HydratedData {
3339
- let siteConfig = fileSiteConfig;
3340
- const pages: Record<string, PageConfig> = {};
3341
-
3342
- for (const slug of Object.keys(filePages)) {
3343
- const stored = readDraftFromStorage(tenantId, slug);
3344
- if (stored?.page) {
3345
- pages[slug] = stored.page;
3346
- if (stored?.site) siteConfig = stored.site;
3347
- } else {
3348
- pages[slug] = filePages[slug];
3349
- }
3350
- }
3351
-
3352
- return { pages, siteConfig };
3306
+ return {
3307
+ pages: { ...filePages },
3308
+ siteConfig: fileSiteConfig,
3309
+ };
3353
3310
  }
3354
3311
 
3355
3312
  END_OF_FILE_CONTENT
@@ -3429,175 +3386,6 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
3429
3386
  END_OF_FILE_CONTENT
3430
3387
  # SKIP: src/registry-types.ts è un file binario e non può essere convertito in testo.
3431
3388
  mkdir -p "src/server"
3432
- echo "Creating src/server/persistToRepo.ts..."
3433
- cat << 'END_OF_FILE_CONTENT' > "src/server/persistToRepo.ts"
3434
- /**
3435
- * Enterprise-grade persist: write JSON to disk and git add/commit/push.
3436
- * Used by POST /api/persist. Credentials via env (GITHUB_TOKEN); no secrets in code.
3437
- */
3438
- import fs from 'fs';
3439
- import path from 'path';
3440
- import { simpleGit, type SimpleGit } from 'simple-git';
3441
-
3442
- /** Minimal ProjectState shape for validation (matches Core kernel). */
3443
- export interface PersistBody {
3444
- projectState: {
3445
- page: unknown;
3446
- site: unknown;
3447
- menu: unknown;
3448
- theme: unknown;
3449
- };
3450
- slug: string;
3451
- }
3452
-
3453
- const SLUG_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
3454
-
3455
- function isNonEmptyObject(v: unknown): v is Record<string, unknown> {
3456
- return typeof v === 'object' && v !== null && !Array.isArray(v);
3457
- }
3458
-
3459
- export function validatePersistBody(body: unknown): { ok: true; data: PersistBody } | { ok: false; status: number; error: string } {
3460
- if (!isNonEmptyObject(body) || !isNonEmptyObject(body.projectState)) {
3461
- return { ok: false, status: 400, error: 'Missing or invalid projectState' };
3462
- }
3463
- const { projectState, slug } = body as PersistBody;
3464
- if (typeof slug !== 'string' || !SLUG_REGEX.test(slug)) {
3465
- return { ok: false, status: 400, error: 'Invalid slug (use lowercase alphanumeric and hyphens)' };
3466
- }
3467
- if (
3468
- projectState.page == null ||
3469
- projectState.site == null ||
3470
- projectState.menu == null ||
3471
- projectState.theme == null
3472
- ) {
3473
- return { ok: false, status: 400, error: 'projectState must include page, site, menu, theme' };
3474
- }
3475
- return { ok: true, data: { projectState, slug } };
3476
- }
3477
-
3478
- export interface PersistOptions {
3479
- /** Absolute path to data dir (e.g. .../src/data). */
3480
- dataDir: string;
3481
- /** Git working directory (repo root). Defaults to dataDir's app root. */
3482
- gitDir: string;
3483
- /** Optional: inject into origin for push (e.g. GITHUB_TOKEN). */
3484
- token?: string;
3485
- }
3486
-
3487
- function writeFiles(data: PersistBody, dataDir: string): string[] {
3488
- const configDir = path.join(dataDir, 'config');
3489
- const pagesDir = path.join(dataDir, 'pages');
3490
- const written: string[] = [];
3491
-
3492
- [configDir, pagesDir].forEach((dir) => {
3493
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
3494
- });
3495
-
3496
- const { projectState, slug } = data;
3497
- const sitePath = path.join(configDir, 'site.json');
3498
- const themePath = path.join(configDir, 'theme.json');
3499
- const menuPath = path.join(configDir, 'menu.json');
3500
- const pagePath = path.join(pagesDir, `${slug}.json`);
3501
-
3502
- fs.writeFileSync(sitePath, JSON.stringify(projectState.site, null, 2), 'utf8');
3503
- written.push(sitePath);
3504
- fs.writeFileSync(themePath, JSON.stringify(projectState.theme, null, 2), 'utf8');
3505
- written.push(themePath);
3506
- fs.writeFileSync(menuPath, JSON.stringify(projectState.menu, null, 2), 'utf8');
3507
- written.push(menuPath);
3508
- fs.writeFileSync(pagePath, JSON.stringify(projectState.page, null, 2), 'utf8');
3509
- written.push(pagePath);
3510
-
3511
- return written;
3512
- }
3513
-
3514
- /** Inject token into HTTPS origin for push (no token stored in config). */
3515
- function originWithToken(origin: string, token: string): string {
3516
- if (origin.startsWith('https://')) {
3517
- return origin.replace(/^https:\/\/([^@]*@)?/, `https://${token}@`);
3518
- }
3519
- return origin;
3520
- }
3521
-
3522
- async function gitAddCommitPush(
3523
- gitDir: string,
3524
- relativePaths: string[],
3525
- commitMessage: string,
3526
- token?: string
3527
- ): Promise<{ success: true } | { success: false; error: string }> {
3528
- const git: SimpleGit = simpleGit(gitDir);
3529
- let originalOrigin: string | null = null;
3530
-
3531
- try {
3532
- const status = await git.status();
3533
- if (status.isBare()) {
3534
- return { success: false, error: 'Repository is bare; cannot commit' };
3535
- }
3536
-
3537
- if (token) {
3538
- const remotes = await git.getRemotes(true);
3539
- const origin = remotes.origin?.refs?.fetch ?? remotes.origin?.refs?.push;
3540
- if (origin) {
3541
- originalOrigin = origin;
3542
- const authUrl = originWithToken(origin, token);
3543
- await git.raw(['remote', 'set-url', 'origin', authUrl]);
3544
- }
3545
- }
3546
-
3547
- await git.add(relativePaths);
3548
- const commitResult = await git.commit(commitMessage);
3549
- if (commitResult.commit === null && relativePaths.length > 0) {
3550
- return { success: false, error: 'Nothing to commit (files unchanged or not in repo)' };
3551
- }
3552
- await git.push();
3553
- return { success: true };
3554
- } catch (e) {
3555
- const msg = e instanceof Error ? e.message : String(e);
3556
- return { success: false, error: msg };
3557
- } finally {
3558
- if (originalOrigin) {
3559
- try {
3560
- await git.raw(['remote', 'set-url', 'origin', originalOrigin]);
3561
- } catch {
3562
- // best-effort restore; do not mask original error
3563
- }
3564
- }
3565
- }
3566
- }
3567
-
3568
- /**
3569
- * Write project state to disk and push to remote.
3570
- * File paths are resolved from dataDir; git paths are relative to gitDir.
3571
- */
3572
- export async function runPersist(
3573
- data: PersistBody,
3574
- options: PersistOptions
3575
- ): Promise<{ success: true; message: string } | { success: false; status: number; error: string }> {
3576
- const { dataDir, gitDir, token } = options;
3577
-
3578
- if (!fs.existsSync(dataDir)) {
3579
- return { success: false, status: 500, error: 'Data directory does not exist' };
3580
- }
3581
-
3582
- const written = writeFiles(data, dataDir);
3583
- const relativePaths = written.map((p) => path.relative(gitDir, p)).filter((p) => !p.startsWith('..'));
3584
- if (relativePaths.length === 0) {
3585
- return { success: false, status: 500, error: 'Computed paths are outside git directory' };
3586
- }
3587
-
3588
- const commitMessage = `jsonpages: persist ${data.slug} @ ${new Date().toISOString().slice(0, 19)}Z`;
3589
- const result = await gitAddCommitPush(gitDir, relativePaths, commitMessage, token);
3590
-
3591
- if (result.success) {
3592
- return { success: true, message: 'Saved and pushed to remote' };
3593
- }
3594
- if (result.error.toLowerCase().includes('failed to push') || result.error.toLowerCase().includes('authentication')) {
3595
- return { success: false, status: 502, error: result.error };
3596
- }
3597
- return { success: false, status: 500, error: result.error };
3598
- }
3599
-
3600
- END_OF_FILE_CONTENT
3601
3389
  echo "Creating src/types.ts..."
3602
3390
  cat << 'END_OF_FILE_CONTENT' > "src/types.ts"
3603
3391
  import type { MenuItem } from '@jsonpages/core';