foundrycms 0.1.0
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/LICENSE +21 -0
- package/README.md +152 -0
- package/dist/__tests__/foundry.test.d.ts +2 -0
- package/dist/__tests__/foundry.test.d.ts.map +1 -0
- package/dist/__tests__/foundry.test.js +1013 -0
- package/dist/__tests__/foundry.test.js.map +1 -0
- package/dist/config-manager.d.ts +33 -0
- package/dist/config-manager.d.ts.map +1 -0
- package/dist/config-manager.js +169 -0
- package/dist/config-manager.js.map +1 -0
- package/dist/hook-system.d.ts +61 -0
- package/dist/hook-system.d.ts.map +1 -0
- package/dist/hook-system.js +114 -0
- package/dist/hook-system.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -0
- package/dist/page-builder/element-registry.d.ts +47 -0
- package/dist/page-builder/element-registry.d.ts.map +1 -0
- package/dist/page-builder/element-registry.js +98 -0
- package/dist/page-builder/element-registry.js.map +1 -0
- package/dist/page-builder/elements/index.d.ts +22 -0
- package/dist/page-builder/elements/index.d.ts.map +1 -0
- package/dist/page-builder/elements/index.js +770 -0
- package/dist/page-builder/elements/index.js.map +1 -0
- package/dist/page-builder/renderer.d.ts +14 -0
- package/dist/page-builder/renderer.d.ts.map +1 -0
- package/dist/page-builder/renderer.js +240 -0
- package/dist/page-builder/renderer.js.map +1 -0
- package/dist/page-builder/serializer.d.ts +1220 -0
- package/dist/page-builder/serializer.d.ts.map +1 -0
- package/dist/page-builder/serializer.js +111 -0
- package/dist/page-builder/serializer.js.map +1 -0
- package/dist/page-builder/template-studio.d.ts +37 -0
- package/dist/page-builder/template-studio.d.ts.map +1 -0
- package/dist/page-builder/template-studio.js +923 -0
- package/dist/page-builder/template-studio.js.map +1 -0
- package/dist/page-builder/types.d.ts +99 -0
- package/dist/page-builder/types.d.ts.map +1 -0
- package/dist/page-builder/types.js +5 -0
- package/dist/page-builder/types.js.map +1 -0
- package/dist/plugin-system.d.ts +128 -0
- package/dist/plugin-system.d.ts.map +1 -0
- package/dist/plugin-system.js +252 -0
- package/dist/plugin-system.js.map +1 -0
- package/dist/plugins/communication.d.ts +6 -0
- package/dist/plugins/communication.d.ts.map +1 -0
- package/dist/plugins/communication.js +922 -0
- package/dist/plugins/communication.js.map +1 -0
- package/dist/plugins/core.d.ts +6 -0
- package/dist/plugins/core.d.ts.map +1 -0
- package/dist/plugins/core.js +675 -0
- package/dist/plugins/core.js.map +1 -0
- package/dist/plugins/growth.d.ts +6 -0
- package/dist/plugins/growth.d.ts.map +1 -0
- package/dist/plugins/growth.js +668 -0
- package/dist/plugins/growth.js.map +1 -0
- package/dist/plugins/index.d.ts +8 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +43 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/operations.d.ts +7 -0
- package/dist/plugins/operations.d.ts.map +1 -0
- package/dist/plugins/operations.js +930 -0
- package/dist/plugins/operations.js.map +1 -0
- package/dist/theme/presets.d.ts +8 -0
- package/dist/theme/presets.d.ts.map +1 -0
- package/dist/theme/presets.js +257 -0
- package/dist/theme/presets.js.map +1 -0
- package/dist/theme/types.d.ts +83 -0
- package/dist/theme/types.d.ts.map +1 -0
- package/dist/theme/types.js +5 -0
- package/dist/theme/types.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Shared helpers
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
function esc(str) {
|
|
6
|
+
return str
|
|
7
|
+
.replace(/&/g, '&')
|
|
8
|
+
.replace(/</g, '<')
|
|
9
|
+
.replace(/>/g, '>')
|
|
10
|
+
.replace(/"/g, '"')
|
|
11
|
+
.replace(/'/g, ''');
|
|
12
|
+
}
|
|
13
|
+
function escAttr(str) {
|
|
14
|
+
return str.replace(/"/g, '"').replace(/'/g, ''');
|
|
15
|
+
}
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// 1. ROW (layout)
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
export const rowElement = {
|
|
20
|
+
type: 'row',
|
|
21
|
+
name: 'Row',
|
|
22
|
+
category: 'layout',
|
|
23
|
+
icon: '▦',
|
|
24
|
+
description: 'Container row with layout, background, and padding options',
|
|
25
|
+
defaultSettings: {
|
|
26
|
+
layout: 'boxed',
|
|
27
|
+
gap: '1.5rem',
|
|
28
|
+
paddingTop: '2rem',
|
|
29
|
+
paddingBottom: '2rem',
|
|
30
|
+
backgroundColor: '',
|
|
31
|
+
backgroundImage: '',
|
|
32
|
+
className: '',
|
|
33
|
+
},
|
|
34
|
+
settingsSchema: z.object({
|
|
35
|
+
layout: z.enum(['full_width', 'boxed', 'narrow']).default('boxed'),
|
|
36
|
+
gap: z.string().default('1.5rem'),
|
|
37
|
+
paddingTop: z.string().default('2rem'),
|
|
38
|
+
paddingBottom: z.string().default('2rem'),
|
|
39
|
+
backgroundColor: z.string().default(''),
|
|
40
|
+
backgroundImage: z.string().default(''),
|
|
41
|
+
className: z.string().default(''),
|
|
42
|
+
}),
|
|
43
|
+
render: (s) => {
|
|
44
|
+
const styles = [];
|
|
45
|
+
if (s.paddingTop)
|
|
46
|
+
styles.push(`padding-top: ${s.paddingTop}`);
|
|
47
|
+
if (s.paddingBottom)
|
|
48
|
+
styles.push(`padding-bottom: ${s.paddingBottom}`);
|
|
49
|
+
if (s.backgroundColor)
|
|
50
|
+
styles.push(`background-color: ${s.backgroundColor}`);
|
|
51
|
+
if (s.backgroundImage)
|
|
52
|
+
styles.push(`background-image: url('${escAttr(s.backgroundImage)}'); background-size: cover; background-position: center`);
|
|
53
|
+
const layoutClass = s.layout === 'full_width' ? 'w-full' : s.layout === 'narrow' ? 'max-w-3xl mx-auto px-4' : 'max-w-7xl mx-auto px-4';
|
|
54
|
+
return ` <div class="${layoutClass} ${s.className || ''}" style="${styles.join('; ')}"></div>`;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// 2. COLUMN (layout)
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
export const columnElement = {
|
|
61
|
+
type: 'column',
|
|
62
|
+
name: 'Column',
|
|
63
|
+
category: 'layout',
|
|
64
|
+
icon: '▐',
|
|
65
|
+
description: 'Column container with width and alignment settings',
|
|
66
|
+
defaultSettings: {
|
|
67
|
+
width: '1/1',
|
|
68
|
+
verticalAlign: 'top',
|
|
69
|
+
padding: '',
|
|
70
|
+
className: '',
|
|
71
|
+
},
|
|
72
|
+
settingsSchema: z.object({
|
|
73
|
+
width: z.enum(['1/1', '1/2', '1/3', '2/3', '1/4', '3/4', '1/6', '5/6']).default('1/1'),
|
|
74
|
+
verticalAlign: z.enum(['top', 'center', 'bottom']).default('top'),
|
|
75
|
+
padding: z.string().default(''),
|
|
76
|
+
className: z.string().default(''),
|
|
77
|
+
}),
|
|
78
|
+
render: (s) => {
|
|
79
|
+
const styles = [];
|
|
80
|
+
if (s.padding)
|
|
81
|
+
styles.push(`padding: ${s.padding}`);
|
|
82
|
+
return ` <div class="foundry-col ${s.className || ''}" style="${styles.join('; ')}"></div>`;
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// 3. SPACER (layout)
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
export const spacerElement = {
|
|
89
|
+
type: 'spacer',
|
|
90
|
+
name: 'Spacer',
|
|
91
|
+
category: 'layout',
|
|
92
|
+
icon: '↕',
|
|
93
|
+
description: 'Vertical spacer with configurable height',
|
|
94
|
+
defaultSettings: {
|
|
95
|
+
height: '2rem',
|
|
96
|
+
},
|
|
97
|
+
settingsSchema: z.object({
|
|
98
|
+
height: z.string().default('2rem'),
|
|
99
|
+
}),
|
|
100
|
+
render: (s) => {
|
|
101
|
+
return ` <div class="foundry-spacer" style="height: ${escAttr(s.height)}" aria-hidden="true"></div>`;
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// 4. DIVIDER (layout)
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
export const dividerElement = {
|
|
108
|
+
type: 'divider',
|
|
109
|
+
name: 'Divider',
|
|
110
|
+
category: 'layout',
|
|
111
|
+
icon: '—',
|
|
112
|
+
description: 'Horizontal divider line with style, color, and width options',
|
|
113
|
+
defaultSettings: {
|
|
114
|
+
style: 'solid',
|
|
115
|
+
color: '#e5e7eb',
|
|
116
|
+
width: '100%',
|
|
117
|
+
thickness: '1px',
|
|
118
|
+
},
|
|
119
|
+
settingsSchema: z.object({
|
|
120
|
+
style: z.enum(['solid', 'dashed', 'dotted', 'gradient']).default('solid'),
|
|
121
|
+
color: z.string().default('#e5e7eb'),
|
|
122
|
+
width: z.string().default('100%'),
|
|
123
|
+
thickness: z.string().default('1px'),
|
|
124
|
+
}),
|
|
125
|
+
render: (s) => {
|
|
126
|
+
if (s.style === 'gradient') {
|
|
127
|
+
return ` <div class="foundry-divider" style="width: ${escAttr(s.width)}; height: ${escAttr(s.thickness)}; background: linear-gradient(90deg, transparent, ${escAttr(s.color)}, transparent); margin: 0 auto" role="separator"></div>`;
|
|
128
|
+
}
|
|
129
|
+
return ` <hr class="foundry-divider" style="width: ${escAttr(s.width)}; border: none; border-top: ${escAttr(s.thickness)} ${escAttr(s.style)} ${escAttr(s.color)}; margin: 0 auto" />`;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// 5. HEADING (content)
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
export const headingElement = {
|
|
136
|
+
type: 'heading',
|
|
137
|
+
name: 'Heading',
|
|
138
|
+
category: 'content',
|
|
139
|
+
icon: 'H',
|
|
140
|
+
description: 'Heading element (H1-H6) with alignment, color, and size',
|
|
141
|
+
keywords: ['title', 'h1', 'h2', 'h3', 'header'],
|
|
142
|
+
defaultSettings: {
|
|
143
|
+
text: 'Heading',
|
|
144
|
+
level: 'h2',
|
|
145
|
+
alignment: 'left',
|
|
146
|
+
color: '',
|
|
147
|
+
fontSize: '',
|
|
148
|
+
fontWeight: 'bold',
|
|
149
|
+
className: '',
|
|
150
|
+
},
|
|
151
|
+
settingsSchema: z.object({
|
|
152
|
+
text: z.string().min(1),
|
|
153
|
+
level: z.enum(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']).default('h2'),
|
|
154
|
+
alignment: z.enum(['left', 'center', 'right']).default('left'),
|
|
155
|
+
color: z.string().default(''),
|
|
156
|
+
fontSize: z.string().default(''),
|
|
157
|
+
fontWeight: z.string().default('bold'),
|
|
158
|
+
className: z.string().default(''),
|
|
159
|
+
}),
|
|
160
|
+
render: (s) => {
|
|
161
|
+
const tag = s.level || 'h2';
|
|
162
|
+
const styles = [];
|
|
163
|
+
if (s.color)
|
|
164
|
+
styles.push(`color: ${s.color}`);
|
|
165
|
+
if (s.fontSize)
|
|
166
|
+
styles.push(`font-size: ${s.fontSize}`);
|
|
167
|
+
if (s.fontWeight)
|
|
168
|
+
styles.push(`font-weight: ${s.fontWeight}`);
|
|
169
|
+
const alignClass = s.alignment === 'center' ? 'text-center' : s.alignment === 'right' ? 'text-right' : 'text-left';
|
|
170
|
+
return ` <${tag} class="foundry-heading ${alignClass} ${s.className || ''}" style="${styles.join('; ')}">${esc(s.text)}</${tag}>`;
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// 6. TEXT BLOCK (content)
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
export const textBlockElement = {
|
|
177
|
+
type: 'text-block',
|
|
178
|
+
name: 'Text Block',
|
|
179
|
+
category: 'content',
|
|
180
|
+
icon: '¶',
|
|
181
|
+
description: 'Rich text content block (HTML)',
|
|
182
|
+
keywords: ['paragraph', 'text', 'rich text', 'body', 'copy'],
|
|
183
|
+
defaultSettings: {
|
|
184
|
+
content: '<p>Enter your text here.</p>',
|
|
185
|
+
alignment: 'left',
|
|
186
|
+
color: '',
|
|
187
|
+
fontSize: '',
|
|
188
|
+
lineHeight: '',
|
|
189
|
+
className: '',
|
|
190
|
+
},
|
|
191
|
+
settingsSchema: z.object({
|
|
192
|
+
content: z.string(),
|
|
193
|
+
alignment: z.enum(['left', 'center', 'right', 'justify']).default('left'),
|
|
194
|
+
color: z.string().default(''),
|
|
195
|
+
fontSize: z.string().default(''),
|
|
196
|
+
lineHeight: z.string().default(''),
|
|
197
|
+
className: z.string().default(''),
|
|
198
|
+
}),
|
|
199
|
+
render: (s) => {
|
|
200
|
+
const styles = [];
|
|
201
|
+
if (s.color)
|
|
202
|
+
styles.push(`color: ${s.color}`);
|
|
203
|
+
if (s.fontSize)
|
|
204
|
+
styles.push(`font-size: ${s.fontSize}`);
|
|
205
|
+
if (s.lineHeight)
|
|
206
|
+
styles.push(`line-height: ${s.lineHeight}`);
|
|
207
|
+
const alignClass = s.alignment === 'center' ? 'text-center' : s.alignment === 'right' ? 'text-right' : s.alignment === 'justify' ? 'text-justify' : 'text-left';
|
|
208
|
+
// Content is trusted HTML (sanitized at save time)
|
|
209
|
+
return ` <div class="foundry-text-block prose max-w-none ${alignClass} ${s.className || ''}" style="${styles.join('; ')}">${s.content}</div>`;
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// 7. BUTTON (content)
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
export const buttonElement = {
|
|
216
|
+
type: 'button',
|
|
217
|
+
name: 'Button',
|
|
218
|
+
category: 'content',
|
|
219
|
+
icon: '▢',
|
|
220
|
+
description: 'Call-to-action button with variant, size, and link',
|
|
221
|
+
keywords: ['cta', 'link', 'action', 'button'],
|
|
222
|
+
defaultSettings: {
|
|
223
|
+
text: 'Click Here',
|
|
224
|
+
url: '#',
|
|
225
|
+
variant: 'primary',
|
|
226
|
+
size: 'md',
|
|
227
|
+
alignment: 'left',
|
|
228
|
+
openInNewTab: false,
|
|
229
|
+
className: '',
|
|
230
|
+
},
|
|
231
|
+
settingsSchema: z.object({
|
|
232
|
+
text: z.string().min(1),
|
|
233
|
+
url: z.string().default('#'),
|
|
234
|
+
variant: z.enum(['primary', 'secondary', 'outline', 'ghost']).default('primary'),
|
|
235
|
+
size: z.enum(['sm', 'md', 'lg', 'xl']).default('md'),
|
|
236
|
+
alignment: z.enum(['left', 'center', 'right']).default('left'),
|
|
237
|
+
openInNewTab: z.boolean().default(false),
|
|
238
|
+
className: z.string().default(''),
|
|
239
|
+
}),
|
|
240
|
+
render: (s) => {
|
|
241
|
+
const sizeClasses = {
|
|
242
|
+
sm: 'px-3 py-1.5 text-sm',
|
|
243
|
+
md: 'px-5 py-2.5 text-base',
|
|
244
|
+
lg: 'px-7 py-3 text-lg',
|
|
245
|
+
xl: 'px-9 py-4 text-xl',
|
|
246
|
+
};
|
|
247
|
+
const variantClasses = {
|
|
248
|
+
primary: 'bg-blue-600 text-white hover:bg-blue-700 border border-blue-600',
|
|
249
|
+
secondary: 'bg-gray-600 text-white hover:bg-gray-700 border border-gray-600',
|
|
250
|
+
outline: 'bg-transparent text-blue-600 hover:bg-blue-50 border border-blue-600',
|
|
251
|
+
ghost: 'bg-transparent text-blue-600 hover:bg-blue-50 border border-transparent',
|
|
252
|
+
};
|
|
253
|
+
const alignClass = s.alignment === 'center' ? 'text-center' : s.alignment === 'right' ? 'text-right' : 'text-left';
|
|
254
|
+
const target = s.openInNewTab ? ' target="_blank" rel="noopener noreferrer"' : '';
|
|
255
|
+
const btnClasses = `inline-block rounded-lg font-medium transition-colors duration-200 no-underline ${sizeClasses[s.size] || sizeClasses.md} ${variantClasses[s.variant] || variantClasses.primary} ${s.className || ''}`;
|
|
256
|
+
return ` <div class="${alignClass}">
|
|
257
|
+
<a href="${escAttr(s.url)}" class="${btnClasses}"${target}>${esc(s.text)}</a>
|
|
258
|
+
</div>`;
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
// 8. ICON (content)
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
export const iconElement = {
|
|
265
|
+
type: 'icon',
|
|
266
|
+
name: 'Icon',
|
|
267
|
+
category: 'content',
|
|
268
|
+
icon: '★',
|
|
269
|
+
description: 'Single icon display with name, size, and color',
|
|
270
|
+
keywords: ['icon', 'symbol', 'emoji'],
|
|
271
|
+
defaultSettings: {
|
|
272
|
+
name: '★',
|
|
273
|
+
size: '2rem',
|
|
274
|
+
color: '#1f2937',
|
|
275
|
+
alignment: 'center',
|
|
276
|
+
},
|
|
277
|
+
settingsSchema: z.object({
|
|
278
|
+
name: z.string().min(1),
|
|
279
|
+
size: z.string().default('2rem'),
|
|
280
|
+
color: z.string().default('#1f2937'),
|
|
281
|
+
alignment: z.enum(['left', 'center', 'right']).default('center'),
|
|
282
|
+
}),
|
|
283
|
+
render: (s) => {
|
|
284
|
+
const alignClass = s.alignment === 'center' ? 'text-center' : s.alignment === 'right' ? 'text-right' : 'text-left';
|
|
285
|
+
return ` <div class="foundry-icon ${alignClass}">
|
|
286
|
+
<span style="font-size: ${escAttr(s.size)}; color: ${escAttr(s.color)}; line-height: 1" role="img" aria-label="${escAttr(s.name)}">${esc(s.name)}</span>
|
|
287
|
+
</div>`;
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
// 9. IMAGE (media)
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
export const imageElement = {
|
|
294
|
+
type: 'image',
|
|
295
|
+
name: 'Image',
|
|
296
|
+
category: 'media',
|
|
297
|
+
icon: '🖼',
|
|
298
|
+
description: 'Single image with caption, sizing, and border radius',
|
|
299
|
+
keywords: ['image', 'photo', 'picture', 'img'],
|
|
300
|
+
defaultSettings: {
|
|
301
|
+
src: '',
|
|
302
|
+
alt: '',
|
|
303
|
+
width: '100%',
|
|
304
|
+
height: 'auto',
|
|
305
|
+
objectFit: 'cover',
|
|
306
|
+
borderRadius: '0',
|
|
307
|
+
caption: '',
|
|
308
|
+
alignment: 'center',
|
|
309
|
+
className: '',
|
|
310
|
+
},
|
|
311
|
+
settingsSchema: z.object({
|
|
312
|
+
src: z.string(),
|
|
313
|
+
alt: z.string().default(''),
|
|
314
|
+
width: z.string().default('100%'),
|
|
315
|
+
height: z.string().default('auto'),
|
|
316
|
+
objectFit: z.enum(['cover', 'contain', 'fill', 'none']).default('cover'),
|
|
317
|
+
borderRadius: z.string().default('0'),
|
|
318
|
+
caption: z.string().default(''),
|
|
319
|
+
alignment: z.enum(['left', 'center', 'right']).default('center'),
|
|
320
|
+
className: z.string().default(''),
|
|
321
|
+
}),
|
|
322
|
+
render: (s) => {
|
|
323
|
+
const alignClass = s.alignment === 'center' ? 'mx-auto text-center' : s.alignment === 'right' ? 'ml-auto text-right' : 'text-left';
|
|
324
|
+
const imgStyle = `width: ${escAttr(s.width)}; height: ${escAttr(s.height)}; object-fit: ${escAttr(s.objectFit)}; border-radius: ${escAttr(s.borderRadius)}`;
|
|
325
|
+
const captionHtml = s.caption
|
|
326
|
+
? `\n <figcaption class="mt-2 text-sm text-gray-500 ${s.alignment === 'center' ? 'text-center' : ''}">${esc(s.caption)}</figcaption>`
|
|
327
|
+
: '';
|
|
328
|
+
if (!s.src) {
|
|
329
|
+
return ` <figure class="foundry-image ${alignClass} ${s.className || ''}">
|
|
330
|
+
<div class="bg-gray-100 flex items-center justify-center" style="width: ${escAttr(s.width)}; height: 200px; border-radius: ${escAttr(s.borderRadius)}">
|
|
331
|
+
<span class="text-gray-400 text-sm">No image selected</span>
|
|
332
|
+
</div>${captionHtml}
|
|
333
|
+
</figure>`;
|
|
334
|
+
}
|
|
335
|
+
return ` <figure class="foundry-image ${alignClass} ${s.className || ''}">
|
|
336
|
+
<img src="${escAttr(s.src)}" alt="${escAttr(s.alt)}" style="${imgStyle}" loading="lazy" />${captionHtml}
|
|
337
|
+
</figure>`;
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
// 10. GALLERY (media)
|
|
342
|
+
// ---------------------------------------------------------------------------
|
|
343
|
+
const galleryImageSchema = z.object({
|
|
344
|
+
src: z.string(),
|
|
345
|
+
alt: z.string().default(''),
|
|
346
|
+
caption: z.string().default(''),
|
|
347
|
+
});
|
|
348
|
+
export const galleryElement = {
|
|
349
|
+
type: 'gallery',
|
|
350
|
+
name: 'Gallery',
|
|
351
|
+
category: 'media',
|
|
352
|
+
icon: '◫',
|
|
353
|
+
description: 'Image gallery with grid layout, columns, gap, and lightbox',
|
|
354
|
+
keywords: ['gallery', 'photos', 'images', 'grid'],
|
|
355
|
+
defaultSettings: {
|
|
356
|
+
images: [],
|
|
357
|
+
columns: 3,
|
|
358
|
+
gap: '1rem',
|
|
359
|
+
borderRadius: '0.5rem',
|
|
360
|
+
lightbox: true,
|
|
361
|
+
},
|
|
362
|
+
settingsSchema: z.object({
|
|
363
|
+
images: z.array(galleryImageSchema).default([]),
|
|
364
|
+
columns: z.number().min(1).max(6).default(3),
|
|
365
|
+
gap: z.string().default('1rem'),
|
|
366
|
+
borderRadius: z.string().default('0.5rem'),
|
|
367
|
+
lightbox: z.boolean().default(true),
|
|
368
|
+
}),
|
|
369
|
+
render: (s) => {
|
|
370
|
+
if (!s.images || s.images.length === 0) {
|
|
371
|
+
return ` <div class="foundry-gallery bg-gray-50 rounded-lg p-8 text-center text-gray-400">
|
|
372
|
+
<p>No images added to gallery</p>
|
|
373
|
+
</div>`;
|
|
374
|
+
}
|
|
375
|
+
const items = s.images
|
|
376
|
+
.map((img, i) => {
|
|
377
|
+
const lightboxAttrs = s.lightbox ? ` data-lightbox="gallery" data-index="${i}"` : '';
|
|
378
|
+
const captionHtml = img.caption ? `<figcaption class="mt-1 text-xs text-gray-500">${esc(img.caption)}</figcaption>` : '';
|
|
379
|
+
return ` <figure class="foundry-gallery-item overflow-hidden" style="border-radius: ${escAttr(s.borderRadius)}">
|
|
380
|
+
<img src="${escAttr(img.src)}" alt="${escAttr(img.alt)}" class="w-full h-48 object-cover cursor-pointer hover:opacity-90 transition-opacity" loading="lazy"${lightboxAttrs} style="border-radius: ${escAttr(s.borderRadius)}" />${captionHtml}
|
|
381
|
+
</figure>`;
|
|
382
|
+
})
|
|
383
|
+
.join('\n');
|
|
384
|
+
return ` <div class="foundry-gallery" style="display: grid; grid-template-columns: repeat(${s.columns}, 1fr); gap: ${escAttr(s.gap)}">
|
|
385
|
+
${items}
|
|
386
|
+
</div>`;
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
// 11. VIDEO (media)
|
|
391
|
+
// ---------------------------------------------------------------------------
|
|
392
|
+
export const videoElement = {
|
|
393
|
+
type: 'video',
|
|
394
|
+
name: 'Video',
|
|
395
|
+
category: 'media',
|
|
396
|
+
icon: '▶',
|
|
397
|
+
description: 'Video embed (YouTube, Vimeo, or self-hosted)',
|
|
398
|
+
keywords: ['video', 'youtube', 'vimeo', 'embed', 'movie'],
|
|
399
|
+
defaultSettings: {
|
|
400
|
+
url: '',
|
|
401
|
+
autoplay: false,
|
|
402
|
+
loop: false,
|
|
403
|
+
controls: true,
|
|
404
|
+
muted: false,
|
|
405
|
+
aspectRatio: '16/9',
|
|
406
|
+
maxWidth: '100%',
|
|
407
|
+
},
|
|
408
|
+
settingsSchema: z.object({
|
|
409
|
+
url: z.string(),
|
|
410
|
+
autoplay: z.boolean().default(false),
|
|
411
|
+
loop: z.boolean().default(false),
|
|
412
|
+
controls: z.boolean().default(true),
|
|
413
|
+
muted: z.boolean().default(false),
|
|
414
|
+
aspectRatio: z.string().default('16/9'),
|
|
415
|
+
maxWidth: z.string().default('100%'),
|
|
416
|
+
}),
|
|
417
|
+
render: (s) => {
|
|
418
|
+
if (!s.url) {
|
|
419
|
+
return ` <div class="foundry-video bg-gray-900 rounded-lg flex items-center justify-center" style="aspect-ratio: ${escAttr(s.aspectRatio)}; max-width: ${escAttr(s.maxWidth)}">
|
|
420
|
+
<span class="text-gray-400">No video URL provided</span>
|
|
421
|
+
</div>`;
|
|
422
|
+
}
|
|
423
|
+
// YouTube detection
|
|
424
|
+
const ytMatch = s.url.match(/(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/);
|
|
425
|
+
if (ytMatch) {
|
|
426
|
+
const params = [];
|
|
427
|
+
if (s.autoplay)
|
|
428
|
+
params.push('autoplay=1');
|
|
429
|
+
if (s.loop)
|
|
430
|
+
params.push(`loop=1&playlist=${ytMatch[1]}`);
|
|
431
|
+
if (!s.controls)
|
|
432
|
+
params.push('controls=0');
|
|
433
|
+
if (s.muted)
|
|
434
|
+
params.push('mute=1');
|
|
435
|
+
const query = params.length > 0 ? `?${params.join('&')}` : '';
|
|
436
|
+
return ` <div class="foundry-video" style="max-width: ${escAttr(s.maxWidth)}">
|
|
437
|
+
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
|
|
438
|
+
<iframe src="https://www.youtube.com/embed/${ytMatch[1]}${query}" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen loading="lazy" title="Video"></iframe>
|
|
439
|
+
</div>
|
|
440
|
+
</div>`;
|
|
441
|
+
}
|
|
442
|
+
// Vimeo detection
|
|
443
|
+
const vimeoMatch = s.url.match(/vimeo\.com\/(\d+)/);
|
|
444
|
+
if (vimeoMatch) {
|
|
445
|
+
const params = [];
|
|
446
|
+
if (s.autoplay)
|
|
447
|
+
params.push('autoplay=1');
|
|
448
|
+
if (s.loop)
|
|
449
|
+
params.push('loop=1');
|
|
450
|
+
if (s.muted)
|
|
451
|
+
params.push('muted=1');
|
|
452
|
+
const query = params.length > 0 ? `?${params.join('&')}` : '';
|
|
453
|
+
return ` <div class="foundry-video" style="max-width: ${escAttr(s.maxWidth)}">
|
|
454
|
+
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
|
|
455
|
+
<iframe src="https://player.vimeo.com/video/${vimeoMatch[1]}${query}" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen loading="lazy" title="Video"></iframe>
|
|
456
|
+
</div>
|
|
457
|
+
</div>`;
|
|
458
|
+
}
|
|
459
|
+
// Self-hosted video
|
|
460
|
+
const attrs = [];
|
|
461
|
+
if (s.autoplay)
|
|
462
|
+
attrs.push('autoplay');
|
|
463
|
+
if (s.loop)
|
|
464
|
+
attrs.push('loop');
|
|
465
|
+
if (s.controls)
|
|
466
|
+
attrs.push('controls');
|
|
467
|
+
if (s.muted)
|
|
468
|
+
attrs.push('muted');
|
|
469
|
+
return ` <div class="foundry-video" style="max-width: ${escAttr(s.maxWidth)}">
|
|
470
|
+
<video class="w-full rounded-lg" style="aspect-ratio: ${escAttr(s.aspectRatio)}" ${attrs.join(' ')} playsinline>
|
|
471
|
+
<source src="${escAttr(s.url)}" />
|
|
472
|
+
Your browser does not support the video tag.
|
|
473
|
+
</video>
|
|
474
|
+
</div>`;
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
// ---------------------------------------------------------------------------
|
|
478
|
+
// 12. PRICING TABLE (data)
|
|
479
|
+
// ---------------------------------------------------------------------------
|
|
480
|
+
const pricingFeatureSchema = z.object({
|
|
481
|
+
text: z.string(),
|
|
482
|
+
included: z.boolean().default(true),
|
|
483
|
+
});
|
|
484
|
+
export const pricingTableElement = {
|
|
485
|
+
type: 'pricing-table',
|
|
486
|
+
name: 'Pricing Table',
|
|
487
|
+
category: 'data',
|
|
488
|
+
icon: '$',
|
|
489
|
+
description: 'Pricing card with name, price, features, and CTA',
|
|
490
|
+
keywords: ['pricing', 'price', 'plan', 'subscription', 'tier'],
|
|
491
|
+
defaultSettings: {
|
|
492
|
+
name: 'Pro Plan',
|
|
493
|
+
price: '$29',
|
|
494
|
+
period: '/month',
|
|
495
|
+
description: 'Everything you need to get started',
|
|
496
|
+
features: [
|
|
497
|
+
{ text: 'Unlimited projects', included: true },
|
|
498
|
+
{ text: 'Priority support', included: true },
|
|
499
|
+
{ text: 'Custom domain', included: true },
|
|
500
|
+
{ text: 'Analytics dashboard', included: false },
|
|
501
|
+
],
|
|
502
|
+
buttonText: 'Get Started',
|
|
503
|
+
buttonUrl: '#',
|
|
504
|
+
highlighted: false,
|
|
505
|
+
highlightLabel: 'Most Popular',
|
|
506
|
+
},
|
|
507
|
+
settingsSchema: z.object({
|
|
508
|
+
name: z.string(),
|
|
509
|
+
price: z.string(),
|
|
510
|
+
period: z.string().default('/month'),
|
|
511
|
+
description: z.string().default(''),
|
|
512
|
+
features: z.array(pricingFeatureSchema).default([]),
|
|
513
|
+
buttonText: z.string().default('Get Started'),
|
|
514
|
+
buttonUrl: z.string().default('#'),
|
|
515
|
+
highlighted: z.boolean().default(false),
|
|
516
|
+
highlightLabel: z.string().default('Most Popular'),
|
|
517
|
+
}),
|
|
518
|
+
render: (s) => {
|
|
519
|
+
const borderClass = s.highlighted ? 'border-2 border-blue-600 shadow-xl scale-105' : 'border border-gray-200 shadow-md';
|
|
520
|
+
const btnClass = s.highlighted
|
|
521
|
+
? 'bg-blue-600 text-white hover:bg-blue-700'
|
|
522
|
+
: 'bg-gray-900 text-white hover:bg-gray-800';
|
|
523
|
+
const badge = s.highlighted
|
|
524
|
+
? `\n <span class="inline-block bg-blue-600 text-white text-xs font-semibold px-3 py-1 rounded-full mb-4">${esc(s.highlightLabel)}</span>`
|
|
525
|
+
: '';
|
|
526
|
+
const featuresHtml = s.features
|
|
527
|
+
.map((f) => {
|
|
528
|
+
const icon = f.included
|
|
529
|
+
? '<svg class="w-5 h-5 text-green-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>'
|
|
530
|
+
: '<svg class="w-5 h-5 text-gray-300 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>';
|
|
531
|
+
const textClass = f.included ? 'text-gray-700' : 'text-gray-400 line-through';
|
|
532
|
+
return ` <li class="flex items-center gap-3">${icon}<span class="${textClass}">${esc(f.text)}</span></li>`;
|
|
533
|
+
})
|
|
534
|
+
.join('\n');
|
|
535
|
+
return ` <div class="foundry-pricing-table rounded-2xl p-8 bg-white ${borderClass} relative">
|
|
536
|
+
<div class="text-center">${badge}
|
|
537
|
+
<h3 class="text-xl font-bold text-gray-900">${esc(s.name)}</h3>
|
|
538
|
+
${s.description ? `<p class="mt-2 text-sm text-gray-500">${esc(s.description)}</p>` : ''}
|
|
539
|
+
<div class="mt-6 flex items-baseline justify-center gap-1">
|
|
540
|
+
<span class="text-5xl font-extrabold text-gray-900">${esc(s.price)}</span>
|
|
541
|
+
<span class="text-gray-500">${esc(s.period)}</span>
|
|
542
|
+
</div>
|
|
543
|
+
</div>
|
|
544
|
+
<ul class="mt-8 space-y-4">
|
|
545
|
+
${featuresHtml}
|
|
546
|
+
</ul>
|
|
547
|
+
<a href="${escAttr(s.buttonUrl)}" class="mt-8 block w-full text-center rounded-lg px-6 py-3 font-semibold transition-colors ${btnClass} no-underline">${esc(s.buttonText)}</a>
|
|
548
|
+
</div>`;
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
// ---------------------------------------------------------------------------
|
|
552
|
+
// 13. TESTIMONIAL (data)
|
|
553
|
+
// ---------------------------------------------------------------------------
|
|
554
|
+
export const testimonialElement = {
|
|
555
|
+
type: 'testimonial',
|
|
556
|
+
name: 'Testimonial',
|
|
557
|
+
category: 'data',
|
|
558
|
+
icon: '❝',
|
|
559
|
+
description: 'Quote with author, title, photo, and star rating',
|
|
560
|
+
keywords: ['testimonial', 'quote', 'review', 'feedback'],
|
|
561
|
+
defaultSettings: {
|
|
562
|
+
text: 'This product completely transformed our workflow. Highly recommended!',
|
|
563
|
+
authorName: 'Jane Smith',
|
|
564
|
+
authorTitle: 'CEO, Acme Corp',
|
|
565
|
+
authorImage: '',
|
|
566
|
+
rating: 5,
|
|
567
|
+
variant: 'card',
|
|
568
|
+
},
|
|
569
|
+
settingsSchema: z.object({
|
|
570
|
+
text: z.string(),
|
|
571
|
+
authorName: z.string(),
|
|
572
|
+
authorTitle: z.string().default(''),
|
|
573
|
+
authorImage: z.string().default(''),
|
|
574
|
+
rating: z.number().min(0).max(5).default(5),
|
|
575
|
+
variant: z.enum(['card', 'minimal', 'large']).default('card'),
|
|
576
|
+
}),
|
|
577
|
+
render: (s) => {
|
|
578
|
+
const stars = s.rating > 0
|
|
579
|
+
? `<div class="flex gap-0.5 mb-4">${Array.from({ length: 5 }, (_, i) => `<svg class="w-5 h-5 ${i < s.rating ? 'text-yellow-400' : 'text-gray-200'}" fill="currentColor" viewBox="0 0 20 20"><path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path></svg>`).join('')}</div>`
|
|
580
|
+
: '';
|
|
581
|
+
const avatar = s.authorImage
|
|
582
|
+
? `<img src="${escAttr(s.authorImage)}" alt="${escAttr(s.authorName)}" class="w-12 h-12 rounded-full object-cover" loading="lazy" />`
|
|
583
|
+
: `<div class="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 font-bold text-lg">${esc(s.authorName.charAt(0).toUpperCase())}</div>`;
|
|
584
|
+
const authorTitleHtml = s.authorTitle ? `<p class="text-sm text-gray-500">${esc(s.authorTitle)}</p>` : '';
|
|
585
|
+
if (s.variant === 'large') {
|
|
586
|
+
return ` <blockquote class="foundry-testimonial text-center max-w-2xl mx-auto">
|
|
587
|
+
${stars}
|
|
588
|
+
<p class="text-2xl font-medium text-gray-900 italic leading-relaxed">"${esc(s.text)}"</p>
|
|
589
|
+
<footer class="mt-6 flex flex-col items-center gap-3">
|
|
590
|
+
${avatar}
|
|
591
|
+
<div>
|
|
592
|
+
<cite class="text-base font-semibold text-gray-900 not-italic">${esc(s.authorName)}</cite>
|
|
593
|
+
${authorTitleHtml}
|
|
594
|
+
</div>
|
|
595
|
+
</footer>
|
|
596
|
+
</blockquote>`;
|
|
597
|
+
}
|
|
598
|
+
if (s.variant === 'minimal') {
|
|
599
|
+
return ` <blockquote class="foundry-testimonial border-l-4 border-blue-600 pl-6 py-2">
|
|
600
|
+
<p class="text-gray-700 italic">"${esc(s.text)}"</p>
|
|
601
|
+
<footer class="mt-3 flex items-center gap-3">
|
|
602
|
+
${avatar}
|
|
603
|
+
<div>
|
|
604
|
+
<cite class="text-sm font-semibold text-gray-900 not-italic">${esc(s.authorName)}</cite>
|
|
605
|
+
${authorTitleHtml}
|
|
606
|
+
</div>
|
|
607
|
+
</footer>
|
|
608
|
+
</blockquote>`;
|
|
609
|
+
}
|
|
610
|
+
// card (default)
|
|
611
|
+
return ` <blockquote class="foundry-testimonial bg-white rounded-xl shadow-md p-6 border border-gray-100">
|
|
612
|
+
${stars}
|
|
613
|
+
<p class="text-gray-700 leading-relaxed">"${esc(s.text)}"</p>
|
|
614
|
+
<footer class="mt-4 flex items-center gap-3 pt-4 border-t border-gray-100">
|
|
615
|
+
${avatar}
|
|
616
|
+
<div>
|
|
617
|
+
<cite class="text-sm font-semibold text-gray-900 not-italic">${esc(s.authorName)}</cite>
|
|
618
|
+
${authorTitleHtml}
|
|
619
|
+
</div>
|
|
620
|
+
</footer>
|
|
621
|
+
</blockquote>`;
|
|
622
|
+
},
|
|
623
|
+
};
|
|
624
|
+
// ---------------------------------------------------------------------------
|
|
625
|
+
// 14. ACCORDION (interactive)
|
|
626
|
+
// ---------------------------------------------------------------------------
|
|
627
|
+
const accordionItemSchema = z.object({
|
|
628
|
+
title: z.string(),
|
|
629
|
+
content: z.string(),
|
|
630
|
+
});
|
|
631
|
+
export const accordionElement = {
|
|
632
|
+
type: 'accordion',
|
|
633
|
+
name: 'Accordion',
|
|
634
|
+
category: 'interactive',
|
|
635
|
+
icon: '≡',
|
|
636
|
+
description: 'Collapsible FAQ/content sections',
|
|
637
|
+
keywords: ['accordion', 'faq', 'collapse', 'toggle', 'expandable'],
|
|
638
|
+
defaultSettings: {
|
|
639
|
+
items: [
|
|
640
|
+
{ title: 'What is your refund policy?', content: 'We offer a full refund within 30 days of purchase. No questions asked.' },
|
|
641
|
+
{ title: 'How do I get started?', content: 'Simply create an account and follow the onboarding wizard. You will be up and running in minutes.' },
|
|
642
|
+
{ title: 'Do you offer support?', content: 'Yes! We provide 24/7 email support and live chat during business hours.' },
|
|
643
|
+
],
|
|
644
|
+
allowMultiple: false,
|
|
645
|
+
variant: 'bordered',
|
|
646
|
+
iconPosition: 'right',
|
|
647
|
+
},
|
|
648
|
+
settingsSchema: z.object({
|
|
649
|
+
items: z.array(accordionItemSchema).default([]),
|
|
650
|
+
allowMultiple: z.boolean().default(false),
|
|
651
|
+
variant: z.enum(['bordered', 'separated', 'minimal']).default('bordered'),
|
|
652
|
+
iconPosition: z.enum(['left', 'right']).default('right'),
|
|
653
|
+
}),
|
|
654
|
+
render: (s) => {
|
|
655
|
+
if (!s.items || s.items.length === 0) {
|
|
656
|
+
return ` <div class="foundry-accordion text-gray-400 text-center p-4">No accordion items</div>`;
|
|
657
|
+
}
|
|
658
|
+
const chevron = '<svg class="w-5 h-5 transition-transform duration-200 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>';
|
|
659
|
+
const wrapperClass = s.variant === 'separated'
|
|
660
|
+
? 'space-y-3'
|
|
661
|
+
: s.variant === 'minimal'
|
|
662
|
+
? 'divide-y divide-gray-100'
|
|
663
|
+
: 'border border-gray-200 rounded-lg divide-y divide-gray-200';
|
|
664
|
+
const itemClasses = s.variant === 'separated'
|
|
665
|
+
? 'border border-gray-200 rounded-lg overflow-hidden'
|
|
666
|
+
: '';
|
|
667
|
+
const items = s.items
|
|
668
|
+
.map((item, i) => {
|
|
669
|
+
const flexDir = s.iconPosition === 'left' ? 'flex-row-reverse' : 'flex-row';
|
|
670
|
+
return ` <details class="foundry-accordion-item group ${itemClasses}"${i === 0 ? ' open' : ''}>
|
|
671
|
+
<summary class="flex ${flexDir} items-center justify-between gap-3 cursor-pointer list-none p-4 hover:bg-gray-50 transition-colors font-medium text-gray-900 [&::-webkit-details-marker]:hidden">
|
|
672
|
+
<span>${esc(item.title)}</span>
|
|
673
|
+
<span class="group-open:rotate-180 transition-transform">${chevron}</span>
|
|
674
|
+
</summary>
|
|
675
|
+
<div class="px-4 pb-4 text-gray-600 leading-relaxed">${item.content}</div>
|
|
676
|
+
</details>`;
|
|
677
|
+
})
|
|
678
|
+
.join('\n');
|
|
679
|
+
return ` <div class="foundry-accordion ${wrapperClass}" data-allow-multiple="${s.allowMultiple}">
|
|
680
|
+
${items}
|
|
681
|
+
</div>`;
|
|
682
|
+
},
|
|
683
|
+
};
|
|
684
|
+
// ---------------------------------------------------------------------------
|
|
685
|
+
// 15. CALL TO ACTION (interactive)
|
|
686
|
+
// ---------------------------------------------------------------------------
|
|
687
|
+
export const callToActionElement = {
|
|
688
|
+
type: 'call-to-action',
|
|
689
|
+
name: 'Call to Action',
|
|
690
|
+
category: 'interactive',
|
|
691
|
+
icon: '📢',
|
|
692
|
+
description: 'Full-width CTA section with heading, subheading, and button',
|
|
693
|
+
keywords: ['cta', 'call to action', 'banner', 'promotion'],
|
|
694
|
+
defaultSettings: {
|
|
695
|
+
heading: 'Ready to get started?',
|
|
696
|
+
subheading: 'Join thousands of happy customers and transform your business today.',
|
|
697
|
+
buttonText: 'Start Free Trial',
|
|
698
|
+
buttonUrl: '#',
|
|
699
|
+
alignment: 'center',
|
|
700
|
+
backgroundColor: '#1e40af',
|
|
701
|
+
textColor: '#ffffff',
|
|
702
|
+
buttonVariant: 'light',
|
|
703
|
+
padding: '4rem 2rem',
|
|
704
|
+
borderRadius: '1rem',
|
|
705
|
+
},
|
|
706
|
+
settingsSchema: z.object({
|
|
707
|
+
heading: z.string(),
|
|
708
|
+
subheading: z.string().default(''),
|
|
709
|
+
buttonText: z.string().default('Get Started'),
|
|
710
|
+
buttonUrl: z.string().default('#'),
|
|
711
|
+
alignment: z.enum(['left', 'center', 'right']).default('center'),
|
|
712
|
+
backgroundColor: z.string().default('#1e40af'),
|
|
713
|
+
textColor: z.string().default('#ffffff'),
|
|
714
|
+
buttonVariant: z.enum(['light', 'outline', 'dark']).default('light'),
|
|
715
|
+
padding: z.string().default('4rem 2rem'),
|
|
716
|
+
borderRadius: z.string().default('1rem'),
|
|
717
|
+
}),
|
|
718
|
+
render: (s) => {
|
|
719
|
+
const alignClass = s.alignment === 'center' ? 'text-center' : s.alignment === 'right' ? 'text-right' : 'text-left';
|
|
720
|
+
const btnAlign = s.alignment === 'center' ? 'mx-auto' : s.alignment === 'right' ? 'ml-auto' : '';
|
|
721
|
+
const btnVariants = {
|
|
722
|
+
light: 'bg-white text-gray-900 hover:bg-gray-100',
|
|
723
|
+
outline: `bg-transparent border-2 border-white hover:bg-white/10`,
|
|
724
|
+
dark: 'bg-gray-900 text-white hover:bg-gray-800',
|
|
725
|
+
};
|
|
726
|
+
const btnClass = btnVariants[s.buttonVariant] || btnVariants.light;
|
|
727
|
+
const subheadingHtml = s.subheading
|
|
728
|
+
? `\n <p class="mt-4 text-lg opacity-90 max-w-2xl ${s.alignment === 'center' ? 'mx-auto' : ''}">${esc(s.subheading)}</p>`
|
|
729
|
+
: '';
|
|
730
|
+
return ` <div class="foundry-cta rounded-2xl ${alignClass}" style="background-color: ${escAttr(s.backgroundColor)}; color: ${escAttr(s.textColor)}; padding: ${escAttr(s.padding)}; border-radius: ${escAttr(s.borderRadius)}">
|
|
731
|
+
<div class="max-w-4xl ${s.alignment === 'center' ? 'mx-auto' : ''}">
|
|
732
|
+
<h2 class="text-3xl font-bold sm:text-4xl">${esc(s.heading)}</h2>${subheadingHtml}
|
|
733
|
+
<div class="mt-8">
|
|
734
|
+
<a href="${escAttr(s.buttonUrl)}" class="inline-block ${btnAlign} rounded-lg px-8 py-3.5 font-semibold text-lg transition-colors no-underline ${btnClass}" style="color: ${s.buttonVariant === 'outline' ? escAttr(s.textColor) : ''}">${esc(s.buttonText)}</a>
|
|
735
|
+
</div>
|
|
736
|
+
</div>
|
|
737
|
+
</div>`;
|
|
738
|
+
},
|
|
739
|
+
};
|
|
740
|
+
// ---------------------------------------------------------------------------
|
|
741
|
+
// All default elements
|
|
742
|
+
// ---------------------------------------------------------------------------
|
|
743
|
+
export const defaultElements = [
|
|
744
|
+
rowElement,
|
|
745
|
+
columnElement,
|
|
746
|
+
spacerElement,
|
|
747
|
+
dividerElement,
|
|
748
|
+
headingElement,
|
|
749
|
+
textBlockElement,
|
|
750
|
+
buttonElement,
|
|
751
|
+
iconElement,
|
|
752
|
+
imageElement,
|
|
753
|
+
galleryElement,
|
|
754
|
+
videoElement,
|
|
755
|
+
pricingTableElement,
|
|
756
|
+
testimonialElement,
|
|
757
|
+
accordionElement,
|
|
758
|
+
callToActionElement,
|
|
759
|
+
];
|
|
760
|
+
/**
|
|
761
|
+
* Register all default elements into an ElementRegistry instance.
|
|
762
|
+
*/
|
|
763
|
+
export function registerDefaultElements(registry) {
|
|
764
|
+
for (const element of defaultElements) {
|
|
765
|
+
if (!registry.has(element.type)) {
|
|
766
|
+
registry.register(element);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
//# sourceMappingURL=index.js.map
|