@jsonpages/cli 3.0.8 → 3.0.11
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 +1556 -1556
- package/package.json +2 -2
- package/src/index.js +16 -6
|
@@ -111,665 +111,665 @@ END_OF_FILE_CONTENT
|
|
|
111
111
|
mkdir -p "src/components/arch-layers"
|
|
112
112
|
echo "Creating src/components/arch-layers/View.tsx..."
|
|
113
113
|
cat << 'END_OF_FILE_CONTENT' > "src/components/arch-layers/View.tsx"
|
|
114
|
-
import React from 'react';
|
|
115
|
-
import { cn } from '@/lib/utils';
|
|
116
|
-
import type { ArchLayersData, ArchLayersSettings, ArchLayerLevel, SyntaxTokenType } from './types';
|
|
117
|
-
|
|
118
|
-
const layerBgStyles: Record<ArchLayerLevel, string> = {
|
|
119
|
-
l0: 'bg-[#3b82f6]',
|
|
120
|
-
l1: 'bg-[rgba(59,130,246,0.6)]',
|
|
121
|
-
l2: 'bg-[rgba(59,130,246,0.35)]',
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const tokenStyles: Record<SyntaxTokenType, string> = {
|
|
125
|
-
plain: 'text-[#cbd5e1]',
|
|
126
|
-
keyword: 'text-[#60a5fa]',
|
|
127
|
-
type: 'text-[#22d3ee]',
|
|
128
|
-
string: 'text-[#4ade80]',
|
|
129
|
-
comment: 'text-[#64748b] italic',
|
|
130
|
-
operator: 'text-[#f472b6]',
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
export const ArchLayers: React.FC<{ data: ArchLayersData; settings?: ArchLayersSettings }> = ({ data }) => {
|
|
134
|
-
return (
|
|
135
|
-
<section
|
|
136
|
-
style={{
|
|
137
|
-
'--local-bg': 'var(--card)',
|
|
138
|
-
'--local-text': 'var(--foreground)',
|
|
139
|
-
'--local-text-muted': 'var(--muted-foreground)',
|
|
140
|
-
'--local-primary': 'var(--primary)',
|
|
141
|
-
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
142
|
-
'--local-border': 'var(--border)',
|
|
143
|
-
'--local-deep': 'var(--background)',
|
|
144
|
-
} as React.CSSProperties}
|
|
145
|
-
className="relative z-0 py-28 bg-[var(--local-bg)]"
|
|
146
|
-
>
|
|
147
|
-
<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" />
|
|
148
|
-
<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" />
|
|
149
|
-
<div className="max-w-[1200px] mx-auto px-8">
|
|
150
|
-
<div className="text-center">
|
|
151
|
-
{data.label && (
|
|
152
|
-
<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">
|
|
153
|
-
<span className="w-5 h-px bg-[var(--local-primary)]" />
|
|
154
|
-
{data.label}
|
|
155
|
-
</div>
|
|
156
|
-
)}
|
|
157
|
-
<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">
|
|
158
|
-
{data.title}
|
|
159
|
-
</h2>
|
|
160
|
-
{data.description && (
|
|
161
|
-
<p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed" data-jp-field="description">
|
|
162
|
-
{data.description}
|
|
163
|
-
</p>
|
|
164
|
-
)}
|
|
165
|
-
</div>
|
|
166
|
-
<div className="mt-14 max-w-[740px] mx-auto">
|
|
167
|
-
{data.layers.map((layer, idx) => (
|
|
168
|
-
<div
|
|
169
|
-
key={layer.id ?? idx}
|
|
170
|
-
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"
|
|
171
|
-
data-jp-item-id={layer.id ?? `legacy-${idx}`}
|
|
172
|
-
data-jp-item-field="layers"
|
|
173
|
-
>
|
|
174
|
-
<div className={cn(
|
|
175
|
-
'shrink-0 w-9 h-9 rounded-lg flex items-center justify-center font-mono text-[0.85rem] font-bold text-white',
|
|
176
|
-
layerBgStyles[layer.layerLevel]
|
|
177
|
-
)}>
|
|
178
|
-
{layer.number}
|
|
179
|
-
</div>
|
|
180
|
-
<div>
|
|
181
|
-
<h4 className="text-[1.05rem] font-bold text-[var(--local-text)] mb-1.5">
|
|
182
|
-
{layer.title}
|
|
183
|
-
</h4>
|
|
184
|
-
<p className="text-[0.92rem] text-[var(--local-text-muted)] leading-relaxed">
|
|
185
|
-
{layer.description}
|
|
186
|
-
</p>
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
189
|
-
))}
|
|
190
|
-
</div>
|
|
191
|
-
{data.codeLines && data.codeLines.length > 0 && (
|
|
192
|
-
<div className="mt-12 max-w-[740px] mx-auto">
|
|
193
|
-
<div className="border border-[rgba(255,255,255,0.08)] rounded-[7px] overflow-hidden bg-[var(--local-deep)]">
|
|
194
|
-
<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)]">
|
|
195
|
-
<span className="w-2.5 h-2.5 rounded-full bg-[#ef4444]" />
|
|
196
|
-
<span className="w-2.5 h-2.5 rounded-full bg-[#f59e0b]" />
|
|
197
|
-
<span className="w-2.5 h-2.5 rounded-full bg-[#22c55e]" />
|
|
198
|
-
{data.codeFilename && (
|
|
199
|
-
<span className="ml-3 font-mono text-[0.75rem] text-[var(--local-text-muted)] opacity-60" data-jp-field="codeFilename">
|
|
200
|
-
{data.codeFilename}
|
|
201
|
-
</span>
|
|
202
|
-
)}
|
|
203
|
-
</div>
|
|
204
|
-
<div className="p-6 font-mono text-[0.82rem] leading-[1.7] overflow-x-auto">
|
|
205
|
-
{data.codeLines.map((line, idx) => (
|
|
206
|
-
<div key={idx} data-jp-item-id={(line as { id?: string }).id ?? `legacy-${idx}`} data-jp-item-field="codeLines">
|
|
207
|
-
<span className={tokenStyles[line.tokenType]}>
|
|
208
|
-
{line.content}
|
|
209
|
-
</span>
|
|
210
|
-
</div>
|
|
211
|
-
))}
|
|
212
|
-
</div>
|
|
213
|
-
</div>
|
|
214
|
-
</div>
|
|
215
|
-
)}
|
|
216
|
-
</div>
|
|
217
|
-
</section>
|
|
218
|
-
);
|
|
219
|
-
};
|
|
114
|
+
import React from 'react';
|
|
115
|
+
import { cn } from '@/lib/utils';
|
|
116
|
+
import type { ArchLayersData, ArchLayersSettings, ArchLayerLevel, SyntaxTokenType } from './types';
|
|
117
|
+
|
|
118
|
+
const layerBgStyles: Record<ArchLayerLevel, string> = {
|
|
119
|
+
l0: 'bg-[#3b82f6]',
|
|
120
|
+
l1: 'bg-[rgba(59,130,246,0.6)]',
|
|
121
|
+
l2: 'bg-[rgba(59,130,246,0.35)]',
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const tokenStyles: Record<SyntaxTokenType, string> = {
|
|
125
|
+
plain: 'text-[#cbd5e1]',
|
|
126
|
+
keyword: 'text-[#60a5fa]',
|
|
127
|
+
type: 'text-[#22d3ee]',
|
|
128
|
+
string: 'text-[#4ade80]',
|
|
129
|
+
comment: 'text-[#64748b] italic',
|
|
130
|
+
operator: 'text-[#f472b6]',
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const ArchLayers: React.FC<{ data: ArchLayersData; settings?: ArchLayersSettings }> = ({ data }) => {
|
|
134
|
+
return (
|
|
135
|
+
<section
|
|
136
|
+
style={{
|
|
137
|
+
'--local-bg': 'var(--card)',
|
|
138
|
+
'--local-text': 'var(--foreground)',
|
|
139
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
140
|
+
'--local-primary': 'var(--primary)',
|
|
141
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
142
|
+
'--local-border': 'var(--border)',
|
|
143
|
+
'--local-deep': 'var(--background)',
|
|
144
|
+
} as React.CSSProperties}
|
|
145
|
+
className="relative z-0 py-28 bg-[var(--local-bg)]"
|
|
146
|
+
>
|
|
147
|
+
<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" />
|
|
148
|
+
<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" />
|
|
149
|
+
<div className="max-w-[1200px] mx-auto px-8">
|
|
150
|
+
<div className="text-center">
|
|
151
|
+
{data.label && (
|
|
152
|
+
<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">
|
|
153
|
+
<span className="w-5 h-px bg-[var(--local-primary)]" />
|
|
154
|
+
{data.label}
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
<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">
|
|
158
|
+
{data.title}
|
|
159
|
+
</h2>
|
|
160
|
+
{data.description && (
|
|
161
|
+
<p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed" data-jp-field="description">
|
|
162
|
+
{data.description}
|
|
163
|
+
</p>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
<div className="mt-14 max-w-[740px] mx-auto">
|
|
167
|
+
{data.layers.map((layer, idx) => (
|
|
168
|
+
<div
|
|
169
|
+
key={layer.id ?? idx}
|
|
170
|
+
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"
|
|
171
|
+
data-jp-item-id={layer.id ?? `legacy-${idx}`}
|
|
172
|
+
data-jp-item-field="layers"
|
|
173
|
+
>
|
|
174
|
+
<div className={cn(
|
|
175
|
+
'shrink-0 w-9 h-9 rounded-lg flex items-center justify-center font-mono text-[0.85rem] font-bold text-white',
|
|
176
|
+
layerBgStyles[layer.layerLevel]
|
|
177
|
+
)}>
|
|
178
|
+
{layer.number}
|
|
179
|
+
</div>
|
|
180
|
+
<div>
|
|
181
|
+
<h4 className="text-[1.05rem] font-bold text-[var(--local-text)] mb-1.5">
|
|
182
|
+
{layer.title}
|
|
183
|
+
</h4>
|
|
184
|
+
<p className="text-[0.92rem] text-[var(--local-text-muted)] leading-relaxed">
|
|
185
|
+
{layer.description}
|
|
186
|
+
</p>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
))}
|
|
190
|
+
</div>
|
|
191
|
+
{data.codeLines && data.codeLines.length > 0 && (
|
|
192
|
+
<div className="mt-12 max-w-[740px] mx-auto">
|
|
193
|
+
<div className="border border-[rgba(255,255,255,0.08)] rounded-[7px] overflow-hidden bg-[var(--local-deep)]">
|
|
194
|
+
<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)]">
|
|
195
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#ef4444]" />
|
|
196
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#f59e0b]" />
|
|
197
|
+
<span className="w-2.5 h-2.5 rounded-full bg-[#22c55e]" />
|
|
198
|
+
{data.codeFilename && (
|
|
199
|
+
<span className="ml-3 font-mono text-[0.75rem] text-[var(--local-text-muted)] opacity-60" data-jp-field="codeFilename">
|
|
200
|
+
{data.codeFilename}
|
|
201
|
+
</span>
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
<div className="p-6 font-mono text-[0.82rem] leading-[1.7] overflow-x-auto">
|
|
205
|
+
{data.codeLines.map((line, idx) => (
|
|
206
|
+
<div key={idx} data-jp-item-id={(line as { id?: string }).id ?? `legacy-${idx}`} data-jp-item-field="codeLines">
|
|
207
|
+
<span className={tokenStyles[line.tokenType]}>
|
|
208
|
+
{line.content}
|
|
209
|
+
</span>
|
|
210
|
+
</div>
|
|
211
|
+
))}
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
)}
|
|
216
|
+
</div>
|
|
217
|
+
</section>
|
|
218
|
+
);
|
|
219
|
+
};
|
|
220
220
|
|
|
221
221
|
END_OF_FILE_CONTENT
|
|
222
222
|
echo "Creating src/components/arch-layers/index.ts..."
|
|
223
223
|
cat << 'END_OF_FILE_CONTENT' > "src/components/arch-layers/index.ts"
|
|
224
|
-
export * from './View';
|
|
225
|
-
export * from './schema';
|
|
226
|
-
export * from './types';
|
|
224
|
+
export * from './View';
|
|
225
|
+
export * from './schema';
|
|
226
|
+
export * from './types';
|
|
227
227
|
|
|
228
228
|
END_OF_FILE_CONTENT
|
|
229
229
|
echo "Creating src/components/arch-layers/schema.ts..."
|
|
230
230
|
cat << 'END_OF_FILE_CONTENT' > "src/components/arch-layers/schema.ts"
|
|
231
|
-
import { z } from 'zod';
|
|
232
|
-
import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
|
|
233
|
-
|
|
234
|
-
export const ArchLayerLevelSchema = z.enum(['l0', 'l1', 'l2']);
|
|
235
|
-
export const SyntaxTokenTypeSchema = z.enum(['plain', 'keyword', 'type', 'string', 'comment', 'operator']);
|
|
236
|
-
|
|
237
|
-
const ArchLayerItemSchema = BaseArrayItem.extend({
|
|
238
|
-
number: z.string().describe('ui:text'),
|
|
239
|
-
layerLevel: ArchLayerLevelSchema.describe('ui:select'),
|
|
240
|
-
title: z.string().describe('ui:text'),
|
|
241
|
-
description: z.string().describe('ui:textarea'),
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
const SyntaxLineSchema = z.object({
|
|
245
|
-
content: z.string().describe('ui:text'),
|
|
246
|
-
tokenType: SyntaxTokenTypeSchema.default('plain').describe('ui:select'),
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
export const ArchLayersSchema = BaseSectionData.extend({
|
|
250
|
-
label: z.string().optional().describe('ui:text'),
|
|
251
|
-
title: z.string().describe('ui:text'),
|
|
252
|
-
description: z.string().optional().describe('ui:textarea'),
|
|
253
|
-
layers: z.array(ArchLayerItemSchema).describe('ui:list'),
|
|
254
|
-
codeFilename: z.string().optional().describe('ui:text'),
|
|
255
|
-
codeLines: z.array(SyntaxLineSchema).optional().describe('ui:list'),
|
|
256
|
-
});
|
|
231
|
+
import { z } from 'zod';
|
|
232
|
+
import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
|
|
233
|
+
|
|
234
|
+
export const ArchLayerLevelSchema = z.enum(['l0', 'l1', 'l2']);
|
|
235
|
+
export const SyntaxTokenTypeSchema = z.enum(['plain', 'keyword', 'type', 'string', 'comment', 'operator']);
|
|
236
|
+
|
|
237
|
+
const ArchLayerItemSchema = BaseArrayItem.extend({
|
|
238
|
+
number: z.string().describe('ui:text'),
|
|
239
|
+
layerLevel: ArchLayerLevelSchema.describe('ui:select'),
|
|
240
|
+
title: z.string().describe('ui:text'),
|
|
241
|
+
description: z.string().describe('ui:textarea'),
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const SyntaxLineSchema = z.object({
|
|
245
|
+
content: z.string().describe('ui:text'),
|
|
246
|
+
tokenType: SyntaxTokenTypeSchema.default('plain').describe('ui:select'),
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
export const ArchLayersSchema = BaseSectionData.extend({
|
|
250
|
+
label: z.string().optional().describe('ui:text'),
|
|
251
|
+
title: z.string().describe('ui:text'),
|
|
252
|
+
description: z.string().optional().describe('ui:textarea'),
|
|
253
|
+
layers: z.array(ArchLayerItemSchema).describe('ui:list'),
|
|
254
|
+
codeFilename: z.string().optional().describe('ui:text'),
|
|
255
|
+
codeLines: z.array(SyntaxLineSchema).optional().describe('ui:list'),
|
|
256
|
+
});
|
|
257
257
|
|
|
258
258
|
END_OF_FILE_CONTENT
|
|
259
259
|
echo "Creating src/components/arch-layers/types.ts..."
|
|
260
260
|
cat << 'END_OF_FILE_CONTENT' > "src/components/arch-layers/types.ts"
|
|
261
|
-
import { z } from 'zod';
|
|
262
|
-
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
263
|
-
import { ArchLayersSchema, ArchLayerLevelSchema, SyntaxTokenTypeSchema } from './schema';
|
|
264
|
-
|
|
265
|
-
export type ArchLayersData = z.infer<typeof ArchLayersSchema>;
|
|
266
|
-
export type ArchLayersSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
267
|
-
export type ArchLayerLevel = z.infer<typeof ArchLayerLevelSchema>;
|
|
268
|
-
export type SyntaxTokenType = z.infer<typeof SyntaxTokenTypeSchema>;
|
|
261
|
+
import { z } from 'zod';
|
|
262
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
263
|
+
import { ArchLayersSchema, ArchLayerLevelSchema, SyntaxTokenTypeSchema } from './schema';
|
|
264
|
+
|
|
265
|
+
export type ArchLayersData = z.infer<typeof ArchLayersSchema>;
|
|
266
|
+
export type ArchLayersSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
267
|
+
export type ArchLayerLevel = z.infer<typeof ArchLayerLevelSchema>;
|
|
268
|
+
export type SyntaxTokenType = z.infer<typeof SyntaxTokenTypeSchema>;
|
|
269
269
|
|
|
270
270
|
END_OF_FILE_CONTENT
|
|
271
271
|
mkdir -p "src/components/code-block"
|
|
272
272
|
echo "Creating src/components/code-block/View.tsx..."
|
|
273
273
|
cat << 'END_OF_FILE_CONTENT' > "src/components/code-block/View.tsx"
|
|
274
|
-
import React from 'react';
|
|
275
|
-
import { cn } from '@/lib/utils';
|
|
276
|
-
import { Icon } from '@/lib/IconResolver';
|
|
277
|
-
import type { CodeBlockData, CodeBlockSettings } from './types';
|
|
278
|
-
|
|
279
|
-
export const CodeBlock: React.FC<{ data: CodeBlockData; settings?: CodeBlockSettings }> = ({ data, settings }) => {
|
|
280
|
-
const showLineNumbers = settings?.showLineNumbers ?? true;
|
|
281
|
-
|
|
282
|
-
return (
|
|
283
|
-
<section
|
|
284
|
-
style={{
|
|
285
|
-
'--local-surface': 'var(--card)',
|
|
286
|
-
'--local-text-muted': 'var(--muted-foreground)',
|
|
287
|
-
'--local-bg': 'var(--background)',
|
|
288
|
-
'--local-border': 'var(--border)',
|
|
289
|
-
'--local-text': 'var(--foreground)',
|
|
290
|
-
'--local-accent': 'var(--primary)',
|
|
291
|
-
'--local-radius-lg': 'var(--radius)',
|
|
292
|
-
} as React.CSSProperties}
|
|
293
|
-
className="py-16 bg-[var(--local-surface)]"
|
|
294
|
-
>
|
|
295
|
-
<div className="container mx-auto px-6 max-w-4xl">
|
|
296
|
-
{data.label && (
|
|
297
|
-
<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">
|
|
298
|
-
<Icon name="terminal" size={14} />
|
|
299
|
-
<span>{data.label}</span>
|
|
300
|
-
</div>
|
|
301
|
-
)}
|
|
302
|
-
<div className="rounded-[var(--local-radius-lg)] bg-[var(--local-bg)] border border-[var(--local-border)] overflow-hidden">
|
|
303
|
-
<div className="p-6 font-mono text-sm overflow-x-auto">
|
|
304
|
-
{data.lines.map((line, idx) => (
|
|
305
|
-
<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">
|
|
306
|
-
{showLineNumbers && (
|
|
307
|
-
<span className="select-none w-6 text-right text-[var(--local-text-muted)]/50">
|
|
308
|
-
{idx + 1}
|
|
309
|
-
</span>
|
|
310
|
-
)}
|
|
311
|
-
<span
|
|
312
|
-
className={cn(
|
|
313
|
-
line.isComment
|
|
314
|
-
? 'text-[var(--local-text-muted)]/60'
|
|
315
|
-
: 'text-[var(--local-text)]'
|
|
316
|
-
)}
|
|
317
|
-
>
|
|
318
|
-
{!line.isComment && (
|
|
319
|
-
<span className="text-[var(--local-accent)] mr-2">$</span>
|
|
320
|
-
)}
|
|
321
|
-
{line.content}
|
|
322
|
-
</span>
|
|
323
|
-
</div>
|
|
324
|
-
))}
|
|
325
|
-
</div>
|
|
326
|
-
</div>
|
|
327
|
-
</div>
|
|
328
|
-
</section>
|
|
329
|
-
);
|
|
330
|
-
};
|
|
274
|
+
import React from 'react';
|
|
275
|
+
import { cn } from '@/lib/utils';
|
|
276
|
+
import { Icon } from '@/lib/IconResolver';
|
|
277
|
+
import type { CodeBlockData, CodeBlockSettings } from './types';
|
|
278
|
+
|
|
279
|
+
export const CodeBlock: React.FC<{ data: CodeBlockData; settings?: CodeBlockSettings }> = ({ data, settings }) => {
|
|
280
|
+
const showLineNumbers = settings?.showLineNumbers ?? true;
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<section
|
|
284
|
+
style={{
|
|
285
|
+
'--local-surface': 'var(--card)',
|
|
286
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
287
|
+
'--local-bg': 'var(--background)',
|
|
288
|
+
'--local-border': 'var(--border)',
|
|
289
|
+
'--local-text': 'var(--foreground)',
|
|
290
|
+
'--local-accent': 'var(--primary)',
|
|
291
|
+
'--local-radius-lg': 'var(--radius)',
|
|
292
|
+
} as React.CSSProperties}
|
|
293
|
+
className="py-16 bg-[var(--local-surface)]"
|
|
294
|
+
>
|
|
295
|
+
<div className="container mx-auto px-6 max-w-4xl">
|
|
296
|
+
{data.label && (
|
|
297
|
+
<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">
|
|
298
|
+
<Icon name="terminal" size={14} />
|
|
299
|
+
<span>{data.label}</span>
|
|
300
|
+
</div>
|
|
301
|
+
)}
|
|
302
|
+
<div className="rounded-[var(--local-radius-lg)] bg-[var(--local-bg)] border border-[var(--local-border)] overflow-hidden">
|
|
303
|
+
<div className="p-6 font-mono text-sm overflow-x-auto">
|
|
304
|
+
{data.lines.map((line, idx) => (
|
|
305
|
+
<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">
|
|
306
|
+
{showLineNumbers && (
|
|
307
|
+
<span className="select-none w-6 text-right text-[var(--local-text-muted)]/50">
|
|
308
|
+
{idx + 1}
|
|
309
|
+
</span>
|
|
310
|
+
)}
|
|
311
|
+
<span
|
|
312
|
+
className={cn(
|
|
313
|
+
line.isComment
|
|
314
|
+
? 'text-[var(--local-text-muted)]/60'
|
|
315
|
+
: 'text-[var(--local-text)]'
|
|
316
|
+
)}
|
|
317
|
+
>
|
|
318
|
+
{!line.isComment && (
|
|
319
|
+
<span className="text-[var(--local-accent)] mr-2">$</span>
|
|
320
|
+
)}
|
|
321
|
+
{line.content}
|
|
322
|
+
</span>
|
|
323
|
+
</div>
|
|
324
|
+
))}
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
</section>
|
|
329
|
+
);
|
|
330
|
+
};
|
|
331
331
|
|
|
332
332
|
END_OF_FILE_CONTENT
|
|
333
333
|
echo "Creating src/components/code-block/index.ts..."
|
|
334
334
|
cat << 'END_OF_FILE_CONTENT' > "src/components/code-block/index.ts"
|
|
335
|
-
export * from './View';
|
|
336
|
-
export * from './schema';
|
|
337
|
-
export * from './types';
|
|
335
|
+
export * from './View';
|
|
336
|
+
export * from './schema';
|
|
337
|
+
export * from './types';
|
|
338
338
|
|
|
339
339
|
END_OF_FILE_CONTENT
|
|
340
340
|
echo "Creating src/components/code-block/schema.ts..."
|
|
341
341
|
cat << 'END_OF_FILE_CONTENT' > "src/components/code-block/schema.ts"
|
|
342
|
-
import { z } from 'zod';
|
|
343
|
-
import { BaseSectionData } from '@/lib/base-schemas';
|
|
344
|
-
|
|
345
|
-
export const LegacyCodeLineSchema = z.object({
|
|
346
|
-
content: z.string().describe('ui:text'),
|
|
347
|
-
isComment: z.boolean().default(false).describe('ui:checkbox'),
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
export const CodeBlockSchema = BaseSectionData.extend({
|
|
351
|
-
label: z.string().optional().describe('ui:text'),
|
|
352
|
-
lines: z.array(LegacyCodeLineSchema).describe('ui:list'),
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
export const CodeBlockSettingsSchema = z.object({
|
|
356
|
-
showLineNumbers: z.boolean().optional().describe('ui:checkbox'),
|
|
357
|
-
});
|
|
342
|
+
import { z } from 'zod';
|
|
343
|
+
import { BaseSectionData } from '@/lib/base-schemas';
|
|
344
|
+
|
|
345
|
+
export const LegacyCodeLineSchema = z.object({
|
|
346
|
+
content: z.string().describe('ui:text'),
|
|
347
|
+
isComment: z.boolean().default(false).describe('ui:checkbox'),
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
export const CodeBlockSchema = BaseSectionData.extend({
|
|
351
|
+
label: z.string().optional().describe('ui:text'),
|
|
352
|
+
lines: z.array(LegacyCodeLineSchema).describe('ui:list'),
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
export const CodeBlockSettingsSchema = z.object({
|
|
356
|
+
showLineNumbers: z.boolean().optional().describe('ui:checkbox'),
|
|
357
|
+
});
|
|
358
358
|
|
|
359
359
|
END_OF_FILE_CONTENT
|
|
360
360
|
echo "Creating src/components/code-block/types.ts..."
|
|
361
361
|
cat << 'END_OF_FILE_CONTENT' > "src/components/code-block/types.ts"
|
|
362
|
-
import { z } from 'zod';
|
|
363
|
-
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
364
|
-
import { CodeBlockSchema, CodeBlockSettingsSchema } from './schema';
|
|
365
|
-
|
|
366
|
-
export type CodeBlockData = z.infer<typeof CodeBlockSchema>;
|
|
367
|
-
export type CodeBlockSettings = z.infer<typeof BaseSectionSettingsSchema> & z.infer<typeof CodeBlockSettingsSchema>;
|
|
362
|
+
import { z } from 'zod';
|
|
363
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
364
|
+
import { CodeBlockSchema, CodeBlockSettingsSchema } from './schema';
|
|
365
|
+
|
|
366
|
+
export type CodeBlockData = z.infer<typeof CodeBlockSchema>;
|
|
367
|
+
export type CodeBlockSettings = z.infer<typeof BaseSectionSettingsSchema> & z.infer<typeof CodeBlockSettingsSchema>;
|
|
368
368
|
|
|
369
369
|
END_OF_FILE_CONTENT
|
|
370
370
|
mkdir -p "src/components/cta-banner"
|
|
371
371
|
echo "Creating src/components/cta-banner/View.tsx..."
|
|
372
372
|
cat << 'END_OF_FILE_CONTENT' > "src/components/cta-banner/View.tsx"
|
|
373
|
-
import React from 'react';
|
|
374
|
-
import { cn } from '@/lib/utils';
|
|
375
|
-
import type { CtaBannerData, CtaBannerSettings } from './types';
|
|
376
|
-
|
|
377
|
-
export const CtaBanner: React.FC<{ data: CtaBannerData; settings?: CtaBannerSettings }> = ({ data }) => {
|
|
378
|
-
return (
|
|
379
|
-
<section
|
|
380
|
-
style={{
|
|
381
|
-
'--local-bg': 'var(--background)',
|
|
382
|
-
'--local-text': 'var(--foreground)',
|
|
383
|
-
'--local-text-muted': 'var(--muted-foreground)',
|
|
384
|
-
'--local-primary': 'var(--primary)',
|
|
385
|
-
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
386
|
-
} as React.CSSProperties}
|
|
387
|
-
className="relative py-28 bg-[var(--local-bg)] overflow-hidden text-center"
|
|
388
|
-
>
|
|
389
|
-
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[60vw] h-[60vw] bg-[radial-gradient(circle,rgba(59,130,246,0.08)_0%,transparent_60%)] pointer-events-none" />
|
|
390
|
-
<div className="relative max-w-[1200px] mx-auto px-8">
|
|
391
|
-
{data.label && (
|
|
392
|
-
<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">
|
|
393
|
-
<span className="w-5 h-px bg-[var(--local-primary)]" />
|
|
394
|
-
{data.label}
|
|
395
|
-
</div>
|
|
396
|
-
)}
|
|
397
|
-
<h2
|
|
398
|
-
className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-6"
|
|
399
|
-
data-jp-field="title"
|
|
400
|
-
>
|
|
401
|
-
{data.title}
|
|
402
|
-
</h2>
|
|
403
|
-
{data.description && (
|
|
404
|
-
<p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed mb-10" data-jp-field="description">
|
|
405
|
-
{data.description}
|
|
406
|
-
</p>
|
|
407
|
-
)}
|
|
408
|
-
{data.ctas && data.ctas.length > 0 && (
|
|
409
|
-
<div className="flex gap-4 justify-center flex-wrap">
|
|
410
|
-
{data.ctas.map((cta, idx) => (
|
|
411
|
-
<a
|
|
412
|
-
key={cta.id ?? idx}
|
|
413
|
-
href={cta.href}
|
|
414
|
-
data-jp-item-id={cta.id ?? `legacy-${idx}`}
|
|
415
|
-
data-jp-item-field="ctas"
|
|
416
|
-
className={cn(
|
|
417
|
-
'inline-flex items-center gap-2 px-8 py-3.5 rounded-[5px] font-semibold text-base transition-all duration-200 no-underline',
|
|
418
|
-
cta.variant === 'primary'
|
|
419
|
-
? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-0.5 hover:shadow-[0_8px_30px_rgba(59,130,246,0.3)]'
|
|
420
|
-
: 'bg-transparent text-[var(--local-text)] border border-[rgba(255,255,255,0.12)] hover:border-[rgba(255,255,255,0.3)] hover:bg-[rgba(255,255,255,0.04)]'
|
|
421
|
-
)}
|
|
422
|
-
>
|
|
423
|
-
{cta.label}
|
|
424
|
-
</a>
|
|
425
|
-
))}
|
|
426
|
-
</div>
|
|
427
|
-
)}
|
|
428
|
-
</div>
|
|
429
|
-
</section>
|
|
430
|
-
);
|
|
431
|
-
};
|
|
373
|
+
import React from 'react';
|
|
374
|
+
import { cn } from '@/lib/utils';
|
|
375
|
+
import type { CtaBannerData, CtaBannerSettings } from './types';
|
|
376
|
+
|
|
377
|
+
export const CtaBanner: React.FC<{ data: CtaBannerData; settings?: CtaBannerSettings }> = ({ data }) => {
|
|
378
|
+
return (
|
|
379
|
+
<section
|
|
380
|
+
style={{
|
|
381
|
+
'--local-bg': 'var(--background)',
|
|
382
|
+
'--local-text': 'var(--foreground)',
|
|
383
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
384
|
+
'--local-primary': 'var(--primary)',
|
|
385
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
386
|
+
} as React.CSSProperties}
|
|
387
|
+
className="relative py-28 bg-[var(--local-bg)] overflow-hidden text-center"
|
|
388
|
+
>
|
|
389
|
+
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[60vw] h-[60vw] bg-[radial-gradient(circle,rgba(59,130,246,0.08)_0%,transparent_60%)] pointer-events-none" />
|
|
390
|
+
<div className="relative max-w-[1200px] mx-auto px-8">
|
|
391
|
+
{data.label && (
|
|
392
|
+
<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">
|
|
393
|
+
<span className="w-5 h-px bg-[var(--local-primary)]" />
|
|
394
|
+
{data.label}
|
|
395
|
+
</div>
|
|
396
|
+
)}
|
|
397
|
+
<h2
|
|
398
|
+
className="font-display text-[clamp(2rem,4vw,3.2rem)] font-extrabold text-[var(--local-text)] leading-[1.15] tracking-tight mb-6"
|
|
399
|
+
data-jp-field="title"
|
|
400
|
+
>
|
|
401
|
+
{data.title}
|
|
402
|
+
</h2>
|
|
403
|
+
{data.description && (
|
|
404
|
+
<p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed mb-10" data-jp-field="description">
|
|
405
|
+
{data.description}
|
|
406
|
+
</p>
|
|
407
|
+
)}
|
|
408
|
+
{data.ctas && data.ctas.length > 0 && (
|
|
409
|
+
<div className="flex gap-4 justify-center flex-wrap">
|
|
410
|
+
{data.ctas.map((cta, idx) => (
|
|
411
|
+
<a
|
|
412
|
+
key={cta.id ?? idx}
|
|
413
|
+
href={cta.href}
|
|
414
|
+
data-jp-item-id={cta.id ?? `legacy-${idx}`}
|
|
415
|
+
data-jp-item-field="ctas"
|
|
416
|
+
className={cn(
|
|
417
|
+
'inline-flex items-center gap-2 px-8 py-3.5 rounded-[5px] font-semibold text-base transition-all duration-200 no-underline',
|
|
418
|
+
cta.variant === 'primary'
|
|
419
|
+
? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-0.5 hover:shadow-[0_8px_30px_rgba(59,130,246,0.3)]'
|
|
420
|
+
: 'bg-transparent text-[var(--local-text)] border border-[rgba(255,255,255,0.12)] hover:border-[rgba(255,255,255,0.3)] hover:bg-[rgba(255,255,255,0.04)]'
|
|
421
|
+
)}
|
|
422
|
+
>
|
|
423
|
+
{cta.label}
|
|
424
|
+
</a>
|
|
425
|
+
))}
|
|
426
|
+
</div>
|
|
427
|
+
)}
|
|
428
|
+
</div>
|
|
429
|
+
</section>
|
|
430
|
+
);
|
|
431
|
+
};
|
|
432
432
|
|
|
433
433
|
END_OF_FILE_CONTENT
|
|
434
434
|
echo "Creating src/components/cta-banner/index.ts..."
|
|
435
435
|
cat << 'END_OF_FILE_CONTENT' > "src/components/cta-banner/index.ts"
|
|
436
|
-
export * from './View';
|
|
437
|
-
export * from './schema';
|
|
438
|
-
export * from './types';
|
|
436
|
+
export * from './View';
|
|
437
|
+
export * from './schema';
|
|
438
|
+
export * from './types';
|
|
439
439
|
|
|
440
440
|
END_OF_FILE_CONTENT
|
|
441
441
|
echo "Creating src/components/cta-banner/schema.ts..."
|
|
442
442
|
cat << 'END_OF_FILE_CONTENT' > "src/components/cta-banner/schema.ts"
|
|
443
|
-
import { z } from 'zod';
|
|
444
|
-
import { BaseSectionData, CtaSchema } from '@/lib/base-schemas';
|
|
445
|
-
|
|
446
|
-
export const CtaBannerSchema = BaseSectionData.extend({
|
|
447
|
-
label: z.string().optional().describe('ui:text'),
|
|
448
|
-
title: z.string().describe('ui:text'),
|
|
449
|
-
description: z.string().optional().describe('ui:textarea'),
|
|
450
|
-
ctas: z.array(CtaSchema).optional().describe('ui:list'),
|
|
451
|
-
});
|
|
443
|
+
import { z } from 'zod';
|
|
444
|
+
import { BaseSectionData, CtaSchema } from '@/lib/base-schemas';
|
|
445
|
+
|
|
446
|
+
export const CtaBannerSchema = BaseSectionData.extend({
|
|
447
|
+
label: z.string().optional().describe('ui:text'),
|
|
448
|
+
title: z.string().describe('ui:text'),
|
|
449
|
+
description: z.string().optional().describe('ui:textarea'),
|
|
450
|
+
ctas: z.array(CtaSchema).optional().describe('ui:list'),
|
|
451
|
+
});
|
|
452
452
|
|
|
453
453
|
END_OF_FILE_CONTENT
|
|
454
454
|
echo "Creating src/components/cta-banner/types.ts..."
|
|
455
455
|
cat << 'END_OF_FILE_CONTENT' > "src/components/cta-banner/types.ts"
|
|
456
|
-
import { z } from 'zod';
|
|
457
|
-
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
458
|
-
import { CtaBannerSchema } from './schema';
|
|
459
|
-
|
|
460
|
-
export type CtaBannerData = z.infer<typeof CtaBannerSchema>;
|
|
461
|
-
export type CtaBannerSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
456
|
+
import { z } from 'zod';
|
|
457
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
458
|
+
import { CtaBannerSchema } from './schema';
|
|
459
|
+
|
|
460
|
+
export type CtaBannerData = z.infer<typeof CtaBannerSchema>;
|
|
461
|
+
export type CtaBannerSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
462
462
|
|
|
463
463
|
END_OF_FILE_CONTENT
|
|
464
464
|
mkdir -p "src/components/feature-grid"
|
|
465
465
|
echo "Creating src/components/feature-grid/View.tsx..."
|
|
466
466
|
cat << 'END_OF_FILE_CONTENT' > "src/components/feature-grid/View.tsx"
|
|
467
|
-
import React from 'react';
|
|
468
|
-
import { cn } from '@/lib/utils';
|
|
469
|
-
import { Icon } from '@/lib/IconResolver';
|
|
470
|
-
import type { FeatureGridData, FeatureGridSettings } from './types';
|
|
471
|
-
|
|
472
|
-
const columnsMap: Record<2 | 3 | 4, string> = {
|
|
473
|
-
2: 'md:grid-cols-2',
|
|
474
|
-
3: 'md:grid-cols-3',
|
|
475
|
-
4: 'md:grid-cols-4',
|
|
476
|
-
};
|
|
477
|
-
|
|
478
|
-
export const FeatureGrid: React.FC<{ data: FeatureGridData; settings?: FeatureGridSettings }> = ({ data, settings }) => {
|
|
479
|
-
const colKey = settings?.columns ?? 3;
|
|
480
|
-
const cols = (colKey === 2 || colKey === 3 || colKey === 4) ? columnsMap[colKey] : columnsMap[3];
|
|
481
|
-
const isBordered = settings?.cardStyle === 'bordered';
|
|
482
|
-
|
|
483
|
-
const localStyles = {
|
|
484
|
-
'--local-bg': 'var(--background)',
|
|
485
|
-
'--local-text': 'var(--foreground)',
|
|
486
|
-
'--local-text-muted': 'var(--muted-foreground)',
|
|
487
|
-
'--local-surface': 'var(--card)',
|
|
488
|
-
'--local-surface-alt': 'var(--muted)',
|
|
489
|
-
'--local-border': 'var(--border)',
|
|
490
|
-
'--local-radius-lg': 'var(--radius)',
|
|
491
|
-
'--local-radius-md': 'calc(var(--radius) - 2px)',
|
|
492
|
-
} as React.CSSProperties;
|
|
493
|
-
|
|
494
|
-
return (
|
|
495
|
-
<section style={localStyles} className="py-20 bg-[var(--local-bg)] relative z-0">
|
|
496
|
-
<div className="container mx-auto px-6">
|
|
497
|
-
<h2 className="text-3xl md:text-4xl font-bold text-center text-[var(--local-text)] mb-16" data-jp-field="sectionTitle">
|
|
498
|
-
{data.sectionTitle}
|
|
499
|
-
</h2>
|
|
500
|
-
<div className={cn('grid grid-cols-1 gap-6', cols)}>
|
|
501
|
-
{data.cards.map((card, idx) => (
|
|
502
|
-
<div
|
|
503
|
-
key={card.id ?? idx}
|
|
504
|
-
className={cn(
|
|
505
|
-
'p-6 rounded-[var(--local-radius-lg)] bg-[var(--local-surface)]',
|
|
506
|
-
isBordered && 'border border-[var(--local-border)]'
|
|
507
|
-
)}
|
|
508
|
-
data-jp-item-id={card.id ?? `legacy-${idx}`}
|
|
509
|
-
data-jp-item-field="cards"
|
|
510
|
-
>
|
|
511
|
-
{card.icon && (
|
|
512
|
-
<div className="w-10 h-10 rounded-[var(--local-radius-md)] bg-[var(--local-surface-alt)] flex items-center justify-center mb-4">
|
|
513
|
-
<Icon name={card.icon} size={20} className="text-[var(--local-text-muted)]" />
|
|
514
|
-
</div>
|
|
515
|
-
)}
|
|
516
|
-
<h3 className="text-lg font-semibold text-[var(--local-text)] mb-2">
|
|
517
|
-
{card.emoji && <span className="mr-2">{card.emoji}</span>}
|
|
518
|
-
{card.title}
|
|
519
|
-
</h3>
|
|
520
|
-
<p className="text-sm text-[var(--local-text-muted)] leading-relaxed">
|
|
521
|
-
{card.description}
|
|
522
|
-
</p>
|
|
523
|
-
</div>
|
|
524
|
-
))}
|
|
525
|
-
</div>
|
|
526
|
-
</div>
|
|
527
|
-
</section>
|
|
528
|
-
);
|
|
529
|
-
};
|
|
467
|
+
import React from 'react';
|
|
468
|
+
import { cn } from '@/lib/utils';
|
|
469
|
+
import { Icon } from '@/lib/IconResolver';
|
|
470
|
+
import type { FeatureGridData, FeatureGridSettings } from './types';
|
|
471
|
+
|
|
472
|
+
const columnsMap: Record<2 | 3 | 4, string> = {
|
|
473
|
+
2: 'md:grid-cols-2',
|
|
474
|
+
3: 'md:grid-cols-3',
|
|
475
|
+
4: 'md:grid-cols-4',
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
export const FeatureGrid: React.FC<{ data: FeatureGridData; settings?: FeatureGridSettings }> = ({ data, settings }) => {
|
|
479
|
+
const colKey = settings?.columns ?? 3;
|
|
480
|
+
const cols = (colKey === 2 || colKey === 3 || colKey === 4) ? columnsMap[colKey] : columnsMap[3];
|
|
481
|
+
const isBordered = settings?.cardStyle === 'bordered';
|
|
482
|
+
|
|
483
|
+
const localStyles = {
|
|
484
|
+
'--local-bg': 'var(--background)',
|
|
485
|
+
'--local-text': 'var(--foreground)',
|
|
486
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
487
|
+
'--local-surface': 'var(--card)',
|
|
488
|
+
'--local-surface-alt': 'var(--muted)',
|
|
489
|
+
'--local-border': 'var(--border)',
|
|
490
|
+
'--local-radius-lg': 'var(--radius)',
|
|
491
|
+
'--local-radius-md': 'calc(var(--radius) - 2px)',
|
|
492
|
+
} as React.CSSProperties;
|
|
493
|
+
|
|
494
|
+
return (
|
|
495
|
+
<section style={localStyles} className="py-20 bg-[var(--local-bg)] relative z-0">
|
|
496
|
+
<div className="container mx-auto px-6">
|
|
497
|
+
<h2 className="text-3xl md:text-4xl font-bold text-center text-[var(--local-text)] mb-16" data-jp-field="sectionTitle">
|
|
498
|
+
{data.sectionTitle}
|
|
499
|
+
</h2>
|
|
500
|
+
<div className={cn('grid grid-cols-1 gap-6', cols)}>
|
|
501
|
+
{data.cards.map((card, idx) => (
|
|
502
|
+
<div
|
|
503
|
+
key={card.id ?? idx}
|
|
504
|
+
className={cn(
|
|
505
|
+
'p-6 rounded-[var(--local-radius-lg)] bg-[var(--local-surface)]',
|
|
506
|
+
isBordered && 'border border-[var(--local-border)]'
|
|
507
|
+
)}
|
|
508
|
+
data-jp-item-id={card.id ?? `legacy-${idx}`}
|
|
509
|
+
data-jp-item-field="cards"
|
|
510
|
+
>
|
|
511
|
+
{card.icon && (
|
|
512
|
+
<div className="w-10 h-10 rounded-[var(--local-radius-md)] bg-[var(--local-surface-alt)] flex items-center justify-center mb-4">
|
|
513
|
+
<Icon name={card.icon} size={20} className="text-[var(--local-text-muted)]" />
|
|
514
|
+
</div>
|
|
515
|
+
)}
|
|
516
|
+
<h3 className="text-lg font-semibold text-[var(--local-text)] mb-2">
|
|
517
|
+
{card.emoji && <span className="mr-2">{card.emoji}</span>}
|
|
518
|
+
{card.title}
|
|
519
|
+
</h3>
|
|
520
|
+
<p className="text-sm text-[var(--local-text-muted)] leading-relaxed">
|
|
521
|
+
{card.description}
|
|
522
|
+
</p>
|
|
523
|
+
</div>
|
|
524
|
+
))}
|
|
525
|
+
</div>
|
|
526
|
+
</div>
|
|
527
|
+
</section>
|
|
528
|
+
);
|
|
529
|
+
};
|
|
530
530
|
|
|
531
531
|
END_OF_FILE_CONTENT
|
|
532
532
|
echo "Creating src/components/feature-grid/index.ts..."
|
|
533
533
|
cat << 'END_OF_FILE_CONTENT' > "src/components/feature-grid/index.ts"
|
|
534
|
-
export * from './View';
|
|
535
|
-
export * from './schema';
|
|
536
|
-
export * from './types';
|
|
534
|
+
export * from './View';
|
|
535
|
+
export * from './schema';
|
|
536
|
+
export * from './types';
|
|
537
537
|
|
|
538
538
|
END_OF_FILE_CONTENT
|
|
539
539
|
echo "Creating src/components/feature-grid/schema.ts..."
|
|
540
540
|
cat << 'END_OF_FILE_CONTENT' > "src/components/feature-grid/schema.ts"
|
|
541
|
-
import { z } from 'zod';
|
|
542
|
-
import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
|
|
543
|
-
|
|
544
|
-
export const FeatureCardSchema = BaseArrayItem.extend({
|
|
545
|
-
icon: z.string().optional().describe('ui:icon-picker'),
|
|
546
|
-
emoji: z.string().optional().describe('ui:text'),
|
|
547
|
-
title: z.string().describe('ui:text'),
|
|
548
|
-
description: z.string().describe('ui:textarea'),
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
export const FeatureGridSchema = BaseSectionData.extend({
|
|
552
|
-
sectionTitle: z.string().describe('ui:text'),
|
|
553
|
-
cards: z.array(FeatureCardSchema).describe('ui:list'),
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
export const FeatureGridSettingsSchema = z.object({
|
|
557
|
-
columns: z.union([z.literal(2), z.literal(3), z.literal(4)]).optional().describe('ui:number'),
|
|
558
|
-
cardStyle: z.enum(['plain', 'bordered']).optional().describe('ui:select'),
|
|
559
|
-
});
|
|
541
|
+
import { z } from 'zod';
|
|
542
|
+
import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
|
|
543
|
+
|
|
544
|
+
export const FeatureCardSchema = BaseArrayItem.extend({
|
|
545
|
+
icon: z.string().optional().describe('ui:icon-picker'),
|
|
546
|
+
emoji: z.string().optional().describe('ui:text'),
|
|
547
|
+
title: z.string().describe('ui:text'),
|
|
548
|
+
description: z.string().describe('ui:textarea'),
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
export const FeatureGridSchema = BaseSectionData.extend({
|
|
552
|
+
sectionTitle: z.string().describe('ui:text'),
|
|
553
|
+
cards: z.array(FeatureCardSchema).describe('ui:list'),
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
export const FeatureGridSettingsSchema = z.object({
|
|
557
|
+
columns: z.union([z.literal(2), z.literal(3), z.literal(4)]).optional().describe('ui:number'),
|
|
558
|
+
cardStyle: z.enum(['plain', 'bordered']).optional().describe('ui:select'),
|
|
559
|
+
});
|
|
560
560
|
|
|
561
561
|
END_OF_FILE_CONTENT
|
|
562
562
|
echo "Creating src/components/feature-grid/types.ts..."
|
|
563
563
|
cat << 'END_OF_FILE_CONTENT' > "src/components/feature-grid/types.ts"
|
|
564
|
-
import { z } from 'zod';
|
|
565
|
-
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
566
|
-
import { FeatureGridSchema, FeatureGridSettingsSchema } from './schema';
|
|
567
|
-
|
|
568
|
-
export type FeatureGridData = z.infer<typeof FeatureGridSchema>;
|
|
569
|
-
export type FeatureGridSettings = z.infer<typeof BaseSectionSettingsSchema> & z.infer<typeof FeatureGridSettingsSchema>;
|
|
564
|
+
import { z } from 'zod';
|
|
565
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
566
|
+
import { FeatureGridSchema, FeatureGridSettingsSchema } from './schema';
|
|
567
|
+
|
|
568
|
+
export type FeatureGridData = z.infer<typeof FeatureGridSchema>;
|
|
569
|
+
export type FeatureGridSettings = z.infer<typeof BaseSectionSettingsSchema> & z.infer<typeof FeatureGridSettingsSchema>;
|
|
570
570
|
|
|
571
571
|
END_OF_FILE_CONTENT
|
|
572
572
|
mkdir -p "src/components/footer"
|
|
573
573
|
echo "Creating src/components/footer/View.tsx..."
|
|
574
574
|
cat << 'END_OF_FILE_CONTENT' > "src/components/footer/View.tsx"
|
|
575
|
-
import React from 'react';
|
|
576
|
-
import type { FooterData, FooterSettings } from './types';
|
|
577
|
-
|
|
578
|
-
export const Footer: React.FC<{ data: FooterData; settings?: FooterSettings }> = ({ data }) => {
|
|
579
|
-
return (
|
|
580
|
-
<footer
|
|
581
|
-
style={{
|
|
582
|
-
'--local-bg': 'var(--background)',
|
|
583
|
-
'--local-text': 'var(--foreground)',
|
|
584
|
-
'--local-text-muted': 'var(--muted-foreground)',
|
|
585
|
-
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
586
|
-
'--local-border': 'rgba(255,255,255,0.05)',
|
|
587
|
-
} as React.CSSProperties}
|
|
588
|
-
className="py-12 border-t border-[var(--local-border)] bg-[var(--local-bg)] relative z-0"
|
|
589
|
-
>
|
|
590
|
-
<div className="max-w-[1200px] mx-auto px-8">
|
|
591
|
-
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
|
|
592
|
-
<div className="flex items-center gap-2 font-bold text-[0.9rem] text-[var(--local-text-muted)]" data-jp-field="brandText">
|
|
593
|
-
{data.brandText}
|
|
594
|
-
{data.brandHighlight && (
|
|
595
|
-
<span className="text-[var(--local-accent)]" data-jp-field="brandHighlight">{data.brandHighlight}</span>
|
|
596
|
-
)}
|
|
597
|
-
</div>
|
|
598
|
-
{data.links && data.links.length > 0 && (
|
|
599
|
-
<nav className="flex gap-6">
|
|
600
|
-
{data.links.map((link, idx) => (
|
|
601
|
-
<a
|
|
602
|
-
key={idx}
|
|
603
|
-
href={link.href}
|
|
604
|
-
className="text-[0.82rem] text-[var(--local-text-muted)] hover:text-[var(--local-accent)] transition-colors no-underline"
|
|
605
|
-
data-jp-item-id={(link as { id?: string }).id ?? `legacy-${idx}`}
|
|
606
|
-
data-jp-item-field="links"
|
|
607
|
-
>
|
|
608
|
-
{link.label}
|
|
609
|
-
</a>
|
|
610
|
-
))}
|
|
611
|
-
</nav>
|
|
612
|
-
)}
|
|
613
|
-
<div className="text-[0.8rem] text-[var(--local-text-muted)] opacity-60" data-jp-field="copyright">
|
|
614
|
-
{data.copyright}
|
|
615
|
-
</div>
|
|
616
|
-
</div>
|
|
617
|
-
</div>
|
|
618
|
-
</footer>
|
|
619
|
-
);
|
|
620
|
-
};
|
|
575
|
+
import React from 'react';
|
|
576
|
+
import type { FooterData, FooterSettings } from './types';
|
|
577
|
+
|
|
578
|
+
export const Footer: React.FC<{ data: FooterData; settings?: FooterSettings }> = ({ data }) => {
|
|
579
|
+
return (
|
|
580
|
+
<footer
|
|
581
|
+
style={{
|
|
582
|
+
'--local-bg': 'var(--background)',
|
|
583
|
+
'--local-text': 'var(--foreground)',
|
|
584
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
585
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
586
|
+
'--local-border': 'rgba(255,255,255,0.05)',
|
|
587
|
+
} as React.CSSProperties}
|
|
588
|
+
className="py-12 border-t border-[var(--local-border)] bg-[var(--local-bg)] relative z-0"
|
|
589
|
+
>
|
|
590
|
+
<div className="max-w-[1200px] mx-auto px-8">
|
|
591
|
+
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
|
|
592
|
+
<div className="flex items-center gap-2 font-bold text-[0.9rem] text-[var(--local-text-muted)]" data-jp-field="brandText">
|
|
593
|
+
{data.brandText}
|
|
594
|
+
{data.brandHighlight && (
|
|
595
|
+
<span className="text-[var(--local-accent)]" data-jp-field="brandHighlight">{data.brandHighlight}</span>
|
|
596
|
+
)}
|
|
597
|
+
</div>
|
|
598
|
+
{data.links && data.links.length > 0 && (
|
|
599
|
+
<nav className="flex gap-6">
|
|
600
|
+
{data.links.map((link, idx) => (
|
|
601
|
+
<a
|
|
602
|
+
key={idx}
|
|
603
|
+
href={link.href}
|
|
604
|
+
className="text-[0.82rem] text-[var(--local-text-muted)] hover:text-[var(--local-accent)] transition-colors no-underline"
|
|
605
|
+
data-jp-item-id={(link as { id?: string }).id ?? `legacy-${idx}`}
|
|
606
|
+
data-jp-item-field="links"
|
|
607
|
+
>
|
|
608
|
+
{link.label}
|
|
609
|
+
</a>
|
|
610
|
+
))}
|
|
611
|
+
</nav>
|
|
612
|
+
)}
|
|
613
|
+
<div className="text-[0.8rem] text-[var(--local-text-muted)] opacity-60" data-jp-field="copyright">
|
|
614
|
+
{data.copyright}
|
|
615
|
+
</div>
|
|
616
|
+
</div>
|
|
617
|
+
</div>
|
|
618
|
+
</footer>
|
|
619
|
+
);
|
|
620
|
+
};
|
|
621
621
|
|
|
622
622
|
END_OF_FILE_CONTENT
|
|
623
623
|
echo "Creating src/components/footer/index.ts..."
|
|
624
624
|
cat << 'END_OF_FILE_CONTENT' > "src/components/footer/index.ts"
|
|
625
|
-
export * from './View';
|
|
626
|
-
export * from './schema';
|
|
627
|
-
export * from './types';
|
|
625
|
+
export * from './View';
|
|
626
|
+
export * from './schema';
|
|
627
|
+
export * from './types';
|
|
628
628
|
|
|
629
629
|
END_OF_FILE_CONTENT
|
|
630
630
|
echo "Creating src/components/footer/schema.ts..."
|
|
631
631
|
cat << 'END_OF_FILE_CONTENT' > "src/components/footer/schema.ts"
|
|
632
|
-
import { z } from 'zod';
|
|
633
|
-
|
|
634
|
-
export const FooterSchema = z.object({
|
|
635
|
-
brandText: z.string().describe('ui:text'),
|
|
636
|
-
brandHighlight: z.string().optional().describe('ui:text'),
|
|
637
|
-
copyright: z.string().describe('ui:text'),
|
|
638
|
-
links: z.array(z.object({
|
|
639
|
-
label: z.string().describe('ui:text'),
|
|
640
|
-
href: z.string().describe('ui:text'),
|
|
641
|
-
})).optional().describe('ui:list'),
|
|
642
|
-
});
|
|
643
|
-
|
|
644
|
-
export const FooterSettingsSchema = z.object({
|
|
645
|
-
showLogo: z.boolean().optional().describe('ui:checkbox'),
|
|
646
|
-
});
|
|
632
|
+
import { z } from 'zod';
|
|
633
|
+
|
|
634
|
+
export const FooterSchema = z.object({
|
|
635
|
+
brandText: z.string().describe('ui:text'),
|
|
636
|
+
brandHighlight: z.string().optional().describe('ui:text'),
|
|
637
|
+
copyright: z.string().describe('ui:text'),
|
|
638
|
+
links: z.array(z.object({
|
|
639
|
+
label: z.string().describe('ui:text'),
|
|
640
|
+
href: z.string().describe('ui:text'),
|
|
641
|
+
})).optional().describe('ui:list'),
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
export const FooterSettingsSchema = z.object({
|
|
645
|
+
showLogo: z.boolean().optional().describe('ui:checkbox'),
|
|
646
|
+
});
|
|
647
647
|
|
|
648
648
|
END_OF_FILE_CONTENT
|
|
649
649
|
echo "Creating src/components/footer/types.ts..."
|
|
650
650
|
cat << 'END_OF_FILE_CONTENT' > "src/components/footer/types.ts"
|
|
651
|
-
import { z } from 'zod';
|
|
652
|
-
import { FooterSchema, FooterSettingsSchema } from './schema';
|
|
653
|
-
|
|
654
|
-
export type FooterData = z.infer<typeof FooterSchema>;
|
|
655
|
-
export type FooterSettings = z.infer<typeof FooterSettingsSchema>;
|
|
651
|
+
import { z } from 'zod';
|
|
652
|
+
import { FooterSchema, FooterSettingsSchema } from './schema';
|
|
653
|
+
|
|
654
|
+
export type FooterData = z.infer<typeof FooterSchema>;
|
|
655
|
+
export type FooterSettings = z.infer<typeof FooterSettingsSchema>;
|
|
656
656
|
|
|
657
657
|
END_OF_FILE_CONTENT
|
|
658
658
|
mkdir -p "src/components/header"
|
|
659
659
|
echo "Creating src/components/header/View.tsx..."
|
|
660
660
|
cat << 'END_OF_FILE_CONTENT' > "src/components/header/View.tsx"
|
|
661
|
-
import React, { useState, useEffect } from 'react';
|
|
662
|
-
import { cn } from '@/lib/utils';
|
|
663
|
-
import type { MenuItem } from '@jsonpages/core';
|
|
664
|
-
import type { HeaderData, HeaderSettings } from './types';
|
|
665
|
-
|
|
666
|
-
export const Header: React.FC<{
|
|
667
|
-
data: HeaderData;
|
|
668
|
-
settings?: HeaderSettings;
|
|
669
|
-
menu: MenuItem[];
|
|
670
|
-
}> = ({ data, menu }) => {
|
|
671
|
-
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
672
|
-
const [scrolled, setScrolled] = useState(false);
|
|
673
|
-
|
|
674
|
-
useEffect(() => {
|
|
675
|
-
const handleScroll = () => setScrolled(window.scrollY > 40);
|
|
676
|
-
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
677
|
-
return () => window.removeEventListener('scroll', handleScroll);
|
|
678
|
-
}, []);
|
|
679
|
-
|
|
680
|
-
return (
|
|
681
|
-
<header
|
|
682
|
-
style={{
|
|
683
|
-
'--local-bg': 'rgba(6,13,27,0.92)',
|
|
684
|
-
'--local-text': 'var(--foreground)',
|
|
685
|
-
'--local-text-muted': 'var(--muted-foreground)',
|
|
686
|
-
'--local-primary': 'var(--primary)',
|
|
687
|
-
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
688
|
-
'--local-border': 'rgba(59,130,246,0.08)',
|
|
689
|
-
} as React.CSSProperties}
|
|
690
|
-
className={cn(
|
|
691
|
-
'w-full py-4 transition-all duration-300 z-0',
|
|
692
|
-
scrolled
|
|
693
|
-
? 'bg-[var(--local-bg)] backdrop-blur-[20px] border-b border-[var(--local-border)]'
|
|
694
|
-
: 'bg-transparent border-b border-transparent'
|
|
695
|
-
)}
|
|
696
|
-
>
|
|
697
|
-
<div className="max-w-[1200px] mx-auto px-8 flex justify-between items-center">
|
|
698
|
-
<a
|
|
699
|
-
href="/"
|
|
700
|
-
className="flex items-center gap-2.5 no-underline font-bold text-xl tracking-tight text-[var(--local-text)]"
|
|
701
|
-
>
|
|
702
|
-
{data.logoIconText && (
|
|
703
|
-
<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">
|
|
704
|
-
{data.logoIconText}
|
|
705
|
-
</div>
|
|
706
|
-
)}
|
|
707
|
-
<span data-jp-field="logoText">
|
|
708
|
-
{data.logoText}
|
|
709
|
-
{data.logoHighlight && (
|
|
710
|
-
<span className="text-[var(--local-accent)]" data-jp-field="logoHighlight">{data.logoHighlight}</span>
|
|
711
|
-
)}
|
|
712
|
-
</span>
|
|
713
|
-
</a>
|
|
714
|
-
|
|
715
|
-
<nav className="hidden md:flex items-center gap-10">
|
|
716
|
-
{menu.map((item, idx) => (
|
|
717
|
-
<a
|
|
718
|
-
key={(item as { id?: string }).id ?? idx}
|
|
719
|
-
href={item.href}
|
|
720
|
-
data-jp-item-id={(item as { id?: string }).id ?? `legacy-${idx}`}
|
|
721
|
-
data-jp-item-field="links"
|
|
722
|
-
target={item.external ? '_blank' : undefined}
|
|
723
|
-
rel={item.external ? 'noopener noreferrer' : undefined}
|
|
724
|
-
className={cn(
|
|
725
|
-
'no-underline text-sm font-medium transition-colors',
|
|
726
|
-
item.isCta
|
|
727
|
-
? 'bg-[var(--local-primary)] text-white px-5 py-2 rounded-lg font-semibold hover:brightness-110 hover:-translate-y-px'
|
|
728
|
-
: 'text-[var(--local-text-muted)] hover:text-[var(--local-text)]'
|
|
729
|
-
)}
|
|
730
|
-
>
|
|
731
|
-
{item.label}
|
|
732
|
-
</a>
|
|
733
|
-
))}
|
|
734
|
-
</nav>
|
|
735
|
-
|
|
736
|
-
<button
|
|
737
|
-
type="button"
|
|
738
|
-
className="md:hidden p-2 text-[var(--local-text-muted)] hover:text-[var(--local-text)]"
|
|
739
|
-
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
|
740
|
-
aria-label={mobileMenuOpen ? 'Close menu' : 'Open menu'}
|
|
741
|
-
>
|
|
742
|
-
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
743
|
-
{mobileMenuOpen ? (
|
|
744
|
-
<><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></>
|
|
745
|
-
) : (
|
|
746
|
-
<><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" /></>
|
|
747
|
-
)}
|
|
748
|
-
</svg>
|
|
749
|
-
</button>
|
|
750
|
-
</div>
|
|
751
|
-
|
|
752
|
-
{mobileMenuOpen && (
|
|
753
|
-
<nav className="md:hidden border-t border-[var(--local-border)] bg-[var(--local-bg)] backdrop-blur-[20px]">
|
|
754
|
-
<div className="max-w-[1200px] mx-auto px-8 py-4 flex flex-col gap-4">
|
|
755
|
-
{menu.map((item, idx) => (
|
|
756
|
-
<a
|
|
757
|
-
key={(item as { id?: string }).id ?? idx}
|
|
758
|
-
href={item.href}
|
|
759
|
-
className="text-base font-medium text-[var(--local-text-muted)] hover:text-[var(--local-text)] transition-colors py-2 no-underline"
|
|
760
|
-
onClick={() => setMobileMenuOpen(false)}
|
|
761
|
-
data-jp-item-id={(item as { id?: string }).id ?? `legacy-${idx}`}
|
|
762
|
-
data-jp-item-field="links"
|
|
763
|
-
>
|
|
764
|
-
{item.label}
|
|
765
|
-
</a>
|
|
766
|
-
))}
|
|
767
|
-
</div>
|
|
768
|
-
</nav>
|
|
769
|
-
)}
|
|
770
|
-
</header>
|
|
771
|
-
);
|
|
772
|
-
};
|
|
661
|
+
import React, { useState, useEffect } from 'react';
|
|
662
|
+
import { cn } from '@/lib/utils';
|
|
663
|
+
import type { MenuItem } from '@jsonpages/core';
|
|
664
|
+
import type { HeaderData, HeaderSettings } from './types';
|
|
665
|
+
|
|
666
|
+
export const Header: React.FC<{
|
|
667
|
+
data: HeaderData;
|
|
668
|
+
settings?: HeaderSettings;
|
|
669
|
+
menu: MenuItem[];
|
|
670
|
+
}> = ({ data, menu }) => {
|
|
671
|
+
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
672
|
+
const [scrolled, setScrolled] = useState(false);
|
|
673
|
+
|
|
674
|
+
useEffect(() => {
|
|
675
|
+
const handleScroll = () => setScrolled(window.scrollY > 40);
|
|
676
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
677
|
+
return () => window.removeEventListener('scroll', handleScroll);
|
|
678
|
+
}, []);
|
|
679
|
+
|
|
680
|
+
return (
|
|
681
|
+
<header
|
|
682
|
+
style={{
|
|
683
|
+
'--local-bg': 'rgba(6,13,27,0.92)',
|
|
684
|
+
'--local-text': 'var(--foreground)',
|
|
685
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
686
|
+
'--local-primary': 'var(--primary)',
|
|
687
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
688
|
+
'--local-border': 'rgba(59,130,246,0.08)',
|
|
689
|
+
} as React.CSSProperties}
|
|
690
|
+
className={cn(
|
|
691
|
+
'w-full py-4 transition-all duration-300 z-0',
|
|
692
|
+
scrolled
|
|
693
|
+
? 'bg-[var(--local-bg)] backdrop-blur-[20px] border-b border-[var(--local-border)]'
|
|
694
|
+
: 'bg-transparent border-b border-transparent'
|
|
695
|
+
)}
|
|
696
|
+
>
|
|
697
|
+
<div className="max-w-[1200px] mx-auto px-8 flex justify-between items-center">
|
|
698
|
+
<a
|
|
699
|
+
href="/"
|
|
700
|
+
className="flex items-center gap-2.5 no-underline font-bold text-xl tracking-tight text-[var(--local-text)]"
|
|
701
|
+
>
|
|
702
|
+
{data.logoIconText && (
|
|
703
|
+
<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">
|
|
704
|
+
{data.logoIconText}
|
|
705
|
+
</div>
|
|
706
|
+
)}
|
|
707
|
+
<span data-jp-field="logoText">
|
|
708
|
+
{data.logoText}
|
|
709
|
+
{data.logoHighlight && (
|
|
710
|
+
<span className="text-[var(--local-accent)]" data-jp-field="logoHighlight">{data.logoHighlight}</span>
|
|
711
|
+
)}
|
|
712
|
+
</span>
|
|
713
|
+
</a>
|
|
714
|
+
|
|
715
|
+
<nav className="hidden md:flex items-center gap-10">
|
|
716
|
+
{menu.map((item, idx) => (
|
|
717
|
+
<a
|
|
718
|
+
key={(item as { id?: string }).id ?? idx}
|
|
719
|
+
href={item.href}
|
|
720
|
+
data-jp-item-id={(item as { id?: string }).id ?? `legacy-${idx}`}
|
|
721
|
+
data-jp-item-field="links"
|
|
722
|
+
target={item.external ? '_blank' : undefined}
|
|
723
|
+
rel={item.external ? 'noopener noreferrer' : undefined}
|
|
724
|
+
className={cn(
|
|
725
|
+
'no-underline text-sm font-medium transition-colors',
|
|
726
|
+
item.isCta
|
|
727
|
+
? 'bg-[var(--local-primary)] text-white px-5 py-2 rounded-lg font-semibold hover:brightness-110 hover:-translate-y-px'
|
|
728
|
+
: 'text-[var(--local-text-muted)] hover:text-[var(--local-text)]'
|
|
729
|
+
)}
|
|
730
|
+
>
|
|
731
|
+
{item.label}
|
|
732
|
+
</a>
|
|
733
|
+
))}
|
|
734
|
+
</nav>
|
|
735
|
+
|
|
736
|
+
<button
|
|
737
|
+
type="button"
|
|
738
|
+
className="md:hidden p-2 text-[var(--local-text-muted)] hover:text-[var(--local-text)]"
|
|
739
|
+
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
|
740
|
+
aria-label={mobileMenuOpen ? 'Close menu' : 'Open menu'}
|
|
741
|
+
>
|
|
742
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
743
|
+
{mobileMenuOpen ? (
|
|
744
|
+
<><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></>
|
|
745
|
+
) : (
|
|
746
|
+
<><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" /></>
|
|
747
|
+
)}
|
|
748
|
+
</svg>
|
|
749
|
+
</button>
|
|
750
|
+
</div>
|
|
751
|
+
|
|
752
|
+
{mobileMenuOpen && (
|
|
753
|
+
<nav className="md:hidden border-t border-[var(--local-border)] bg-[var(--local-bg)] backdrop-blur-[20px]">
|
|
754
|
+
<div className="max-w-[1200px] mx-auto px-8 py-4 flex flex-col gap-4">
|
|
755
|
+
{menu.map((item, idx) => (
|
|
756
|
+
<a
|
|
757
|
+
key={(item as { id?: string }).id ?? idx}
|
|
758
|
+
href={item.href}
|
|
759
|
+
className="text-base font-medium text-[var(--local-text-muted)] hover:text-[var(--local-text)] transition-colors py-2 no-underline"
|
|
760
|
+
onClick={() => setMobileMenuOpen(false)}
|
|
761
|
+
data-jp-item-id={(item as { id?: string }).id ?? `legacy-${idx}`}
|
|
762
|
+
data-jp-item-field="links"
|
|
763
|
+
>
|
|
764
|
+
{item.label}
|
|
765
|
+
</a>
|
|
766
|
+
))}
|
|
767
|
+
</div>
|
|
768
|
+
</nav>
|
|
769
|
+
)}
|
|
770
|
+
</header>
|
|
771
|
+
);
|
|
772
|
+
};
|
|
773
773
|
|
|
774
774
|
END_OF_FILE_CONTENT
|
|
775
775
|
echo "Creating src/components/header/index.ts..."
|
|
@@ -827,780 +827,780 @@ END_OF_FILE_CONTENT
|
|
|
827
827
|
mkdir -p "src/components/hero"
|
|
828
828
|
echo "Creating src/components/hero/View.tsx..."
|
|
829
829
|
cat << 'END_OF_FILE_CONTENT' > "src/components/hero/View.tsx"
|
|
830
|
-
import React from 'react';
|
|
831
|
-
import { cn } from '@/lib/utils';
|
|
832
|
-
import type { HeroData, HeroSettings } from './types';
|
|
833
|
-
|
|
834
|
-
export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ data }) => {
|
|
835
|
-
return (
|
|
836
|
-
<section
|
|
837
|
-
style={{
|
|
838
|
-
'--local-bg': 'var(--background)',
|
|
839
|
-
'--local-text': 'var(--foreground)',
|
|
840
|
-
'--local-text-muted': 'var(--muted-foreground)',
|
|
841
|
-
'--local-primary': 'var(--primary)',
|
|
842
|
-
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
843
|
-
'--local-cyan': 'var(--color-secondary, #22d3ee)',
|
|
844
|
-
'--local-border': 'var(--border)',
|
|
845
|
-
} as React.CSSProperties}
|
|
846
|
-
className="jp-hero relative min-h-screen flex items-center overflow-hidden pt-24 bg-[var(--local-bg)]"
|
|
847
|
-
>
|
|
848
|
-
<div className="absolute -top-[40%] -right-[20%] w-[70vw] h-[70vw] rounded-full bg-[radial-gradient(circle,rgba(59,130,246,0.06)_0%,transparent_70%)] pointer-events-none" />
|
|
849
|
-
<div className="absolute -bottom-[10%] -left-[10%] w-[50vw] h-[50vw] rounded-full bg-[radial-gradient(circle,rgba(34,211,238,0.03)_0%,transparent_60%)] pointer-events-none" />
|
|
850
|
-
|
|
851
|
-
<div className="relative max-w-[1200px] mx-auto px-8 w-full">
|
|
852
|
-
<div className="max-w-[820px]">
|
|
853
|
-
{data.badge && (
|
|
854
|
-
<div className="inline-flex items-center gap-2 bg-[rgba(59,130,246,0.08)] border border-[rgba(59,130,246,0.2)] px-4 py-1.5 rounded-full text-[0.78rem] font-semibold text-[var(--local-accent)] mb-8 tracking-wide jp-animate-in" data-jp-field="badge">
|
|
855
|
-
<span className="w-1.5 h-1.5 rounded-full bg-[var(--local-accent)] jp-pulse-dot" />
|
|
856
|
-
{data.badge}
|
|
857
|
-
</div>
|
|
858
|
-
)}
|
|
859
|
-
<h1
|
|
860
|
-
className="font-display text-[clamp(2.8rem,6vw,4.8rem)] font-black text-[var(--local-text)] leading-[1.08] tracking-tight mb-6 jp-animate-in jp-d1"
|
|
861
|
-
data-jp-field="title"
|
|
862
|
-
>
|
|
863
|
-
{data.title}
|
|
864
|
-
{data.titleHighlight && (
|
|
865
|
-
<>
|
|
866
|
-
<br />
|
|
867
|
-
<em className="not-italic bg-gradient-to-br from-[var(--local-accent)] to-[var(--local-cyan)] bg-clip-text text-transparent" data-jp-field="titleHighlight">
|
|
868
|
-
{data.titleHighlight}
|
|
869
|
-
</em>
|
|
870
|
-
</>
|
|
871
|
-
)}
|
|
872
|
-
</h1>
|
|
873
|
-
{data.description && (
|
|
874
|
-
<p className="text-xl text-[var(--local-text-muted)] max-w-[600px] leading-relaxed mb-10 jp-animate-in jp-d2" data-jp-field="description">
|
|
875
|
-
{data.description}
|
|
876
|
-
</p>
|
|
877
|
-
)}
|
|
878
|
-
{data.ctas && data.ctas.length > 0 && (
|
|
879
|
-
<div className="flex gap-4 flex-wrap jp-animate-in jp-d3">
|
|
880
|
-
{data.ctas.map((cta, idx) => (
|
|
881
|
-
<a
|
|
882
|
-
key={cta.id ?? idx}
|
|
883
|
-
href={cta.href}
|
|
884
|
-
data-jp-item-id={cta.id ?? `legacy-${idx}`}
|
|
885
|
-
data-jp-item-field="ctas"
|
|
886
|
-
className={cn(
|
|
887
|
-
'inline-flex items-center gap-2 px-8 py-3.5 rounded-[5px] font-semibold text-base transition-all duration-200 no-underline',
|
|
888
|
-
cta.variant === 'primary'
|
|
889
|
-
? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-0.5 hover:shadow-[0_8px_30px_rgba(59,130,246,0.3)]'
|
|
890
|
-
: 'bg-transparent text-[var(--local-text)] border border-[rgba(255,255,255,0.12)] hover:border-[rgba(255,255,255,0.3)] hover:bg-[rgba(255,255,255,0.04)]'
|
|
891
|
-
)}
|
|
892
|
-
>
|
|
893
|
-
{cta.label}
|
|
894
|
-
</a>
|
|
895
|
-
))}
|
|
896
|
-
</div>
|
|
897
|
-
)}
|
|
898
|
-
{data.metrics && data.metrics.length > 0 && (
|
|
899
|
-
<div className="flex gap-12 mt-16 pt-12 border-t border-[rgba(255,255,255,0.06)] flex-wrap jp-animate-in jp-d4">
|
|
900
|
-
{data.metrics.map((metric, idx) => (
|
|
901
|
-
<div
|
|
902
|
-
key={(metric as { id?: string }).id ?? idx}
|
|
903
|
-
data-jp-item-id={(metric as { id?: string }).id ?? `legacy-${idx}`}
|
|
904
|
-
data-jp-item-field="metrics"
|
|
905
|
-
>
|
|
906
|
-
<div className="text-[2rem] font-bold text-[var(--local-text)] font-display">
|
|
907
|
-
{metric.val}
|
|
908
|
-
</div>
|
|
909
|
-
<div className="text-[0.82rem] text-[var(--muted-foreground)] mt-0.5 opacity-70">
|
|
910
|
-
{metric.label}
|
|
911
|
-
</div>
|
|
912
|
-
</div>
|
|
913
|
-
))}
|
|
914
|
-
</div>
|
|
915
|
-
)}
|
|
916
|
-
</div>
|
|
917
|
-
</div>
|
|
918
|
-
</section>
|
|
919
|
-
);
|
|
920
|
-
};
|
|
830
|
+
import React from 'react';
|
|
831
|
+
import { cn } from '@/lib/utils';
|
|
832
|
+
import type { HeroData, HeroSettings } from './types';
|
|
833
|
+
|
|
834
|
+
export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ data }) => {
|
|
835
|
+
return (
|
|
836
|
+
<section
|
|
837
|
+
style={{
|
|
838
|
+
'--local-bg': 'var(--background)',
|
|
839
|
+
'--local-text': 'var(--foreground)',
|
|
840
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
841
|
+
'--local-primary': 'var(--primary)',
|
|
842
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
843
|
+
'--local-cyan': 'var(--color-secondary, #22d3ee)',
|
|
844
|
+
'--local-border': 'var(--border)',
|
|
845
|
+
} as React.CSSProperties}
|
|
846
|
+
className="jp-hero relative min-h-screen flex items-center overflow-hidden pt-24 bg-[var(--local-bg)]"
|
|
847
|
+
>
|
|
848
|
+
<div className="absolute -top-[40%] -right-[20%] w-[70vw] h-[70vw] rounded-full bg-[radial-gradient(circle,rgba(59,130,246,0.06)_0%,transparent_70%)] pointer-events-none" />
|
|
849
|
+
<div className="absolute -bottom-[10%] -left-[10%] w-[50vw] h-[50vw] rounded-full bg-[radial-gradient(circle,rgba(34,211,238,0.03)_0%,transparent_60%)] pointer-events-none" />
|
|
850
|
+
|
|
851
|
+
<div className="relative max-w-[1200px] mx-auto px-8 w-full">
|
|
852
|
+
<div className="max-w-[820px]">
|
|
853
|
+
{data.badge && (
|
|
854
|
+
<div className="inline-flex items-center gap-2 bg-[rgba(59,130,246,0.08)] border border-[rgba(59,130,246,0.2)] px-4 py-1.5 rounded-full text-[0.78rem] font-semibold text-[var(--local-accent)] mb-8 tracking-wide jp-animate-in" data-jp-field="badge">
|
|
855
|
+
<span className="w-1.5 h-1.5 rounded-full bg-[var(--local-accent)] jp-pulse-dot" />
|
|
856
|
+
{data.badge}
|
|
857
|
+
</div>
|
|
858
|
+
)}
|
|
859
|
+
<h1
|
|
860
|
+
className="font-display text-[clamp(2.8rem,6vw,4.8rem)] font-black text-[var(--local-text)] leading-[1.08] tracking-tight mb-6 jp-animate-in jp-d1"
|
|
861
|
+
data-jp-field="title"
|
|
862
|
+
>
|
|
863
|
+
{data.title}
|
|
864
|
+
{data.titleHighlight && (
|
|
865
|
+
<>
|
|
866
|
+
<br />
|
|
867
|
+
<em className="not-italic bg-gradient-to-br from-[var(--local-accent)] to-[var(--local-cyan)] bg-clip-text text-transparent" data-jp-field="titleHighlight">
|
|
868
|
+
{data.titleHighlight}
|
|
869
|
+
</em>
|
|
870
|
+
</>
|
|
871
|
+
)}
|
|
872
|
+
</h1>
|
|
873
|
+
{data.description && (
|
|
874
|
+
<p className="text-xl text-[var(--local-text-muted)] max-w-[600px] leading-relaxed mb-10 jp-animate-in jp-d2" data-jp-field="description">
|
|
875
|
+
{data.description}
|
|
876
|
+
</p>
|
|
877
|
+
)}
|
|
878
|
+
{data.ctas && data.ctas.length > 0 && (
|
|
879
|
+
<div className="flex gap-4 flex-wrap jp-animate-in jp-d3">
|
|
880
|
+
{data.ctas.map((cta, idx) => (
|
|
881
|
+
<a
|
|
882
|
+
key={cta.id ?? idx}
|
|
883
|
+
href={cta.href}
|
|
884
|
+
data-jp-item-id={cta.id ?? `legacy-${idx}`}
|
|
885
|
+
data-jp-item-field="ctas"
|
|
886
|
+
className={cn(
|
|
887
|
+
'inline-flex items-center gap-2 px-8 py-3.5 rounded-[5px] font-semibold text-base transition-all duration-200 no-underline',
|
|
888
|
+
cta.variant === 'primary'
|
|
889
|
+
? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-0.5 hover:shadow-[0_8px_30px_rgba(59,130,246,0.3)]'
|
|
890
|
+
: 'bg-transparent text-[var(--local-text)] border border-[rgba(255,255,255,0.12)] hover:border-[rgba(255,255,255,0.3)] hover:bg-[rgba(255,255,255,0.04)]'
|
|
891
|
+
)}
|
|
892
|
+
>
|
|
893
|
+
{cta.label}
|
|
894
|
+
</a>
|
|
895
|
+
))}
|
|
896
|
+
</div>
|
|
897
|
+
)}
|
|
898
|
+
{data.metrics && data.metrics.length > 0 && (
|
|
899
|
+
<div className="flex gap-12 mt-16 pt-12 border-t border-[rgba(255,255,255,0.06)] flex-wrap jp-animate-in jp-d4">
|
|
900
|
+
{data.metrics.map((metric, idx) => (
|
|
901
|
+
<div
|
|
902
|
+
key={(metric as { id?: string }).id ?? idx}
|
|
903
|
+
data-jp-item-id={(metric as { id?: string }).id ?? `legacy-${idx}`}
|
|
904
|
+
data-jp-item-field="metrics"
|
|
905
|
+
>
|
|
906
|
+
<div className="text-[2rem] font-bold text-[var(--local-text)] font-display">
|
|
907
|
+
{metric.val}
|
|
908
|
+
</div>
|
|
909
|
+
<div className="text-[0.82rem] text-[var(--muted-foreground)] mt-0.5 opacity-70">
|
|
910
|
+
{metric.label}
|
|
911
|
+
</div>
|
|
912
|
+
</div>
|
|
913
|
+
))}
|
|
914
|
+
</div>
|
|
915
|
+
)}
|
|
916
|
+
</div>
|
|
917
|
+
</div>
|
|
918
|
+
</section>
|
|
919
|
+
);
|
|
920
|
+
};
|
|
921
921
|
|
|
922
922
|
END_OF_FILE_CONTENT
|
|
923
923
|
echo "Creating src/components/hero/index.ts..."
|
|
924
924
|
cat << 'END_OF_FILE_CONTENT' > "src/components/hero/index.ts"
|
|
925
|
-
export * from './View';
|
|
926
|
-
export * from './schema';
|
|
927
|
-
export * from './types';
|
|
925
|
+
export * from './View';
|
|
926
|
+
export * from './schema';
|
|
927
|
+
export * from './types';
|
|
928
928
|
|
|
929
929
|
END_OF_FILE_CONTENT
|
|
930
930
|
echo "Creating src/components/hero/schema.ts..."
|
|
931
931
|
cat << 'END_OF_FILE_CONTENT' > "src/components/hero/schema.ts"
|
|
932
|
-
import { z } from 'zod';
|
|
933
|
-
import { BaseSectionData, CtaSchema } from '@/lib/base-schemas';
|
|
934
|
-
|
|
935
|
-
const HeroMetricSchema = z.object({
|
|
936
|
-
val: z.string().describe('ui:text'),
|
|
937
|
-
label: z.string().describe('ui:text'),
|
|
938
|
-
});
|
|
939
|
-
|
|
940
|
-
export const HeroSchema = BaseSectionData.extend({
|
|
941
|
-
badge: z.string().optional().describe('ui:text'),
|
|
942
|
-
title: z.string().describe('ui:text'),
|
|
943
|
-
titleHighlight: z.string().optional().describe('ui:text'),
|
|
944
|
-
description: z.string().optional().describe('ui:textarea'),
|
|
945
|
-
ctas: z.array(CtaSchema).optional().describe('ui:list'),
|
|
946
|
-
metrics: z.array(HeroMetricSchema).optional().describe('ui:list'),
|
|
947
|
-
});
|
|
932
|
+
import { z } from 'zod';
|
|
933
|
+
import { BaseSectionData, CtaSchema } from '@/lib/base-schemas';
|
|
934
|
+
|
|
935
|
+
const HeroMetricSchema = z.object({
|
|
936
|
+
val: z.string().describe('ui:text'),
|
|
937
|
+
label: z.string().describe('ui:text'),
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
export const HeroSchema = BaseSectionData.extend({
|
|
941
|
+
badge: z.string().optional().describe('ui:text'),
|
|
942
|
+
title: z.string().describe('ui:text'),
|
|
943
|
+
titleHighlight: z.string().optional().describe('ui:text'),
|
|
944
|
+
description: z.string().optional().describe('ui:textarea'),
|
|
945
|
+
ctas: z.array(CtaSchema).optional().describe('ui:list'),
|
|
946
|
+
metrics: z.array(HeroMetricSchema).optional().describe('ui:list'),
|
|
947
|
+
});
|
|
948
948
|
|
|
949
949
|
END_OF_FILE_CONTENT
|
|
950
950
|
echo "Creating src/components/hero/types.ts..."
|
|
951
951
|
cat << 'END_OF_FILE_CONTENT' > "src/components/hero/types.ts"
|
|
952
|
-
import { z } from 'zod';
|
|
953
|
-
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
954
|
-
import { HeroSchema } from './schema';
|
|
955
|
-
|
|
956
|
-
export type HeroData = z.infer<typeof HeroSchema>;
|
|
957
|
-
export type HeroSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
952
|
+
import { z } from 'zod';
|
|
953
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
954
|
+
import { HeroSchema } from './schema';
|
|
955
|
+
|
|
956
|
+
export type HeroData = z.infer<typeof HeroSchema>;
|
|
957
|
+
export type HeroSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
958
958
|
|
|
959
959
|
END_OF_FILE_CONTENT
|
|
960
960
|
mkdir -p "src/components/image-test"
|
|
961
961
|
mkdir -p "src/components/pa-section"
|
|
962
962
|
echo "Creating src/components/pa-section/View.tsx..."
|
|
963
963
|
cat << 'END_OF_FILE_CONTENT' > "src/components/pa-section/View.tsx"
|
|
964
|
-
import React from 'react';
|
|
965
|
-
import type { PaSectionData, PaSectionSettings } from './types';
|
|
966
|
-
|
|
967
|
-
export const PaSection: React.FC<{ data: PaSectionData; settings?: PaSectionSettings }> = ({ data }) => {
|
|
968
|
-
return (
|
|
969
|
-
<section
|
|
970
|
-
style={{
|
|
971
|
-
'--local-bg': 'var(--card)',
|
|
972
|
-
'--local-text': 'var(--foreground)',
|
|
973
|
-
'--local-text-muted': 'var(--muted-foreground)',
|
|
974
|
-
'--local-primary': 'var(--primary)',
|
|
975
|
-
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
976
|
-
'--local-deep': 'var(--background)',
|
|
977
|
-
} as React.CSSProperties}
|
|
978
|
-
className="relative z-0 py-28 bg-[var(--local-bg)]"
|
|
979
|
-
>
|
|
980
|
-
<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" />
|
|
981
|
-
<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" />
|
|
982
|
-
<div className="max-w-[1200px] mx-auto px-8">
|
|
983
|
-
{data.label && (
|
|
984
|
-
<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">
|
|
985
|
-
<span className="w-5 h-px bg-[var(--local-primary)]" />
|
|
986
|
-
{data.label}
|
|
987
|
-
</div>
|
|
988
|
-
)}
|
|
989
|
-
<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">
|
|
990
|
-
{data.title}
|
|
991
|
-
</h2>
|
|
992
|
-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center mt-12">
|
|
993
|
-
<div>
|
|
994
|
-
<h3 className="text-2xl font-bold text-[var(--local-text)] mb-4" data-jp-field="subtitle">
|
|
995
|
-
{data.subtitle}
|
|
996
|
-
</h3>
|
|
997
|
-
{data.paragraphs.map((p, idx) => (
|
|
998
|
-
<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">
|
|
999
|
-
{p.text}
|
|
1000
|
-
</p>
|
|
1001
|
-
))}
|
|
1002
|
-
{data.badges && data.badges.length > 0 && (
|
|
1003
|
-
<div className="flex gap-2.5 flex-wrap mt-4">
|
|
1004
|
-
{data.badges.map((badge, idx) => (
|
|
1005
|
-
<span
|
|
1006
|
-
key={idx}
|
|
1007
|
-
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"
|
|
1008
|
-
data-jp-item-id={(badge as { id?: string }).id ?? `legacy-${idx}`}
|
|
1009
|
-
data-jp-item-field="badges"
|
|
1010
|
-
>
|
|
1011
|
-
{badge.label}
|
|
1012
|
-
</span>
|
|
1013
|
-
))}
|
|
1014
|
-
</div>
|
|
1015
|
-
)}
|
|
1016
|
-
</div>
|
|
1017
|
-
<div className="border border-[rgba(255,255,255,0.06)] rounded-lg p-12 bg-[rgba(255,255,255,0.02)] text-center">
|
|
1018
|
-
{data.engines && data.engines.length >= 2 && (
|
|
1019
|
-
<div className="flex items-center justify-center gap-6 mb-8">
|
|
1020
|
-
{data.engines.map((engine, idx) => (
|
|
1021
|
-
<React.Fragment key={idx}>
|
|
1022
|
-
{idx > 0 && (
|
|
1023
|
-
<span className="text-[var(--local-text-muted)] text-2xl opacity-50">⇄</span>
|
|
1024
|
-
)}
|
|
1025
|
-
<div
|
|
1026
|
-
className={
|
|
1027
|
-
engine.variant === 'tailwind'
|
|
1028
|
-
? '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]'
|
|
1029
|
-
: '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]'
|
|
1030
|
-
}
|
|
1031
|
-
data-jp-item-id={(engine as { id?: string }).id ?? `legacy-${idx}`}
|
|
1032
|
-
data-jp-item-field="engines"
|
|
1033
|
-
>
|
|
1034
|
-
{engine.label}
|
|
1035
|
-
</div>
|
|
1036
|
-
</React.Fragment>
|
|
1037
|
-
))}
|
|
1038
|
-
</div>
|
|
1039
|
-
)}
|
|
1040
|
-
{data.codeSnippet && (
|
|
1041
|
-
<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">
|
|
1042
|
-
<pre className="whitespace-pre-wrap m-0">{data.codeSnippet}</pre>
|
|
1043
|
-
<div className="mt-4 text-[0.75rem] text-center opacity-50">
|
|
1044
|
-
Same JSON. Different Render Engine.
|
|
1045
|
-
</div>
|
|
1046
|
-
</div>
|
|
1047
|
-
)}
|
|
1048
|
-
</div>
|
|
1049
|
-
</div>
|
|
1050
|
-
</div>
|
|
1051
|
-
</section>
|
|
1052
|
-
);
|
|
1053
|
-
};
|
|
964
|
+
import React from 'react';
|
|
965
|
+
import type { PaSectionData, PaSectionSettings } from './types';
|
|
966
|
+
|
|
967
|
+
export const PaSection: React.FC<{ data: PaSectionData; settings?: PaSectionSettings }> = ({ data }) => {
|
|
968
|
+
return (
|
|
969
|
+
<section
|
|
970
|
+
style={{
|
|
971
|
+
'--local-bg': 'var(--card)',
|
|
972
|
+
'--local-text': 'var(--foreground)',
|
|
973
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
974
|
+
'--local-primary': 'var(--primary)',
|
|
975
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
976
|
+
'--local-deep': 'var(--background)',
|
|
977
|
+
} as React.CSSProperties}
|
|
978
|
+
className="relative z-0 py-28 bg-[var(--local-bg)]"
|
|
979
|
+
>
|
|
980
|
+
<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" />
|
|
981
|
+
<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" />
|
|
982
|
+
<div className="max-w-[1200px] mx-auto px-8">
|
|
983
|
+
{data.label && (
|
|
984
|
+
<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">
|
|
985
|
+
<span className="w-5 h-px bg-[var(--local-primary)]" />
|
|
986
|
+
{data.label}
|
|
987
|
+
</div>
|
|
988
|
+
)}
|
|
989
|
+
<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">
|
|
990
|
+
{data.title}
|
|
991
|
+
</h2>
|
|
992
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center mt-12">
|
|
993
|
+
<div>
|
|
994
|
+
<h3 className="text-2xl font-bold text-[var(--local-text)] mb-4" data-jp-field="subtitle">
|
|
995
|
+
{data.subtitle}
|
|
996
|
+
</h3>
|
|
997
|
+
{data.paragraphs.map((p, idx) => (
|
|
998
|
+
<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">
|
|
999
|
+
{p.text}
|
|
1000
|
+
</p>
|
|
1001
|
+
))}
|
|
1002
|
+
{data.badges && data.badges.length > 0 && (
|
|
1003
|
+
<div className="flex gap-2.5 flex-wrap mt-4">
|
|
1004
|
+
{data.badges.map((badge, idx) => (
|
|
1005
|
+
<span
|
|
1006
|
+
key={idx}
|
|
1007
|
+
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"
|
|
1008
|
+
data-jp-item-id={(badge as { id?: string }).id ?? `legacy-${idx}`}
|
|
1009
|
+
data-jp-item-field="badges"
|
|
1010
|
+
>
|
|
1011
|
+
{badge.label}
|
|
1012
|
+
</span>
|
|
1013
|
+
))}
|
|
1014
|
+
</div>
|
|
1015
|
+
)}
|
|
1016
|
+
</div>
|
|
1017
|
+
<div className="border border-[rgba(255,255,255,0.06)] rounded-lg p-12 bg-[rgba(255,255,255,0.02)] text-center">
|
|
1018
|
+
{data.engines && data.engines.length >= 2 && (
|
|
1019
|
+
<div className="flex items-center justify-center gap-6 mb-8">
|
|
1020
|
+
{data.engines.map((engine, idx) => (
|
|
1021
|
+
<React.Fragment key={idx}>
|
|
1022
|
+
{idx > 0 && (
|
|
1023
|
+
<span className="text-[var(--local-text-muted)] text-2xl opacity-50">⇄</span>
|
|
1024
|
+
)}
|
|
1025
|
+
<div
|
|
1026
|
+
className={
|
|
1027
|
+
engine.variant === 'tailwind'
|
|
1028
|
+
? '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]'
|
|
1029
|
+
: '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]'
|
|
1030
|
+
}
|
|
1031
|
+
data-jp-item-id={(engine as { id?: string }).id ?? `legacy-${idx}`}
|
|
1032
|
+
data-jp-item-field="engines"
|
|
1033
|
+
>
|
|
1034
|
+
{engine.label}
|
|
1035
|
+
</div>
|
|
1036
|
+
</React.Fragment>
|
|
1037
|
+
))}
|
|
1038
|
+
</div>
|
|
1039
|
+
)}
|
|
1040
|
+
{data.codeSnippet && (
|
|
1041
|
+
<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">
|
|
1042
|
+
<pre className="whitespace-pre-wrap m-0">{data.codeSnippet}</pre>
|
|
1043
|
+
<div className="mt-4 text-[0.75rem] text-center opacity-50">
|
|
1044
|
+
Same JSON. Different Render Engine.
|
|
1045
|
+
</div>
|
|
1046
|
+
</div>
|
|
1047
|
+
)}
|
|
1048
|
+
</div>
|
|
1049
|
+
</div>
|
|
1050
|
+
</div>
|
|
1051
|
+
</section>
|
|
1052
|
+
);
|
|
1053
|
+
};
|
|
1054
1054
|
|
|
1055
1055
|
END_OF_FILE_CONTENT
|
|
1056
1056
|
echo "Creating src/components/pa-section/index.ts..."
|
|
1057
1057
|
cat << 'END_OF_FILE_CONTENT' > "src/components/pa-section/index.ts"
|
|
1058
|
-
export * from './View';
|
|
1059
|
-
export * from './schema';
|
|
1060
|
-
export * from './types';
|
|
1058
|
+
export * from './View';
|
|
1059
|
+
export * from './schema';
|
|
1060
|
+
export * from './types';
|
|
1061
1061
|
|
|
1062
1062
|
END_OF_FILE_CONTENT
|
|
1063
1063
|
echo "Creating src/components/pa-section/schema.ts..."
|
|
1064
1064
|
cat << 'END_OF_FILE_CONTENT' > "src/components/pa-section/schema.ts"
|
|
1065
|
-
import { z } from 'zod';
|
|
1066
|
-
import { BaseSectionData } from '@/lib/base-schemas';
|
|
1067
|
-
|
|
1068
|
-
const PaBadgeSchema = z.object({
|
|
1069
|
-
label: z.string().describe('ui:text'),
|
|
1070
|
-
});
|
|
1071
|
-
|
|
1072
|
-
const PaEngineSchema = z.object({
|
|
1073
|
-
label: z.string().describe('ui:text'),
|
|
1074
|
-
variant: z.enum(['tailwind', 'bootstrap']).describe('ui:select'),
|
|
1075
|
-
});
|
|
1076
|
-
|
|
1077
|
-
export const PaSectionSchema = BaseSectionData.extend({
|
|
1078
|
-
label: z.string().optional().describe('ui:text'),
|
|
1079
|
-
title: z.string().describe('ui:text'),
|
|
1080
|
-
subtitle: z.string().describe('ui:text'),
|
|
1081
|
-
paragraphs: z.array(z.object({ text: z.string().describe('ui:textarea') })).describe('ui:list'),
|
|
1082
|
-
badges: z.array(PaBadgeSchema).optional().describe('ui:list'),
|
|
1083
|
-
engines: z.array(PaEngineSchema).optional().describe('ui:list'),
|
|
1084
|
-
codeSnippet: z.string().optional().describe('ui:textarea'),
|
|
1085
|
-
});
|
|
1065
|
+
import { z } from 'zod';
|
|
1066
|
+
import { BaseSectionData } from '@/lib/base-schemas';
|
|
1067
|
+
|
|
1068
|
+
const PaBadgeSchema = z.object({
|
|
1069
|
+
label: z.string().describe('ui:text'),
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
const PaEngineSchema = z.object({
|
|
1073
|
+
label: z.string().describe('ui:text'),
|
|
1074
|
+
variant: z.enum(['tailwind', 'bootstrap']).describe('ui:select'),
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
export const PaSectionSchema = BaseSectionData.extend({
|
|
1078
|
+
label: z.string().optional().describe('ui:text'),
|
|
1079
|
+
title: z.string().describe('ui:text'),
|
|
1080
|
+
subtitle: z.string().describe('ui:text'),
|
|
1081
|
+
paragraphs: z.array(z.object({ text: z.string().describe('ui:textarea') })).describe('ui:list'),
|
|
1082
|
+
badges: z.array(PaBadgeSchema).optional().describe('ui:list'),
|
|
1083
|
+
engines: z.array(PaEngineSchema).optional().describe('ui:list'),
|
|
1084
|
+
codeSnippet: z.string().optional().describe('ui:textarea'),
|
|
1085
|
+
});
|
|
1086
1086
|
|
|
1087
1087
|
END_OF_FILE_CONTENT
|
|
1088
1088
|
echo "Creating src/components/pa-section/types.ts..."
|
|
1089
1089
|
cat << 'END_OF_FILE_CONTENT' > "src/components/pa-section/types.ts"
|
|
1090
|
-
import { z } from 'zod';
|
|
1091
|
-
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
1092
|
-
import { PaSectionSchema } from './schema';
|
|
1093
|
-
|
|
1094
|
-
export type PaSectionData = z.infer<typeof PaSectionSchema>;
|
|
1095
|
-
export type PaSectionSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
1090
|
+
import { z } from 'zod';
|
|
1091
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
1092
|
+
import { PaSectionSchema } from './schema';
|
|
1093
|
+
|
|
1094
|
+
export type PaSectionData = z.infer<typeof PaSectionSchema>;
|
|
1095
|
+
export type PaSectionSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
1096
1096
|
|
|
1097
1097
|
END_OF_FILE_CONTENT
|
|
1098
1098
|
mkdir -p "src/components/philosophy"
|
|
1099
1099
|
echo "Creating src/components/philosophy/View.tsx..."
|
|
1100
1100
|
cat << 'END_OF_FILE_CONTENT' > "src/components/philosophy/View.tsx"
|
|
1101
|
-
import React from 'react';
|
|
1102
|
-
import type { PhilosophyData, PhilosophySettings } from './types';
|
|
1103
|
-
|
|
1104
|
-
export const Philosophy: React.FC<{ data: PhilosophyData; settings?: PhilosophySettings }> = ({ data }) => {
|
|
1105
|
-
const renderQuote = () => {
|
|
1106
|
-
if (!data.quoteHighlightWord) {
|
|
1107
|
-
return <>{data.quote}</>;
|
|
1108
|
-
}
|
|
1109
|
-
const parts = data.quote.split(data.quoteHighlightWord);
|
|
1110
|
-
return (
|
|
1111
|
-
<>
|
|
1112
|
-
{parts.map((part, idx) => (
|
|
1113
|
-
<React.Fragment key={idx}>
|
|
1114
|
-
{part}
|
|
1115
|
-
{idx < parts.length - 1 && (
|
|
1116
|
-
<em className="not-italic text-[var(--local-accent)]">
|
|
1117
|
-
{data.quoteHighlightWord}
|
|
1118
|
-
</em>
|
|
1119
|
-
)}
|
|
1120
|
-
</React.Fragment>
|
|
1121
|
-
))}
|
|
1122
|
-
</>
|
|
1123
|
-
);
|
|
1124
|
-
};
|
|
1125
|
-
|
|
1126
|
-
return (
|
|
1127
|
-
<section
|
|
1128
|
-
style={{
|
|
1129
|
-
'--local-bg': 'var(--background)',
|
|
1130
|
-
'--local-text': 'var(--foreground)',
|
|
1131
|
-
'--local-text-muted': 'var(--muted-foreground)',
|
|
1132
|
-
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
1133
|
-
'--local-primary': 'var(--primary)',
|
|
1134
|
-
} as React.CSSProperties}
|
|
1135
|
-
className="relative z-0 py-28 bg-[var(--local-bg)]"
|
|
1136
|
-
>
|
|
1137
|
-
<div className="max-w-[1200px] mx-auto px-8">
|
|
1138
|
-
<div className="max-w-[760px] mx-auto text-center">
|
|
1139
|
-
{data.label && (
|
|
1140
|
-
<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">
|
|
1141
|
-
<span className="w-5 h-px bg-[var(--local-primary)]" />
|
|
1142
|
-
{data.label}
|
|
1143
|
-
</div>
|
|
1144
|
-
)}
|
|
1145
|
-
<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">
|
|
1146
|
-
{data.title}
|
|
1147
|
-
</h2>
|
|
1148
|
-
<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">
|
|
1149
|
-
“{renderQuote()}”
|
|
1150
|
-
</blockquote>
|
|
1151
|
-
{data.description && (
|
|
1152
|
-
<p className="text-[1.05rem] text-[var(--local-text-muted)] max-w-[560px] mx-auto leading-relaxed" data-jp-field="description">
|
|
1153
|
-
{data.description}
|
|
1154
|
-
</p>
|
|
1155
|
-
)}
|
|
1156
|
-
</div>
|
|
1157
|
-
</div>
|
|
1158
|
-
</section>
|
|
1159
|
-
);
|
|
1160
|
-
};
|
|
1101
|
+
import React from 'react';
|
|
1102
|
+
import type { PhilosophyData, PhilosophySettings } from './types';
|
|
1103
|
+
|
|
1104
|
+
export const Philosophy: React.FC<{ data: PhilosophyData; settings?: PhilosophySettings }> = ({ data }) => {
|
|
1105
|
+
const renderQuote = () => {
|
|
1106
|
+
if (!data.quoteHighlightWord) {
|
|
1107
|
+
return <>{data.quote}</>;
|
|
1108
|
+
}
|
|
1109
|
+
const parts = data.quote.split(data.quoteHighlightWord);
|
|
1110
|
+
return (
|
|
1111
|
+
<>
|
|
1112
|
+
{parts.map((part, idx) => (
|
|
1113
|
+
<React.Fragment key={idx}>
|
|
1114
|
+
{part}
|
|
1115
|
+
{idx < parts.length - 1 && (
|
|
1116
|
+
<em className="not-italic text-[var(--local-accent)]">
|
|
1117
|
+
{data.quoteHighlightWord}
|
|
1118
|
+
</em>
|
|
1119
|
+
)}
|
|
1120
|
+
</React.Fragment>
|
|
1121
|
+
))}
|
|
1122
|
+
</>
|
|
1123
|
+
);
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
return (
|
|
1127
|
+
<section
|
|
1128
|
+
style={{
|
|
1129
|
+
'--local-bg': 'var(--background)',
|
|
1130
|
+
'--local-text': 'var(--foreground)',
|
|
1131
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
1132
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
1133
|
+
'--local-primary': 'var(--primary)',
|
|
1134
|
+
} as React.CSSProperties}
|
|
1135
|
+
className="relative z-0 py-28 bg-[var(--local-bg)]"
|
|
1136
|
+
>
|
|
1137
|
+
<div className="max-w-[1200px] mx-auto px-8">
|
|
1138
|
+
<div className="max-w-[760px] mx-auto text-center">
|
|
1139
|
+
{data.label && (
|
|
1140
|
+
<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">
|
|
1141
|
+
<span className="w-5 h-px bg-[var(--local-primary)]" />
|
|
1142
|
+
{data.label}
|
|
1143
|
+
</div>
|
|
1144
|
+
)}
|
|
1145
|
+
<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">
|
|
1146
|
+
{data.title}
|
|
1147
|
+
</h2>
|
|
1148
|
+
<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">
|
|
1149
|
+
“{renderQuote()}”
|
|
1150
|
+
</blockquote>
|
|
1151
|
+
{data.description && (
|
|
1152
|
+
<p className="text-[1.05rem] text-[var(--local-text-muted)] max-w-[560px] mx-auto leading-relaxed" data-jp-field="description">
|
|
1153
|
+
{data.description}
|
|
1154
|
+
</p>
|
|
1155
|
+
)}
|
|
1156
|
+
</div>
|
|
1157
|
+
</div>
|
|
1158
|
+
</section>
|
|
1159
|
+
);
|
|
1160
|
+
};
|
|
1161
1161
|
|
|
1162
1162
|
END_OF_FILE_CONTENT
|
|
1163
1163
|
echo "Creating src/components/philosophy/index.ts..."
|
|
1164
1164
|
cat << 'END_OF_FILE_CONTENT' > "src/components/philosophy/index.ts"
|
|
1165
|
-
export * from './View';
|
|
1166
|
-
export * from './schema';
|
|
1167
|
-
export * from './types';
|
|
1165
|
+
export * from './View';
|
|
1166
|
+
export * from './schema';
|
|
1167
|
+
export * from './types';
|
|
1168
1168
|
|
|
1169
1169
|
END_OF_FILE_CONTENT
|
|
1170
1170
|
echo "Creating src/components/philosophy/schema.ts..."
|
|
1171
1171
|
cat << 'END_OF_FILE_CONTENT' > "src/components/philosophy/schema.ts"
|
|
1172
|
-
import { z } from 'zod';
|
|
1173
|
-
import { BaseSectionData } from '@/lib/base-schemas';
|
|
1174
|
-
|
|
1175
|
-
export const PhilosophySchema = BaseSectionData.extend({
|
|
1176
|
-
label: z.string().optional().describe('ui:text'),
|
|
1177
|
-
title: z.string().describe('ui:text'),
|
|
1178
|
-
quote: z.string().describe('ui:textarea'),
|
|
1179
|
-
quoteHighlightWord: z.string().optional().describe('ui:text'),
|
|
1180
|
-
description: z.string().optional().describe('ui:textarea'),
|
|
1181
|
-
});
|
|
1172
|
+
import { z } from 'zod';
|
|
1173
|
+
import { BaseSectionData } from '@/lib/base-schemas';
|
|
1174
|
+
|
|
1175
|
+
export const PhilosophySchema = BaseSectionData.extend({
|
|
1176
|
+
label: z.string().optional().describe('ui:text'),
|
|
1177
|
+
title: z.string().describe('ui:text'),
|
|
1178
|
+
quote: z.string().describe('ui:textarea'),
|
|
1179
|
+
quoteHighlightWord: z.string().optional().describe('ui:text'),
|
|
1180
|
+
description: z.string().optional().describe('ui:textarea'),
|
|
1181
|
+
});
|
|
1182
1182
|
|
|
1183
1183
|
END_OF_FILE_CONTENT
|
|
1184
1184
|
echo "Creating src/components/philosophy/types.ts..."
|
|
1185
1185
|
cat << 'END_OF_FILE_CONTENT' > "src/components/philosophy/types.ts"
|
|
1186
|
-
import { z } from 'zod';
|
|
1187
|
-
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
1188
|
-
import { PhilosophySchema } from './schema';
|
|
1189
|
-
|
|
1190
|
-
export type PhilosophyData = z.infer<typeof PhilosophySchema>;
|
|
1191
|
-
export type PhilosophySettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
1186
|
+
import { z } from 'zod';
|
|
1187
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
1188
|
+
import { PhilosophySchema } from './schema';
|
|
1189
|
+
|
|
1190
|
+
export type PhilosophyData = z.infer<typeof PhilosophySchema>;
|
|
1191
|
+
export type PhilosophySettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
1192
1192
|
|
|
1193
1193
|
END_OF_FILE_CONTENT
|
|
1194
1194
|
mkdir -p "src/components/pillars-grid"
|
|
1195
1195
|
echo "Creating src/components/pillars-grid/View.tsx..."
|
|
1196
1196
|
cat << 'END_OF_FILE_CONTENT' > "src/components/pillars-grid/View.tsx"
|
|
1197
|
-
import React from 'react';
|
|
1198
|
-
import { cn } from '@/lib/utils';
|
|
1199
|
-
import { Icon, isIconName } from '@/lib/IconResolver';
|
|
1200
|
-
import type { PillarsGridData, PillarsGridSettings, PillarIconVariant, PillarTagVariant } from './types';
|
|
1201
|
-
|
|
1202
|
-
const iconVariantStyles: Record<PillarIconVariant, string> = {
|
|
1203
|
-
split: 'bg-[rgba(59,130,246,0.1)] text-[#60a5fa]',
|
|
1204
|
-
registry: 'bg-[rgba(34,211,238,0.1)] text-[#22d3ee]',
|
|
1205
|
-
federation: 'bg-[rgba(168,85,247,0.1)] text-[#c084fc]',
|
|
1206
|
-
};
|
|
1207
|
-
|
|
1208
|
-
const tagVariantStyles: Record<PillarTagVariant, string> = {
|
|
1209
|
-
core: 'bg-[rgba(59,130,246,0.1)] text-[#60a5fa]',
|
|
1210
|
-
pattern: 'bg-[rgba(34,211,238,0.1)] text-[#22d3ee]',
|
|
1211
|
-
enterprise: 'bg-[rgba(168,85,247,0.1)] text-[#c084fc]',
|
|
1212
|
-
};
|
|
1213
|
-
|
|
1214
|
-
export const PillarsGrid: React.FC<{ data: PillarsGridData; settings?: PillarsGridSettings }> = ({ data }) => {
|
|
1215
|
-
return (
|
|
1216
|
-
<section
|
|
1217
|
-
style={{
|
|
1218
|
-
'--local-bg': 'var(--background)',
|
|
1219
|
-
'--local-text': 'var(--foreground)',
|
|
1220
|
-
'--local-text-muted': 'var(--muted-foreground)',
|
|
1221
|
-
'--local-primary': 'var(--primary)',
|
|
1222
|
-
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
1223
|
-
'--local-border': 'var(--border)',
|
|
1224
|
-
} as React.CSSProperties}
|
|
1225
|
-
className="relative z-0 py-28 bg-[var(--local-bg)]"
|
|
1226
|
-
>
|
|
1227
|
-
<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" />
|
|
1228
|
-
<div className="max-w-[1200px] mx-auto px-8">
|
|
1229
|
-
{data.label && (
|
|
1230
|
-
<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">
|
|
1231
|
-
<span className="w-5 h-px bg-[var(--local-primary)]" />
|
|
1232
|
-
{data.label}
|
|
1233
|
-
</div>
|
|
1234
|
-
)}
|
|
1235
|
-
<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">
|
|
1236
|
-
{data.title}
|
|
1237
|
-
</h2>
|
|
1238
|
-
{data.description && (
|
|
1239
|
-
<p className="text-lg text-[var(--local-text-muted)] max-w-[600px] leading-relaxed" data-jp-field="description">
|
|
1240
|
-
{data.description}
|
|
1241
|
-
</p>
|
|
1242
|
-
)}
|
|
1243
|
-
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mt-14">
|
|
1244
|
-
{data.pillars.map((pillar, idx) => (
|
|
1245
|
-
<div
|
|
1246
|
-
key={pillar.id ?? idx}
|
|
1247
|
-
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)]"
|
|
1248
|
-
data-jp-item-id={pillar.id ?? `legacy-${idx}`}
|
|
1249
|
-
data-jp-item-field="pillars"
|
|
1250
|
-
>
|
|
1251
|
-
<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" />
|
|
1252
|
-
<div className={cn(
|
|
1253
|
-
'w-12 h-12 rounded-xl flex items-center justify-center mb-6 text-xl font-bold',
|
|
1254
|
-
iconVariantStyles[pillar.iconVariant]
|
|
1255
|
-
)}>
|
|
1256
|
-
{pillar.icon && isIconName(pillar.icon) ? (
|
|
1257
|
-
<Icon name={pillar.icon} size={24} className="shrink-0" />
|
|
1258
|
-
) : pillar.icon ? (
|
|
1259
|
-
<span>{pillar.icon}</span>
|
|
1260
|
-
) : null}
|
|
1261
|
-
</div>
|
|
1262
|
-
<h3 className="text-xl font-bold text-[var(--local-text)] mb-3">
|
|
1263
|
-
{pillar.title}
|
|
1264
|
-
</h3>
|
|
1265
|
-
<p className="text-[0.95rem] text-[var(--local-text-muted)] leading-relaxed">
|
|
1266
|
-
{pillar.description}
|
|
1267
|
-
</p>
|
|
1268
|
-
<span className={cn(
|
|
1269
|
-
'inline-block text-[0.7rem] font-semibold uppercase tracking-wide px-3 py-1 rounded mt-4',
|
|
1270
|
-
tagVariantStyles[pillar.tagVariant]
|
|
1271
|
-
)}>
|
|
1272
|
-
{pillar.tag}
|
|
1273
|
-
</span>
|
|
1274
|
-
</div>
|
|
1275
|
-
))}
|
|
1276
|
-
</div>
|
|
1277
|
-
</div>
|
|
1278
|
-
</section>
|
|
1279
|
-
);
|
|
1280
|
-
};
|
|
1197
|
+
import React from 'react';
|
|
1198
|
+
import { cn } from '@/lib/utils';
|
|
1199
|
+
import { Icon, isIconName } from '@/lib/IconResolver';
|
|
1200
|
+
import type { PillarsGridData, PillarsGridSettings, PillarIconVariant, PillarTagVariant } from './types';
|
|
1201
|
+
|
|
1202
|
+
const iconVariantStyles: Record<PillarIconVariant, string> = {
|
|
1203
|
+
split: 'bg-[rgba(59,130,246,0.1)] text-[#60a5fa]',
|
|
1204
|
+
registry: 'bg-[rgba(34,211,238,0.1)] text-[#22d3ee]',
|
|
1205
|
+
federation: 'bg-[rgba(168,85,247,0.1)] text-[#c084fc]',
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
const tagVariantStyles: Record<PillarTagVariant, string> = {
|
|
1209
|
+
core: 'bg-[rgba(59,130,246,0.1)] text-[#60a5fa]',
|
|
1210
|
+
pattern: 'bg-[rgba(34,211,238,0.1)] text-[#22d3ee]',
|
|
1211
|
+
enterprise: 'bg-[rgba(168,85,247,0.1)] text-[#c084fc]',
|
|
1212
|
+
};
|
|
1213
|
+
|
|
1214
|
+
export const PillarsGrid: React.FC<{ data: PillarsGridData; settings?: PillarsGridSettings }> = ({ data }) => {
|
|
1215
|
+
return (
|
|
1216
|
+
<section
|
|
1217
|
+
style={{
|
|
1218
|
+
'--local-bg': 'var(--background)',
|
|
1219
|
+
'--local-text': 'var(--foreground)',
|
|
1220
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
1221
|
+
'--local-primary': 'var(--primary)',
|
|
1222
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
1223
|
+
'--local-border': 'var(--border)',
|
|
1224
|
+
} as React.CSSProperties}
|
|
1225
|
+
className="relative z-0 py-28 bg-[var(--local-bg)]"
|
|
1226
|
+
>
|
|
1227
|
+
<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" />
|
|
1228
|
+
<div className="max-w-[1200px] mx-auto px-8">
|
|
1229
|
+
{data.label && (
|
|
1230
|
+
<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">
|
|
1231
|
+
<span className="w-5 h-px bg-[var(--local-primary)]" />
|
|
1232
|
+
{data.label}
|
|
1233
|
+
</div>
|
|
1234
|
+
)}
|
|
1235
|
+
<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">
|
|
1236
|
+
{data.title}
|
|
1237
|
+
</h2>
|
|
1238
|
+
{data.description && (
|
|
1239
|
+
<p className="text-lg text-[var(--local-text-muted)] max-w-[600px] leading-relaxed" data-jp-field="description">
|
|
1240
|
+
{data.description}
|
|
1241
|
+
</p>
|
|
1242
|
+
)}
|
|
1243
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mt-14">
|
|
1244
|
+
{data.pillars.map((pillar, idx) => (
|
|
1245
|
+
<div
|
|
1246
|
+
key={pillar.id ?? idx}
|
|
1247
|
+
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)]"
|
|
1248
|
+
data-jp-item-id={pillar.id ?? `legacy-${idx}`}
|
|
1249
|
+
data-jp-item-field="pillars"
|
|
1250
|
+
>
|
|
1251
|
+
<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" />
|
|
1252
|
+
<div className={cn(
|
|
1253
|
+
'w-12 h-12 rounded-xl flex items-center justify-center mb-6 text-xl font-bold',
|
|
1254
|
+
iconVariantStyles[pillar.iconVariant]
|
|
1255
|
+
)}>
|
|
1256
|
+
{pillar.icon && isIconName(pillar.icon) ? (
|
|
1257
|
+
<Icon name={pillar.icon} size={24} className="shrink-0" />
|
|
1258
|
+
) : pillar.icon ? (
|
|
1259
|
+
<span>{pillar.icon}</span>
|
|
1260
|
+
) : null}
|
|
1261
|
+
</div>
|
|
1262
|
+
<h3 className="text-xl font-bold text-[var(--local-text)] mb-3">
|
|
1263
|
+
{pillar.title}
|
|
1264
|
+
</h3>
|
|
1265
|
+
<p className="text-[0.95rem] text-[var(--local-text-muted)] leading-relaxed">
|
|
1266
|
+
{pillar.description}
|
|
1267
|
+
</p>
|
|
1268
|
+
<span className={cn(
|
|
1269
|
+
'inline-block text-[0.7rem] font-semibold uppercase tracking-wide px-3 py-1 rounded mt-4',
|
|
1270
|
+
tagVariantStyles[pillar.tagVariant]
|
|
1271
|
+
)}>
|
|
1272
|
+
{pillar.tag}
|
|
1273
|
+
</span>
|
|
1274
|
+
</div>
|
|
1275
|
+
))}
|
|
1276
|
+
</div>
|
|
1277
|
+
</div>
|
|
1278
|
+
</section>
|
|
1279
|
+
);
|
|
1280
|
+
};
|
|
1281
1281
|
|
|
1282
1282
|
END_OF_FILE_CONTENT
|
|
1283
1283
|
echo "Creating src/components/pillars-grid/index.ts..."
|
|
1284
1284
|
cat << 'END_OF_FILE_CONTENT' > "src/components/pillars-grid/index.ts"
|
|
1285
|
-
export * from './View';
|
|
1286
|
-
export * from './schema';
|
|
1287
|
-
export * from './types';
|
|
1285
|
+
export * from './View';
|
|
1286
|
+
export * from './schema';
|
|
1287
|
+
export * from './types';
|
|
1288
1288
|
|
|
1289
1289
|
END_OF_FILE_CONTENT
|
|
1290
1290
|
echo "Creating src/components/pillars-grid/schema.ts..."
|
|
1291
1291
|
cat << 'END_OF_FILE_CONTENT' > "src/components/pillars-grid/schema.ts"
|
|
1292
|
-
import { z } from 'zod';
|
|
1293
|
-
import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
|
|
1294
|
-
|
|
1295
|
-
export const PillarIconVariantSchema = z.enum(['split', 'registry', 'federation']);
|
|
1296
|
-
export const PillarTagVariantSchema = z.enum(['core', 'pattern', 'enterprise']);
|
|
1297
|
-
|
|
1298
|
-
const PillarCardSchema = BaseArrayItem.extend({
|
|
1299
|
-
icon: z.string().describe('ui:icon-picker'),
|
|
1300
|
-
iconVariant: PillarIconVariantSchema.describe('ui:select'),
|
|
1301
|
-
title: z.string().describe('ui:text'),
|
|
1302
|
-
description: z.string().describe('ui:textarea'),
|
|
1303
|
-
tag: z.string().describe('ui:text'),
|
|
1304
|
-
tagVariant: PillarTagVariantSchema.describe('ui:select'),
|
|
1305
|
-
});
|
|
1306
|
-
|
|
1307
|
-
export const PillarsGridSchema = BaseSectionData.extend({
|
|
1308
|
-
label: z.string().optional().describe('ui:text'),
|
|
1309
|
-
title: z.string().describe('ui:text'),
|
|
1310
|
-
description: z.string().optional().describe('ui:textarea'),
|
|
1311
|
-
pillars: z.array(PillarCardSchema).describe('ui:list'),
|
|
1312
|
-
});
|
|
1292
|
+
import { z } from 'zod';
|
|
1293
|
+
import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
|
|
1294
|
+
|
|
1295
|
+
export const PillarIconVariantSchema = z.enum(['split', 'registry', 'federation']);
|
|
1296
|
+
export const PillarTagVariantSchema = z.enum(['core', 'pattern', 'enterprise']);
|
|
1297
|
+
|
|
1298
|
+
const PillarCardSchema = BaseArrayItem.extend({
|
|
1299
|
+
icon: z.string().describe('ui:icon-picker'),
|
|
1300
|
+
iconVariant: PillarIconVariantSchema.describe('ui:select'),
|
|
1301
|
+
title: z.string().describe('ui:text'),
|
|
1302
|
+
description: z.string().describe('ui:textarea'),
|
|
1303
|
+
tag: z.string().describe('ui:text'),
|
|
1304
|
+
tagVariant: PillarTagVariantSchema.describe('ui:select'),
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
export const PillarsGridSchema = BaseSectionData.extend({
|
|
1308
|
+
label: z.string().optional().describe('ui:text'),
|
|
1309
|
+
title: z.string().describe('ui:text'),
|
|
1310
|
+
description: z.string().optional().describe('ui:textarea'),
|
|
1311
|
+
pillars: z.array(PillarCardSchema).describe('ui:list'),
|
|
1312
|
+
});
|
|
1313
1313
|
|
|
1314
1314
|
END_OF_FILE_CONTENT
|
|
1315
1315
|
echo "Creating src/components/pillars-grid/types.ts..."
|
|
1316
1316
|
cat << 'END_OF_FILE_CONTENT' > "src/components/pillars-grid/types.ts"
|
|
1317
|
-
import { z } from 'zod';
|
|
1318
|
-
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
1319
|
-
import { PillarsGridSchema, PillarIconVariantSchema, PillarTagVariantSchema } from './schema';
|
|
1320
|
-
|
|
1321
|
-
export type PillarsGridData = z.infer<typeof PillarsGridSchema>;
|
|
1322
|
-
export type PillarsGridSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
1323
|
-
export type PillarIconVariant = z.infer<typeof PillarIconVariantSchema>;
|
|
1324
|
-
export type PillarTagVariant = z.infer<typeof PillarTagVariantSchema>;
|
|
1317
|
+
import { z } from 'zod';
|
|
1318
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
1319
|
+
import { PillarsGridSchema, PillarIconVariantSchema, PillarTagVariantSchema } from './schema';
|
|
1320
|
+
|
|
1321
|
+
export type PillarsGridData = z.infer<typeof PillarsGridSchema>;
|
|
1322
|
+
export type PillarsGridSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
1323
|
+
export type PillarIconVariant = z.infer<typeof PillarIconVariantSchema>;
|
|
1324
|
+
export type PillarTagVariant = z.infer<typeof PillarTagVariantSchema>;
|
|
1325
1325
|
|
|
1326
1326
|
END_OF_FILE_CONTENT
|
|
1327
1327
|
mkdir -p "src/components/problem-statement"
|
|
1328
1328
|
echo "Creating src/components/problem-statement/View.tsx..."
|
|
1329
1329
|
cat << 'END_OF_FILE_CONTENT' > "src/components/problem-statement/View.tsx"
|
|
1330
|
-
import React from 'react';
|
|
1331
|
-
import { cn } from '@/lib/utils';
|
|
1332
|
-
import type { ProblemStatementData, ProblemStatementSettings, SiloBlockVariant } from './types';
|
|
1333
|
-
|
|
1334
|
-
const variantStyles: Record<SiloBlockVariant, string> = {
|
|
1335
|
-
red: 'bg-[rgba(239,68,68,0.08)] border-[rgba(239,68,68,0.3)] text-[#f87171]',
|
|
1336
|
-
amber: 'bg-[rgba(245,158,11,0.08)] border-[rgba(245,158,11,0.3)] text-[#fbbf24]',
|
|
1337
|
-
green: 'bg-[rgba(34,197,94,0.08)] border-[rgba(34,197,94,0.3)] text-[#4ade80]',
|
|
1338
|
-
blue: 'bg-[rgba(59,130,246,0.08)] border-[rgba(59,130,246,0.3)] text-[#60a5fa]',
|
|
1339
|
-
};
|
|
1340
|
-
|
|
1341
|
-
export const ProblemStatement: React.FC<{ data: ProblemStatementData; settings?: ProblemStatementSettings }> = ({ data }) => {
|
|
1342
|
-
return (
|
|
1343
|
-
<section
|
|
1344
|
-
style={{
|
|
1345
|
-
'--local-bg': 'var(--background)',
|
|
1346
|
-
'--local-surface': 'var(--card)',
|
|
1347
|
-
'--local-text': 'var(--foreground)',
|
|
1348
|
-
'--local-text-muted': 'var(--muted-foreground)',
|
|
1349
|
-
'--local-border': 'var(--border)',
|
|
1350
|
-
} as React.CSSProperties}
|
|
1351
|
-
className="jp-problem relative z-0 py-28 bg-gradient-to-b from-[var(--local-bg)] to-[var(--local-surface)]"
|
|
1352
|
-
>
|
|
1353
|
-
<div className="max-w-[1200px] mx-auto px-8">
|
|
1354
|
-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
|
|
1355
|
-
<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">
|
|
1356
|
-
<div className="text-center p-8">
|
|
1357
|
-
{data.siloGroups.map((group, gIdx) => (
|
|
1358
|
-
<div
|
|
1359
|
-
key={gIdx}
|
|
1360
|
-
className="mb-4"
|
|
1361
|
-
data-jp-item-id={(group as { id?: string }).id ?? `legacy-${gIdx}`}
|
|
1362
|
-
data-jp-item-field="siloGroups"
|
|
1363
|
-
>
|
|
1364
|
-
<div className="flex flex-wrap justify-center gap-1.5">
|
|
1365
|
-
{group.blocks.map((block, bIdx) => (
|
|
1366
|
-
<span
|
|
1367
|
-
key={(block as { id?: string }).id ?? bIdx}
|
|
1368
|
-
className={cn(
|
|
1369
|
-
'inline-block px-4 py-2 rounded-lg text-[0.8rem] font-semibold border',
|
|
1370
|
-
variantStyles[block.variant]
|
|
1371
|
-
)}
|
|
1372
|
-
data-jp-item-id={(block as { id?: string }).id ?? `legacy-${bIdx}`}
|
|
1373
|
-
data-jp-item-field="blocks"
|
|
1374
|
-
>
|
|
1375
|
-
{block.label}
|
|
1376
|
-
</span>
|
|
1377
|
-
))}
|
|
1378
|
-
</div>
|
|
1379
|
-
<span className="text-[0.7rem] text-[var(--local-text-muted)] uppercase tracking-widest mt-2 block opacity-60">
|
|
1380
|
-
{group.label}
|
|
1381
|
-
</span>
|
|
1382
|
-
</div>
|
|
1383
|
-
))}
|
|
1384
|
-
</div>
|
|
1385
|
-
</div>
|
|
1386
|
-
<div>
|
|
1387
|
-
<h3 className="text-2xl font-bold text-[var(--local-text)] mb-4" data-jp-field="title">
|
|
1388
|
-
{data.title}
|
|
1389
|
-
</h3>
|
|
1390
|
-
{data.paragraphs.map((p, idx) => (
|
|
1391
|
-
<p
|
|
1392
|
-
key={idx}
|
|
1393
|
-
className="text-[var(--local-text-muted)] mb-5 text-[1.05rem] leading-relaxed"
|
|
1394
|
-
data-jp-item-id={(p as { id?: string }).id ?? `legacy-${idx}`}
|
|
1395
|
-
data-jp-item-field="paragraphs"
|
|
1396
|
-
>
|
|
1397
|
-
{p.isBold ? <strong className="text-[var(--local-text)]">{p.text}</strong> : p.text}
|
|
1398
|
-
</p>
|
|
1399
|
-
))}
|
|
1400
|
-
</div>
|
|
1401
|
-
</div>
|
|
1402
|
-
</div>
|
|
1403
|
-
</section>
|
|
1404
|
-
);
|
|
1405
|
-
};
|
|
1330
|
+
import React from 'react';
|
|
1331
|
+
import { cn } from '@/lib/utils';
|
|
1332
|
+
import type { ProblemStatementData, ProblemStatementSettings, SiloBlockVariant } from './types';
|
|
1333
|
+
|
|
1334
|
+
const variantStyles: Record<SiloBlockVariant, string> = {
|
|
1335
|
+
red: 'bg-[rgba(239,68,68,0.08)] border-[rgba(239,68,68,0.3)] text-[#f87171]',
|
|
1336
|
+
amber: 'bg-[rgba(245,158,11,0.08)] border-[rgba(245,158,11,0.3)] text-[#fbbf24]',
|
|
1337
|
+
green: 'bg-[rgba(34,197,94,0.08)] border-[rgba(34,197,94,0.3)] text-[#4ade80]',
|
|
1338
|
+
blue: 'bg-[rgba(59,130,246,0.08)] border-[rgba(59,130,246,0.3)] text-[#60a5fa]',
|
|
1339
|
+
};
|
|
1340
|
+
|
|
1341
|
+
export const ProblemStatement: React.FC<{ data: ProblemStatementData; settings?: ProblemStatementSettings }> = ({ data }) => {
|
|
1342
|
+
return (
|
|
1343
|
+
<section
|
|
1344
|
+
style={{
|
|
1345
|
+
'--local-bg': 'var(--background)',
|
|
1346
|
+
'--local-surface': 'var(--card)',
|
|
1347
|
+
'--local-text': 'var(--foreground)',
|
|
1348
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
1349
|
+
'--local-border': 'var(--border)',
|
|
1350
|
+
} as React.CSSProperties}
|
|
1351
|
+
className="jp-problem relative z-0 py-28 bg-gradient-to-b from-[var(--local-bg)] to-[var(--local-surface)]"
|
|
1352
|
+
>
|
|
1353
|
+
<div className="max-w-[1200px] mx-auto px-8">
|
|
1354
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
|
|
1355
|
+
<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">
|
|
1356
|
+
<div className="text-center p-8">
|
|
1357
|
+
{data.siloGroups.map((group, gIdx) => (
|
|
1358
|
+
<div
|
|
1359
|
+
key={gIdx}
|
|
1360
|
+
className="mb-4"
|
|
1361
|
+
data-jp-item-id={(group as { id?: string }).id ?? `legacy-${gIdx}`}
|
|
1362
|
+
data-jp-item-field="siloGroups"
|
|
1363
|
+
>
|
|
1364
|
+
<div className="flex flex-wrap justify-center gap-1.5">
|
|
1365
|
+
{group.blocks.map((block, bIdx) => (
|
|
1366
|
+
<span
|
|
1367
|
+
key={(block as { id?: string }).id ?? bIdx}
|
|
1368
|
+
className={cn(
|
|
1369
|
+
'inline-block px-4 py-2 rounded-lg text-[0.8rem] font-semibold border',
|
|
1370
|
+
variantStyles[block.variant]
|
|
1371
|
+
)}
|
|
1372
|
+
data-jp-item-id={(block as { id?: string }).id ?? `legacy-${bIdx}`}
|
|
1373
|
+
data-jp-item-field="blocks"
|
|
1374
|
+
>
|
|
1375
|
+
{block.label}
|
|
1376
|
+
</span>
|
|
1377
|
+
))}
|
|
1378
|
+
</div>
|
|
1379
|
+
<span className="text-[0.7rem] text-[var(--local-text-muted)] uppercase tracking-widest mt-2 block opacity-60">
|
|
1380
|
+
{group.label}
|
|
1381
|
+
</span>
|
|
1382
|
+
</div>
|
|
1383
|
+
))}
|
|
1384
|
+
</div>
|
|
1385
|
+
</div>
|
|
1386
|
+
<div>
|
|
1387
|
+
<h3 className="text-2xl font-bold text-[var(--local-text)] mb-4" data-jp-field="title">
|
|
1388
|
+
{data.title}
|
|
1389
|
+
</h3>
|
|
1390
|
+
{data.paragraphs.map((p, idx) => (
|
|
1391
|
+
<p
|
|
1392
|
+
key={idx}
|
|
1393
|
+
className="text-[var(--local-text-muted)] mb-5 text-[1.05rem] leading-relaxed"
|
|
1394
|
+
data-jp-item-id={(p as { id?: string }).id ?? `legacy-${idx}`}
|
|
1395
|
+
data-jp-item-field="paragraphs"
|
|
1396
|
+
>
|
|
1397
|
+
{p.isBold ? <strong className="text-[var(--local-text)]">{p.text}</strong> : p.text}
|
|
1398
|
+
</p>
|
|
1399
|
+
))}
|
|
1400
|
+
</div>
|
|
1401
|
+
</div>
|
|
1402
|
+
</div>
|
|
1403
|
+
</section>
|
|
1404
|
+
);
|
|
1405
|
+
};
|
|
1406
1406
|
|
|
1407
1407
|
END_OF_FILE_CONTENT
|
|
1408
1408
|
echo "Creating src/components/problem-statement/index.ts..."
|
|
1409
1409
|
cat << 'END_OF_FILE_CONTENT' > "src/components/problem-statement/index.ts"
|
|
1410
|
-
export * from './View';
|
|
1411
|
-
export * from './schema';
|
|
1412
|
-
export * from './types';
|
|
1410
|
+
export * from './View';
|
|
1411
|
+
export * from './schema';
|
|
1412
|
+
export * from './types';
|
|
1413
1413
|
|
|
1414
1414
|
END_OF_FILE_CONTENT
|
|
1415
1415
|
echo "Creating src/components/problem-statement/schema.ts..."
|
|
1416
1416
|
cat << 'END_OF_FILE_CONTENT' > "src/components/problem-statement/schema.ts"
|
|
1417
|
-
import { z } from 'zod';
|
|
1418
|
-
import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
|
|
1419
|
-
|
|
1420
|
-
export const SiloBlockVariantSchema = z.enum(['red', 'amber', 'green', 'blue']);
|
|
1421
|
-
const SiloBlockSchema = BaseArrayItem.extend({
|
|
1422
|
-
label: z.string().describe('ui:text'),
|
|
1423
|
-
variant: SiloBlockVariantSchema.describe('ui:select'),
|
|
1424
|
-
});
|
|
1425
|
-
|
|
1426
|
-
const SiloGroupSchema = BaseArrayItem.extend({
|
|
1427
|
-
blocks: z.array(SiloBlockSchema).describe('ui:list'),
|
|
1428
|
-
label: z.string().describe('ui:text'),
|
|
1429
|
-
});
|
|
1430
|
-
|
|
1431
|
-
const ProblemParagraphSchema = BaseArrayItem.extend({
|
|
1432
|
-
text: z.string().describe('ui:textarea'),
|
|
1433
|
-
isBold: z.boolean().default(false).describe('ui:checkbox'),
|
|
1434
|
-
});
|
|
1435
|
-
|
|
1436
|
-
export const ProblemStatementSchema = BaseSectionData.extend({
|
|
1437
|
-
siloGroups: z.array(SiloGroupSchema).describe('ui:list'),
|
|
1438
|
-
title: z.string().describe('ui:text'),
|
|
1439
|
-
paragraphs: z.array(ProblemParagraphSchema).describe('ui:list'),
|
|
1440
|
-
highlight: z.string().optional().describe('ui:text'),
|
|
1441
|
-
});
|
|
1417
|
+
import { z } from 'zod';
|
|
1418
|
+
import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
|
|
1419
|
+
|
|
1420
|
+
export const SiloBlockVariantSchema = z.enum(['red', 'amber', 'green', 'blue']);
|
|
1421
|
+
const SiloBlockSchema = BaseArrayItem.extend({
|
|
1422
|
+
label: z.string().describe('ui:text'),
|
|
1423
|
+
variant: SiloBlockVariantSchema.describe('ui:select'),
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1426
|
+
const SiloGroupSchema = BaseArrayItem.extend({
|
|
1427
|
+
blocks: z.array(SiloBlockSchema).describe('ui:list'),
|
|
1428
|
+
label: z.string().describe('ui:text'),
|
|
1429
|
+
});
|
|
1430
|
+
|
|
1431
|
+
const ProblemParagraphSchema = BaseArrayItem.extend({
|
|
1432
|
+
text: z.string().describe('ui:textarea'),
|
|
1433
|
+
isBold: z.boolean().default(false).describe('ui:checkbox'),
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
export const ProblemStatementSchema = BaseSectionData.extend({
|
|
1437
|
+
siloGroups: z.array(SiloGroupSchema).describe('ui:list'),
|
|
1438
|
+
title: z.string().describe('ui:text'),
|
|
1439
|
+
paragraphs: z.array(ProblemParagraphSchema).describe('ui:list'),
|
|
1440
|
+
highlight: z.string().optional().describe('ui:text'),
|
|
1441
|
+
});
|
|
1442
1442
|
|
|
1443
1443
|
END_OF_FILE_CONTENT
|
|
1444
1444
|
echo "Creating src/components/problem-statement/types.ts..."
|
|
1445
1445
|
cat << 'END_OF_FILE_CONTENT' > "src/components/problem-statement/types.ts"
|
|
1446
|
-
import { z } from 'zod';
|
|
1447
|
-
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
1448
|
-
import { ProblemStatementSchema, SiloBlockVariantSchema } from './schema';
|
|
1449
|
-
|
|
1450
|
-
export type ProblemStatementData = z.infer<typeof ProblemStatementSchema>;
|
|
1451
|
-
export type ProblemStatementSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
1452
|
-
export type SiloBlockVariant = z.infer<typeof SiloBlockVariantSchema>;
|
|
1446
|
+
import { z } from 'zod';
|
|
1447
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
1448
|
+
import { ProblemStatementSchema, SiloBlockVariantSchema } from './schema';
|
|
1449
|
+
|
|
1450
|
+
export type ProblemStatementData = z.infer<typeof ProblemStatementSchema>;
|
|
1451
|
+
export type ProblemStatementSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
1452
|
+
export type SiloBlockVariant = z.infer<typeof SiloBlockVariantSchema>;
|
|
1453
1453
|
|
|
1454
1454
|
END_OF_FILE_CONTENT
|
|
1455
1455
|
mkdir -p "src/components/product-triad"
|
|
1456
1456
|
echo "Creating src/components/product-triad/View.tsx..."
|
|
1457
1457
|
cat << 'END_OF_FILE_CONTENT' > "src/components/product-triad/View.tsx"
|
|
1458
|
-
import React from 'react';
|
|
1459
|
-
import { cn } from '@/lib/utils';
|
|
1460
|
-
import type { ProductTriadData, ProductTriadSettings } from './types';
|
|
1461
|
-
|
|
1462
|
-
export const ProductTriad: React.FC<{ data: ProductTriadData; settings?: ProductTriadSettings }> = ({ data }) => {
|
|
1463
|
-
return (
|
|
1464
|
-
<section
|
|
1465
|
-
style={{
|
|
1466
|
-
'--local-bg': 'var(--background)',
|
|
1467
|
-
'--local-text': 'var(--foreground)',
|
|
1468
|
-
'--local-text-muted': 'var(--muted-foreground)',
|
|
1469
|
-
'--local-primary': 'var(--primary)',
|
|
1470
|
-
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
1471
|
-
'--local-border': 'var(--border)',
|
|
1472
|
-
} as React.CSSProperties}
|
|
1473
|
-
className="relative z-0 py-28 bg-[var(--local-bg)]"
|
|
1474
|
-
>
|
|
1475
|
-
<div className="max-w-[1200px] mx-auto px-8">
|
|
1476
|
-
<div className="text-center">
|
|
1477
|
-
{data.label && (
|
|
1478
|
-
<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">
|
|
1479
|
-
<span className="w-5 h-px bg-[var(--local-primary)]" />
|
|
1480
|
-
{data.label}
|
|
1481
|
-
</div>
|
|
1482
|
-
)}
|
|
1483
|
-
<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">
|
|
1484
|
-
{data.title}
|
|
1485
|
-
</h2>
|
|
1486
|
-
{data.description && (
|
|
1487
|
-
<p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed" data-jp-field="description">
|
|
1488
|
-
{data.description}
|
|
1489
|
-
</p>
|
|
1490
|
-
)}
|
|
1491
|
-
</div>
|
|
1492
|
-
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mt-14">
|
|
1493
|
-
{data.products.map((product, idx) => (
|
|
1494
|
-
<div
|
|
1495
|
-
key={product.id ?? idx}
|
|
1496
|
-
className={cn(
|
|
1497
|
-
'relative border rounded-lg p-10 transition-all duration-300 hover:-translate-y-1',
|
|
1498
|
-
product.featured
|
|
1499
|
-
? '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)]'
|
|
1500
|
-
: 'border-[rgba(255,255,255,0.06)] bg-[rgba(255,255,255,0.015)] hover:border-[rgba(59,130,246,0.2)]'
|
|
1501
|
-
)}
|
|
1502
|
-
data-jp-item-id={product.id ?? `legacy-${idx}`}
|
|
1503
|
-
data-jp-item-field="products"
|
|
1504
|
-
>
|
|
1505
|
-
{product.featured && (
|
|
1506
|
-
<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">
|
|
1507
|
-
Most Popular
|
|
1508
|
-
</div>
|
|
1509
|
-
)}
|
|
1510
|
-
<div className="text-[0.75rem] font-bold uppercase tracking-[0.1em] text-[var(--local-accent)] mb-2">
|
|
1511
|
-
{product.tier}
|
|
1512
|
-
</div>
|
|
1513
|
-
<div className="text-2xl font-extrabold text-[var(--local-text)] mb-2">
|
|
1514
|
-
{product.name}
|
|
1515
|
-
</div>
|
|
1516
|
-
<div className="font-display text-[2.2rem] font-extrabold text-[var(--local-text)] mb-1">
|
|
1517
|
-
{product.price}
|
|
1518
|
-
{product.priceSuffix && (
|
|
1519
|
-
<span className="text-[0.9rem] font-normal text-[var(--local-text-muted)]">
|
|
1520
|
-
{product.priceSuffix}
|
|
1521
|
-
</span>
|
|
1522
|
-
)}
|
|
1523
|
-
</div>
|
|
1524
|
-
<div className="text-[0.85rem] text-[var(--local-text-muted)] mb-6 pb-6 border-b border-[rgba(255,255,255,0.06)]">
|
|
1525
|
-
{product.delivery}
|
|
1526
|
-
</div>
|
|
1527
|
-
<ul className="mb-8 space-y-0">
|
|
1528
|
-
{product.features.map((feature, fIdx) => (
|
|
1529
|
-
<li
|
|
1530
|
-
key={fIdx}
|
|
1531
|
-
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]"
|
|
1532
|
-
>
|
|
1533
|
-
{feature.text}
|
|
1534
|
-
</li>
|
|
1535
|
-
))}
|
|
1536
|
-
</ul>
|
|
1537
|
-
{product.ctaLabel && product.ctaHref && (
|
|
1538
|
-
<a
|
|
1539
|
-
href={product.ctaHref}
|
|
1540
|
-
className={cn(
|
|
1541
|
-
'block text-center py-3 rounded-[5px] no-underline font-semibold text-[0.95rem] transition-all duration-200',
|
|
1542
|
-
product.ctaVariant === 'primary'
|
|
1543
|
-
? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-px'
|
|
1544
|
-
: '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)]'
|
|
1545
|
-
)}
|
|
1546
|
-
>
|
|
1547
|
-
{product.ctaLabel}
|
|
1548
|
-
</a>
|
|
1549
|
-
)}
|
|
1550
|
-
</div>
|
|
1551
|
-
))}
|
|
1552
|
-
</div>
|
|
1553
|
-
</div>
|
|
1554
|
-
</section>
|
|
1555
|
-
);
|
|
1556
|
-
};
|
|
1458
|
+
import React from 'react';
|
|
1459
|
+
import { cn } from '@/lib/utils';
|
|
1460
|
+
import type { ProductTriadData, ProductTriadSettings } from './types';
|
|
1461
|
+
|
|
1462
|
+
export const ProductTriad: React.FC<{ data: ProductTriadData; settings?: ProductTriadSettings }> = ({ data }) => {
|
|
1463
|
+
return (
|
|
1464
|
+
<section
|
|
1465
|
+
style={{
|
|
1466
|
+
'--local-bg': 'var(--background)',
|
|
1467
|
+
'--local-text': 'var(--foreground)',
|
|
1468
|
+
'--local-text-muted': 'var(--muted-foreground)',
|
|
1469
|
+
'--local-primary': 'var(--primary)',
|
|
1470
|
+
'--local-accent': 'var(--color-accent, #60a5fa)',
|
|
1471
|
+
'--local-border': 'var(--border)',
|
|
1472
|
+
} as React.CSSProperties}
|
|
1473
|
+
className="relative z-0 py-28 bg-[var(--local-bg)]"
|
|
1474
|
+
>
|
|
1475
|
+
<div className="max-w-[1200px] mx-auto px-8">
|
|
1476
|
+
<div className="text-center">
|
|
1477
|
+
{data.label && (
|
|
1478
|
+
<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">
|
|
1479
|
+
<span className="w-5 h-px bg-[var(--local-primary)]" />
|
|
1480
|
+
{data.label}
|
|
1481
|
+
</div>
|
|
1482
|
+
)}
|
|
1483
|
+
<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">
|
|
1484
|
+
{data.title}
|
|
1485
|
+
</h2>
|
|
1486
|
+
{data.description && (
|
|
1487
|
+
<p className="text-lg text-[var(--local-text-muted)] max-w-[600px] mx-auto leading-relaxed" data-jp-field="description">
|
|
1488
|
+
{data.description}
|
|
1489
|
+
</p>
|
|
1490
|
+
)}
|
|
1491
|
+
</div>
|
|
1492
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mt-14">
|
|
1493
|
+
{data.products.map((product, idx) => (
|
|
1494
|
+
<div
|
|
1495
|
+
key={product.id ?? idx}
|
|
1496
|
+
className={cn(
|
|
1497
|
+
'relative border rounded-lg p-10 transition-all duration-300 hover:-translate-y-1',
|
|
1498
|
+
product.featured
|
|
1499
|
+
? '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)]'
|
|
1500
|
+
: 'border-[rgba(255,255,255,0.06)] bg-[rgba(255,255,255,0.015)] hover:border-[rgba(59,130,246,0.2)]'
|
|
1501
|
+
)}
|
|
1502
|
+
data-jp-item-id={product.id ?? `legacy-${idx}`}
|
|
1503
|
+
data-jp-item-field="products"
|
|
1504
|
+
>
|
|
1505
|
+
{product.featured && (
|
|
1506
|
+
<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">
|
|
1507
|
+
Most Popular
|
|
1508
|
+
</div>
|
|
1509
|
+
)}
|
|
1510
|
+
<div className="text-[0.75rem] font-bold uppercase tracking-[0.1em] text-[var(--local-accent)] mb-2">
|
|
1511
|
+
{product.tier}
|
|
1512
|
+
</div>
|
|
1513
|
+
<div className="text-2xl font-extrabold text-[var(--local-text)] mb-2">
|
|
1514
|
+
{product.name}
|
|
1515
|
+
</div>
|
|
1516
|
+
<div className="font-display text-[2.2rem] font-extrabold text-[var(--local-text)] mb-1">
|
|
1517
|
+
{product.price}
|
|
1518
|
+
{product.priceSuffix && (
|
|
1519
|
+
<span className="text-[0.9rem] font-normal text-[var(--local-text-muted)]">
|
|
1520
|
+
{product.priceSuffix}
|
|
1521
|
+
</span>
|
|
1522
|
+
)}
|
|
1523
|
+
</div>
|
|
1524
|
+
<div className="text-[0.85rem] text-[var(--local-text-muted)] mb-6 pb-6 border-b border-[rgba(255,255,255,0.06)]">
|
|
1525
|
+
{product.delivery}
|
|
1526
|
+
</div>
|
|
1527
|
+
<ul className="mb-8 space-y-0">
|
|
1528
|
+
{product.features.map((feature, fIdx) => (
|
|
1529
|
+
<li
|
|
1530
|
+
key={fIdx}
|
|
1531
|
+
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]"
|
|
1532
|
+
>
|
|
1533
|
+
{feature.text}
|
|
1534
|
+
</li>
|
|
1535
|
+
))}
|
|
1536
|
+
</ul>
|
|
1537
|
+
{product.ctaLabel && product.ctaHref && (
|
|
1538
|
+
<a
|
|
1539
|
+
href={product.ctaHref}
|
|
1540
|
+
className={cn(
|
|
1541
|
+
'block text-center py-3 rounded-[5px] no-underline font-semibold text-[0.95rem] transition-all duration-200',
|
|
1542
|
+
product.ctaVariant === 'primary'
|
|
1543
|
+
? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-px'
|
|
1544
|
+
: '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)]'
|
|
1545
|
+
)}
|
|
1546
|
+
>
|
|
1547
|
+
{product.ctaLabel}
|
|
1548
|
+
</a>
|
|
1549
|
+
)}
|
|
1550
|
+
</div>
|
|
1551
|
+
))}
|
|
1552
|
+
</div>
|
|
1553
|
+
</div>
|
|
1554
|
+
</section>
|
|
1555
|
+
);
|
|
1556
|
+
};
|
|
1557
1557
|
|
|
1558
1558
|
END_OF_FILE_CONTENT
|
|
1559
1559
|
echo "Creating src/components/product-triad/index.ts..."
|
|
1560
1560
|
cat << 'END_OF_FILE_CONTENT' > "src/components/product-triad/index.ts"
|
|
1561
|
-
export * from './View';
|
|
1562
|
-
export * from './schema';
|
|
1563
|
-
export * from './types';
|
|
1561
|
+
export * from './View';
|
|
1562
|
+
export * from './schema';
|
|
1563
|
+
export * from './types';
|
|
1564
1564
|
|
|
1565
1565
|
END_OF_FILE_CONTENT
|
|
1566
1566
|
echo "Creating src/components/product-triad/schema.ts..."
|
|
1567
1567
|
cat << 'END_OF_FILE_CONTENT' > "src/components/product-triad/schema.ts"
|
|
1568
|
-
import { z } from 'zod';
|
|
1569
|
-
import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
|
|
1570
|
-
|
|
1571
|
-
const ProductFeatureSchema = z.object({
|
|
1572
|
-
text: z.string().describe('ui:text'),
|
|
1573
|
-
});
|
|
1574
|
-
|
|
1575
|
-
const ProductCardSchema = BaseArrayItem.extend({
|
|
1576
|
-
tier: z.string().describe('ui:text'),
|
|
1577
|
-
name: z.string().describe('ui:text'),
|
|
1578
|
-
price: z.string().describe('ui:text'),
|
|
1579
|
-
priceSuffix: z.string().optional().describe('ui:text'),
|
|
1580
|
-
delivery: z.string().describe('ui:text'),
|
|
1581
|
-
features: z.array(ProductFeatureSchema).describe('ui:list'),
|
|
1582
|
-
featured: z.boolean().default(false).describe('ui:checkbox'),
|
|
1583
|
-
ctaLabel: z.string().optional().describe('ui:text'),
|
|
1584
|
-
ctaHref: z.string().optional().describe('ui:text'),
|
|
1585
|
-
ctaVariant: z.enum(['primary', 'secondary']).default('secondary').describe('ui:select'),
|
|
1586
|
-
});
|
|
1587
|
-
|
|
1588
|
-
export const ProductTriadSchema = BaseSectionData.extend({
|
|
1589
|
-
label: z.string().optional().describe('ui:text'),
|
|
1590
|
-
title: z.string().describe('ui:text'),
|
|
1591
|
-
description: z.string().optional().describe('ui:textarea'),
|
|
1592
|
-
products: z.array(ProductCardSchema).describe('ui:list'),
|
|
1593
|
-
});
|
|
1568
|
+
import { z } from 'zod';
|
|
1569
|
+
import { BaseSectionData, BaseArrayItem } from '@/lib/base-schemas';
|
|
1570
|
+
|
|
1571
|
+
const ProductFeatureSchema = z.object({
|
|
1572
|
+
text: z.string().describe('ui:text'),
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
const ProductCardSchema = BaseArrayItem.extend({
|
|
1576
|
+
tier: z.string().describe('ui:text'),
|
|
1577
|
+
name: z.string().describe('ui:text'),
|
|
1578
|
+
price: z.string().describe('ui:text'),
|
|
1579
|
+
priceSuffix: z.string().optional().describe('ui:text'),
|
|
1580
|
+
delivery: z.string().describe('ui:text'),
|
|
1581
|
+
features: z.array(ProductFeatureSchema).describe('ui:list'),
|
|
1582
|
+
featured: z.boolean().default(false).describe('ui:checkbox'),
|
|
1583
|
+
ctaLabel: z.string().optional().describe('ui:text'),
|
|
1584
|
+
ctaHref: z.string().optional().describe('ui:text'),
|
|
1585
|
+
ctaVariant: z.enum(['primary', 'secondary']).default('secondary').describe('ui:select'),
|
|
1586
|
+
});
|
|
1587
|
+
|
|
1588
|
+
export const ProductTriadSchema = BaseSectionData.extend({
|
|
1589
|
+
label: z.string().optional().describe('ui:text'),
|
|
1590
|
+
title: z.string().describe('ui:text'),
|
|
1591
|
+
description: z.string().optional().describe('ui:textarea'),
|
|
1592
|
+
products: z.array(ProductCardSchema).describe('ui:list'),
|
|
1593
|
+
});
|
|
1594
1594
|
|
|
1595
1595
|
END_OF_FILE_CONTENT
|
|
1596
1596
|
echo "Creating src/components/product-triad/types.ts..."
|
|
1597
1597
|
cat << 'END_OF_FILE_CONTENT' > "src/components/product-triad/types.ts"
|
|
1598
|
-
import { z } from 'zod';
|
|
1599
|
-
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
1600
|
-
import { ProductTriadSchema } from './schema';
|
|
1601
|
-
|
|
1602
|
-
export type ProductTriadData = z.infer<typeof ProductTriadSchema>;
|
|
1603
|
-
export type ProductTriadSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
1598
|
+
import { z } from 'zod';
|
|
1599
|
+
import { BaseSectionSettingsSchema } from '@/lib/base-schemas';
|
|
1600
|
+
import { ProductTriadSchema } from './schema';
|
|
1601
|
+
|
|
1602
|
+
export type ProductTriadData = z.infer<typeof ProductTriadSchema>;
|
|
1603
|
+
export type ProductTriadSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
1604
1604
|
|
|
1605
1605
|
END_OF_FILE_CONTENT
|
|
1606
1606
|
mkdir -p "src/components/ui"
|
|
@@ -2178,85 +2178,85 @@ END_OF_FILE_CONTENT
|
|
|
2178
2178
|
# SKIP: src/data/config/menu.json:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
2179
2179
|
echo "Creating src/data/config/site.json..."
|
|
2180
2180
|
cat << 'END_OF_FILE_CONTENT' > "src/data/config/site.json"
|
|
2181
|
-
{
|
|
2182
|
-
"identity": {
|
|
2183
|
-
"title": "JsonPages",
|
|
2184
|
-
"logoUrl": "/logo.svg"
|
|
2185
|
-
},
|
|
2186
|
-
"pages": [
|
|
2187
|
-
{
|
|
2188
|
-
"slug": "home",
|
|
2189
|
-
"label": "Home"
|
|
2190
|
-
},
|
|
2191
|
-
{
|
|
2192
|
-
"slug": "architecture",
|
|
2193
|
-
"label": "Architecture"
|
|
2194
|
-
},
|
|
2195
|
-
{
|
|
2196
|
-
"slug": "usage",
|
|
2197
|
-
"label": "Usage"
|
|
2198
|
-
}
|
|
2199
|
-
],
|
|
2200
|
-
"header": {
|
|
2201
|
-
"id": "global-header",
|
|
2202
|
-
"type": "header",
|
|
2203
|
-
"data": {
|
|
2204
|
-
"logoText": "Json",
|
|
2205
|
-
"logoHighlight": "Pages",
|
|
2206
|
-
"logoIconText": "{ }",
|
|
2207
|
-
"links": [
|
|
2208
|
-
{
|
|
2209
|
-
"label": "Architecture",
|
|
2210
|
-
"href": "#architecture"
|
|
2211
|
-
},
|
|
2212
|
-
{
|
|
2213
|
-
"label": "Products",
|
|
2214
|
-
"href": "#products"
|
|
2215
|
-
},
|
|
2216
|
-
{
|
|
2217
|
-
"label": "PA Ready",
|
|
2218
|
-
"href": "#pa-ready"
|
|
2219
|
-
},
|
|
2220
|
-
{
|
|
2221
|
-
"label": "Philosophy",
|
|
2222
|
-
"href": "#philosophy"
|
|
2223
|
-
}
|
|
2224
|
-
]
|
|
2225
|
-
},
|
|
2226
|
-
"settings": {
|
|
2227
|
-
"sticky": true
|
|
2228
|
-
}
|
|
2229
|
-
},
|
|
2230
|
-
"footer": {
|
|
2231
|
-
"id": "global-footer",
|
|
2232
|
-
"type": "footer",
|
|
2233
|
-
"data": {
|
|
2234
|
-
"brandText": "Json",
|
|
2235
|
-
"brandHighlight": "Pages",
|
|
2236
|
-
"copyright": "© 2026 JsonPages Ecosystem. Architected by Guido Filippo Serio.",
|
|
2237
|
-
"links": [
|
|
2238
|
-
{
|
|
2239
|
-
"label": "Documentation",
|
|
2240
|
-
"href": "#"
|
|
2241
|
-
},
|
|
2242
|
-
{
|
|
2243
|
-
"label": "API Reference",
|
|
2244
|
-
"href": "#"
|
|
2245
|
-
},
|
|
2246
|
-
{
|
|
2247
|
-
"label": "Changelog",
|
|
2248
|
-
"href": "#"
|
|
2249
|
-
},
|
|
2250
|
-
{
|
|
2251
|
-
"label": "Privacy",
|
|
2252
|
-
"href": "#"
|
|
2253
|
-
}
|
|
2254
|
-
]
|
|
2255
|
-
},
|
|
2256
|
-
"settings": {
|
|
2257
|
-
"showLogo": true
|
|
2258
|
-
}
|
|
2259
|
-
}
|
|
2181
|
+
{
|
|
2182
|
+
"identity": {
|
|
2183
|
+
"title": "JsonPages",
|
|
2184
|
+
"logoUrl": "/logo.svg"
|
|
2185
|
+
},
|
|
2186
|
+
"pages": [
|
|
2187
|
+
{
|
|
2188
|
+
"slug": "home",
|
|
2189
|
+
"label": "Home"
|
|
2190
|
+
},
|
|
2191
|
+
{
|
|
2192
|
+
"slug": "architecture",
|
|
2193
|
+
"label": "Architecture"
|
|
2194
|
+
},
|
|
2195
|
+
{
|
|
2196
|
+
"slug": "usage",
|
|
2197
|
+
"label": "Usage"
|
|
2198
|
+
}
|
|
2199
|
+
],
|
|
2200
|
+
"header": {
|
|
2201
|
+
"id": "global-header",
|
|
2202
|
+
"type": "header",
|
|
2203
|
+
"data": {
|
|
2204
|
+
"logoText": "Json",
|
|
2205
|
+
"logoHighlight": "Pages",
|
|
2206
|
+
"logoIconText": "{ }",
|
|
2207
|
+
"links": [
|
|
2208
|
+
{
|
|
2209
|
+
"label": "Architecture",
|
|
2210
|
+
"href": "#architecture"
|
|
2211
|
+
},
|
|
2212
|
+
{
|
|
2213
|
+
"label": "Products",
|
|
2214
|
+
"href": "#products"
|
|
2215
|
+
},
|
|
2216
|
+
{
|
|
2217
|
+
"label": "PA Ready",
|
|
2218
|
+
"href": "#pa-ready"
|
|
2219
|
+
},
|
|
2220
|
+
{
|
|
2221
|
+
"label": "Philosophy",
|
|
2222
|
+
"href": "#philosophy"
|
|
2223
|
+
}
|
|
2224
|
+
]
|
|
2225
|
+
},
|
|
2226
|
+
"settings": {
|
|
2227
|
+
"sticky": true
|
|
2228
|
+
}
|
|
2229
|
+
},
|
|
2230
|
+
"footer": {
|
|
2231
|
+
"id": "global-footer",
|
|
2232
|
+
"type": "footer",
|
|
2233
|
+
"data": {
|
|
2234
|
+
"brandText": "Json",
|
|
2235
|
+
"brandHighlight": "Pages",
|
|
2236
|
+
"copyright": "© 2026 JsonPages Ecosystem. Architected by Guido Filippo Serio.",
|
|
2237
|
+
"links": [
|
|
2238
|
+
{
|
|
2239
|
+
"label": "Documentation",
|
|
2240
|
+
"href": "#"
|
|
2241
|
+
},
|
|
2242
|
+
{
|
|
2243
|
+
"label": "API Reference",
|
|
2244
|
+
"href": "#"
|
|
2245
|
+
},
|
|
2246
|
+
{
|
|
2247
|
+
"label": "Changelog",
|
|
2248
|
+
"href": "#"
|
|
2249
|
+
},
|
|
2250
|
+
{
|
|
2251
|
+
"label": "Privacy",
|
|
2252
|
+
"href": "#"
|
|
2253
|
+
}
|
|
2254
|
+
]
|
|
2255
|
+
},
|
|
2256
|
+
"settings": {
|
|
2257
|
+
"showLogo": true
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
2260
|
}
|
|
2261
2261
|
END_OF_FILE_CONTENT
|
|
2262
2262
|
# SKIP: src/data/config/site.json:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
@@ -2921,39 +2921,39 @@ END_OF_FILE_CONTENT
|
|
|
2921
2921
|
mkdir -p "src/lib"
|
|
2922
2922
|
echo "Creating src/lib/ComponentRegistry.tsx..."
|
|
2923
2923
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/ComponentRegistry.tsx"
|
|
2924
|
-
import React from 'react';
|
|
2925
|
-
import { Header } from '@/components/header';
|
|
2926
|
-
import { Footer } from '@/components/footer';
|
|
2927
|
-
import { Hero } from '@/components/hero';
|
|
2928
|
-
import { FeatureGrid } from '@/components/feature-grid';
|
|
2929
|
-
import { CodeBlock } from '@/components/code-block';
|
|
2930
|
-
import { ProblemStatement } from '@/components/problem-statement';
|
|
2931
|
-
import { PillarsGrid } from '@/components/pillars-grid';
|
|
2932
|
-
import { ArchLayers } from '@/components/arch-layers';
|
|
2933
|
-
import { ProductTriad } from '@/components/product-triad';
|
|
2934
|
-
import { PaSection } from '@/components/pa-section';
|
|
2935
|
-
import { Philosophy } from '@/components/philosophy';
|
|
2936
|
-
import { CtaBanner } from '@/components/cta-banner';
|
|
2937
|
-
|
|
2938
|
-
import type { SectionType } from '@jsonpages/core';
|
|
2939
|
-
import type { SectionComponentPropsMap } from '@/types';
|
|
2940
|
-
|
|
2941
|
-
export const ComponentRegistry: {
|
|
2942
|
-
[K in SectionType]: React.FC<SectionComponentPropsMap[K]>;
|
|
2943
|
-
} = {
|
|
2944
|
-
'header': Header,
|
|
2945
|
-
'footer': Footer,
|
|
2946
|
-
'hero': Hero,
|
|
2947
|
-
'feature-grid': FeatureGrid,
|
|
2948
|
-
'code-block': CodeBlock,
|
|
2949
|
-
'problem-statement': ProblemStatement,
|
|
2950
|
-
'pillars-grid': PillarsGrid,
|
|
2951
|
-
'arch-layers': ArchLayers,
|
|
2952
|
-
'product-triad': ProductTriad,
|
|
2953
|
-
'pa-section': PaSection,
|
|
2954
|
-
'philosophy': Philosophy,
|
|
2955
|
-
'cta-banner': CtaBanner,
|
|
2956
|
-
};
|
|
2924
|
+
import React from 'react';
|
|
2925
|
+
import { Header } from '@/components/header';
|
|
2926
|
+
import { Footer } from '@/components/footer';
|
|
2927
|
+
import { Hero } from '@/components/hero';
|
|
2928
|
+
import { FeatureGrid } from '@/components/feature-grid';
|
|
2929
|
+
import { CodeBlock } from '@/components/code-block';
|
|
2930
|
+
import { ProblemStatement } from '@/components/problem-statement';
|
|
2931
|
+
import { PillarsGrid } from '@/components/pillars-grid';
|
|
2932
|
+
import { ArchLayers } from '@/components/arch-layers';
|
|
2933
|
+
import { ProductTriad } from '@/components/product-triad';
|
|
2934
|
+
import { PaSection } from '@/components/pa-section';
|
|
2935
|
+
import { Philosophy } from '@/components/philosophy';
|
|
2936
|
+
import { CtaBanner } from '@/components/cta-banner';
|
|
2937
|
+
|
|
2938
|
+
import type { SectionType } from '@jsonpages/core';
|
|
2939
|
+
import type { SectionComponentPropsMap } from '@/types';
|
|
2940
|
+
|
|
2941
|
+
export const ComponentRegistry: {
|
|
2942
|
+
[K in SectionType]: React.FC<SectionComponentPropsMap[K]>;
|
|
2943
|
+
} = {
|
|
2944
|
+
'header': Header,
|
|
2945
|
+
'footer': Footer,
|
|
2946
|
+
'hero': Hero,
|
|
2947
|
+
'feature-grid': FeatureGrid,
|
|
2948
|
+
'code-block': CodeBlock,
|
|
2949
|
+
'problem-statement': ProblemStatement,
|
|
2950
|
+
'pillars-grid': PillarsGrid,
|
|
2951
|
+
'arch-layers': ArchLayers,
|
|
2952
|
+
'product-triad': ProductTriad,
|
|
2953
|
+
'pa-section': PaSection,
|
|
2954
|
+
'philosophy': Philosophy,
|
|
2955
|
+
'cta-banner': CtaBanner,
|
|
2956
|
+
};
|
|
2957
2957
|
|
|
2958
2958
|
END_OF_FILE_CONTENT
|
|
2959
2959
|
echo "Creating src/lib/IconResolver.tsx..."
|
|
@@ -3016,146 +3016,146 @@ export const Icon: React.FC<IconProps> = ({ name, size = 20, className }) => {
|
|
|
3016
3016
|
END_OF_FILE_CONTENT
|
|
3017
3017
|
echo "Creating src/lib/addSectionConfig.ts..."
|
|
3018
3018
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/addSectionConfig.ts"
|
|
3019
|
-
/**
|
|
3020
|
-
* Add-section config for the ICE admin (tenant-alpha).
|
|
3021
|
-
* Provides default data and labels for each addable section type.
|
|
3022
|
-
* Core remains agnostic; this file is tenant-specific.
|
|
3023
|
-
*/
|
|
3024
|
-
import type { AddSectionConfig } from '@jsonpages/core';
|
|
3025
|
-
|
|
3026
|
-
const addableSectionTypes = [
|
|
3027
|
-
'hero',
|
|
3028
|
-
'feature-grid',
|
|
3029
|
-
'code-block',
|
|
3030
|
-
'problem-statement',
|
|
3031
|
-
'pillars-grid',
|
|
3032
|
-
'arch-layers',
|
|
3033
|
-
'product-triad',
|
|
3034
|
-
'pa-section',
|
|
3035
|
-
'philosophy',
|
|
3036
|
-
'cta-banner',
|
|
3037
|
-
] as const;
|
|
3038
|
-
|
|
3039
|
-
const sectionTypeLabels: Record<string, string> = {
|
|
3040
|
-
'hero': 'Hero',
|
|
3041
|
-
'feature-grid': 'Feature Grid',
|
|
3042
|
-
'code-block': 'Code Block',
|
|
3043
|
-
'problem-statement': 'Problem Statement',
|
|
3044
|
-
'pillars-grid': 'Pillars Grid',
|
|
3045
|
-
'arch-layers': 'Architecture Layers',
|
|
3046
|
-
'product-triad': 'Product Triad',
|
|
3047
|
-
'pa-section': 'PA Section',
|
|
3048
|
-
'philosophy': 'Philosophy',
|
|
3049
|
-
'cta-banner': 'CTA Banner',
|
|
3050
|
-
};
|
|
3051
|
-
|
|
3052
|
-
function getDefaultSectionData(sectionType: string): Record<string, unknown> {
|
|
3053
|
-
switch (sectionType) {
|
|
3054
|
-
case 'hero':
|
|
3055
|
-
return { title: 'New Hero', description: '' };
|
|
3056
|
-
case 'feature-grid':
|
|
3057
|
-
return { sectionTitle: 'Features', cards: [] };
|
|
3058
|
-
case 'code-block':
|
|
3059
|
-
return { lines: [] };
|
|
3060
|
-
case 'problem-statement':
|
|
3061
|
-
return { title: 'Problem Statement', siloGroups: [], paragraphs: [] };
|
|
3062
|
-
case 'pillars-grid':
|
|
3063
|
-
return { title: 'Pillars', pillars: [] };
|
|
3064
|
-
case 'arch-layers':
|
|
3065
|
-
return { title: 'Architecture', layers: [] };
|
|
3066
|
-
case 'product-triad':
|
|
3067
|
-
return { title: 'Products', products: [] };
|
|
3068
|
-
case 'pa-section':
|
|
3069
|
-
return { title: 'Section', subtitle: 'Subtitle', paragraphs: [{ text: '' }] };
|
|
3070
|
-
case 'philosophy':
|
|
3071
|
-
return { title: 'Philosophy', quote: 'Your quote here.' };
|
|
3072
|
-
case 'cta-banner':
|
|
3073
|
-
return { title: 'Call to Action', description: '' };
|
|
3074
|
-
default:
|
|
3075
|
-
return {};
|
|
3076
|
-
}
|
|
3077
|
-
}
|
|
3078
|
-
|
|
3079
|
-
export const addSectionConfig: AddSectionConfig = {
|
|
3080
|
-
addableSectionTypes: [...addableSectionTypes],
|
|
3081
|
-
sectionTypeLabels,
|
|
3082
|
-
getDefaultSectionData,
|
|
3083
|
-
};
|
|
3019
|
+
/**
|
|
3020
|
+
* Add-section config for the ICE admin (tenant-alpha).
|
|
3021
|
+
* Provides default data and labels for each addable section type.
|
|
3022
|
+
* Core remains agnostic; this file is tenant-specific.
|
|
3023
|
+
*/
|
|
3024
|
+
import type { AddSectionConfig } from '@jsonpages/core';
|
|
3025
|
+
|
|
3026
|
+
const addableSectionTypes = [
|
|
3027
|
+
'hero',
|
|
3028
|
+
'feature-grid',
|
|
3029
|
+
'code-block',
|
|
3030
|
+
'problem-statement',
|
|
3031
|
+
'pillars-grid',
|
|
3032
|
+
'arch-layers',
|
|
3033
|
+
'product-triad',
|
|
3034
|
+
'pa-section',
|
|
3035
|
+
'philosophy',
|
|
3036
|
+
'cta-banner',
|
|
3037
|
+
] as const;
|
|
3038
|
+
|
|
3039
|
+
const sectionTypeLabels: Record<string, string> = {
|
|
3040
|
+
'hero': 'Hero',
|
|
3041
|
+
'feature-grid': 'Feature Grid',
|
|
3042
|
+
'code-block': 'Code Block',
|
|
3043
|
+
'problem-statement': 'Problem Statement',
|
|
3044
|
+
'pillars-grid': 'Pillars Grid',
|
|
3045
|
+
'arch-layers': 'Architecture Layers',
|
|
3046
|
+
'product-triad': 'Product Triad',
|
|
3047
|
+
'pa-section': 'PA Section',
|
|
3048
|
+
'philosophy': 'Philosophy',
|
|
3049
|
+
'cta-banner': 'CTA Banner',
|
|
3050
|
+
};
|
|
3051
|
+
|
|
3052
|
+
function getDefaultSectionData(sectionType: string): Record<string, unknown> {
|
|
3053
|
+
switch (sectionType) {
|
|
3054
|
+
case 'hero':
|
|
3055
|
+
return { title: 'New Hero', description: '' };
|
|
3056
|
+
case 'feature-grid':
|
|
3057
|
+
return { sectionTitle: 'Features', cards: [] };
|
|
3058
|
+
case 'code-block':
|
|
3059
|
+
return { lines: [] };
|
|
3060
|
+
case 'problem-statement':
|
|
3061
|
+
return { title: 'Problem Statement', siloGroups: [], paragraphs: [] };
|
|
3062
|
+
case 'pillars-grid':
|
|
3063
|
+
return { title: 'Pillars', pillars: [] };
|
|
3064
|
+
case 'arch-layers':
|
|
3065
|
+
return { title: 'Architecture', layers: [] };
|
|
3066
|
+
case 'product-triad':
|
|
3067
|
+
return { title: 'Products', products: [] };
|
|
3068
|
+
case 'pa-section':
|
|
3069
|
+
return { title: 'Section', subtitle: 'Subtitle', paragraphs: [{ text: '' }] };
|
|
3070
|
+
case 'philosophy':
|
|
3071
|
+
return { title: 'Philosophy', quote: 'Your quote here.' };
|
|
3072
|
+
case 'cta-banner':
|
|
3073
|
+
return { title: 'Call to Action', description: '' };
|
|
3074
|
+
default:
|
|
3075
|
+
return {};
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
export const addSectionConfig: AddSectionConfig = {
|
|
3080
|
+
addableSectionTypes: [...addableSectionTypes],
|
|
3081
|
+
sectionTypeLabels,
|
|
3082
|
+
getDefaultSectionData,
|
|
3083
|
+
};
|
|
3084
3084
|
|
|
3085
3085
|
END_OF_FILE_CONTENT
|
|
3086
3086
|
echo "Creating src/lib/base-schemas.ts..."
|
|
3087
3087
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/base-schemas.ts"
|
|
3088
|
-
import { z } from 'zod';
|
|
3089
|
-
|
|
3090
|
-
/**
|
|
3091
|
-
* Base schemas shared by section capsules (CIP governance).
|
|
3092
|
-
* Capsules extend these for consistent anchorId, array items, and settings.
|
|
3093
|
-
*/
|
|
3094
|
-
export const BaseSectionData = z.object({
|
|
3095
|
-
anchorId: z.string().optional().describe('ui:text'),
|
|
3096
|
-
});
|
|
3097
|
-
|
|
3098
|
-
export const BaseArrayItem = z.object({
|
|
3099
|
-
id: z.string().optional(),
|
|
3100
|
-
});
|
|
3101
|
-
|
|
3102
|
-
export const BaseSectionSettingsSchema = z.object({
|
|
3103
|
-
paddingTop: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),
|
|
3104
|
-
paddingBottom: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),
|
|
3105
|
-
theme: z.enum(['dark', 'light', 'accent']).default('dark').describe('ui:select'),
|
|
3106
|
-
container: z.enum(['boxed', 'fluid']).default('boxed').describe('ui:select'),
|
|
3107
|
-
});
|
|
3108
|
-
|
|
3109
|
-
export const CtaSchema = z.object({
|
|
3110
|
-
id: z.string().optional(),
|
|
3111
|
-
label: z.string().describe('ui:text'),
|
|
3112
|
-
href: z.string().describe('ui:text'),
|
|
3113
|
-
variant: z.enum(['primary', 'secondary']).default('primary').describe('ui:select'),
|
|
3114
|
-
});
|
|
3088
|
+
import { z } from 'zod';
|
|
3089
|
+
|
|
3090
|
+
/**
|
|
3091
|
+
* Base schemas shared by section capsules (CIP governance).
|
|
3092
|
+
* Capsules extend these for consistent anchorId, array items, and settings.
|
|
3093
|
+
*/
|
|
3094
|
+
export const BaseSectionData = z.object({
|
|
3095
|
+
anchorId: z.string().optional().describe('ui:text'),
|
|
3096
|
+
});
|
|
3097
|
+
|
|
3098
|
+
export const BaseArrayItem = z.object({
|
|
3099
|
+
id: z.string().optional(),
|
|
3100
|
+
});
|
|
3101
|
+
|
|
3102
|
+
export const BaseSectionSettingsSchema = z.object({
|
|
3103
|
+
paddingTop: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),
|
|
3104
|
+
paddingBottom: z.enum(['none', 'sm', 'md', 'lg', 'xl', '2xl']).default('md').describe('ui:select'),
|
|
3105
|
+
theme: z.enum(['dark', 'light', 'accent']).default('dark').describe('ui:select'),
|
|
3106
|
+
container: z.enum(['boxed', 'fluid']).default('boxed').describe('ui:select'),
|
|
3107
|
+
});
|
|
3108
|
+
|
|
3109
|
+
export const CtaSchema = z.object({
|
|
3110
|
+
id: z.string().optional(),
|
|
3111
|
+
label: z.string().describe('ui:text'),
|
|
3112
|
+
href: z.string().describe('ui:text'),
|
|
3113
|
+
variant: z.enum(['primary', 'secondary']).default('primary').describe('ui:select'),
|
|
3114
|
+
});
|
|
3115
3115
|
|
|
3116
3116
|
END_OF_FILE_CONTENT
|
|
3117
3117
|
echo "Creating src/lib/schemas.ts..."
|
|
3118
3118
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/schemas.ts"
|
|
3119
|
-
/**
|
|
3120
|
-
* SECTION_SCHEMAS registry — SSOT for FormFactory.
|
|
3121
|
-
* Re-exports base schemas and aggregates all section schemas from TBP capsules.
|
|
3122
|
-
*/
|
|
3123
|
-
export {
|
|
3124
|
-
BaseSectionData,
|
|
3125
|
-
BaseArrayItem,
|
|
3126
|
-
BaseSectionSettingsSchema,
|
|
3127
|
-
CtaSchema,
|
|
3128
|
-
} from './base-schemas';
|
|
3129
|
-
|
|
3130
|
-
import { HeaderSchema } from '@/components/header';
|
|
3131
|
-
import { FooterSchema } from '@/components/footer';
|
|
3132
|
-
import { HeroSchema } from '@/components/hero';
|
|
3133
|
-
import { FeatureGridSchema } from '@/components/feature-grid';
|
|
3134
|
-
import { CodeBlockSchema } from '@/components/code-block';
|
|
3135
|
-
import { ProblemStatementSchema } from '@/components/problem-statement';
|
|
3136
|
-
import { PillarsGridSchema } from '@/components/pillars-grid';
|
|
3137
|
-
import { ArchLayersSchema } from '@/components/arch-layers';
|
|
3138
|
-
import { ProductTriadSchema } from '@/components/product-triad';
|
|
3139
|
-
import { PaSectionSchema } from '@/components/pa-section';
|
|
3140
|
-
import { PhilosophySchema } from '@/components/philosophy';
|
|
3141
|
-
import { CtaBannerSchema } from '@/components/cta-banner';
|
|
3142
|
-
|
|
3143
|
-
export const SECTION_SCHEMAS = {
|
|
3144
|
-
'header': HeaderSchema,
|
|
3145
|
-
'footer': FooterSchema,
|
|
3146
|
-
'hero': HeroSchema,
|
|
3147
|
-
'feature-grid': FeatureGridSchema,
|
|
3148
|
-
'code-block': CodeBlockSchema,
|
|
3149
|
-
'problem-statement': ProblemStatementSchema,
|
|
3150
|
-
'pillars-grid': PillarsGridSchema,
|
|
3151
|
-
'arch-layers': ArchLayersSchema,
|
|
3152
|
-
'product-triad': ProductTriadSchema,
|
|
3153
|
-
'pa-section': PaSectionSchema,
|
|
3154
|
-
'philosophy': PhilosophySchema,
|
|
3155
|
-
'cta-banner': CtaBannerSchema,
|
|
3156
|
-
} as const;
|
|
3157
|
-
|
|
3158
|
-
export type SectionType = keyof typeof SECTION_SCHEMAS;
|
|
3119
|
+
/**
|
|
3120
|
+
* SECTION_SCHEMAS registry — SSOT for FormFactory.
|
|
3121
|
+
* Re-exports base schemas and aggregates all section schemas from TBP capsules.
|
|
3122
|
+
*/
|
|
3123
|
+
export {
|
|
3124
|
+
BaseSectionData,
|
|
3125
|
+
BaseArrayItem,
|
|
3126
|
+
BaseSectionSettingsSchema,
|
|
3127
|
+
CtaSchema,
|
|
3128
|
+
} from './base-schemas';
|
|
3129
|
+
|
|
3130
|
+
import { HeaderSchema } from '@/components/header';
|
|
3131
|
+
import { FooterSchema } from '@/components/footer';
|
|
3132
|
+
import { HeroSchema } from '@/components/hero';
|
|
3133
|
+
import { FeatureGridSchema } from '@/components/feature-grid';
|
|
3134
|
+
import { CodeBlockSchema } from '@/components/code-block';
|
|
3135
|
+
import { ProblemStatementSchema } from '@/components/problem-statement';
|
|
3136
|
+
import { PillarsGridSchema } from '@/components/pillars-grid';
|
|
3137
|
+
import { ArchLayersSchema } from '@/components/arch-layers';
|
|
3138
|
+
import { ProductTriadSchema } from '@/components/product-triad';
|
|
3139
|
+
import { PaSectionSchema } from '@/components/pa-section';
|
|
3140
|
+
import { PhilosophySchema } from '@/components/philosophy';
|
|
3141
|
+
import { CtaBannerSchema } from '@/components/cta-banner';
|
|
3142
|
+
|
|
3143
|
+
export const SECTION_SCHEMAS = {
|
|
3144
|
+
'header': HeaderSchema,
|
|
3145
|
+
'footer': FooterSchema,
|
|
3146
|
+
'hero': HeroSchema,
|
|
3147
|
+
'feature-grid': FeatureGridSchema,
|
|
3148
|
+
'code-block': CodeBlockSchema,
|
|
3149
|
+
'problem-statement': ProblemStatementSchema,
|
|
3150
|
+
'pillars-grid': PillarsGridSchema,
|
|
3151
|
+
'arch-layers': ArchLayersSchema,
|
|
3152
|
+
'product-triad': ProductTriadSchema,
|
|
3153
|
+
'pa-section': PaSectionSchema,
|
|
3154
|
+
'philosophy': PhilosophySchema,
|
|
3155
|
+
'cta-banner': CtaBannerSchema,
|
|
3156
|
+
} as const;
|
|
3157
|
+
|
|
3158
|
+
export type SectionType = keyof typeof SECTION_SCHEMAS;
|
|
3159
3159
|
|
|
3160
3160
|
END_OF_FILE_CONTENT
|
|
3161
3161
|
echo "Creating src/lib/utils.ts..."
|
|
@@ -3189,68 +3189,68 @@ END_OF_FILE_CONTENT
|
|
|
3189
3189
|
# SKIP: src/registry-types.ts è un file binario e non può essere convertito in testo.
|
|
3190
3190
|
echo "Creating src/types.ts..."
|
|
3191
3191
|
cat << 'END_OF_FILE_CONTENT' > "src/types.ts"
|
|
3192
|
-
import type { MenuItem } from '@jsonpages/core';
|
|
3193
|
-
import type { HeaderData, HeaderSettings } from '@/components/header';
|
|
3194
|
-
import type { FooterData, FooterSettings } from '@/components/footer';
|
|
3195
|
-
import type { HeroData, HeroSettings } from '@/components/hero';
|
|
3196
|
-
import type { FeatureGridData, FeatureGridSettings } from '@/components/feature-grid';
|
|
3197
|
-
import type { CodeBlockData, CodeBlockSettings } from '@/components/code-block';
|
|
3198
|
-
import type { ProblemStatementData, ProblemStatementSettings } from '@/components/problem-statement';
|
|
3199
|
-
import type { PillarsGridData, PillarsGridSettings } from '@/components/pillars-grid';
|
|
3200
|
-
import type { ArchLayersData, ArchLayersSettings } from '@/components/arch-layers';
|
|
3201
|
-
import type { ProductTriadData, ProductTriadSettings } from '@/components/product-triad';
|
|
3202
|
-
import type { PaSectionData, PaSectionSettings } from '@/components/pa-section';
|
|
3203
|
-
import type { PhilosophyData, PhilosophySettings } from '@/components/philosophy';
|
|
3204
|
-
import type { CtaBannerData, CtaBannerSettings } from '@/components/cta-banner';
|
|
3205
|
-
|
|
3206
|
-
export type SectionComponentPropsMap = {
|
|
3207
|
-
'header': { data: HeaderData; settings?: HeaderSettings; menu: MenuItem[] };
|
|
3208
|
-
'footer': { data: FooterData; settings?: FooterSettings };
|
|
3209
|
-
'hero': { data: HeroData; settings?: HeroSettings };
|
|
3210
|
-
'feature-grid': { data: FeatureGridData; settings?: FeatureGridSettings };
|
|
3211
|
-
'code-block': { data: CodeBlockData; settings?: CodeBlockSettings };
|
|
3212
|
-
'problem-statement': { data: ProblemStatementData; settings?: ProblemStatementSettings };
|
|
3213
|
-
'pillars-grid': { data: PillarsGridData; settings?: PillarsGridSettings };
|
|
3214
|
-
'arch-layers': { data: ArchLayersData; settings?: ArchLayersSettings };
|
|
3215
|
-
'product-triad': { data: ProductTriadData; settings?: ProductTriadSettings };
|
|
3216
|
-
'pa-section': { data: PaSectionData; settings?: PaSectionSettings };
|
|
3217
|
-
'philosophy': { data: PhilosophyData; settings?: PhilosophySettings };
|
|
3218
|
-
'cta-banner': { data: CtaBannerData; settings?: CtaBannerSettings };
|
|
3219
|
-
};
|
|
3220
|
-
|
|
3221
|
-
declare module '@jsonpages/core' {
|
|
3222
|
-
export interface SectionDataRegistry {
|
|
3223
|
-
'header': HeaderData;
|
|
3224
|
-
'footer': FooterData;
|
|
3225
|
-
'hero': HeroData;
|
|
3226
|
-
'feature-grid': FeatureGridData;
|
|
3227
|
-
'code-block': CodeBlockData;
|
|
3228
|
-
'problem-statement': ProblemStatementData;
|
|
3229
|
-
'pillars-grid': PillarsGridData;
|
|
3230
|
-
'arch-layers': ArchLayersData;
|
|
3231
|
-
'product-triad': ProductTriadData;
|
|
3232
|
-
'pa-section': PaSectionData;
|
|
3233
|
-
'philosophy': PhilosophyData;
|
|
3234
|
-
'cta-banner': CtaBannerData;
|
|
3235
|
-
}
|
|
3236
|
-
|
|
3237
|
-
export interface SectionSettingsRegistry {
|
|
3238
|
-
'header': HeaderSettings;
|
|
3239
|
-
'footer': FooterSettings;
|
|
3240
|
-
'hero': HeroSettings;
|
|
3241
|
-
'feature-grid': FeatureGridSettings;
|
|
3242
|
-
'code-block': CodeBlockSettings;
|
|
3243
|
-
'problem-statement': ProblemStatementSettings;
|
|
3244
|
-
'pillars-grid': PillarsGridSettings;
|
|
3245
|
-
'arch-layers': ArchLayersSettings;
|
|
3246
|
-
'product-triad': ProductTriadSettings;
|
|
3247
|
-
'pa-section': PaSectionSettings;
|
|
3248
|
-
'philosophy': PhilosophySettings;
|
|
3249
|
-
'cta-banner': CtaBannerSettings;
|
|
3250
|
-
}
|
|
3251
|
-
}
|
|
3252
|
-
|
|
3253
|
-
export * from '@jsonpages/core';
|
|
3192
|
+
import type { MenuItem } from '@jsonpages/core';
|
|
3193
|
+
import type { HeaderData, HeaderSettings } from '@/components/header';
|
|
3194
|
+
import type { FooterData, FooterSettings } from '@/components/footer';
|
|
3195
|
+
import type { HeroData, HeroSettings } from '@/components/hero';
|
|
3196
|
+
import type { FeatureGridData, FeatureGridSettings } from '@/components/feature-grid';
|
|
3197
|
+
import type { CodeBlockData, CodeBlockSettings } from '@/components/code-block';
|
|
3198
|
+
import type { ProblemStatementData, ProblemStatementSettings } from '@/components/problem-statement';
|
|
3199
|
+
import type { PillarsGridData, PillarsGridSettings } from '@/components/pillars-grid';
|
|
3200
|
+
import type { ArchLayersData, ArchLayersSettings } from '@/components/arch-layers';
|
|
3201
|
+
import type { ProductTriadData, ProductTriadSettings } from '@/components/product-triad';
|
|
3202
|
+
import type { PaSectionData, PaSectionSettings } from '@/components/pa-section';
|
|
3203
|
+
import type { PhilosophyData, PhilosophySettings } from '@/components/philosophy';
|
|
3204
|
+
import type { CtaBannerData, CtaBannerSettings } from '@/components/cta-banner';
|
|
3205
|
+
|
|
3206
|
+
export type SectionComponentPropsMap = {
|
|
3207
|
+
'header': { data: HeaderData; settings?: HeaderSettings; menu: MenuItem[] };
|
|
3208
|
+
'footer': { data: FooterData; settings?: FooterSettings };
|
|
3209
|
+
'hero': { data: HeroData; settings?: HeroSettings };
|
|
3210
|
+
'feature-grid': { data: FeatureGridData; settings?: FeatureGridSettings };
|
|
3211
|
+
'code-block': { data: CodeBlockData; settings?: CodeBlockSettings };
|
|
3212
|
+
'problem-statement': { data: ProblemStatementData; settings?: ProblemStatementSettings };
|
|
3213
|
+
'pillars-grid': { data: PillarsGridData; settings?: PillarsGridSettings };
|
|
3214
|
+
'arch-layers': { data: ArchLayersData; settings?: ArchLayersSettings };
|
|
3215
|
+
'product-triad': { data: ProductTriadData; settings?: ProductTriadSettings };
|
|
3216
|
+
'pa-section': { data: PaSectionData; settings?: PaSectionSettings };
|
|
3217
|
+
'philosophy': { data: PhilosophyData; settings?: PhilosophySettings };
|
|
3218
|
+
'cta-banner': { data: CtaBannerData; settings?: CtaBannerSettings };
|
|
3219
|
+
};
|
|
3220
|
+
|
|
3221
|
+
declare module '@jsonpages/core' {
|
|
3222
|
+
export interface SectionDataRegistry {
|
|
3223
|
+
'header': HeaderData;
|
|
3224
|
+
'footer': FooterData;
|
|
3225
|
+
'hero': HeroData;
|
|
3226
|
+
'feature-grid': FeatureGridData;
|
|
3227
|
+
'code-block': CodeBlockData;
|
|
3228
|
+
'problem-statement': ProblemStatementData;
|
|
3229
|
+
'pillars-grid': PillarsGridData;
|
|
3230
|
+
'arch-layers': ArchLayersData;
|
|
3231
|
+
'product-triad': ProductTriadData;
|
|
3232
|
+
'pa-section': PaSectionData;
|
|
3233
|
+
'philosophy': PhilosophyData;
|
|
3234
|
+
'cta-banner': CtaBannerData;
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
export interface SectionSettingsRegistry {
|
|
3238
|
+
'header': HeaderSettings;
|
|
3239
|
+
'footer': FooterSettings;
|
|
3240
|
+
'hero': HeroSettings;
|
|
3241
|
+
'feature-grid': FeatureGridSettings;
|
|
3242
|
+
'code-block': CodeBlockSettings;
|
|
3243
|
+
'problem-statement': ProblemStatementSettings;
|
|
3244
|
+
'pillars-grid': PillarsGridSettings;
|
|
3245
|
+
'arch-layers': ArchLayersSettings;
|
|
3246
|
+
'product-triad': ProductTriadSettings;
|
|
3247
|
+
'pa-section': PaSectionSettings;
|
|
3248
|
+
'philosophy': PhilosophySettings;
|
|
3249
|
+
'cta-banner': CtaBannerSettings;
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3253
|
+
export * from '@jsonpages/core';
|
|
3254
3254
|
|
|
3255
3255
|
END_OF_FILE_CONTENT
|
|
3256
3256
|
echo "Creating src/vite-env.d.ts..."
|