@jsonpages/cli 3.0.40 → 3.0.42

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.
Files changed (2) hide show
  1. package/assets/src_tenant_alpha.sh +1452 -1452
  2. package/package.json +29 -29
@@ -27,147 +27,147 @@ 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
- * Supports Hybrid Persistence: Local Filesystem (Dev) or Cloud Bridge (Prod).
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, ProjectState } from '@jsonpages/core';
43
- import type { SiteConfig, ThemeConfig, MenuConfig } from '@/types';
44
-
45
- import siteData from '@/data/config/site.json';
46
- import themeData from '@/data/config/theme.json';
47
- import menuData from '@/data/config/menu.json';
48
- import { getFilePages } from '@/lib/getFilePages';
49
-
50
- import fontsCss from './fonts.css?inline';
51
- import tenantCss from './index.css?inline';
52
-
53
- // Cloud Configuration (Injected by Vercel/Netlify Env Vars)
54
- const CLOUD_API_URL = import.meta.env.VITE_JSONPAGES_CLOUD_URL;
55
- const CLOUD_API_KEY = import.meta.env.VITE_JSONPAGES_API_KEY;
56
-
57
- const themeConfig = themeData as unknown as ThemeConfig;
58
- const menuConfig = menuData as unknown as MenuConfig;
59
- const TENANT_ID = 'alpha';
60
-
61
- const filePages = getFilePages();
62
- const fileSiteConfig = siteData as unknown as SiteConfig;
63
- const MAX_UPLOAD_SIZE_BYTES = 5 * 1024 * 1024;
64
-
65
- function getInitialData() {
66
- return getHydratedData(TENANT_ID, filePages, fileSiteConfig);
67
- }
68
-
69
- function App() {
70
- const [{ pages, siteConfig }] = useState(getInitialData);
71
- const [assetsManifest, setAssetsManifest] = useState<LibraryImageEntry[]>([]);
72
-
73
- useEffect(() => {
74
- // In Cloud mode, listing assets might be different or disabled for MVP
75
- // For now, we keep the local fetch which will fail gracefully on Vercel (404)
76
- fetch('/api/list-assets')
77
- .then((r) => (r.ok ? r.json() : []))
78
- .then((list: LibraryImageEntry[]) => setAssetsManifest(Array.isArray(list) ? list : []))
79
- .catch(() => setAssetsManifest([]));
80
- }, []);
81
-
82
- console.log("🔍 DEBUG ENV:", {
83
- URL: import.meta.env.VITE_JSONPAGES_CLOUD_URL,
84
- KEY: import.meta.env.VITE_JSONPAGES_API_KEY ? "PRESENT" : "MISSING"
85
- });
86
- const config: JsonPagesConfig = {
87
- tenantId: TENANT_ID,
88
- registry: ComponentRegistry as JsonPagesConfig['registry'],
89
- schemas: SECTION_SCHEMAS as unknown as JsonPagesConfig['schemas'],
90
- pages,
91
- siteConfig,
92
- themeConfig,
93
- menuConfig,
94
- themeCss: { tenant: fontsCss + '\n' + tenantCss },
95
- addSection: addSectionConfig,
96
- persistence: {
97
- async saveToFile(state: ProjectState, slug: string): Promise<void> {
98
-
99
- // ☁️ SCENARIO A: CLOUD BRIDGE (Production)
100
- if (CLOUD_API_URL && CLOUD_API_KEY) {
101
- console.log(`☁️ Saving ${slug} via Cloud Bridge...`);
102
-
103
- const res = await fetch(`${CLOUD_API_URL}/save`, {
104
- method: 'POST',
105
- headers: {
106
- 'Content-Type': 'application/json',
107
- 'Authorization': `Bearer ${CLOUD_API_KEY}`
108
- },
109
- body: JSON.stringify({
110
- // Mapping logical slug to physical path in repo
111
- path: `src/data/pages/${slug}.json`,
112
- // We save the page config specifically
113
- content: state.page,
114
- message: `Content update for ${slug} via Visual Editor`
115
- }),
116
- });
117
-
118
- if (!res.ok) {
119
- const err = await res.json().catch(() => ({}));
120
- throw new Error(err.error || `Cloud save failed: ${res.status}`);
121
- }
122
- return;
123
- }
124
-
125
- // 💻 SCENARIO B: LOCAL FILESYSTEM (Development)
126
- console.log(`💻 Saving ${slug} to Local Filesystem...`);
127
- const res = await fetch('/api/save-to-file', {
128
- method: 'POST',
129
- headers: { 'Content-Type': 'application/json' },
130
- body: JSON.stringify({ projectState: state, slug }),
131
- });
132
-
133
- const body = (await res.json().catch(() => ({}))) as { error?: string };
134
- if (!res.ok) throw new Error(body.error ?? `Save to file failed: ${res.status}`);
135
- },
136
- },
137
- assets: {
138
- assetsBaseUrl: '/assets',
139
- assetsManifest,
140
- async onAssetUpload(file: File): Promise<string> {
141
- // Note: Asset upload in Cloud Mode requires the R2 Bridge (Next Step in Roadmap)
142
- // For now, this works in Local Mode.
143
- if (!file.type.startsWith('image/')) throw new Error('Invalid file type.');
144
- if (file.size > MAX_UPLOAD_SIZE_BYTES) throw new Error(`File too large. Max ${MAX_UPLOAD_SIZE_BYTES / 1024 / 1024}MB.`);
145
-
146
- const base64 = await new Promise<string>((resolve, reject) => {
147
- const reader = new FileReader();
148
- reader.onload = () => resolve((reader.result as string).split(',')[1] ?? '');
149
- reader.onerror = () => reject(reader.error);
150
- reader.readAsDataURL(file);
151
- });
152
-
153
- const res = await fetch('/api/upload-asset', {
154
- method: 'POST',
155
- headers: { 'Content-Type': 'application/json' },
156
- body: JSON.stringify({ filename: file.name, mimeType: file.type || undefined, data: base64 }),
157
- });
158
-
159
- const body = (await res.json().catch(() => ({}))) as { url?: string; error?: string };
160
- if (!res.ok) throw new Error(body.error || `Upload failed: ${res.status}`);
161
- if (typeof body.url !== 'string') throw new Error('Invalid server response: missing url');
162
- return body.url;
163
- },
164
- },
165
- };
166
-
167
- return <JsonPagesEngine config={config} />;
168
- }
169
-
170
- export default App;
30
+ /**
31
+ * Thin Entry Point (Tenant).
32
+ * Data from getHydratedData (file-backed or draft); assets from public/assets/images.
33
+ * Supports Hybrid Persistence: Local Filesystem (Dev) or Cloud Bridge (Prod).
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, ProjectState } from '@jsonpages/core';
43
+ import type { SiteConfig, ThemeConfig, MenuConfig } from '@/types';
44
+
45
+ import siteData from '@/data/config/site.json';
46
+ import themeData from '@/data/config/theme.json';
47
+ import menuData from '@/data/config/menu.json';
48
+ import { getFilePages } from '@/lib/getFilePages';
49
+
50
+ import fontsCss from './fonts.css?inline';
51
+ import tenantCss from './index.css?inline';
52
+
53
+ // Cloud Configuration (Injected by Vercel/Netlify Env Vars)
54
+ const CLOUD_API_URL = import.meta.env.VITE_JSONPAGES_CLOUD_URL;
55
+ const CLOUD_API_KEY = import.meta.env.VITE_JSONPAGES_API_KEY;
56
+
57
+ const themeConfig = themeData as unknown as ThemeConfig;
58
+ const menuConfig = menuData as unknown as MenuConfig;
59
+ const TENANT_ID = 'alpha';
60
+
61
+ const filePages = getFilePages();
62
+ const fileSiteConfig = siteData as unknown as SiteConfig;
63
+ const MAX_UPLOAD_SIZE_BYTES = 5 * 1024 * 1024;
64
+
65
+ function getInitialData() {
66
+ return getHydratedData(TENANT_ID, filePages, fileSiteConfig);
67
+ }
68
+
69
+ function App() {
70
+ const [{ pages, siteConfig }] = useState(getInitialData);
71
+ const [assetsManifest, setAssetsManifest] = useState<LibraryImageEntry[]>([]);
72
+
73
+ useEffect(() => {
74
+ // In Cloud mode, listing assets might be different or disabled for MVP
75
+ // For now, we keep the local fetch which will fail gracefully on Vercel (404)
76
+ fetch('/api/list-assets')
77
+ .then((r) => (r.ok ? r.json() : []))
78
+ .then((list: LibraryImageEntry[]) => setAssetsManifest(Array.isArray(list) ? list : []))
79
+ .catch(() => setAssetsManifest([]));
80
+ }, []);
81
+
82
+ console.log("🔍 DEBUG ENV:", {
83
+ URL: import.meta.env.VITE_JSONPAGES_CLOUD_URL,
84
+ KEY: import.meta.env.VITE_JSONPAGES_API_KEY ? "PRESENT" : "MISSING"
85
+ });
86
+ const config: JsonPagesConfig = {
87
+ tenantId: TENANT_ID,
88
+ registry: ComponentRegistry as JsonPagesConfig['registry'],
89
+ schemas: SECTION_SCHEMAS as unknown as JsonPagesConfig['schemas'],
90
+ pages,
91
+ siteConfig,
92
+ themeConfig,
93
+ menuConfig,
94
+ themeCss: { tenant: fontsCss + '\n' + tenantCss },
95
+ addSection: addSectionConfig,
96
+ persistence: {
97
+ async saveToFile(state: ProjectState, slug: string): Promise<void> {
98
+
99
+ // ☁️ SCENARIO A: CLOUD BRIDGE (Production)
100
+ if (CLOUD_API_URL && CLOUD_API_KEY) {
101
+ console.log(`☁️ Saving ${slug} via Cloud Bridge...`);
102
+
103
+ const res = await fetch(`${CLOUD_API_URL}/save`, {
104
+ method: 'POST',
105
+ headers: {
106
+ 'Content-Type': 'application/json',
107
+ 'Authorization': `Bearer ${CLOUD_API_KEY}`
108
+ },
109
+ body: JSON.stringify({
110
+ // Mapping logical slug to physical path in repo
111
+ path: `src/data/pages/${slug}.json`,
112
+ // We save the page config specifically
113
+ content: state.page,
114
+ message: `Content update for ${slug} via Visual Editor`
115
+ }),
116
+ });
117
+
118
+ if (!res.ok) {
119
+ const err = await res.json().catch(() => ({}));
120
+ throw new Error(err.error || `Cloud save failed: ${res.status}`);
121
+ }
122
+ return;
123
+ }
124
+
125
+ // 💻 SCENARIO B: LOCAL FILESYSTEM (Development)
126
+ console.log(`💻 Saving ${slug} to Local Filesystem...`);
127
+ const res = await fetch('/api/save-to-file', {
128
+ method: 'POST',
129
+ headers: { 'Content-Type': 'application/json' },
130
+ body: JSON.stringify({ projectState: state, slug }),
131
+ });
132
+
133
+ const body = (await res.json().catch(() => ({}))) as { error?: string };
134
+ if (!res.ok) throw new Error(body.error ?? `Save to file failed: ${res.status}`);
135
+ },
136
+ },
137
+ assets: {
138
+ assetsBaseUrl: '/assets',
139
+ assetsManifest,
140
+ async onAssetUpload(file: File): Promise<string> {
141
+ // Note: Asset upload in Cloud Mode requires the R2 Bridge (Next Step in Roadmap)
142
+ // For now, this works in Local Mode.
143
+ if (!file.type.startsWith('image/')) throw new Error('Invalid file type.');
144
+ if (file.size > MAX_UPLOAD_SIZE_BYTES) throw new Error(`File too large. Max ${MAX_UPLOAD_SIZE_BYTES / 1024 / 1024}MB.`);
145
+
146
+ const base64 = await new Promise<string>((resolve, reject) => {
147
+ const reader = new FileReader();
148
+ reader.onload = () => resolve((reader.result as string).split(',')[1] ?? '');
149
+ reader.onerror = () => reject(reader.error);
150
+ reader.readAsDataURL(file);
151
+ });
152
+
153
+ const res = await fetch('/api/upload-asset', {
154
+ method: 'POST',
155
+ headers: { 'Content-Type': 'application/json' },
156
+ body: JSON.stringify({ filename: file.name, mimeType: file.type || undefined, data: base64 }),
157
+ });
158
+
159
+ const body = (await res.json().catch(() => ({}))) as { url?: string; error?: string };
160
+ if (!res.ok) throw new Error(body.error || `Upload failed: ${res.status}`);
161
+ if (typeof body.url !== 'string') throw new Error('Invalid server response: missing url');
162
+ return body.url;
163
+ },
164
+ },
165
+ };
166
+
167
+ return <JsonPagesEngine config={config} />;
168
+ }
169
+
170
+ export default App;
171
171
 
172
172
  END_OF_FILE_CONTENT
173
173
  # SKIP: src/App.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
@@ -305,161 +305,161 @@ END_OF_FILE_CONTENT
305
305
  mkdir -p "src/components/arch-layers"
306
306
  echo "Creating src/components/arch-layers/View.tsx..."
307
307
  cat << 'END_OF_FILE_CONTENT' > "src/components/arch-layers/View.tsx"
308
- import React from 'react';
309
- import { cn } from '@/lib/utils';
310
- import type { ArchLayersData, ArchLayersSettings, ArchLayerLevel, SyntaxTokenType } from './types';
311
-
312
- const layerBgStyles: Record<ArchLayerLevel, string> = {
313
- l0: 'bg-[#3b82f6]',
314
- l1: 'bg-[rgba(59,130,246,0.6)]',
315
- l2: 'bg-[rgba(59,130,246,0.35)]',
316
- };
317
-
318
- const tokenStyles: Record<SyntaxTokenType, string> = {
319
- plain: 'text-[#cbd5e1]',
320
- keyword: 'text-[#60a5fa]',
321
- type: 'text-[#22d3ee]',
322
- string: 'text-[#4ade80]',
323
- comment: 'text-[#64748b] italic',
324
- operator: 'text-[#f472b6]',
325
- };
326
-
327
- export const ArchLayers: React.FC<{ data: ArchLayersData; settings?: ArchLayersSettings }> = ({ data }) => {
328
- return (
329
- <section
330
- style={{
331
- '--local-bg': 'var(--card)',
332
- '--local-text': 'var(--foreground)',
333
- '--local-text-muted': 'var(--muted-foreground)',
334
- '--local-primary': 'var(--primary)',
335
- '--local-accent': 'var(--color-accent, #60a5fa)',
336
- '--local-border': 'var(--border)',
337
- '--local-deep': 'var(--background)',
338
- } as React.CSSProperties}
339
- className="relative z-0 py-28 bg-[var(--local-bg)]"
340
- >
341
- <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" />
342
- <div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.1)] to-transparent" />
343
- <div className="max-w-[1200px] mx-auto px-8">
344
- <div className="text-center">
345
- {data.label && (
346
- <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">
347
- <span className="w-5 h-px bg-[var(--local-primary)]" />
348
- {data.label}
349
- </div>
350
- )}
351
- <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
352
- {data.title}
353
- </h2>
354
- {data.description && (
355
- <p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed" data-jp-field="description">
356
- {data.description}
357
- </p>
358
- )}
359
- </div>
360
- <div className="mt-14 max-w-[740px] mx-auto">
361
- {data.layers.map((layer, idx) => (
362
- <div
363
- key={layer.id ?? idx}
364
- className="group border border-[rgba(255,255,255,0.06)] rounded-[7px] p-8 mb-4 bg-[rgba(255,255,255,0.015)] flex items-start gap-6 transition-all duration-300 hover:border-[rgba(59,130,246,0.2)] hover:translate-x-1.5"
365
- data-jp-item-id={layer.id ?? `legacy-${idx}`}
366
- data-jp-item-field="layers"
367
- >
368
- <div className={cn(
369
- 'shrink-0 w-9 h-9 rounded-lg flex items-center justify-center font-mono text-[0.85rem] font-bold text-white',
370
- layerBgStyles[layer.layerLevel]
371
- )}>
372
- {layer.number}
373
- </div>
374
- <div>
375
- <h4 className="text-[1.05rem] font-bold text-[var(--local-text)] mb-1.5">
376
- {layer.title}
377
- </h4>
378
- <p className="text-[0.92rem] text-[var(--local-text-muted)] leading-relaxed">
379
- {layer.description}
380
- </p>
381
- </div>
382
- </div>
383
- ))}
384
- </div>
385
- {data.codeLines && data.codeLines.length > 0 && (
386
- <div className="mt-12 max-w-[740px] mx-auto">
387
- <div className="border border-[rgba(255,255,255,0.08)] rounded-[7px] overflow-hidden bg-[var(--local-deep)]">
388
- <div className="flex items-center gap-2 px-5 py-3 bg-[rgba(255,255,255,0.03)] border-b border-[rgba(255,255,255,0.06)]">
389
- <span className="w-2.5 h-2.5 rounded-full bg-[#ef4444]" />
390
- <span className="w-2.5 h-2.5 rounded-full bg-[#f59e0b]" />
391
- <span className="w-2.5 h-2.5 rounded-full bg-[#22c55e]" />
392
- {data.codeFilename && (
393
- <span className="ml-3 font-mono text-[0.75rem] text-[var(--local-text-muted)] opacity-60" data-jp-field="codeFilename">
394
- {data.codeFilename}
395
- </span>
396
- )}
397
- </div>
398
- <div className="p-6 font-mono text-[0.82rem] leading-[1.7] overflow-x-auto">
399
- {data.codeLines.map((line, idx) => (
400
- <div key={idx} data-jp-item-id={(line as { id?: string }).id ?? `legacy-${idx}`} data-jp-item-field="codeLines">
401
- <span className={tokenStyles[line.tokenType]}>
402
- {line.content}
403
- </span>
404
- </div>
405
- ))}
406
- </div>
407
- </div>
408
- </div>
409
- )}
410
- </div>
411
- </section>
412
- );
413
- };
308
+ import React from 'react';
309
+ import { cn } from '@/lib/utils';
310
+ import type { ArchLayersData, ArchLayersSettings, ArchLayerLevel, SyntaxTokenType } from './types';
311
+
312
+ const layerBgStyles: Record<ArchLayerLevel, string> = {
313
+ l0: 'bg-[#3b82f6]',
314
+ l1: 'bg-[rgba(59,130,246,0.6)]',
315
+ l2: 'bg-[rgba(59,130,246,0.35)]',
316
+ };
317
+
318
+ const tokenStyles: Record<SyntaxTokenType, string> = {
319
+ plain: 'text-[#cbd5e1]',
320
+ keyword: 'text-[#60a5fa]',
321
+ type: 'text-[#22d3ee]',
322
+ string: 'text-[#4ade80]',
323
+ comment: 'text-[#64748b] italic',
324
+ operator: 'text-[#f472b6]',
325
+ };
326
+
327
+ export const ArchLayers: React.FC<{ data: ArchLayersData; settings?: ArchLayersSettings }> = ({ data }) => {
328
+ return (
329
+ <section
330
+ style={{
331
+ '--local-bg': 'var(--card)',
332
+ '--local-text': 'var(--foreground)',
333
+ '--local-text-muted': 'var(--muted-foreground)',
334
+ '--local-primary': 'var(--primary)',
335
+ '--local-accent': 'var(--color-accent, #60a5fa)',
336
+ '--local-border': 'var(--border)',
337
+ '--local-deep': 'var(--background)',
338
+ } as React.CSSProperties}
339
+ className="relative z-0 py-28 bg-[var(--local-bg)]"
340
+ >
341
+ <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" />
342
+ <div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.1)] to-transparent" />
343
+ <div className="max-w-[1200px] mx-auto px-8">
344
+ <div className="text-center">
345
+ {data.label && (
346
+ <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">
347
+ <span className="w-5 h-px bg-[var(--local-primary)]" />
348
+ {data.label}
349
+ </div>
350
+ )}
351
+ <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
352
+ {data.title}
353
+ </h2>
354
+ {data.description && (
355
+ <p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed" data-jp-field="description">
356
+ {data.description}
357
+ </p>
358
+ )}
359
+ </div>
360
+ <div className="mt-14 max-w-[740px] mx-auto">
361
+ {data.layers.map((layer, idx) => (
362
+ <div
363
+ key={layer.id ?? idx}
364
+ className="group border border-[rgba(255,255,255,0.06)] rounded-[7px] p-8 mb-4 bg-[rgba(255,255,255,0.015)] flex items-start gap-6 transition-all duration-300 hover:border-[rgba(59,130,246,0.2)] hover:translate-x-1.5"
365
+ data-jp-item-id={layer.id ?? `legacy-${idx}`}
366
+ data-jp-item-field="layers"
367
+ >
368
+ <div className={cn(
369
+ 'shrink-0 w-9 h-9 rounded-lg flex items-center justify-center font-mono text-[0.85rem] font-bold text-white',
370
+ layerBgStyles[layer.layerLevel]
371
+ )}>
372
+ {layer.number}
373
+ </div>
374
+ <div>
375
+ <h4 className="text-[1.05rem] font-bold text-[var(--local-text)] mb-1.5">
376
+ {layer.title}
377
+ </h4>
378
+ <p className="text-[0.92rem] text-[var(--local-text-muted)] leading-relaxed">
379
+ {layer.description}
380
+ </p>
381
+ </div>
382
+ </div>
383
+ ))}
384
+ </div>
385
+ {data.codeLines && data.codeLines.length > 0 && (
386
+ <div className="mt-12 max-w-[740px] mx-auto">
387
+ <div className="border border-[rgba(255,255,255,0.08)] rounded-[7px] overflow-hidden bg-[var(--local-deep)]">
388
+ <div className="flex items-center gap-2 px-5 py-3 bg-[rgba(255,255,255,0.03)] border-b border-[rgba(255,255,255,0.06)]">
389
+ <span className="w-2.5 h-2.5 rounded-full bg-[#ef4444]" />
390
+ <span className="w-2.5 h-2.5 rounded-full bg-[#f59e0b]" />
391
+ <span className="w-2.5 h-2.5 rounded-full bg-[#22c55e]" />
392
+ {data.codeFilename && (
393
+ <span className="ml-3 font-mono text-[0.75rem] text-[var(--local-text-muted)] opacity-60" data-jp-field="codeFilename">
394
+ {data.codeFilename}
395
+ </span>
396
+ )}
397
+ </div>
398
+ <div className="p-6 font-mono text-[0.82rem] leading-[1.7] overflow-x-auto">
399
+ {data.codeLines.map((line, idx) => (
400
+ <div key={idx} data-jp-item-id={(line as { id?: string }).id ?? `legacy-${idx}`} data-jp-item-field="codeLines">
401
+ <span className={tokenStyles[line.tokenType]}>
402
+ {line.content}
403
+ </span>
404
+ </div>
405
+ ))}
406
+ </div>
407
+ </div>
408
+ </div>
409
+ )}
410
+ </div>
411
+ </section>
412
+ );
413
+ };
414
414
 
415
415
  END_OF_FILE_CONTENT
416
416
  echo "Creating src/components/arch-layers/index.ts..."
417
417
  cat << 'END_OF_FILE_CONTENT' > "src/components/arch-layers/index.ts"
418
- export * from './View';
419
- export * from './schema';
420
- export * from './types';
418
+ export * from './View';
419
+ export * from './schema';
420
+ export * from './types';
421
421
 
422
422
  END_OF_FILE_CONTENT
423
423
  echo "Creating src/components/arch-layers/schema.ts..."
424
424
  cat << 'END_OF_FILE_CONTENT' > "src/components/arch-layers/schema.ts"
425
- import { z } from 'zod';
426
- import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
427
-
428
- export const ArchLayerLevelSchema = z.enum(['l0', 'l1', 'l2']);
429
- export const SyntaxTokenTypeSchema = z.enum(['plain', 'keyword', 'type', 'string', 'comment', 'operator']);
430
-
431
- const ArchLayerItemSchema = BaseArrayItem.extend({
432
- number: z.string().describe('ui:text'),
433
- layerLevel: ArchLayerLevelSchema.describe('ui:select'),
434
- title: z.string().describe('ui:text'),
435
- description: z.string().describe('ui:textarea'),
436
- });
437
-
438
- const SyntaxLineSchema = z.object({
439
- content: z.string().describe('ui:text'),
440
- tokenType: SyntaxTokenTypeSchema.default('plain').describe('ui:select'),
441
- });
442
-
443
- export const ArchLayersSchema = BaseSectionData.extend({
444
- label: z.string().optional().describe('ui:text'),
445
- title: z.string().describe('ui:text'),
446
- description: z.string().optional().describe('ui:textarea'),
447
- layers: z.array(ArchLayerItemSchema).describe('ui:list'),
448
- codeFilename: z.string().optional().describe('ui:text'),
449
- codeLines: z.array(SyntaxLineSchema).optional().describe('ui:list'),
450
- });
425
+ import { z } from 'zod';
426
+ import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
427
+
428
+ export const ArchLayerLevelSchema = z.enum(['l0', 'l1', 'l2']);
429
+ export const SyntaxTokenTypeSchema = z.enum(['plain', 'keyword', 'type', 'string', 'comment', 'operator']);
430
+
431
+ const ArchLayerItemSchema = BaseArrayItem.extend({
432
+ number: z.string().describe('ui:text'),
433
+ layerLevel: ArchLayerLevelSchema.describe('ui:select'),
434
+ title: z.string().describe('ui:text'),
435
+ description: z.string().describe('ui:textarea'),
436
+ });
437
+
438
+ const SyntaxLineSchema = z.object({
439
+ content: z.string().describe('ui:text'),
440
+ tokenType: SyntaxTokenTypeSchema.default('plain').describe('ui:select'),
441
+ });
442
+
443
+ export const ArchLayersSchema = BaseSectionData.extend({
444
+ label: z.string().optional().describe('ui:text'),
445
+ title: z.string().describe('ui:text'),
446
+ description: z.string().optional().describe('ui:textarea'),
447
+ layers: z.array(ArchLayerItemSchema).describe('ui:list'),
448
+ codeFilename: z.string().optional().describe('ui:text'),
449
+ codeLines: z.array(SyntaxLineSchema).optional().describe('ui:list'),
450
+ });
451
451
 
452
452
  END_OF_FILE_CONTENT
453
453
  echo "Creating src/components/arch-layers/types.ts..."
454
454
  cat << 'END_OF_FILE_CONTENT' > "src/components/arch-layers/types.ts"
455
- import { z } from 'zod';
456
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
457
- import { ArchLayersSchema, ArchLayerLevelSchema, SyntaxTokenTypeSchema } from './schema';
458
-
459
- export type ArchLayersData = z.infer<typeof ArchLayersSchema>;
460
- export type ArchLayersSettings = z.infer<typeof BaseSectionSettingsSchema>;
461
- export type ArchLayerLevel = z.infer<typeof ArchLayerLevelSchema>;
462
- export type SyntaxTokenType = z.infer<typeof SyntaxTokenTypeSchema>;
455
+ import { z } from 'zod';
456
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
457
+ import { ArchLayersSchema, ArchLayerLevelSchema, SyntaxTokenTypeSchema } from './schema';
458
+
459
+ export type ArchLayersData = z.infer<typeof ArchLayersSchema>;
460
+ export type ArchLayersSettings = z.infer<typeof BaseSectionSettingsSchema>;
461
+ export type ArchLayerLevel = z.infer<typeof ArchLayerLevelSchema>;
462
+ export type SyntaxTokenType = z.infer<typeof SyntaxTokenTypeSchema>;
463
463
 
464
464
  END_OF_FILE_CONTENT
465
465
  mkdir -p "src/components/cli-section"
@@ -832,100 +832,100 @@ END_OF_FILE_CONTENT
832
832
  mkdir -p "src/components/code-block"
833
833
  echo "Creating src/components/code-block/View.tsx..."
834
834
  cat << 'END_OF_FILE_CONTENT' > "src/components/code-block/View.tsx"
835
- import React from 'react';
836
- import { cn } from '@/lib/utils';
837
- import { Icon } from '@/lib/IconResolver';
838
- import type { CodeBlockData, CodeBlockSettings } from './types';
839
-
840
- export const CodeBlock: React.FC<{ data: CodeBlockData; settings?: CodeBlockSettings }> = ({ data, settings }) => {
841
- const showLineNumbers = settings?.showLineNumbers ?? true;
842
-
843
- return (
844
- <section
845
- style={{
846
- '--local-surface': 'var(--card)',
847
- '--local-text-muted': 'var(--muted-foreground)',
848
- '--local-bg': 'var(--background)',
849
- '--local-border': 'var(--border)',
850
- '--local-text': 'var(--foreground)',
851
- '--local-accent': 'var(--primary)',
852
- '--local-radius-lg': 'var(--radius)',
853
- } as React.CSSProperties}
854
- className="py-16 bg-[var(--local-surface)]"
855
- >
856
- <div className="container mx-auto px-6 max-w-4xl">
857
- {data.label && (
858
- <div className="flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-[var(--local-text-muted)] mb-4" data-jp-field="label">
859
- <Icon name="terminal" size={14} />
860
- <span>{data.label}</span>
861
- </div>
862
- )}
863
- <div className="rounded-[var(--local-radius-lg)] bg-[var(--local-bg)] border border-[var(--local-border)] overflow-hidden">
864
- <div className="p-6 font-mono text-sm overflow-x-auto">
865
- {data.lines.map((line, idx) => (
866
- <div key={idx} className="flex items-start gap-4 py-1" data-jp-item-id={(line as { id?: string }).id ?? `legacy-${idx}`} data-jp-item-field="lines">
867
- {showLineNumbers && (
868
- <span className="select-none w-6 text-right text-[var(--local-text-muted)]/50">
869
- {idx + 1}
870
- </span>
871
- )}
872
- <span
873
- className={cn(
874
- line.isComment
875
- ? 'text-[var(--local-text-muted)]/60'
876
- : 'text-[var(--local-text)]'
877
- )}
878
- >
879
- {!line.isComment && (
880
- <span className="text-[var(--local-accent)] mr-2">$</span>
881
- )}
882
- {line.content}
883
- </span>
884
- </div>
885
- ))}
886
- </div>
887
- </div>
888
- </div>
889
- </section>
890
- );
891
- };
835
+ import React from 'react';
836
+ import { cn } from '@/lib/utils';
837
+ import { Icon } from '@/lib/IconResolver';
838
+ import type { CodeBlockData, CodeBlockSettings } from './types';
839
+
840
+ export const CodeBlock: React.FC<{ data: CodeBlockData; settings?: CodeBlockSettings }> = ({ data, settings }) => {
841
+ const showLineNumbers = settings?.showLineNumbers ?? true;
842
+
843
+ return (
844
+ <section
845
+ style={{
846
+ '--local-surface': 'var(--card)',
847
+ '--local-text-muted': 'var(--muted-foreground)',
848
+ '--local-bg': 'var(--background)',
849
+ '--local-border': 'var(--border)',
850
+ '--local-text': 'var(--foreground)',
851
+ '--local-accent': 'var(--primary)',
852
+ '--local-radius-lg': 'var(--radius)',
853
+ } as React.CSSProperties}
854
+ className="py-16 bg-[var(--local-surface)]"
855
+ >
856
+ <div className="container mx-auto px-6 max-w-4xl">
857
+ {data.label && (
858
+ <div className="flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-[var(--local-text-muted)] mb-4" data-jp-field="label">
859
+ <Icon name="terminal" size={14} />
860
+ <span>{data.label}</span>
861
+ </div>
862
+ )}
863
+ <div className="rounded-[var(--local-radius-lg)] bg-[var(--local-bg)] border border-[var(--local-border)] overflow-hidden">
864
+ <div className="p-6 font-mono text-sm overflow-x-auto">
865
+ {data.lines.map((line, idx) => (
866
+ <div key={idx} className="flex items-start gap-4 py-1" data-jp-item-id={(line as { id?: string }).id ?? `legacy-${idx}`} data-jp-item-field="lines">
867
+ {showLineNumbers && (
868
+ <span className="select-none w-6 text-right text-[var(--local-text-muted)]/50">
869
+ {idx + 1}
870
+ </span>
871
+ )}
872
+ <span
873
+ className={cn(
874
+ line.isComment
875
+ ? 'text-[var(--local-text-muted)]/60'
876
+ : 'text-[var(--local-text)]'
877
+ )}
878
+ >
879
+ {!line.isComment && (
880
+ <span className="text-[var(--local-accent)] mr-2">$</span>
881
+ )}
882
+ {line.content}
883
+ </span>
884
+ </div>
885
+ ))}
886
+ </div>
887
+ </div>
888
+ </div>
889
+ </section>
890
+ );
891
+ };
892
892
 
893
893
  END_OF_FILE_CONTENT
894
894
  echo "Creating src/components/code-block/index.ts..."
895
895
  cat << 'END_OF_FILE_CONTENT' > "src/components/code-block/index.ts"
896
- export * from './View';
897
- export * from './schema';
898
- export * from './types';
896
+ export * from './View';
897
+ export * from './schema';
898
+ export * from './types';
899
899
 
900
900
  END_OF_FILE_CONTENT
901
901
  echo "Creating src/components/code-block/schema.ts..."
902
902
  cat << 'END_OF_FILE_CONTENT' > "src/components/code-block/schema.ts"
903
- import { z } from 'zod';
904
- import { BaseSectionData } from '@/lib/base-schemas';
905
-
906
- export const LegacyCodeLineSchema = z.object({
907
- content: z.string().describe('ui:text'),
908
- isComment: z.boolean().default(false).describe('ui:checkbox'),
909
- });
910
-
911
- export const CodeBlockSchema = BaseSectionData.extend({
912
- label: z.string().optional().describe('ui:text'),
913
- lines: z.array(LegacyCodeLineSchema).describe('ui:list'),
914
- });
915
-
916
- export const CodeBlockSettingsSchema = z.object({
917
- showLineNumbers: z.boolean().optional().describe('ui:checkbox'),
918
- });
903
+ import { z } from 'zod';
904
+ import { BaseSectionData } from '@/lib/base-schemas';
905
+
906
+ export const LegacyCodeLineSchema = z.object({
907
+ content: z.string().describe('ui:text'),
908
+ isComment: z.boolean().default(false).describe('ui:checkbox'),
909
+ });
910
+
911
+ export const CodeBlockSchema = BaseSectionData.extend({
912
+ label: z.string().optional().describe('ui:text'),
913
+ lines: z.array(LegacyCodeLineSchema).describe('ui:list'),
914
+ });
915
+
916
+ export const CodeBlockSettingsSchema = z.object({
917
+ showLineNumbers: z.boolean().optional().describe('ui:checkbox'),
918
+ });
919
919
 
920
920
  END_OF_FILE_CONTENT
921
921
  echo "Creating src/components/code-block/types.ts..."
922
922
  cat << 'END_OF_FILE_CONTENT' > "src/components/code-block/types.ts"
923
- import { z } from 'zod';
924
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
925
- import { CodeBlockSchema, CodeBlockSettingsSchema } from './schema';
926
-
927
- export type CodeBlockData = z.infer<typeof CodeBlockSchema>;
928
- export type CodeBlockSettings = z.infer<typeof BaseSectionSettingsSchema> & z.infer<typeof CodeBlockSettingsSchema>;
923
+ import { z } from 'zod';
924
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
925
+ import { CodeBlockSchema, CodeBlockSettingsSchema } from './schema';
926
+
927
+ export type CodeBlockData = z.infer<typeof CodeBlockSchema>;
928
+ export type CodeBlockSettings = z.infer<typeof BaseSectionSettingsSchema> & z.infer<typeof CodeBlockSettingsSchema>;
929
929
 
930
930
  END_OF_FILE_CONTENT
931
931
  mkdir -p "src/components/cta-banner"
@@ -1002,9 +1002,9 @@ export const CtaBanner: React.FC<{ data: CtaBannerData; settings?: CtaBannerSett
1002
1002
  END_OF_FILE_CONTENT
1003
1003
  echo "Creating src/components/cta-banner/index.ts..."
1004
1004
  cat << 'END_OF_FILE_CONTENT' > "src/components/cta-banner/index.ts"
1005
- export * from './View';
1006
- export * from './schema';
1007
- export * from './types';
1005
+ export * from './View';
1006
+ export * from './schema';
1007
+ export * from './types';
1008
1008
 
1009
1009
  END_OF_FILE_CONTENT
1010
1010
  echo "Creating src/components/cta-banner/schema.ts..."
@@ -1542,195 +1542,195 @@ END_OF_FILE_CONTENT
1542
1542
  mkdir -p "src/components/feature-grid"
1543
1543
  echo "Creating src/components/feature-grid/View.tsx..."
1544
1544
  cat << 'END_OF_FILE_CONTENT' > "src/components/feature-grid/View.tsx"
1545
- import React from 'react';
1546
- import { cn } from '@/lib/utils';
1547
- import { Icon } from '@/lib/IconResolver';
1548
- import type { FeatureGridData, FeatureGridSettings } from './types';
1549
-
1550
- const columnsMap: Record<2 | 3 | 4, string> = {
1551
- 2: 'md:grid-cols-2',
1552
- 3: 'md:grid-cols-3',
1553
- 4: 'md:grid-cols-4',
1554
- };
1555
-
1556
- export const FeatureGrid: React.FC<{ data: FeatureGridData; settings?: FeatureGridSettings }> = ({ data, settings }) => {
1557
- const colKey = settings?.columns ?? 3;
1558
- const cols = (colKey === 2 || colKey === 3 || colKey === 4) ? columnsMap[colKey] : columnsMap[3];
1559
- const isBordered = settings?.cardStyle === 'bordered';
1560
-
1561
- const localStyles = {
1562
- '--local-bg': 'var(--background)',
1563
- '--local-text': 'var(--foreground)',
1564
- '--local-text-muted': 'var(--muted-foreground)',
1565
- '--local-surface': 'var(--card)',
1566
- '--local-surface-alt': 'var(--muted)',
1567
- '--local-border': 'var(--border)',
1568
- '--local-radius-lg': 'var(--radius)',
1569
- '--local-radius-md': 'calc(var(--radius) - 2px)',
1570
- } as React.CSSProperties;
1571
-
1572
- return (
1573
- <section style={localStyles} className="py-20 bg-[var(--local-bg)] relative z-0">
1574
- <div className="container mx-auto px-6">
1575
- <h2 className="text-3xl md:text-4xl font-bold text-center text-[var(--local-text)] mb-16" data-jp-field="sectionTitle">
1576
- {data.sectionTitle}
1577
- </h2>
1578
- <div className={cn('grid grid-cols-1 gap-6', cols)}>
1579
- {data.cards.map((card, idx) => (
1580
- <div
1581
- key={card.id ?? idx}
1582
- className={cn(
1583
- 'p-6 rounded-[var(--local-radius-lg)] bg-[var(--local-surface)]',
1584
- isBordered && 'border border-[var(--local-border)]'
1585
- )}
1586
- data-jp-item-id={card.id ?? `legacy-${idx}`}
1587
- data-jp-item-field="cards"
1588
- >
1589
- {card.icon && (
1590
- <div className="w-10 h-10 rounded-[var(--local-radius-md)] bg-[var(--local-surface-alt)] flex items-center justify-center mb-4">
1591
- <Icon name={card.icon} size={20} className="text-[var(--local-text-muted)]" />
1592
- </div>
1593
- )}
1594
- <h3 className="text-lg font-semibold text-[var(--local-text)] mb-2">
1595
- {card.emoji && <span className="mr-2">{card.emoji}</span>}
1596
- {card.title}
1597
- </h3>
1598
- <p className="text-sm text-[var(--local-text-muted)] leading-relaxed">
1599
- {card.description}
1600
- </p>
1601
- </div>
1602
- ))}
1603
- </div>
1604
- </div>
1605
- </section>
1606
- );
1607
- };
1545
+ import React from 'react';
1546
+ import { cn } from '@/lib/utils';
1547
+ import { Icon } from '@/lib/IconResolver';
1548
+ import type { FeatureGridData, FeatureGridSettings } from './types';
1549
+
1550
+ const columnsMap: Record<2 | 3 | 4, string> = {
1551
+ 2: 'md:grid-cols-2',
1552
+ 3: 'md:grid-cols-3',
1553
+ 4: 'md:grid-cols-4',
1554
+ };
1555
+
1556
+ export const FeatureGrid: React.FC<{ data: FeatureGridData; settings?: FeatureGridSettings }> = ({ data, settings }) => {
1557
+ const colKey = settings?.columns ?? 3;
1558
+ const cols = (colKey === 2 || colKey === 3 || colKey === 4) ? columnsMap[colKey] : columnsMap[3];
1559
+ const isBordered = settings?.cardStyle === 'bordered';
1560
+
1561
+ const localStyles = {
1562
+ '--local-bg': 'var(--background)',
1563
+ '--local-text': 'var(--foreground)',
1564
+ '--local-text-muted': 'var(--muted-foreground)',
1565
+ '--local-surface': 'var(--card)',
1566
+ '--local-surface-alt': 'var(--muted)',
1567
+ '--local-border': 'var(--border)',
1568
+ '--local-radius-lg': 'var(--radius)',
1569
+ '--local-radius-md': 'calc(var(--radius) - 2px)',
1570
+ } as React.CSSProperties;
1571
+
1572
+ return (
1573
+ <section style={localStyles} className="py-20 bg-[var(--local-bg)] relative z-0">
1574
+ <div className="container mx-auto px-6">
1575
+ <h2 className="text-3xl md:text-4xl font-bold text-center text-[var(--local-text)] mb-16" data-jp-field="sectionTitle">
1576
+ {data.sectionTitle}
1577
+ </h2>
1578
+ <div className={cn('grid grid-cols-1 gap-6', cols)}>
1579
+ {data.cards.map((card, idx) => (
1580
+ <div
1581
+ key={card.id ?? idx}
1582
+ className={cn(
1583
+ 'p-6 rounded-[var(--local-radius-lg)] bg-[var(--local-surface)]',
1584
+ isBordered && 'border border-[var(--local-border)]'
1585
+ )}
1586
+ data-jp-item-id={card.id ?? `legacy-${idx}`}
1587
+ data-jp-item-field="cards"
1588
+ >
1589
+ {card.icon && (
1590
+ <div className="w-10 h-10 rounded-[var(--local-radius-md)] bg-[var(--local-surface-alt)] flex items-center justify-center mb-4">
1591
+ <Icon name={card.icon} size={20} className="text-[var(--local-text-muted)]" />
1592
+ </div>
1593
+ )}
1594
+ <h3 className="text-lg font-semibold text-[var(--local-text)] mb-2">
1595
+ {card.emoji && <span className="mr-2">{card.emoji}</span>}
1596
+ {card.title}
1597
+ </h3>
1598
+ <p className="text-sm text-[var(--local-text-muted)] leading-relaxed">
1599
+ {card.description}
1600
+ </p>
1601
+ </div>
1602
+ ))}
1603
+ </div>
1604
+ </div>
1605
+ </section>
1606
+ );
1607
+ };
1608
1608
 
1609
1609
  END_OF_FILE_CONTENT
1610
1610
  echo "Creating src/components/feature-grid/index.ts..."
1611
1611
  cat << 'END_OF_FILE_CONTENT' > "src/components/feature-grid/index.ts"
1612
- export * from './View';
1613
- export * from './schema';
1614
- export * from './types';
1612
+ export * from './View';
1613
+ export * from './schema';
1614
+ export * from './types';
1615
1615
 
1616
1616
  END_OF_FILE_CONTENT
1617
1617
  echo "Creating src/components/feature-grid/schema.ts..."
1618
1618
  cat << 'END_OF_FILE_CONTENT' > "src/components/feature-grid/schema.ts"
1619
- import { z } from 'zod';
1620
- import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
1621
-
1622
- export const FeatureCardSchema = BaseArrayItem.extend({
1623
- icon: z.string().optional().describe('ui:icon-picker'),
1624
- emoji: z.string().optional().describe('ui:text'),
1625
- title: z.string().describe('ui:text'),
1626
- description: z.string().describe('ui:textarea'),
1627
- });
1628
-
1629
- export const FeatureGridSchema = BaseSectionData.extend({
1630
- sectionTitle: z.string().describe('ui:text'),
1631
- cards: z.array(FeatureCardSchema).describe('ui:list'),
1632
- });
1633
-
1634
- export const FeatureGridSettingsSchema = z.object({
1635
- columns: z.union([z.literal(2), z.literal(3), z.literal(4)]).optional().describe('ui:number'),
1636
- cardStyle: z.enum(['plain', 'bordered']).optional().describe('ui:select'),
1637
- });
1619
+ import { z } from 'zod';
1620
+ import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
1621
+
1622
+ export const FeatureCardSchema = BaseArrayItem.extend({
1623
+ icon: z.string().optional().describe('ui:icon-picker'),
1624
+ emoji: z.string().optional().describe('ui:text'),
1625
+ title: z.string().describe('ui:text'),
1626
+ description: z.string().describe('ui:textarea'),
1627
+ });
1628
+
1629
+ export const FeatureGridSchema = BaseSectionData.extend({
1630
+ sectionTitle: z.string().describe('ui:text'),
1631
+ cards: z.array(FeatureCardSchema).describe('ui:list'),
1632
+ });
1633
+
1634
+ export const FeatureGridSettingsSchema = z.object({
1635
+ columns: z.union([z.literal(2), z.literal(3), z.literal(4)]).optional().describe('ui:number'),
1636
+ cardStyle: z.enum(['plain', 'bordered']).optional().describe('ui:select'),
1637
+ });
1638
1638
 
1639
1639
  END_OF_FILE_CONTENT
1640
1640
  echo "Creating src/components/feature-grid/types.ts..."
1641
1641
  cat << 'END_OF_FILE_CONTENT' > "src/components/feature-grid/types.ts"
1642
- import { z } from 'zod';
1643
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
1644
- import { FeatureGridSchema, FeatureGridSettingsSchema } from './schema';
1645
-
1646
- export type FeatureGridData = z.infer<typeof FeatureGridSchema>;
1647
- export type FeatureGridSettings = z.infer<typeof BaseSectionSettingsSchema> & z.infer<typeof FeatureGridSettingsSchema>;
1642
+ import { z } from 'zod';
1643
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
1644
+ import { FeatureGridSchema, FeatureGridSettingsSchema } from './schema';
1645
+
1646
+ export type FeatureGridData = z.infer<typeof FeatureGridSchema>;
1647
+ export type FeatureGridSettings = z.infer<typeof BaseSectionSettingsSchema> & z.infer<typeof FeatureGridSettingsSchema>;
1648
1648
 
1649
1649
  END_OF_FILE_CONTENT
1650
1650
  mkdir -p "src/components/footer"
1651
1651
  echo "Creating src/components/footer/View.tsx..."
1652
1652
  cat << 'END_OF_FILE_CONTENT' > "src/components/footer/View.tsx"
1653
- import React from 'react';
1654
- import type { FooterData, FooterSettings } from './types';
1655
-
1656
- export const Footer: React.FC<{ data: FooterData; settings?: FooterSettings }> = ({ data }) => {
1657
- return (
1658
- <footer
1659
- style={{
1660
- '--local-bg': 'var(--background)',
1661
- '--local-text': 'var(--foreground)',
1662
- '--local-text-muted': 'var(--muted-foreground)',
1663
- '--local-accent': 'var(--color-accent, #60a5fa)',
1664
- '--local-border': 'rgba(255,255,255,0.05)',
1665
- } as React.CSSProperties}
1666
- className="py-12 border-t border-[var(--local-border)] bg-[var(--local-bg)] relative z-0"
1667
- >
1668
- <div className="max-w-[1200px] mx-auto px-8">
1669
- <div className="flex flex-col md:flex-row items-center justify-between gap-4">
1670
- <div className="flex items-center gap-2 font-bold text-[0.9rem] text-[var(--local-text-muted)]" data-jp-field="brandText">
1671
- {data.brandText}
1672
- {data.brandHighlight && (
1673
- <span className="text-[var(--local-accent)]" data-jp-field="brandHighlight">{data.brandHighlight}</span>
1674
- )}
1675
- </div>
1676
- {data.links && data.links.length > 0 && (
1677
- <nav className="flex gap-6">
1678
- {data.links.map((link, idx) => (
1679
- <a
1680
- key={idx}
1681
- href={link.href}
1682
- className="text-[0.82rem] text-[var(--local-text-muted)] hover:text-[var(--local-accent)] transition-colors no-underline"
1683
- data-jp-item-id={(link as { id?: string }).id ?? `legacy-${idx}`}
1684
- data-jp-item-field="links"
1685
- >
1686
- {link.label}
1687
- </a>
1688
- ))}
1689
- </nav>
1690
- )}
1691
- <div className="text-[0.8rem] text-[var(--local-text-muted)] opacity-60" data-jp-field="copyright">
1692
- {data.copyright}
1693
- </div>
1694
- </div>
1695
- </div>
1696
- </footer>
1697
- );
1698
- };
1653
+ import React from 'react';
1654
+ import type { FooterData, FooterSettings } from './types';
1655
+
1656
+ export const Footer: React.FC<{ data: FooterData; settings?: FooterSettings }> = ({ data }) => {
1657
+ return (
1658
+ <footer
1659
+ style={{
1660
+ '--local-bg': 'var(--background)',
1661
+ '--local-text': 'var(--foreground)',
1662
+ '--local-text-muted': 'var(--muted-foreground)',
1663
+ '--local-accent': 'var(--color-accent, #60a5fa)',
1664
+ '--local-border': 'rgba(255,255,255,0.05)',
1665
+ } as React.CSSProperties}
1666
+ className="py-12 border-t border-[var(--local-border)] bg-[var(--local-bg)] relative z-0"
1667
+ >
1668
+ <div className="max-w-[1200px] mx-auto px-8">
1669
+ <div className="flex flex-col md:flex-row items-center justify-between gap-4">
1670
+ <div className="flex items-center gap-2 font-bold text-[0.9rem] text-[var(--local-text-muted)]" data-jp-field="brandText">
1671
+ {data.brandText}
1672
+ {data.brandHighlight && (
1673
+ <span className="text-[var(--local-accent)]" data-jp-field="brandHighlight">{data.brandHighlight}</span>
1674
+ )}
1675
+ </div>
1676
+ {data.links && data.links.length > 0 && (
1677
+ <nav className="flex gap-6">
1678
+ {data.links.map((link, idx) => (
1679
+ <a
1680
+ key={idx}
1681
+ href={link.href}
1682
+ className="text-[0.82rem] text-[var(--local-text-muted)] hover:text-[var(--local-accent)] transition-colors no-underline"
1683
+ data-jp-item-id={(link as { id?: string }).id ?? `legacy-${idx}`}
1684
+ data-jp-item-field="links"
1685
+ >
1686
+ {link.label}
1687
+ </a>
1688
+ ))}
1689
+ </nav>
1690
+ )}
1691
+ <div className="text-[0.8rem] text-[var(--local-text-muted)] opacity-60" data-jp-field="copyright">
1692
+ {data.copyright}
1693
+ </div>
1694
+ </div>
1695
+ </div>
1696
+ </footer>
1697
+ );
1698
+ };
1699
1699
 
1700
1700
  END_OF_FILE_CONTENT
1701
1701
  echo "Creating src/components/footer/index.ts..."
1702
1702
  cat << 'END_OF_FILE_CONTENT' > "src/components/footer/index.ts"
1703
- export * from './View';
1704
- export * from './schema';
1705
- export * from './types';
1703
+ export * from './View';
1704
+ export * from './schema';
1705
+ export * from './types';
1706
1706
 
1707
1707
  END_OF_FILE_CONTENT
1708
1708
  echo "Creating src/components/footer/schema.ts..."
1709
1709
  cat << 'END_OF_FILE_CONTENT' > "src/components/footer/schema.ts"
1710
- import { z } from 'zod';
1711
-
1712
- export const FooterSchema = z.object({
1713
- brandText: z.string().describe('ui:text'),
1714
- brandHighlight: z.string().optional().describe('ui:text'),
1715
- copyright: z.string().describe('ui:text'),
1716
- links: z.array(z.object({
1717
- label: z.string().describe('ui:text'),
1718
- href: z.string().describe('ui:text'),
1719
- })).optional().describe('ui:list'),
1720
- });
1721
-
1722
- export const FooterSettingsSchema = z.object({
1723
- showLogo: z.boolean().optional().describe('ui:checkbox'),
1724
- });
1710
+ import { z } from 'zod';
1711
+
1712
+ export const FooterSchema = z.object({
1713
+ brandText: z.string().describe('ui:text'),
1714
+ brandHighlight: z.string().optional().describe('ui:text'),
1715
+ copyright: z.string().describe('ui:text'),
1716
+ links: z.array(z.object({
1717
+ label: z.string().describe('ui:text'),
1718
+ href: z.string().describe('ui:text'),
1719
+ })).optional().describe('ui:list'),
1720
+ });
1721
+
1722
+ export const FooterSettingsSchema = z.object({
1723
+ showLogo: z.boolean().optional().describe('ui:checkbox'),
1724
+ });
1725
1725
 
1726
1726
  END_OF_FILE_CONTENT
1727
1727
  echo "Creating src/components/footer/types.ts..."
1728
1728
  cat << 'END_OF_FILE_CONTENT' > "src/components/footer/types.ts"
1729
- import { z } from 'zod';
1730
- import { FooterSchema, FooterSettingsSchema } from './schema';
1731
-
1732
- export type FooterData = z.infer<typeof FooterSchema>;
1733
- export type FooterSettings = z.infer<typeof FooterSettingsSchema>;
1729
+ import { z } from 'zod';
1730
+ import { FooterSchema, FooterSettingsSchema } from './schema';
1731
+
1732
+ export type FooterData = z.infer<typeof FooterSchema>;
1733
+ export type FooterSettings = z.infer<typeof FooterSettingsSchema>;
1734
1734
 
1735
1735
  END_OF_FILE_CONTENT
1736
1736
  mkdir -p "src/components/git-section"
@@ -1893,118 +1893,118 @@ END_OF_FILE_CONTENT
1893
1893
  mkdir -p "src/components/header"
1894
1894
  echo "Creating src/components/header/View.tsx..."
1895
1895
  cat << 'END_OF_FILE_CONTENT' > "src/components/header/View.tsx"
1896
- import React, { useState, useEffect } from 'react';
1897
- import { cn } from '@/lib/utils';
1898
- import type { MenuItem } from '@jsonpages/core';
1899
- import type { HeaderData, HeaderSettings } from './types';
1900
-
1901
- export const Header: React.FC<{
1902
- data: HeaderData;
1903
- settings?: HeaderSettings;
1904
- menu: MenuItem[];
1905
- }> = ({ data, menu }) => {
1906
- const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
1907
- const [scrolled, setScrolled] = useState(false);
1908
-
1909
- useEffect(() => {
1910
- const handleScroll = () => setScrolled(window.scrollY > 40);
1911
- window.addEventListener('scroll', handleScroll, { passive: true });
1912
- return () => window.removeEventListener('scroll', handleScroll);
1913
- }, []);
1914
-
1915
- return (
1916
- <header
1917
- style={{
1918
- '--local-bg': 'rgba(6,13,27,0.92)',
1919
- '--local-text': 'var(--foreground)',
1920
- '--local-text-muted': 'var(--muted-foreground)',
1921
- '--local-primary': 'var(--primary)',
1922
- '--local-accent': 'var(--color-accent, #60a5fa)',
1923
- '--local-border': 'rgba(59,130,246,0.08)',
1924
- } as React.CSSProperties}
1925
- className={cn(
1926
- 'w-full py-4 transition-all duration-300 z-0',
1927
- scrolled
1928
- ? 'bg-[var(--local-bg)] backdrop-blur-[20px] border-b border-[var(--local-border)]'
1929
- : 'bg-transparent border-b border-transparent'
1930
- )}
1931
- >
1932
- <div className="max-w-[1200px] mx-auto px-8 flex justify-between items-center">
1933
- <a
1934
- href="/"
1935
- className="flex items-center gap-2.5 no-underline font-bold text-xl tracking-tight text-[var(--local-text)]"
1936
- >
1937
- {data.logoIconText && (
1938
- <div className="w-8 h-8 rounded-md bg-gradient-to-br from-[var(--local-primary)] to-[var(--local-accent)] flex items-center justify-center font-mono text-[0.8rem] font-bold text-[var(--background)]" data-jp-field="logoIconText">
1939
- {data.logoIconText}
1940
- </div>
1941
- )}
1942
- <span data-jp-field="logoText">
1943
- {data.logoText}
1944
- {data.logoHighlight && (
1945
- <span className="text-[var(--local-accent)]" data-jp-field="logoHighlight">{data.logoHighlight}</span>
1946
- )}
1947
- </span>
1948
- </a>
1949
-
1950
- <nav className="hidden md:flex items-center gap-10">
1951
- {menu.map((item, idx) => (
1952
- <a
1953
- key={(item as { id?: string }).id ?? idx}
1954
- href={item.href}
1955
- data-jp-item-id={(item as { id?: string }).id ?? `legacy-${idx}`}
1956
- data-jp-item-field="links"
1957
- target={item.external ? '_blank' : undefined}
1958
- rel={item.external ? 'noopener noreferrer' : undefined}
1959
- className={cn(
1960
- 'no-underline text-sm font-medium transition-colors',
1961
- item.isCta
1962
- ? 'bg-[var(--local-primary)] text-white px-5 py-2 rounded-lg font-semibold hover:brightness-110 hover:-translate-y-px'
1963
- : 'text-[var(--local-text-muted)] hover:text-[var(--local-text)]'
1964
- )}
1965
- >
1966
- {item.label}
1967
- </a>
1968
- ))}
1969
- </nav>
1970
-
1971
- <button
1972
- type="button"
1973
- className="md:hidden p-2 text-[var(--local-text-muted)] hover:text-[var(--local-text)]"
1974
- onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
1975
- aria-label={mobileMenuOpen ? 'Close menu' : 'Open menu'}
1976
- >
1977
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
1978
- {mobileMenuOpen ? (
1979
- <><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></>
1980
- ) : (
1981
- <><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="18" x2="21" y2="18" /></>
1982
- )}
1983
- </svg>
1984
- </button>
1985
- </div>
1986
-
1987
- {mobileMenuOpen && (
1988
- <nav className="md:hidden border-t border-[var(--local-border)] bg-[var(--local-bg)] backdrop-blur-[20px]">
1989
- <div className="max-w-[1200px] mx-auto px-8 py-4 flex flex-col gap-4">
1990
- {menu.map((item, idx) => (
1991
- <a
1992
- key={(item as { id?: string }).id ?? idx}
1993
- href={item.href}
1994
- className="text-base font-medium text-[var(--local-text-muted)] hover:text-[var(--local-text)] transition-colors py-2 no-underline"
1995
- onClick={() => setMobileMenuOpen(false)}
1996
- data-jp-item-id={(item as { id?: string }).id ?? `legacy-${idx}`}
1997
- data-jp-item-field="links"
1998
- >
1999
- {item.label}
2000
- </a>
2001
- ))}
2002
- </div>
2003
- </nav>
2004
- )}
2005
- </header>
2006
- );
2007
- };
1896
+ import React, { useState, useEffect } from 'react';
1897
+ import { cn } from '@/lib/utils';
1898
+ import type { MenuItem } from '@jsonpages/core';
1899
+ import type { HeaderData, HeaderSettings } from './types';
1900
+
1901
+ export const Header: React.FC<{
1902
+ data: HeaderData;
1903
+ settings?: HeaderSettings;
1904
+ menu: MenuItem[];
1905
+ }> = ({ data, menu }) => {
1906
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
1907
+ const [scrolled, setScrolled] = useState(false);
1908
+
1909
+ useEffect(() => {
1910
+ const handleScroll = () => setScrolled(window.scrollY > 40);
1911
+ window.addEventListener('scroll', handleScroll, { passive: true });
1912
+ return () => window.removeEventListener('scroll', handleScroll);
1913
+ }, []);
1914
+
1915
+ return (
1916
+ <header
1917
+ style={{
1918
+ '--local-bg': 'rgba(6,13,27,0.92)',
1919
+ '--local-text': 'var(--foreground)',
1920
+ '--local-text-muted': 'var(--muted-foreground)',
1921
+ '--local-primary': 'var(--primary)',
1922
+ '--local-accent': 'var(--color-accent, #60a5fa)',
1923
+ '--local-border': 'rgba(59,130,246,0.08)',
1924
+ } as React.CSSProperties}
1925
+ className={cn(
1926
+ 'w-full py-4 transition-all duration-300 z-0',
1927
+ scrolled
1928
+ ? 'bg-[var(--local-bg)] backdrop-blur-[20px] border-b border-[var(--local-border)]'
1929
+ : 'bg-transparent border-b border-transparent'
1930
+ )}
1931
+ >
1932
+ <div className="max-w-[1200px] mx-auto px-8 flex justify-between items-center">
1933
+ <a
1934
+ href="/"
1935
+ className="flex items-center gap-2.5 no-underline font-bold text-xl tracking-tight text-[var(--local-text)]"
1936
+ >
1937
+ {data.logoIconText && (
1938
+ <div className="w-8 h-8 rounded-md bg-gradient-to-br from-[var(--local-primary)] to-[var(--local-accent)] flex items-center justify-center font-mono text-[0.8rem] font-bold text-[var(--background)]" data-jp-field="logoIconText">
1939
+ {data.logoIconText}
1940
+ </div>
1941
+ )}
1942
+ <span data-jp-field="logoText">
1943
+ {data.logoText}
1944
+ {data.logoHighlight && (
1945
+ <span className="text-[var(--local-accent)]" data-jp-field="logoHighlight">{data.logoHighlight}</span>
1946
+ )}
1947
+ </span>
1948
+ </a>
1949
+
1950
+ <nav className="hidden md:flex items-center gap-10">
1951
+ {menu.map((item, idx) => (
1952
+ <a
1953
+ key={(item as { id?: string }).id ?? idx}
1954
+ href={item.href}
1955
+ data-jp-item-id={(item as { id?: string }).id ?? `legacy-${idx}`}
1956
+ data-jp-item-field="links"
1957
+ target={item.external ? '_blank' : undefined}
1958
+ rel={item.external ? 'noopener noreferrer' : undefined}
1959
+ className={cn(
1960
+ 'no-underline text-sm font-medium transition-colors',
1961
+ item.isCta
1962
+ ? 'bg-[var(--local-primary)] text-white px-5 py-2 rounded-lg font-semibold hover:brightness-110 hover:-translate-y-px'
1963
+ : 'text-[var(--local-text-muted)] hover:text-[var(--local-text)]'
1964
+ )}
1965
+ >
1966
+ {item.label}
1967
+ </a>
1968
+ ))}
1969
+ </nav>
1970
+
1971
+ <button
1972
+ type="button"
1973
+ className="md:hidden p-2 text-[var(--local-text-muted)] hover:text-[var(--local-text)]"
1974
+ onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
1975
+ aria-label={mobileMenuOpen ? 'Close menu' : 'Open menu'}
1976
+ >
1977
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
1978
+ {mobileMenuOpen ? (
1979
+ <><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></>
1980
+ ) : (
1981
+ <><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="18" x2="21" y2="18" /></>
1982
+ )}
1983
+ </svg>
1984
+ </button>
1985
+ </div>
1986
+
1987
+ {mobileMenuOpen && (
1988
+ <nav className="md:hidden border-t border-[var(--local-border)] bg-[var(--local-bg)] backdrop-blur-[20px]">
1989
+ <div className="max-w-[1200px] mx-auto px-8 py-4 flex flex-col gap-4">
1990
+ {menu.map((item, idx) => (
1991
+ <a
1992
+ key={(item as { id?: string }).id ?? idx}
1993
+ href={item.href}
1994
+ className="text-base font-medium text-[var(--local-text-muted)] hover:text-[var(--local-text)] transition-colors py-2 no-underline"
1995
+ onClick={() => setMobileMenuOpen(false)}
1996
+ data-jp-item-id={(item as { id?: string }).id ?? `legacy-${idx}`}
1997
+ data-jp-item-field="links"
1998
+ >
1999
+ {item.label}
2000
+ </a>
2001
+ ))}
2002
+ </div>
2003
+ </nav>
2004
+ )}
2005
+ </header>
2006
+ );
2007
+ };
2008
2008
 
2009
2009
  END_OF_FILE_CONTENT
2010
2010
  echo "Creating src/components/header/index.ts..."
@@ -2280,39 +2280,39 @@ export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ da
2280
2280
  END_OF_FILE_CONTENT
2281
2281
  echo "Creating src/components/hero/index.ts..."
2282
2282
  cat << 'END_OF_FILE_CONTENT' > "src/components/hero/index.ts"
2283
- export * from './View';
2284
- export * from './schema';
2285
- export * from './types';
2283
+ export * from './View';
2284
+ export * from './schema';
2285
+ export * from './types';
2286
2286
 
2287
2287
  END_OF_FILE_CONTENT
2288
2288
  echo "Creating src/components/hero/schema.ts..."
2289
2289
  cat << 'END_OF_FILE_CONTENT' > "src/components/hero/schema.ts"
2290
- import { z } from 'zod';
2291
- import { BaseSectionData, CtaSchema } from '@/lib/base-schemas';
2292
-
2293
- const HeroMetricSchema = z.object({
2294
- val: z.string().describe('ui:text'),
2295
- label: z.string().describe('ui:text'),
2296
- });
2297
-
2298
- export const HeroSchema = BaseSectionData.extend({
2299
- badge: z.string().optional().describe('ui:text'),
2300
- title: z.string().describe('ui:text'),
2301
- titleHighlight: z.string().optional().describe('ui:text'),
2302
- description: z.string().optional().describe('ui:textarea'),
2303
- ctas: z.array(CtaSchema).optional().describe('ui:list'),
2304
- metrics: z.array(HeroMetricSchema).optional().describe('ui:list'),
2305
- });
2290
+ import { z } from 'zod';
2291
+ import { BaseSectionData, CtaSchema } from '@/lib/base-schemas';
2292
+
2293
+ const HeroMetricSchema = z.object({
2294
+ val: z.string().describe('ui:text'),
2295
+ label: z.string().describe('ui:text'),
2296
+ });
2297
+
2298
+ export const HeroSchema = BaseSectionData.extend({
2299
+ badge: z.string().optional().describe('ui:text'),
2300
+ title: z.string().describe('ui:text'),
2301
+ titleHighlight: z.string().optional().describe('ui:text'),
2302
+ description: z.string().optional().describe('ui:textarea'),
2303
+ ctas: z.array(CtaSchema).optional().describe('ui:list'),
2304
+ metrics: z.array(HeroMetricSchema).optional().describe('ui:list'),
2305
+ });
2306
2306
 
2307
2307
  END_OF_FILE_CONTENT
2308
2308
  echo "Creating src/components/hero/types.ts..."
2309
2309
  cat << 'END_OF_FILE_CONTENT' > "src/components/hero/types.ts"
2310
- import { z } from 'zod';
2311
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
2312
- import { HeroSchema } from './schema';
2313
-
2314
- export type HeroData = z.infer<typeof HeroSchema>;
2315
- export type HeroSettings = z.infer<typeof BaseSectionSettingsSchema>;
2310
+ import { z } from 'zod';
2311
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
2312
+ import { HeroSchema } from './schema';
2313
+
2314
+ export type HeroData = z.infer<typeof HeroSchema>;
2315
+ export type HeroSettings = z.infer<typeof BaseSectionSettingsSchema>;
2316
2316
 
2317
2317
  END_OF_FILE_CONTENT
2318
2318
  mkdir -p "src/components/image-break"
@@ -2399,646 +2399,646 @@ mkdir -p "src/components/image-test"
2399
2399
  mkdir -p "src/components/pa-section"
2400
2400
  echo "Creating src/components/pa-section/View.tsx..."
2401
2401
  cat << 'END_OF_FILE_CONTENT' > "src/components/pa-section/View.tsx"
2402
- import React from 'react';
2403
- import type { PaSectionData, PaSectionSettings } from './types';
2404
-
2405
- export const PaSection: React.FC<{ data: PaSectionData; settings?: PaSectionSettings }> = ({ data }) => {
2406
- return (
2407
- <section
2408
- style={{
2409
- '--local-bg': 'var(--card)',
2410
- '--local-text': 'var(--foreground)',
2411
- '--local-text-muted': 'var(--muted-foreground)',
2412
- '--local-primary': 'var(--primary)',
2413
- '--local-accent': 'var(--color-accent, #60a5fa)',
2414
- '--local-deep': 'var(--background)',
2415
- } as React.CSSProperties}
2416
- className="relative z-0 py-28 bg-[var(--local-bg)]"
2417
- >
2418
- <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" />
2419
- <div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.1)] to-transparent" />
2420
- <div className="max-w-[1200px] mx-auto px-8">
2421
- {data.label && (
2422
- <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">
2423
- <span className="w-5 h-px bg-[var(--local-primary)]" />
2424
- {data.label}
2425
- </div>
2426
- )}
2427
- <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
2428
- {data.title}
2429
- </h2>
2430
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center mt-12">
2431
- <div>
2432
- <h3 className="text-2xl font-bold text-[var(--local-text)] mb-4" data-jp-field="subtitle">
2433
- {data.subtitle}
2434
- </h3>
2435
- {data.paragraphs.map((p, idx) => (
2436
- <p key={idx} className="text-[var(--local-text-muted)] mb-5 text-[1.05rem] leading-relaxed" data-jp-item-id={(p as { id?: string }).id ?? `legacy-${idx}`} data-jp-item-field="paragraphs">
2437
- {p.text}
2438
- </p>
2439
- ))}
2440
- {data.badges && data.badges.length > 0 && (
2441
- <div className="flex gap-2.5 flex-wrap mt-4">
2442
- {data.badges.map((badge, idx) => (
2443
- <span
2444
- key={idx}
2445
- className="inline-flex items-center gap-1.5 bg-[rgba(34,197,94,0.08)] border border-[rgba(34,197,94,0.2)] text-[#4ade80] px-3 py-1.5 rounded-md text-[0.78rem] font-semibold"
2446
- data-jp-item-id={(badge as { id?: string }).id ?? `legacy-${idx}`}
2447
- data-jp-item-field="badges"
2448
- >
2449
- {badge.label}
2450
- </span>
2451
- ))}
2452
- </div>
2453
- )}
2454
- </div>
2455
- <div className="border border-[rgba(255,255,255,0.06)] rounded-lg p-12 bg-[rgba(255,255,255,0.02)] text-center">
2456
- {data.engines && data.engines.length >= 2 && (
2457
- <div className="flex items-center justify-center gap-6 mb-8">
2458
- {data.engines.map((engine, idx) => (
2459
- <React.Fragment key={idx}>
2460
- {idx > 0 && (
2461
- <span className="text-[var(--local-text-muted)] text-2xl opacity-50">⇄</span>
2462
- )}
2463
- <div
2464
- className={
2465
- engine.variant === 'tailwind'
2466
- ? 'px-6 py-4 rounded-xl font-bold text-[0.95rem] border bg-[rgba(59,130,246,0.08)] border-[rgba(59,130,246,0.2)] text-[#60a5fa]'
2467
- : 'px-6 py-4 rounded-xl font-bold text-[0.95rem] border bg-[rgba(34,197,94,0.08)] border-[rgba(34,197,94,0.2)] text-[#4ade80]'
2468
- }
2469
- data-jp-item-id={(engine as { id?: string }).id ?? `legacy-${idx}`}
2470
- data-jp-item-field="engines"
2471
- >
2472
- {engine.label}
2473
- </div>
2474
- </React.Fragment>
2475
- ))}
2476
- </div>
2477
- )}
2478
- {data.codeSnippet && (
2479
- <div className="font-mono text-[0.85rem] text-[var(--local-text-muted)] bg-[var(--local-deep)] p-4 rounded-lg text-left border border-[rgba(255,255,255,0.04)]" data-jp-field="codeSnippet">
2480
- <pre className="whitespace-pre-wrap m-0">{data.codeSnippet}</pre>
2481
- <div className="mt-4 text-[0.75rem] text-center opacity-50">
2482
- Same JSON. Different Render Engine.
2483
- </div>
2484
- </div>
2485
- )}
2486
- </div>
2487
- </div>
2488
- </div>
2489
- </section>
2490
- );
2491
- };
2402
+ import React from 'react';
2403
+ import type { PaSectionData, PaSectionSettings } from './types';
2404
+
2405
+ export const PaSection: React.FC<{ data: PaSectionData; settings?: PaSectionSettings }> = ({ data }) => {
2406
+ return (
2407
+ <section
2408
+ style={{
2409
+ '--local-bg': 'var(--card)',
2410
+ '--local-text': 'var(--foreground)',
2411
+ '--local-text-muted': 'var(--muted-foreground)',
2412
+ '--local-primary': 'var(--primary)',
2413
+ '--local-accent': 'var(--color-accent, #60a5fa)',
2414
+ '--local-deep': 'var(--background)',
2415
+ } as React.CSSProperties}
2416
+ className="relative z-0 py-28 bg-[var(--local-bg)]"
2417
+ >
2418
+ <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" />
2419
+ <div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.1)] to-transparent" />
2420
+ <div className="max-w-[1200px] mx-auto px-8">
2421
+ {data.label && (
2422
+ <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">
2423
+ <span className="w-5 h-px bg-[var(--local-primary)]" />
2424
+ {data.label}
2425
+ </div>
2426
+ )}
2427
+ <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
2428
+ {data.title}
2429
+ </h2>
2430
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center mt-12">
2431
+ <div>
2432
+ <h3 className="text-2xl font-bold text-[var(--local-text)] mb-4" data-jp-field="subtitle">
2433
+ {data.subtitle}
2434
+ </h3>
2435
+ {data.paragraphs.map((p, idx) => (
2436
+ <p key={idx} className="text-[var(--local-text-muted)] mb-5 text-[1.05rem] leading-relaxed" data-jp-item-id={(p as { id?: string }).id ?? `legacy-${idx}`} data-jp-item-field="paragraphs">
2437
+ {p.text}
2438
+ </p>
2439
+ ))}
2440
+ {data.badges && data.badges.length > 0 && (
2441
+ <div className="flex gap-2.5 flex-wrap mt-4">
2442
+ {data.badges.map((badge, idx) => (
2443
+ <span
2444
+ key={idx}
2445
+ className="inline-flex items-center gap-1.5 bg-[rgba(34,197,94,0.08)] border border-[rgba(34,197,94,0.2)] text-[#4ade80] px-3 py-1.5 rounded-md text-[0.78rem] font-semibold"
2446
+ data-jp-item-id={(badge as { id?: string }).id ?? `legacy-${idx}`}
2447
+ data-jp-item-field="badges"
2448
+ >
2449
+ {badge.label}
2450
+ </span>
2451
+ ))}
2452
+ </div>
2453
+ )}
2454
+ </div>
2455
+ <div className="border border-[rgba(255,255,255,0.06)] rounded-lg p-12 bg-[rgba(255,255,255,0.02)] text-center">
2456
+ {data.engines && data.engines.length >= 2 && (
2457
+ <div className="flex items-center justify-center gap-6 mb-8">
2458
+ {data.engines.map((engine, idx) => (
2459
+ <React.Fragment key={idx}>
2460
+ {idx > 0 && (
2461
+ <span className="text-[var(--local-text-muted)] text-2xl opacity-50">⇄</span>
2462
+ )}
2463
+ <div
2464
+ className={
2465
+ engine.variant === 'tailwind'
2466
+ ? 'px-6 py-4 rounded-xl font-bold text-[0.95rem] border bg-[rgba(59,130,246,0.08)] border-[rgba(59,130,246,0.2)] text-[#60a5fa]'
2467
+ : 'px-6 py-4 rounded-xl font-bold text-[0.95rem] border bg-[rgba(34,197,94,0.08)] border-[rgba(34,197,94,0.2)] text-[#4ade80]'
2468
+ }
2469
+ data-jp-item-id={(engine as { id?: string }).id ?? `legacy-${idx}`}
2470
+ data-jp-item-field="engines"
2471
+ >
2472
+ {engine.label}
2473
+ </div>
2474
+ </React.Fragment>
2475
+ ))}
2476
+ </div>
2477
+ )}
2478
+ {data.codeSnippet && (
2479
+ <div className="font-mono text-[0.85rem] text-[var(--local-text-muted)] bg-[var(--local-deep)] p-4 rounded-lg text-left border border-[rgba(255,255,255,0.04)]" data-jp-field="codeSnippet">
2480
+ <pre className="whitespace-pre-wrap m-0">{data.codeSnippet}</pre>
2481
+ <div className="mt-4 text-[0.75rem] text-center opacity-50">
2482
+ Same JSON. Different Render Engine.
2483
+ </div>
2484
+ </div>
2485
+ )}
2486
+ </div>
2487
+ </div>
2488
+ </div>
2489
+ </section>
2490
+ );
2491
+ };
2492
2492
 
2493
2493
  END_OF_FILE_CONTENT
2494
2494
  echo "Creating src/components/pa-section/index.ts..."
2495
2495
  cat << 'END_OF_FILE_CONTENT' > "src/components/pa-section/index.ts"
2496
- export * from './View';
2497
- export * from './schema';
2498
- export * from './types';
2496
+ export * from './View';
2497
+ export * from './schema';
2498
+ export * from './types';
2499
2499
 
2500
2500
  END_OF_FILE_CONTENT
2501
2501
  echo "Creating src/components/pa-section/schema.ts..."
2502
2502
  cat << 'END_OF_FILE_CONTENT' > "src/components/pa-section/schema.ts"
2503
- import { z } from 'zod';
2504
- import { BaseSectionData } from '@/lib/base-schemas';
2505
-
2506
- const PaBadgeSchema = z.object({
2507
- label: z.string().describe('ui:text'),
2508
- });
2509
-
2510
- const PaEngineSchema = z.object({
2511
- label: z.string().describe('ui:text'),
2512
- variant: z.enum(['tailwind', 'bootstrap']).describe('ui:select'),
2513
- });
2514
-
2515
- export const PaSectionSchema = BaseSectionData.extend({
2516
- label: z.string().optional().describe('ui:text'),
2517
- title: z.string().describe('ui:text'),
2518
- subtitle: z.string().describe('ui:text'),
2519
- paragraphs: z.array(z.object({ text: z.string().describe('ui:textarea') })).describe('ui:list'),
2520
- badges: z.array(PaBadgeSchema).optional().describe('ui:list'),
2521
- engines: z.array(PaEngineSchema).optional().describe('ui:list'),
2522
- codeSnippet: z.string().optional().describe('ui:textarea'),
2523
- });
2503
+ import { z } from 'zod';
2504
+ import { BaseSectionData } from '@/lib/base-schemas';
2505
+
2506
+ const PaBadgeSchema = z.object({
2507
+ label: z.string().describe('ui:text'),
2508
+ });
2509
+
2510
+ const PaEngineSchema = z.object({
2511
+ label: z.string().describe('ui:text'),
2512
+ variant: z.enum(['tailwind', 'bootstrap']).describe('ui:select'),
2513
+ });
2514
+
2515
+ export const PaSectionSchema = BaseSectionData.extend({
2516
+ label: z.string().optional().describe('ui:text'),
2517
+ title: z.string().describe('ui:text'),
2518
+ subtitle: z.string().describe('ui:text'),
2519
+ paragraphs: z.array(z.object({ text: z.string().describe('ui:textarea') })).describe('ui:list'),
2520
+ badges: z.array(PaBadgeSchema).optional().describe('ui:list'),
2521
+ engines: z.array(PaEngineSchema).optional().describe('ui:list'),
2522
+ codeSnippet: z.string().optional().describe('ui:textarea'),
2523
+ });
2524
2524
 
2525
2525
  END_OF_FILE_CONTENT
2526
2526
  echo "Creating src/components/pa-section/types.ts..."
2527
2527
  cat << 'END_OF_FILE_CONTENT' > "src/components/pa-section/types.ts"
2528
- import { z } from 'zod';
2529
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
2530
- import { PaSectionSchema } from './schema';
2531
-
2532
- export type PaSectionData = z.infer<typeof PaSectionSchema>;
2533
- export type PaSectionSettings = z.infer<typeof BaseSectionSettingsSchema>;
2528
+ import { z } from 'zod';
2529
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
2530
+ import { PaSectionSchema } from './schema';
2531
+
2532
+ export type PaSectionData = z.infer<typeof PaSectionSchema>;
2533
+ export type PaSectionSettings = z.infer<typeof BaseSectionSettingsSchema>;
2534
2534
 
2535
2535
  END_OF_FILE_CONTENT
2536
2536
  mkdir -p "src/components/philosophy"
2537
2537
  echo "Creating src/components/philosophy/View.tsx..."
2538
2538
  cat << 'END_OF_FILE_CONTENT' > "src/components/philosophy/View.tsx"
2539
- import React from 'react';
2540
- import type { PhilosophyData, PhilosophySettings } from './types';
2541
-
2542
- export const Philosophy: React.FC<{ data: PhilosophyData; settings?: PhilosophySettings }> = ({ data }) => {
2543
- const renderQuote = () => {
2544
- if (!data.quoteHighlightWord) {
2545
- return <>{data.quote}</>;
2546
- }
2547
- const parts = data.quote.split(data.quoteHighlightWord);
2548
- return (
2549
- <>
2550
- {parts.map((part, idx) => (
2551
- <React.Fragment key={idx}>
2552
- {part}
2553
- {idx < parts.length - 1 && (
2554
- <em className="not-italic text-[var(--local-accent)]">
2555
- {data.quoteHighlightWord}
2556
- </em>
2557
- )}
2558
- </React.Fragment>
2559
- ))}
2560
- </>
2561
- );
2562
- };
2563
-
2564
- return (
2565
- <section
2566
- style={{
2567
- '--local-bg': 'var(--background)',
2568
- '--local-text': 'var(--foreground)',
2569
- '--local-text-muted': 'var(--muted-foreground)',
2570
- '--local-accent': 'var(--color-accent, #60a5fa)',
2571
- '--local-primary': 'var(--primary)',
2572
- } as React.CSSProperties}
2573
- className="relative z-0 py-28 bg-[var(--local-bg)]"
2574
- >
2575
- <div className="max-w-[1200px] mx-auto px-8">
2576
- <div className="max-w-[760px] mx-auto text-center">
2577
- {data.label && (
2578
- <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">
2579
- <span className="w-5 h-px bg-[var(--local-primary)]" />
2580
- {data.label}
2581
- </div>
2582
- )}
2583
- <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
2584
- {data.title}
2585
- </h2>
2586
- <blockquote className="font-display text-[clamp(1.6rem,3vw,2.4rem)] text-[var(--local-text)] font-bold leading-[1.35] my-8" data-jp-field="quote">
2587
- &ldquo;{renderQuote()}&rdquo;
2588
- </blockquote>
2589
- {data.description && (
2590
- <p className="text-[1.05rem] text-[var(--local-text-muted)] max-w-[560px] mx-auto leading-relaxed" data-jp-field="description">
2591
- {data.description}
2592
- </p>
2593
- )}
2594
- </div>
2595
- </div>
2596
- </section>
2597
- );
2598
- };
2539
+ import React from 'react';
2540
+ import type { PhilosophyData, PhilosophySettings } from './types';
2541
+
2542
+ export const Philosophy: React.FC<{ data: PhilosophyData; settings?: PhilosophySettings }> = ({ data }) => {
2543
+ const renderQuote = () => {
2544
+ if (!data.quoteHighlightWord) {
2545
+ return <>{data.quote}</>;
2546
+ }
2547
+ const parts = data.quote.split(data.quoteHighlightWord);
2548
+ return (
2549
+ <>
2550
+ {parts.map((part, idx) => (
2551
+ <React.Fragment key={idx}>
2552
+ {part}
2553
+ {idx < parts.length - 1 && (
2554
+ <em className="not-italic text-[var(--local-accent)]">
2555
+ {data.quoteHighlightWord}
2556
+ </em>
2557
+ )}
2558
+ </React.Fragment>
2559
+ ))}
2560
+ </>
2561
+ );
2562
+ };
2563
+
2564
+ return (
2565
+ <section
2566
+ style={{
2567
+ '--local-bg': 'var(--background)',
2568
+ '--local-text': 'var(--foreground)',
2569
+ '--local-text-muted': 'var(--muted-foreground)',
2570
+ '--local-accent': 'var(--color-accent, #60a5fa)',
2571
+ '--local-primary': 'var(--primary)',
2572
+ } as React.CSSProperties}
2573
+ className="relative z-0 py-28 bg-[var(--local-bg)]"
2574
+ >
2575
+ <div className="max-w-[1200px] mx-auto px-8">
2576
+ <div className="max-w-[760px] mx-auto text-center">
2577
+ {data.label && (
2578
+ <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">
2579
+ <span className="w-5 h-px bg-[var(--local-primary)]" />
2580
+ {data.label}
2581
+ </div>
2582
+ )}
2583
+ <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
2584
+ {data.title}
2585
+ </h2>
2586
+ <blockquote className="font-display text-[clamp(1.6rem,3vw,2.4rem)] text-[var(--local-text)] font-bold leading-[1.35] my-8" data-jp-field="quote">
2587
+ &ldquo;{renderQuote()}&rdquo;
2588
+ </blockquote>
2589
+ {data.description && (
2590
+ <p className="text-[1.05rem] text-[var(--local-text-muted)] max-w-[560px] mx-auto leading-relaxed" data-jp-field="description">
2591
+ {data.description}
2592
+ </p>
2593
+ )}
2594
+ </div>
2595
+ </div>
2596
+ </section>
2597
+ );
2598
+ };
2599
2599
 
2600
2600
  END_OF_FILE_CONTENT
2601
2601
  echo "Creating src/components/philosophy/index.ts..."
2602
2602
  cat << 'END_OF_FILE_CONTENT' > "src/components/philosophy/index.ts"
2603
- export * from './View';
2604
- export * from './schema';
2605
- export * from './types';
2603
+ export * from './View';
2604
+ export * from './schema';
2605
+ export * from './types';
2606
2606
 
2607
2607
  END_OF_FILE_CONTENT
2608
2608
  echo "Creating src/components/philosophy/schema.ts..."
2609
2609
  cat << 'END_OF_FILE_CONTENT' > "src/components/philosophy/schema.ts"
2610
- import { z } from 'zod';
2611
- import { BaseSectionData } from '@/lib/base-schemas';
2612
-
2613
- export const PhilosophySchema = BaseSectionData.extend({
2614
- label: z.string().optional().describe('ui:text'),
2615
- title: z.string().describe('ui:text'),
2616
- quote: z.string().describe('ui:textarea'),
2617
- quoteHighlightWord: z.string().optional().describe('ui:text'),
2618
- description: z.string().optional().describe('ui:textarea'),
2619
- });
2610
+ import { z } from 'zod';
2611
+ import { BaseSectionData } from '@/lib/base-schemas';
2612
+
2613
+ export const PhilosophySchema = BaseSectionData.extend({
2614
+ label: z.string().optional().describe('ui:text'),
2615
+ title: z.string().describe('ui:text'),
2616
+ quote: z.string().describe('ui:textarea'),
2617
+ quoteHighlightWord: z.string().optional().describe('ui:text'),
2618
+ description: z.string().optional().describe('ui:textarea'),
2619
+ });
2620
2620
 
2621
2621
  END_OF_FILE_CONTENT
2622
2622
  echo "Creating src/components/philosophy/types.ts..."
2623
2623
  cat << 'END_OF_FILE_CONTENT' > "src/components/philosophy/types.ts"
2624
- import { z } from 'zod';
2625
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
2626
- import { PhilosophySchema } from './schema';
2627
-
2628
- export type PhilosophyData = z.infer<typeof PhilosophySchema>;
2629
- export type PhilosophySettings = z.infer<typeof BaseSectionSettingsSchema>;
2624
+ import { z } from 'zod';
2625
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
2626
+ import { PhilosophySchema } from './schema';
2627
+
2628
+ export type PhilosophyData = z.infer<typeof PhilosophySchema>;
2629
+ export type PhilosophySettings = z.infer<typeof BaseSectionSettingsSchema>;
2630
2630
 
2631
2631
  END_OF_FILE_CONTENT
2632
2632
  mkdir -p "src/components/pillars-grid"
2633
2633
  echo "Creating src/components/pillars-grid/View.tsx..."
2634
2634
  cat << 'END_OF_FILE_CONTENT' > "src/components/pillars-grid/View.tsx"
2635
- import React from 'react';
2636
- import { cn } from '@/lib/utils';
2637
- import { Icon, isIconName } from '@/lib/IconResolver';
2638
- import type { PillarsGridData, PillarsGridSettings, PillarIconVariant, PillarTagVariant } from './types';
2639
-
2640
- const iconVariantStyles: Record<PillarIconVariant, string> = {
2641
- split: 'bg-[rgba(59,130,246,0.1)] text-[#60a5fa]',
2642
- registry: 'bg-[rgba(34,211,238,0.1)] text-[#22d3ee]',
2643
- federation: 'bg-[rgba(168,85,247,0.1)] text-[#c084fc]',
2644
- };
2645
-
2646
- const tagVariantStyles: Record<PillarTagVariant, string> = {
2647
- core: 'bg-[rgba(59,130,246,0.1)] text-[#60a5fa]',
2648
- pattern: 'bg-[rgba(34,211,238,0.1)] text-[#22d3ee]',
2649
- enterprise: 'bg-[rgba(168,85,247,0.1)] text-[#c084fc]',
2650
- };
2651
-
2652
- export const PillarsGrid: React.FC<{ data: PillarsGridData; settings?: PillarsGridSettings }> = ({ data }) => {
2653
- return (
2654
- <section
2655
- style={{
2656
- '--local-bg': 'var(--background)',
2657
- '--local-text': 'var(--foreground)',
2658
- '--local-text-muted': 'var(--muted-foreground)',
2659
- '--local-primary': 'var(--primary)',
2660
- '--local-accent': 'var(--color-accent, #60a5fa)',
2661
- '--local-border': 'var(--border)',
2662
- } as React.CSSProperties}
2663
- className="relative z-0 py-28 bg-[var(--local-bg)]"
2664
- >
2665
- <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[80%] h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.15)] to-transparent" />
2666
- <div className="max-w-[1200px] mx-auto px-8">
2667
- {data.label && (
2668
- <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">
2669
- <span className="w-5 h-px bg-[var(--local-primary)]" />
2670
- {data.label}
2671
- </div>
2672
- )}
2673
- <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
2674
- {data.title}
2675
- </h2>
2676
- {data.description && (
2677
- <p className="text-lg text-[var(--local-text-muted)] max-w-[600px] leading-relaxed" data-jp-field="description">
2678
- {data.description}
2679
- </p>
2680
- )}
2681
- <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mt-14">
2682
- {data.pillars.map((pillar, idx) => (
2683
- <div
2684
- key={pillar.id ?? idx}
2685
- className="jp-pillar-card group relative border border-[rgba(255,255,255,0.06)] rounded-lg p-10 bg-[rgba(255,255,255,0.015)] transition-all duration-300 overflow-hidden hover:border-[rgba(59,130,246,0.2)] hover:-translate-y-1 hover:bg-[rgba(59,130,246,0.03)]"
2686
- data-jp-item-id={pillar.id ?? `legacy-${idx}`}
2687
- data-jp-item-field="pillars"
2688
- >
2689
- <div className="absolute top-0 left-0 right-0 h-[3px] bg-gradient-to-r from-[var(--local-primary)] to-[#22d3ee] opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
2690
- <div className={cn(
2691
- 'w-12 h-12 rounded-xl flex items-center justify-center mb-6 text-xl font-bold',
2692
- iconVariantStyles[pillar.iconVariant]
2693
- )}>
2694
- {pillar.icon && isIconName(pillar.icon) ? (
2695
- <Icon name={pillar.icon} size={24} className="shrink-0" />
2696
- ) : pillar.icon ? (
2697
- <span>{pillar.icon}</span>
2698
- ) : null}
2699
- </div>
2700
- <h3 className="text-xl font-bold text-[var(--local-text)] mb-3">
2701
- {pillar.title}
2702
- </h3>
2703
- <p className="text-[0.95rem] text-[var(--local-text-muted)] leading-relaxed">
2704
- {pillar.description}
2705
- </p>
2706
- <span className={cn(
2707
- 'inline-block text-[0.7rem] font-semibold uppercase tracking-wide px-3 py-1 rounded mt-4',
2708
- tagVariantStyles[pillar.tagVariant]
2709
- )}>
2710
- {pillar.tag}
2711
- </span>
2712
- </div>
2713
- ))}
2714
- </div>
2715
- </div>
2716
- </section>
2717
- );
2718
- };
2635
+ import React from 'react';
2636
+ import { cn } from '@/lib/utils';
2637
+ import { Icon, isIconName } from '@/lib/IconResolver';
2638
+ import type { PillarsGridData, PillarsGridSettings, PillarIconVariant, PillarTagVariant } from './types';
2639
+
2640
+ const iconVariantStyles: Record<PillarIconVariant, string> = {
2641
+ split: 'bg-[rgba(59,130,246,0.1)] text-[#60a5fa]',
2642
+ registry: 'bg-[rgba(34,211,238,0.1)] text-[#22d3ee]',
2643
+ federation: 'bg-[rgba(168,85,247,0.1)] text-[#c084fc]',
2644
+ };
2645
+
2646
+ const tagVariantStyles: Record<PillarTagVariant, string> = {
2647
+ core: 'bg-[rgba(59,130,246,0.1)] text-[#60a5fa]',
2648
+ pattern: 'bg-[rgba(34,211,238,0.1)] text-[#22d3ee]',
2649
+ enterprise: 'bg-[rgba(168,85,247,0.1)] text-[#c084fc]',
2650
+ };
2651
+
2652
+ export const PillarsGrid: React.FC<{ data: PillarsGridData; settings?: PillarsGridSettings }> = ({ data }) => {
2653
+ return (
2654
+ <section
2655
+ style={{
2656
+ '--local-bg': 'var(--background)',
2657
+ '--local-text': 'var(--foreground)',
2658
+ '--local-text-muted': 'var(--muted-foreground)',
2659
+ '--local-primary': 'var(--primary)',
2660
+ '--local-accent': 'var(--color-accent, #60a5fa)',
2661
+ '--local-border': 'var(--border)',
2662
+ } as React.CSSProperties}
2663
+ className="relative z-0 py-28 bg-[var(--local-bg)]"
2664
+ >
2665
+ <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[80%] h-px bg-gradient-to-r from-transparent via-[rgba(59,130,246,0.15)] to-transparent" />
2666
+ <div className="max-w-[1200px] mx-auto px-8">
2667
+ {data.label && (
2668
+ <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">
2669
+ <span className="w-5 h-px bg-[var(--local-primary)]" />
2670
+ {data.label}
2671
+ </div>
2672
+ )}
2673
+ <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
2674
+ {data.title}
2675
+ </h2>
2676
+ {data.description && (
2677
+ <p className="text-lg text-[var(--local-text-muted)] max-w-[600px] leading-relaxed" data-jp-field="description">
2678
+ {data.description}
2679
+ </p>
2680
+ )}
2681
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mt-14">
2682
+ {data.pillars.map((pillar, idx) => (
2683
+ <div
2684
+ key={pillar.id ?? idx}
2685
+ className="jp-pillar-card group relative border border-[rgba(255,255,255,0.06)] rounded-lg p-10 bg-[rgba(255,255,255,0.015)] transition-all duration-300 overflow-hidden hover:border-[rgba(59,130,246,0.2)] hover:-translate-y-1 hover:bg-[rgba(59,130,246,0.03)]"
2686
+ data-jp-item-id={pillar.id ?? `legacy-${idx}`}
2687
+ data-jp-item-field="pillars"
2688
+ >
2689
+ <div className="absolute top-0 left-0 right-0 h-[3px] bg-gradient-to-r from-[var(--local-primary)] to-[#22d3ee] opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
2690
+ <div className={cn(
2691
+ 'w-12 h-12 rounded-xl flex items-center justify-center mb-6 text-xl font-bold',
2692
+ iconVariantStyles[pillar.iconVariant]
2693
+ )}>
2694
+ {pillar.icon && isIconName(pillar.icon) ? (
2695
+ <Icon name={pillar.icon} size={24} className="shrink-0" />
2696
+ ) : pillar.icon ? (
2697
+ <span>{pillar.icon}</span>
2698
+ ) : null}
2699
+ </div>
2700
+ <h3 className="text-xl font-bold text-[var(--local-text)] mb-3">
2701
+ {pillar.title}
2702
+ </h3>
2703
+ <p className="text-[0.95rem] text-[var(--local-text-muted)] leading-relaxed">
2704
+ {pillar.description}
2705
+ </p>
2706
+ <span className={cn(
2707
+ 'inline-block text-[0.7rem] font-semibold uppercase tracking-wide px-3 py-1 rounded mt-4',
2708
+ tagVariantStyles[pillar.tagVariant]
2709
+ )}>
2710
+ {pillar.tag}
2711
+ </span>
2712
+ </div>
2713
+ ))}
2714
+ </div>
2715
+ </div>
2716
+ </section>
2717
+ );
2718
+ };
2719
2719
 
2720
2720
  END_OF_FILE_CONTENT
2721
2721
  echo "Creating src/components/pillars-grid/index.ts..."
2722
2722
  cat << 'END_OF_FILE_CONTENT' > "src/components/pillars-grid/index.ts"
2723
- export * from './View';
2724
- export * from './schema';
2725
- export * from './types';
2723
+ export * from './View';
2724
+ export * from './schema';
2725
+ export * from './types';
2726
2726
 
2727
2727
  END_OF_FILE_CONTENT
2728
2728
  echo "Creating src/components/pillars-grid/schema.ts..."
2729
2729
  cat << 'END_OF_FILE_CONTENT' > "src/components/pillars-grid/schema.ts"
2730
- import { z } from 'zod';
2731
- import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
2732
-
2733
- export const PillarIconVariantSchema = z.enum(['split', 'registry', 'federation']);
2734
- export const PillarTagVariantSchema = z.enum(['core', 'pattern', 'enterprise']);
2735
-
2736
- const PillarCardSchema = BaseArrayItem.extend({
2737
- icon: z.string().describe('ui:icon-picker'),
2738
- iconVariant: PillarIconVariantSchema.describe('ui:select'),
2739
- title: z.string().describe('ui:text'),
2740
- description: z.string().describe('ui:textarea'),
2741
- tag: z.string().describe('ui:text'),
2742
- tagVariant: PillarTagVariantSchema.describe('ui:select'),
2743
- });
2744
-
2745
- export const PillarsGridSchema = BaseSectionData.extend({
2746
- label: z.string().optional().describe('ui:text'),
2747
- title: z.string().describe('ui:text'),
2748
- description: z.string().optional().describe('ui:textarea'),
2749
- pillars: z.array(PillarCardSchema).describe('ui:list'),
2750
- });
2730
+ import { z } from 'zod';
2731
+ import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
2732
+
2733
+ export const PillarIconVariantSchema = z.enum(['split', 'registry', 'federation']);
2734
+ export const PillarTagVariantSchema = z.enum(['core', 'pattern', 'enterprise']);
2735
+
2736
+ const PillarCardSchema = BaseArrayItem.extend({
2737
+ icon: z.string().describe('ui:icon-picker'),
2738
+ iconVariant: PillarIconVariantSchema.describe('ui:select'),
2739
+ title: z.string().describe('ui:text'),
2740
+ description: z.string().describe('ui:textarea'),
2741
+ tag: z.string().describe('ui:text'),
2742
+ tagVariant: PillarTagVariantSchema.describe('ui:select'),
2743
+ });
2744
+
2745
+ export const PillarsGridSchema = BaseSectionData.extend({
2746
+ label: z.string().optional().describe('ui:text'),
2747
+ title: z.string().describe('ui:text'),
2748
+ description: z.string().optional().describe('ui:textarea'),
2749
+ pillars: z.array(PillarCardSchema).describe('ui:list'),
2750
+ });
2751
2751
 
2752
2752
  END_OF_FILE_CONTENT
2753
2753
  echo "Creating src/components/pillars-grid/types.ts..."
2754
2754
  cat << 'END_OF_FILE_CONTENT' > "src/components/pillars-grid/types.ts"
2755
- import { z } from 'zod';
2756
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
2757
- import { PillarsGridSchema, PillarIconVariantSchema, PillarTagVariantSchema } from './schema';
2758
-
2759
- export type PillarsGridData = z.infer<typeof PillarsGridSchema>;
2760
- export type PillarsGridSettings = z.infer<typeof BaseSectionSettingsSchema>;
2761
- export type PillarIconVariant = z.infer<typeof PillarIconVariantSchema>;
2762
- export type PillarTagVariant = z.infer<typeof PillarTagVariantSchema>;
2755
+ import { z } from 'zod';
2756
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
2757
+ import { PillarsGridSchema, PillarIconVariantSchema, PillarTagVariantSchema } from './schema';
2758
+
2759
+ export type PillarsGridData = z.infer<typeof PillarsGridSchema>;
2760
+ export type PillarsGridSettings = z.infer<typeof BaseSectionSettingsSchema>;
2761
+ export type PillarIconVariant = z.infer<typeof PillarIconVariantSchema>;
2762
+ export type PillarTagVariant = z.infer<typeof PillarTagVariantSchema>;
2763
2763
 
2764
2764
  END_OF_FILE_CONTENT
2765
2765
  mkdir -p "src/components/problem-statement"
2766
2766
  echo "Creating src/components/problem-statement/View.tsx..."
2767
2767
  cat << 'END_OF_FILE_CONTENT' > "src/components/problem-statement/View.tsx"
2768
- import React from 'react';
2769
- import { cn } from '@/lib/utils';
2770
- import type { ProblemStatementData, ProblemStatementSettings, SiloBlockVariant } from './types';
2771
-
2772
- const variantStyles: Record<SiloBlockVariant, string> = {
2773
- red: 'bg-[rgba(239,68,68,0.08)] border-[rgba(239,68,68,0.3)] text-[#f87171]',
2774
- amber: 'bg-[rgba(245,158,11,0.08)] border-[rgba(245,158,11,0.3)] text-[#fbbf24]',
2775
- green: 'bg-[rgba(34,197,94,0.08)] border-[rgba(34,197,94,0.3)] text-[#4ade80]',
2776
- blue: 'bg-[rgba(59,130,246,0.08)] border-[rgba(59,130,246,0.3)] text-[#60a5fa]',
2777
- };
2778
-
2779
- export const ProblemStatement: React.FC<{ data: ProblemStatementData; settings?: ProblemStatementSettings }> = ({ data }) => {
2780
- return (
2781
- <section
2782
- style={{
2783
- '--local-bg': 'var(--background)',
2784
- '--local-surface': 'var(--card)',
2785
- '--local-text': 'var(--foreground)',
2786
- '--local-text-muted': 'var(--muted-foreground)',
2787
- '--local-border': 'var(--border)',
2788
- } as React.CSSProperties}
2789
- className="jp-problem relative z-0 py-28 bg-gradient-to-b from-[var(--local-bg)] to-[var(--local-surface)]"
2790
- >
2791
- <div className="max-w-[1200px] mx-auto px-8">
2792
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
2793
- <div className="relative h-[360px] border border-[rgba(255,255,255,0.06)] rounded-lg bg-[rgba(255,255,255,0.02)] overflow-hidden flex items-center justify-center">
2794
- <div className="text-center p-8">
2795
- {data.siloGroups.map((group, gIdx) => (
2796
- <div
2797
- key={gIdx}
2798
- className="mb-4"
2799
- data-jp-item-id={(group as { id?: string }).id ?? `legacy-${gIdx}`}
2800
- data-jp-item-field="siloGroups"
2801
- >
2802
- <div className="flex flex-wrap justify-center gap-1.5">
2803
- {group.blocks.map((block, bIdx) => (
2804
- <span
2805
- key={(block as { id?: string }).id ?? bIdx}
2806
- className={cn(
2807
- 'inline-block px-4 py-2 rounded-lg text-[0.8rem] font-semibold border',
2808
- variantStyles[block.variant]
2809
- )}
2810
- data-jp-item-id={(block as { id?: string }).id ?? `legacy-${bIdx}`}
2811
- data-jp-item-field="blocks"
2812
- >
2813
- {block.label}
2814
- </span>
2815
- ))}
2816
- </div>
2817
- <span className="text-[0.7rem] text-[var(--local-text-muted)] uppercase tracking-widest mt-2 block opacity-60">
2818
- {group.label}
2819
- </span>
2820
- </div>
2821
- ))}
2822
- </div>
2823
- </div>
2824
- <div>
2825
- <h3 className="text-2xl font-bold text-[var(--local-text)] mb-4" data-jp-field="title">
2826
- {data.title}
2827
- </h3>
2828
- {data.paragraphs.map((p, idx) => (
2829
- <p
2830
- key={idx}
2831
- className="text-[var(--local-text-muted)] mb-5 text-[1.05rem] leading-relaxed"
2832
- data-jp-item-id={(p as { id?: string }).id ?? `legacy-${idx}`}
2833
- data-jp-item-field="paragraphs"
2834
- >
2835
- {p.isBold ? <strong className="text-[var(--local-text)]">{p.text}</strong> : p.text}
2836
- </p>
2837
- ))}
2838
- </div>
2839
- </div>
2840
- </div>
2841
- </section>
2842
- );
2843
- };
2768
+ import React from 'react';
2769
+ import { cn } from '@/lib/utils';
2770
+ import type { ProblemStatementData, ProblemStatementSettings, SiloBlockVariant } from './types';
2771
+
2772
+ const variantStyles: Record<SiloBlockVariant, string> = {
2773
+ red: 'bg-[rgba(239,68,68,0.08)] border-[rgba(239,68,68,0.3)] text-[#f87171]',
2774
+ amber: 'bg-[rgba(245,158,11,0.08)] border-[rgba(245,158,11,0.3)] text-[#fbbf24]',
2775
+ green: 'bg-[rgba(34,197,94,0.08)] border-[rgba(34,197,94,0.3)] text-[#4ade80]',
2776
+ blue: 'bg-[rgba(59,130,246,0.08)] border-[rgba(59,130,246,0.3)] text-[#60a5fa]',
2777
+ };
2778
+
2779
+ export const ProblemStatement: React.FC<{ data: ProblemStatementData; settings?: ProblemStatementSettings }> = ({ data }) => {
2780
+ return (
2781
+ <section
2782
+ style={{
2783
+ '--local-bg': 'var(--background)',
2784
+ '--local-surface': 'var(--card)',
2785
+ '--local-text': 'var(--foreground)',
2786
+ '--local-text-muted': 'var(--muted-foreground)',
2787
+ '--local-border': 'var(--border)',
2788
+ } as React.CSSProperties}
2789
+ className="jp-problem relative z-0 py-28 bg-gradient-to-b from-[var(--local-bg)] to-[var(--local-surface)]"
2790
+ >
2791
+ <div className="max-w-[1200px] mx-auto px-8">
2792
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
2793
+ <div className="relative h-[360px] border border-[rgba(255,255,255,0.06)] rounded-lg bg-[rgba(255,255,255,0.02)] overflow-hidden flex items-center justify-center">
2794
+ <div className="text-center p-8">
2795
+ {data.siloGroups.map((group, gIdx) => (
2796
+ <div
2797
+ key={gIdx}
2798
+ className="mb-4"
2799
+ data-jp-item-id={(group as { id?: string }).id ?? `legacy-${gIdx}`}
2800
+ data-jp-item-field="siloGroups"
2801
+ >
2802
+ <div className="flex flex-wrap justify-center gap-1.5">
2803
+ {group.blocks.map((block, bIdx) => (
2804
+ <span
2805
+ key={(block as { id?: string }).id ?? bIdx}
2806
+ className={cn(
2807
+ 'inline-block px-4 py-2 rounded-lg text-[0.8rem] font-semibold border',
2808
+ variantStyles[block.variant]
2809
+ )}
2810
+ data-jp-item-id={(block as { id?: string }).id ?? `legacy-${bIdx}`}
2811
+ data-jp-item-field="blocks"
2812
+ >
2813
+ {block.label}
2814
+ </span>
2815
+ ))}
2816
+ </div>
2817
+ <span className="text-[0.7rem] text-[var(--local-text-muted)] uppercase tracking-widest mt-2 block opacity-60">
2818
+ {group.label}
2819
+ </span>
2820
+ </div>
2821
+ ))}
2822
+ </div>
2823
+ </div>
2824
+ <div>
2825
+ <h3 className="text-2xl font-bold text-[var(--local-text)] mb-4" data-jp-field="title">
2826
+ {data.title}
2827
+ </h3>
2828
+ {data.paragraphs.map((p, idx) => (
2829
+ <p
2830
+ key={idx}
2831
+ className="text-[var(--local-text-muted)] mb-5 text-[1.05rem] leading-relaxed"
2832
+ data-jp-item-id={(p as { id?: string }).id ?? `legacy-${idx}`}
2833
+ data-jp-item-field="paragraphs"
2834
+ >
2835
+ {p.isBold ? <strong className="text-[var(--local-text)]">{p.text}</strong> : p.text}
2836
+ </p>
2837
+ ))}
2838
+ </div>
2839
+ </div>
2840
+ </div>
2841
+ </section>
2842
+ );
2843
+ };
2844
2844
 
2845
2845
  END_OF_FILE_CONTENT
2846
2846
  echo "Creating src/components/problem-statement/index.ts..."
2847
2847
  cat << 'END_OF_FILE_CONTENT' > "src/components/problem-statement/index.ts"
2848
- export * from './View';
2849
- export * from './schema';
2850
- export * from './types';
2848
+ export * from './View';
2849
+ export * from './schema';
2850
+ export * from './types';
2851
2851
 
2852
2852
  END_OF_FILE_CONTENT
2853
2853
  echo "Creating src/components/problem-statement/schema.ts..."
2854
2854
  cat << 'END_OF_FILE_CONTENT' > "src/components/problem-statement/schema.ts"
2855
- import { z } from 'zod';
2856
- import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
2857
-
2858
- export const SiloBlockVariantSchema = z.enum(['red', 'amber', 'green', 'blue']);
2859
- const SiloBlockSchema = BaseArrayItem.extend({
2860
- label: z.string().describe('ui:text'),
2861
- variant: SiloBlockVariantSchema.describe('ui:select'),
2862
- });
2863
-
2864
- const SiloGroupSchema = BaseArrayItem.extend({
2865
- blocks: z.array(SiloBlockSchema).describe('ui:list'),
2866
- label: z.string().describe('ui:text'),
2867
- });
2868
-
2869
- const ProblemParagraphSchema = BaseArrayItem.extend({
2870
- text: z.string().describe('ui:textarea'),
2871
- isBold: z.boolean().default(false).describe('ui:checkbox'),
2872
- });
2873
-
2874
- export const ProblemStatementSchema = BaseSectionData.extend({
2875
- siloGroups: z.array(SiloGroupSchema).describe('ui:list'),
2876
- title: z.string().describe('ui:text'),
2877
- paragraphs: z.array(ProblemParagraphSchema).describe('ui:list'),
2878
- highlight: z.string().optional().describe('ui:text'),
2879
- });
2855
+ import { z } from 'zod';
2856
+ import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
2857
+
2858
+ export const SiloBlockVariantSchema = z.enum(['red', 'amber', 'green', 'blue']);
2859
+ const SiloBlockSchema = BaseArrayItem.extend({
2860
+ label: z.string().describe('ui:text'),
2861
+ variant: SiloBlockVariantSchema.describe('ui:select'),
2862
+ });
2863
+
2864
+ const SiloGroupSchema = BaseArrayItem.extend({
2865
+ blocks: z.array(SiloBlockSchema).describe('ui:list'),
2866
+ label: z.string().describe('ui:text'),
2867
+ });
2868
+
2869
+ const ProblemParagraphSchema = BaseArrayItem.extend({
2870
+ text: z.string().describe('ui:textarea'),
2871
+ isBold: z.boolean().default(false).describe('ui:checkbox'),
2872
+ });
2873
+
2874
+ export const ProblemStatementSchema = BaseSectionData.extend({
2875
+ siloGroups: z.array(SiloGroupSchema).describe('ui:list'),
2876
+ title: z.string().describe('ui:text'),
2877
+ paragraphs: z.array(ProblemParagraphSchema).describe('ui:list'),
2878
+ highlight: z.string().optional().describe('ui:text'),
2879
+ });
2880
2880
 
2881
2881
  END_OF_FILE_CONTENT
2882
2882
  echo "Creating src/components/problem-statement/types.ts..."
2883
2883
  cat << 'END_OF_FILE_CONTENT' > "src/components/problem-statement/types.ts"
2884
- import { z } from 'zod';
2885
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
2886
- import { ProblemStatementSchema, SiloBlockVariantSchema } from './schema';
2887
-
2888
- export type ProblemStatementData = z.infer<typeof ProblemStatementSchema>;
2889
- export type ProblemStatementSettings = z.infer<typeof BaseSectionSettingsSchema>;
2890
- export type SiloBlockVariant = z.infer<typeof SiloBlockVariantSchema>;
2884
+ import { z } from 'zod';
2885
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
2886
+ import { ProblemStatementSchema, SiloBlockVariantSchema } from './schema';
2887
+
2888
+ export type ProblemStatementData = z.infer<typeof ProblemStatementSchema>;
2889
+ export type ProblemStatementSettings = z.infer<typeof BaseSectionSettingsSchema>;
2890
+ export type SiloBlockVariant = z.infer<typeof SiloBlockVariantSchema>;
2891
2891
 
2892
2892
  END_OF_FILE_CONTENT
2893
2893
  mkdir -p "src/components/product-triad"
2894
2894
  echo "Creating src/components/product-triad/View.tsx..."
2895
2895
  cat << 'END_OF_FILE_CONTENT' > "src/components/product-triad/View.tsx"
2896
- import React from 'react';
2897
- import { cn } from '@/lib/utils';
2898
- import type { ProductTriadData, ProductTriadSettings } from './types';
2899
-
2900
- export const ProductTriad: React.FC<{ data: ProductTriadData; settings?: ProductTriadSettings }> = ({ data }) => {
2901
- return (
2902
- <section
2903
- style={{
2904
- '--local-bg': 'var(--background)',
2905
- '--local-text': 'var(--foreground)',
2906
- '--local-text-muted': 'var(--muted-foreground)',
2907
- '--local-primary': 'var(--primary)',
2908
- '--local-accent': 'var(--color-accent, #60a5fa)',
2909
- '--local-border': 'var(--border)',
2910
- } as React.CSSProperties}
2911
- className="relative z-0 py-28 bg-[var(--local-bg)]"
2912
- >
2913
- <div className="max-w-[1200px] mx-auto px-8">
2914
- <div className="text-center">
2915
- {data.label && (
2916
- <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">
2917
- <span className="w-5 h-px bg-[var(--local-primary)]" />
2918
- {data.label}
2919
- </div>
2920
- )}
2921
- <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
2922
- {data.title}
2923
- </h2>
2924
- {data.description && (
2925
- <p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed" data-jp-field="description">
2926
- {data.description}
2927
- </p>
2928
- )}
2929
- </div>
2930
- <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mt-14">
2931
- {data.products.map((product, idx) => (
2932
- <div
2933
- key={product.id ?? idx}
2934
- className={cn(
2935
- 'relative border rounded-lg p-10 transition-all duration-300 hover:-translate-y-1',
2936
- product.featured
2937
- ? 'border-[rgba(59,130,246,0.3)] bg-gradient-to-b from-[rgba(59,130,246,0.06)] to-[rgba(59,130,246,0.01)] hover:border-[rgba(59,130,246,0.4)]'
2938
- : 'border-[rgba(255,255,255,0.06)] bg-[rgba(255,255,255,0.015)] hover:border-[rgba(59,130,246,0.2)]'
2939
- )}
2940
- data-jp-item-id={product.id ?? `legacy-${idx}`}
2941
- data-jp-item-field="products"
2942
- >
2943
- {product.featured && (
2944
- <div className="absolute -top-3 left-1/2 -translate-x-1/2 bg-[var(--local-primary)] text-white text-[0.7rem] font-bold px-4 py-1 rounded-full uppercase tracking-wide">
2945
- Most Popular
2946
- </div>
2947
- )}
2948
- <div className="text-[0.75rem] font-bold uppercase tracking-[0.1em] text-[var(--local-accent)] mb-2">
2949
- {product.tier}
2950
- </div>
2951
- <div className="text-2xl font-extrabold text-[var(--local-text)] mb-2">
2952
- {product.name}
2953
- </div>
2954
- <div className="font-display text-[2.2rem] font-extrabold text-[var(--local-text)] mb-1">
2955
- {product.price}
2956
- {product.priceSuffix && (
2957
- <span className="text-[0.9rem] font-normal text-[var(--local-text-muted)]">
2958
- {product.priceSuffix}
2959
- </span>
2960
- )}
2961
- </div>
2962
- <div className="text-[0.85rem] text-[var(--local-text-muted)] mb-6 pb-6 border-b border-[rgba(255,255,255,0.06)]">
2963
- {product.delivery}
2964
- </div>
2965
- <ul className="mb-8 space-y-0">
2966
- {product.features.map((feature, fIdx) => (
2967
- <li
2968
- key={fIdx}
2969
- className="text-[0.9rem] text-[#cbd5e1] py-1.5 pl-6 relative before:content-['✓'] before:absolute before:left-0 before:text-[var(--local-accent)] before:font-bold before:text-[0.8rem]"
2970
- >
2971
- {feature.text}
2972
- </li>
2973
- ))}
2974
- </ul>
2975
- {product.ctaLabel && product.ctaHref && (
2976
- <a
2977
- href={product.ctaHref}
2978
- className={cn(
2979
- 'block text-center py-3 rounded-[5px] no-underline font-semibold text-[0.95rem] transition-all duration-200',
2980
- product.ctaVariant === 'primary'
2981
- ? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-px'
2982
- : 'bg-[rgba(255,255,255,0.05)] text-[#e2e8f0] border border-[rgba(255,255,255,0.1)] hover:bg-[rgba(255,255,255,0.08)] hover:border-[rgba(255,255,255,0.2)]'
2983
- )}
2984
- >
2985
- {product.ctaLabel}
2986
- </a>
2987
- )}
2988
- </div>
2989
- ))}
2990
- </div>
2991
- </div>
2992
- </section>
2993
- );
2994
- };
2896
+ import React from 'react';
2897
+ import { cn } from '@/lib/utils';
2898
+ import type { ProductTriadData, ProductTriadSettings } from './types';
2899
+
2900
+ export const ProductTriad: React.FC<{ data: ProductTriadData; settings?: ProductTriadSettings }> = ({ data }) => {
2901
+ return (
2902
+ <section
2903
+ style={{
2904
+ '--local-bg': 'var(--background)',
2905
+ '--local-text': 'var(--foreground)',
2906
+ '--local-text-muted': 'var(--muted-foreground)',
2907
+ '--local-primary': 'var(--primary)',
2908
+ '--local-accent': 'var(--color-accent, #60a5fa)',
2909
+ '--local-border': 'var(--border)',
2910
+ } as React.CSSProperties}
2911
+ className="relative z-0 py-28 bg-[var(--local-bg)]"
2912
+ >
2913
+ <div className="max-w-[1200px] mx-auto px-8">
2914
+ <div className="text-center">
2915
+ {data.label && (
2916
+ <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">
2917
+ <span className="w-5 h-px bg-[var(--local-primary)]" />
2918
+ {data.label}
2919
+ </div>
2920
+ )}
2921
+ <h2 className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-4" data-jp-field="title">
2922
+ {data.title}
2923
+ </h2>
2924
+ {data.description && (
2925
+ <p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed" data-jp-field="description">
2926
+ {data.description}
2927
+ </p>
2928
+ )}
2929
+ </div>
2930
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mt-14">
2931
+ {data.products.map((product, idx) => (
2932
+ <div
2933
+ key={product.id ?? idx}
2934
+ className={cn(
2935
+ 'relative border rounded-lg p-10 transition-all duration-300 hover:-translate-y-1',
2936
+ product.featured
2937
+ ? 'border-[rgba(59,130,246,0.3)] bg-gradient-to-b from-[rgba(59,130,246,0.06)] to-[rgba(59,130,246,0.01)] hover:border-[rgba(59,130,246,0.4)]'
2938
+ : 'border-[rgba(255,255,255,0.06)] bg-[rgba(255,255,255,0.015)] hover:border-[rgba(59,130,246,0.2)]'
2939
+ )}
2940
+ data-jp-item-id={product.id ?? `legacy-${idx}`}
2941
+ data-jp-item-field="products"
2942
+ >
2943
+ {product.featured && (
2944
+ <div className="absolute -top-3 left-1/2 -translate-x-1/2 bg-[var(--local-primary)] text-white text-[0.7rem] font-bold px-4 py-1 rounded-full uppercase tracking-wide">
2945
+ Most Popular
2946
+ </div>
2947
+ )}
2948
+ <div className="text-[0.75rem] font-bold uppercase tracking-[0.1em] text-[var(--local-accent)] mb-2">
2949
+ {product.tier}
2950
+ </div>
2951
+ <div className="text-2xl font-extrabold text-[var(--local-text)] mb-2">
2952
+ {product.name}
2953
+ </div>
2954
+ <div className="font-display text-[2.2rem] font-extrabold text-[var(--local-text)] mb-1">
2955
+ {product.price}
2956
+ {product.priceSuffix && (
2957
+ <span className="text-[0.9rem] font-normal text-[var(--local-text-muted)]">
2958
+ {product.priceSuffix}
2959
+ </span>
2960
+ )}
2961
+ </div>
2962
+ <div className="text-[0.85rem] text-[var(--local-text-muted)] mb-6 pb-6 border-b border-[rgba(255,255,255,0.06)]">
2963
+ {product.delivery}
2964
+ </div>
2965
+ <ul className="mb-8 space-y-0">
2966
+ {product.features.map((feature, fIdx) => (
2967
+ <li
2968
+ key={fIdx}
2969
+ className="text-[0.9rem] text-[#cbd5e1] py-1.5 pl-6 relative before:content-['✓'] before:absolute before:left-0 before:text-[var(--local-accent)] before:font-bold before:text-[0.8rem]"
2970
+ >
2971
+ {feature.text}
2972
+ </li>
2973
+ ))}
2974
+ </ul>
2975
+ {product.ctaLabel && product.ctaHref && (
2976
+ <a
2977
+ href={product.ctaHref}
2978
+ className={cn(
2979
+ 'block text-center py-3 rounded-[5px] no-underline font-semibold text-[0.95rem] transition-all duration-200',
2980
+ product.ctaVariant === 'primary'
2981
+ ? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-px'
2982
+ : 'bg-[rgba(255,255,255,0.05)] text-[#e2e8f0] border border-[rgba(255,255,255,0.1)] hover:bg-[rgba(255,255,255,0.08)] hover:border-[rgba(255,255,255,0.2)]'
2983
+ )}
2984
+ >
2985
+ {product.ctaLabel}
2986
+ </a>
2987
+ )}
2988
+ </div>
2989
+ ))}
2990
+ </div>
2991
+ </div>
2992
+ </section>
2993
+ );
2994
+ };
2995
2995
 
2996
2996
  END_OF_FILE_CONTENT
2997
2997
  echo "Creating src/components/product-triad/index.ts..."
2998
2998
  cat << 'END_OF_FILE_CONTENT' > "src/components/product-triad/index.ts"
2999
- export * from './View';
3000
- export * from './schema';
3001
- export * from './types';
2999
+ export * from './View';
3000
+ export * from './schema';
3001
+ export * from './types';
3002
3002
 
3003
3003
  END_OF_FILE_CONTENT
3004
3004
  echo "Creating src/components/product-triad/schema.ts..."
3005
3005
  cat << 'END_OF_FILE_CONTENT' > "src/components/product-triad/schema.ts"
3006
- import { z } from 'zod';
3007
- import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
3008
-
3009
- const ProductFeatureSchema = z.object({
3010
- text: z.string().describe('ui:text'),
3011
- });
3012
-
3013
- const ProductCardSchema = BaseArrayItem.extend({
3014
- tier: z.string().describe('ui:text'),
3015
- name: z.string().describe('ui:text'),
3016
- price: z.string().describe('ui:text'),
3017
- priceSuffix: z.string().optional().describe('ui:text'),
3018
- delivery: z.string().describe('ui:text'),
3019
- features: z.array(ProductFeatureSchema).describe('ui:list'),
3020
- featured: z.boolean().default(false).describe('ui:checkbox'),
3021
- ctaLabel: z.string().optional().describe('ui:text'),
3022
- ctaHref: z.string().optional().describe('ui:text'),
3023
- ctaVariant: z.enum(['primary', 'secondary']).default('secondary').describe('ui:select'),
3024
- });
3025
-
3026
- export const ProductTriadSchema = BaseSectionData.extend({
3027
- label: z.string().optional().describe('ui:text'),
3028
- title: z.string().describe('ui:text'),
3029
- description: z.string().optional().describe('ui:textarea'),
3030
- products: z.array(ProductCardSchema).describe('ui:list'),
3031
- });
3006
+ import { z } from 'zod';
3007
+ import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
3008
+
3009
+ const ProductFeatureSchema = z.object({
3010
+ text: z.string().describe('ui:text'),
3011
+ });
3012
+
3013
+ const ProductCardSchema = BaseArrayItem.extend({
3014
+ tier: z.string().describe('ui:text'),
3015
+ name: z.string().describe('ui:text'),
3016
+ price: z.string().describe('ui:text'),
3017
+ priceSuffix: z.string().optional().describe('ui:text'),
3018
+ delivery: z.string().describe('ui:text'),
3019
+ features: z.array(ProductFeatureSchema).describe('ui:list'),
3020
+ featured: z.boolean().default(false).describe('ui:checkbox'),
3021
+ ctaLabel: z.string().optional().describe('ui:text'),
3022
+ ctaHref: z.string().optional().describe('ui:text'),
3023
+ ctaVariant: z.enum(['primary', 'secondary']).default('secondary').describe('ui:select'),
3024
+ });
3025
+
3026
+ export const ProductTriadSchema = BaseSectionData.extend({
3027
+ label: z.string().optional().describe('ui:text'),
3028
+ title: z.string().describe('ui:text'),
3029
+ description: z.string().optional().describe('ui:textarea'),
3030
+ products: z.array(ProductCardSchema).describe('ui:list'),
3031
+ });
3032
3032
 
3033
3033
  END_OF_FILE_CONTENT
3034
3034
  echo "Creating src/components/product-triad/types.ts..."
3035
3035
  cat << 'END_OF_FILE_CONTENT' > "src/components/product-triad/types.ts"
3036
- import { z } from 'zod';
3037
- import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
3038
- import { ProductTriadSchema } from './schema';
3039
-
3040
- export type ProductTriadData = z.infer<typeof ProductTriadSchema>;
3041
- export type ProductTriadSettings = z.infer<typeof BaseSectionSettingsSchema>;
3036
+ import { z } from 'zod';
3037
+ import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
3038
+ import { ProductTriadSchema } from './schema';
3039
+
3040
+ export type ProductTriadData = z.infer<typeof ProductTriadSchema>;
3041
+ export type ProductTriadSettings = z.infer<typeof BaseSectionSettingsSchema>;
3042
3042
 
3043
3043
  END_OF_FILE_CONTENT
3044
3044
  mkdir -p "src/components/ui"
@@ -4361,180 +4361,180 @@ export const useDocumentMeta = (meta: PageMeta): void => {
4361
4361
  END_OF_FILE_CONTENT
4362
4362
  echo "Creating src/index.css..."
4363
4363
  cat << 'END_OF_FILE_CONTENT' > "src/index.css"
4364
- @import "tailwindcss";
4365
-
4366
- @source "./**/*.tsx";
4367
-
4368
- @theme {
4369
- /*
4370
- 🎯 MASTER MAPPING (V2.7 Landing)
4371
- */
4372
- --color-background: var(--background);
4373
- --color-foreground: var(--foreground);
4374
-
4375
- --color-card: var(--card);
4376
- --color-card-foreground: var(--card-foreground);
4377
-
4378
- --color-primary: var(--primary);
4379
- --color-primary-foreground: var(--primary-foreground);
4380
-
4381
- --color-secondary: var(--secondary);
4382
- --color-secondary-foreground: var(--secondary-foreground);
4383
-
4384
- --color-muted: var(--muted);
4385
- --color-muted-foreground: var(--muted-foreground);
4386
-
4387
- --color-accent: var(--accent);
4388
- --color-border: var(--border);
4389
-
4390
- --radius-lg: var(--radius);
4391
- --radius-md: calc(var(--radius) - 2px);
4392
- --radius-sm: calc(var(--radius) - 4px);
4393
-
4394
- --font-primary: var(--theme-font-primary);
4395
- --font-mono: var(--theme-font-mono);
4396
-
4397
- /*
4398
- 🔧 DISPLAY FONT — Forward-compatible workaround
4399
- theme-manager.ts does NOT inject --theme-font-display (Skeleton v2.7 gap).
4400
- The var() falls through to the hardcoded fallback today.
4401
- When Skeleton v2.8 wires display into theme-manager, the var() will resolve
4402
- automatically and the fallback becomes dead code.
4403
- */
4404
- --font-display: var(--theme-font-display, 'Playfair Display', Georgia, serif);
4405
- }
4406
-
4407
- /*
4408
- 🌍 TENANT BRAND TOKENS (JSP 1.5)
4409
- */
4410
- :root {
4411
- --background: var(--theme-background);
4412
- --foreground: var(--theme-text);
4413
- --card: var(--theme-surface);
4414
- --card-foreground: var(--theme-text);
4415
- --primary: var(--theme-primary);
4416
- --primary-foreground: oklch(0.98 0 0);
4417
- --secondary: var(--theme-secondary);
4418
- --secondary-foreground: var(--theme-text);
4419
- --muted: var(--theme-surface-alt);
4420
- --muted-foreground: var(--theme-text-muted);
4421
- --border: var(--theme-border);
4422
- --radius: 0.45rem;
4423
-
4424
- /*
4425
- 🔧 ACCENT CHAIN — Forward-compatible workaround
4426
- theme-manager.ts already injects --theme-accent on :root,
4427
- but the original index.css never bridged it into the semantic layer.
4428
- This closes the gap: --theme-accent → --accent → --color-accent.
4429
- Falls back to --theme-primary if accent is undefined.
4430
- */
4431
- --accent: var(--theme-accent, var(--theme-primary));
4432
- }
4433
-
4434
- @layer base {
4435
- * { border-color: var(--border); }
4436
- body {
4437
- background-color: var(--background);
4438
- color: var(--foreground);
4439
- font-family: var(--font-primary);
4440
- line-height: 1.7;
4441
- overflow-x: hidden;
4442
- @apply antialiased;
4443
- }
4444
- }
4445
-
4446
- /* ==========================================================================
4447
- FONT DISPLAY UTILITY
4448
- Maps .font-display class to the display font family (Playfair Display)
4449
- ========================================================================== */
4450
- .font-display {
4451
- font-family: var(--font-display, var(--font-primary));
4452
- }
4453
-
4454
- /* ==========================================================================
4455
- LANDING ANIMATIONS
4456
- ========================================================================== */
4457
- @keyframes jp-fadeUp {
4458
- from { opacity: 0; transform: translateY(30px); }
4459
- to { opacity: 1; transform: translateY(0); }
4460
- }
4461
-
4462
- @keyframes jp-pulseDot {
4463
- 0%, 100% { opacity: 1; }
4464
- 50% { opacity: 0.3; }
4465
- }
4466
-
4467
- .jp-animate-in {
4468
- opacity: 0;
4469
- animation: jp-fadeUp 0.7s ease forwards;
4470
- }
4471
- .jp-d1 { animation-delay: 0.1s; }
4472
- .jp-d2 { animation-delay: 0.2s; }
4473
- .jp-d3 { animation-delay: 0.3s; }
4474
- .jp-d4 { animation-delay: 0.4s; }
4475
- .jp-d5 { animation-delay: 0.5s; }
4476
-
4477
- .jp-pulse-dot {
4478
- animation: jp-pulseDot 2s ease infinite;
4479
- }
4480
-
4481
- /* ==========================================================================
4482
- SMOOTH SCROLL
4483
- ========================================================================== */
4484
- html {
4485
- scroll-behavior: smooth;
4486
- }
4487
-
4488
- /* ==========================================================================
4489
- ICE ADMIN — Section highlight in preview iframe
4490
- The preview iframe only receives tenant CSS; core's overlay classes
4491
- (z-[50], absolute, etc.) are not in this build. Define them here so
4492
- the section highlight is always visible in /admin.
4493
- ========================================================================== */
4494
- [data-jp-section-overlay] {
4495
- position: absolute;
4496
- inset: 0;
4497
- z-index: 9999;
4498
- pointer-events: none;
4499
- transition: border-color 0.2s, background-color 0.2s;
4500
- border: 2px solid transparent;
4501
- }
4502
-
4503
- [data-section-id]:hover [data-jp-section-overlay] {
4504
- border-color: rgba(96, 165, 250, 0.5);
4505
- border-style: dashed;
4506
- }
4507
-
4508
- [data-section-id][data-jp-selected] [data-jp-section-overlay] {
4509
- border-color: rgb(37, 99, 235);
4510
- border-style: solid;
4511
- background-color: rgba(59, 130, 246, 0.05);
4512
- }
4513
-
4514
- [data-jp-section-overlay] > div {
4515
- position: absolute;
4516
- top: 0;
4517
- right: 0;
4518
- padding: 0.25rem 0.5rem;
4519
- font-size: 9px;
4520
- font-weight: 800;
4521
- text-transform: uppercase;
4522
- letter-spacing: 0.1em;
4523
- background: rgb(37, 99, 235);
4524
- color: white;
4525
- transition: opacity 0.2s;
4526
- }
4527
-
4528
- [data-section-id]:hover [data-jp-section-overlay] > div,
4529
- [data-section-id][data-jp-selected] [data-jp-section-overlay] > div {
4530
- opacity: 1;
4531
- }
4532
-
4533
- [data-section-id] [data-jp-section-overlay] > div {
4534
- opacity: 0;
4535
- }
4536
-
4537
-
4364
+ @import "tailwindcss";
4365
+
4366
+ @source "./**/*.tsx";
4367
+
4368
+ @theme {
4369
+ /*
4370
+ 🎯 MASTER MAPPING (V2.7 Landing)
4371
+ */
4372
+ --color-background: var(--background);
4373
+ --color-foreground: var(--foreground);
4374
+
4375
+ --color-card: var(--card);
4376
+ --color-card-foreground: var(--card-foreground);
4377
+
4378
+ --color-primary: var(--primary);
4379
+ --color-primary-foreground: var(--primary-foreground);
4380
+
4381
+ --color-secondary: var(--secondary);
4382
+ --color-secondary-foreground: var(--secondary-foreground);
4383
+
4384
+ --color-muted: var(--muted);
4385
+ --color-muted-foreground: var(--muted-foreground);
4386
+
4387
+ --color-accent: var(--accent);
4388
+ --color-border: var(--border);
4389
+
4390
+ --radius-lg: var(--radius);
4391
+ --radius-md: calc(var(--radius) - 2px);
4392
+ --radius-sm: calc(var(--radius) - 4px);
4393
+
4394
+ --font-primary: var(--theme-font-primary);
4395
+ --font-mono: var(--theme-font-mono);
4396
+
4397
+ /*
4398
+ 🔧 DISPLAY FONT — Forward-compatible workaround
4399
+ theme-manager.ts does NOT inject --theme-font-display (Skeleton v2.7 gap).
4400
+ The var() falls through to the hardcoded fallback today.
4401
+ When Skeleton v2.8 wires display into theme-manager, the var() will resolve
4402
+ automatically and the fallback becomes dead code.
4403
+ */
4404
+ --font-display: var(--theme-font-display, 'Playfair Display', Georgia, serif);
4405
+ }
4406
+
4407
+ /*
4408
+ 🌍 TENANT BRAND TOKENS (JSP 1.5)
4409
+ */
4410
+ :root {
4411
+ --background: var(--theme-background);
4412
+ --foreground: var(--theme-text);
4413
+ --card: var(--theme-surface);
4414
+ --card-foreground: var(--theme-text);
4415
+ --primary: var(--theme-primary);
4416
+ --primary-foreground: oklch(0.98 0 0);
4417
+ --secondary: var(--theme-secondary);
4418
+ --secondary-foreground: var(--theme-text);
4419
+ --muted: var(--theme-surface-alt);
4420
+ --muted-foreground: var(--theme-text-muted);
4421
+ --border: var(--theme-border);
4422
+ --radius: 0.45rem;
4423
+
4424
+ /*
4425
+ 🔧 ACCENT CHAIN — Forward-compatible workaround
4426
+ theme-manager.ts already injects --theme-accent on :root,
4427
+ but the original index.css never bridged it into the semantic layer.
4428
+ This closes the gap: --theme-accent → --accent → --color-accent.
4429
+ Falls back to --theme-primary if accent is undefined.
4430
+ */
4431
+ --accent: var(--theme-accent, var(--theme-primary));
4432
+ }
4433
+
4434
+ @layer base {
4435
+ * { border-color: var(--border); }
4436
+ body {
4437
+ background-color: var(--background);
4438
+ color: var(--foreground);
4439
+ font-family: var(--font-primary);
4440
+ line-height: 1.7;
4441
+ overflow-x: hidden;
4442
+ @apply antialiased;
4443
+ }
4444
+ }
4445
+
4446
+ /* ==========================================================================
4447
+ FONT DISPLAY UTILITY
4448
+ Maps .font-display class to the display font family (Playfair Display)
4449
+ ========================================================================== */
4450
+ .font-display {
4451
+ font-family: var(--font-display, var(--font-primary));
4452
+ }
4453
+
4454
+ /* ==========================================================================
4455
+ LANDING ANIMATIONS
4456
+ ========================================================================== */
4457
+ @keyframes jp-fadeUp {
4458
+ from { opacity: 0; transform: translateY(30px); }
4459
+ to { opacity: 1; transform: translateY(0); }
4460
+ }
4461
+
4462
+ @keyframes jp-pulseDot {
4463
+ 0%, 100% { opacity: 1; }
4464
+ 50% { opacity: 0.3; }
4465
+ }
4466
+
4467
+ .jp-animate-in {
4468
+ opacity: 0;
4469
+ animation: jp-fadeUp 0.7s ease forwards;
4470
+ }
4471
+ .jp-d1 { animation-delay: 0.1s; }
4472
+ .jp-d2 { animation-delay: 0.2s; }
4473
+ .jp-d3 { animation-delay: 0.3s; }
4474
+ .jp-d4 { animation-delay: 0.4s; }
4475
+ .jp-d5 { animation-delay: 0.5s; }
4476
+
4477
+ .jp-pulse-dot {
4478
+ animation: jp-pulseDot 2s ease infinite;
4479
+ }
4480
+
4481
+ /* ==========================================================================
4482
+ SMOOTH SCROLL
4483
+ ========================================================================== */
4484
+ html {
4485
+ scroll-behavior: smooth;
4486
+ }
4487
+
4488
+ /* ==========================================================================
4489
+ ICE ADMIN — Section highlight in preview iframe
4490
+ The preview iframe only receives tenant CSS; core's overlay classes
4491
+ (z-[50], absolute, etc.) are not in this build. Define them here so
4492
+ the section highlight is always visible in /admin.
4493
+ ========================================================================== */
4494
+ [data-jp-section-overlay] {
4495
+ position: absolute;
4496
+ inset: 0;
4497
+ z-index: 9999;
4498
+ pointer-events: none;
4499
+ transition: border-color 0.2s, background-color 0.2s;
4500
+ border: 2px solid transparent;
4501
+ }
4502
+
4503
+ [data-section-id]:hover [data-jp-section-overlay] {
4504
+ border-color: rgba(96, 165, 250, 0.5);
4505
+ border-style: dashed;
4506
+ }
4507
+
4508
+ [data-section-id][data-jp-selected] [data-jp-section-overlay] {
4509
+ border-color: rgb(37, 99, 235);
4510
+ border-style: solid;
4511
+ background-color: rgba(59, 130, 246, 0.05);
4512
+ }
4513
+
4514
+ [data-jp-section-overlay] > div {
4515
+ position: absolute;
4516
+ top: 0;
4517
+ right: 0;
4518
+ padding: 0.25rem 0.5rem;
4519
+ font-size: 9px;
4520
+ font-weight: 800;
4521
+ text-transform: uppercase;
4522
+ letter-spacing: 0.1em;
4523
+ background: rgb(37, 99, 235);
4524
+ color: white;
4525
+ transition: opacity 0.2s;
4526
+ }
4527
+
4528
+ [data-section-id]:hover [data-jp-section-overlay] > div,
4529
+ [data-section-id][data-jp-selected] [data-jp-section-overlay] > div {
4530
+ opacity: 1;
4531
+ }
4532
+
4533
+ [data-section-id] [data-jp-section-overlay] > div {
4534
+ opacity: 0;
4535
+ }
4536
+
4537
+
4538
4538
 
