@jsonpages/cli 3.0.40 → 3.0.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/src_tenant_alpha.sh +1452 -1452
- 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
|
-
“{renderQuote()}”
|
|
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
|
+
“{renderQuote()}”
|
|
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.
|