4539
4539
  END_OF_FILE_CONTENT
4540
4540
  mkdir -p "src/lib"
@@ -4706,44 +4706,44 @@ export const addSectionConfig: AddSectionConfig = {
4706
4706
  END_OF_FILE_CONTENT
4707
4707
  echo "Creating src/lib/base-schemas.ts..."
4708
4708
  cat << 'END_OF_FILE_CONTENT' > "src/lib/base-schemas.ts"
4709
- import { z } from 'zod';
4710
-
4711
- /**
4712
- * Image picker field: object { url, alt? } with ui:image-picker for Form Factory.
4713
- * Use in section data and resolve with resolveAssetUrl(url, tenantId) in View.
4714
- */
4715
- export const ImageSelectionSchema = z
4716
- .object({
4717
- url: z.string(),
4718
- alt: z.string().optional(),
4719
- })
4720
- .describe('ui:image-picker');
4721
-
4722
- /**
4723
- * Base schemas shared by section capsules (CIP governance).
4724
- * Capsules extend these for consistent anchorId, array items, and settings.
4725
- */
4726
- export const BaseSectionData = z.object({
4727
- anchorId: z.string().optional().describe('ui:text'),
4728
- });
4729
-
4730
- export const BaseArrayItem = z.object({
4731
- id: z.string().optional(),
4732
- });
4733
-
4734
- export const BaseSectionSettingsSchema = z.object({
4735
- paddingTop: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),
4736
- paddingBottom: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),
4737
- theme: z.enum(['dark', 'light', 'accent']).default('dark').describe('ui:select'),
4738
- container: z.enum(['boxed', 'fluid']).default('boxed').describe('ui:select'),
4739
- });
4740
-
4741
- export const CtaSchema = z.object({
4742
- id: z.string().optional(),
4743
- label: z.string().describe('ui:text'),
4744
- href: z.string().describe('ui:text'),
4745
- variant: z.enum(['primary', 'secondary']).default('primary').describe('ui:select'),
4746
- });
4709
+ import { z } from 'zod';
4710
+
4711
+ /**
4712
+ * Image picker field: object { url, alt? } with ui:image-picker for Form Factory.
4713
+ * Use in section data and resolve with resolveAssetUrl(url, tenantId) in View.
4714
+ */
4715
+ export const ImageSelectionSchema = z
4716
+ .object({
4717
+ url: z.string(),
4718
+ alt: z.string().optional(),
4719
+ })
4720
+ .describe('ui:image-picker');
4721
+
4722
+ /**
4723
+ * Base schemas shared by section capsules (CIP governance).
4724
+ * Capsules extend these for consistent anchorId, array items, and settings.
4725
+ */
4726
+ export const BaseSectionData = z.object({
4727
+ anchorId: z.string().optional().describe('ui:text'),
4728
+ });
4729
+
4730
+ export const BaseArrayItem = z.object({
4731
+ id: z.string().optional(),
4732
+ });
4733
+
4734
+ export const BaseSectionSettingsSchema = z.object({
4735
+ paddingTop: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),
4736
+ paddingBottom: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),
4737
+ theme: z.enum(['dark', 'light', 'accent']).default('dark').describe('ui:select'),
4738
+ container: z.enum(['boxed', 'fluid']).default('boxed').describe('ui:select'),
4739
+ });
4740
+
4741
+ export const CtaSchema = z.object({
4742
+ id: z.string().optional(),
4743
+ label: z.string().describe('ui:text'),
4744
+ href: z.string().describe('ui:text'),
4745
+ variant: z.enum(['primary', 'secondary']).default('primary').describe('ui:select'),
4746
+ });
4747
4747
 
4748
4748
  END_OF_FILE_CONTENT
4749
4749
  echo "Creating src/lib/draftStorage.ts..."
@@ -4868,20 +4868,20 @@ export function cn(...inputs: ClassValue[]) {
4868
4868
  END_OF_FILE_CONTENT
4869
4869
  echo "Creating src/main.tsx..."
4870
4870
  cat << 'END_OF_FILE_CONTENT' > "src/main.tsx"
4871
- import '@/types'; // TBP: load type augmentation from capsule-driven types
4872
- import React from 'react';
4873
- import ReactDOM from 'react-dom/client';
4874
- import App from './App';
4875
- // ... resto del file
4876
-
4877
- ReactDOM.createRoot(document.getElementById('root')!).render(
4878
- <React.StrictMode>
4879
- <App />
4880
- </React.StrictMode>
4881
- );
4882
-
4883
-
4884
-
4871
+ import '@/types'; // TBP: load type augmentation from capsule-driven types
4872
+ import React from 'react';
4873
+ import ReactDOM from 'react-dom/client';
4874
+ import App from './App';
4875
+ // ... resto del file
4876
+
4877
+ ReactDOM.createRoot(document.getElementById('root')!).render(
4878
+ <React.StrictMode>
4879
+ <App />
4880
+ </React.StrictMode>
4881
+ );
4882
+
4883
+
4884
+
4885
4885
 
4886
4886
  END_OF_FILE_CONTENT
4887
4887
  # SKIP: src/main.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.