@damarkuncoro/landing-page 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/dist/index.mjs ADDED
@@ -0,0 +1,2230 @@
1
+ // src/core/theme.ts
2
+ var defaultTheme = {
3
+ colors: {
4
+ primary: "#3b82f6",
5
+ secondary: "#8b5cf6",
6
+ accent: "#10b981",
7
+ background: "#ffffff",
8
+ text: "#1f2937",
9
+ muted: "#6b7280"
10
+ },
11
+ spacing: {
12
+ xs: "0.25rem",
13
+ sm: "0.5rem",
14
+ md: "1rem",
15
+ lg: "1.5rem",
16
+ xl: "2rem"
17
+ },
18
+ fonts: {
19
+ heading: "system-ui, -apple-system, sans-serif",
20
+ body: "system-ui, -apple-system, sans-serif",
21
+ mono: "monospace"
22
+ },
23
+ breakpoints: {
24
+ sm: "640px",
25
+ md: "768px",
26
+ lg: "1024px",
27
+ xl: "1280px"
28
+ }
29
+ };
30
+ function createTheme(config) {
31
+ return {
32
+ ...defaultTheme,
33
+ ...config,
34
+ colors: {
35
+ ...defaultTheme.colors,
36
+ ...config?.colors
37
+ },
38
+ spacing: {
39
+ ...defaultTheme.spacing,
40
+ ...config?.spacing
41
+ },
42
+ fonts: {
43
+ ...defaultTheme.fonts,
44
+ ...config?.fonts
45
+ },
46
+ breakpoints: {
47
+ ...defaultTheme.breakpoints,
48
+ ...config?.breakpoints
49
+ }
50
+ };
51
+ }
52
+
53
+ // src/schema/validate.ts
54
+ import Ajv from "ajv";
55
+ import addFormats from "ajv-formats";
56
+
57
+ // src/schema/landingPageSchema.ts
58
+ var landingPageSchema = {
59
+ $id: "urn:landing-page:schema",
60
+ title: "Landing Page",
61
+ description: "Configuration for a config-driven landing page",
62
+ type: "object",
63
+ properties: {
64
+ id: {
65
+ type: "string",
66
+ description: "Unique identifier for the landing page"
67
+ },
68
+ className: {
69
+ type: "string",
70
+ description: "CSS class name for the landing page container"
71
+ },
72
+ title: {
73
+ type: "string",
74
+ description: "Page title for SEO and accessibility",
75
+ minLength: 1
76
+ },
77
+ description: {
78
+ type: "string",
79
+ description: "Page description for SEO",
80
+ minLength: 1
81
+ },
82
+ sections: {
83
+ type: "array",
84
+ description: "Sections that make up the landing page",
85
+ minItems: 1,
86
+ items: {
87
+ $ref: "#/definitions/Section"
88
+ }
89
+ },
90
+ theme: {
91
+ $ref: "#/definitions/Theme"
92
+ }
93
+ },
94
+ required: ["title", "description", "sections"],
95
+ definitions: {
96
+ Section: {
97
+ type: "object",
98
+ properties: {
99
+ id: {
100
+ type: "string",
101
+ description: "Unique identifier for the section"
102
+ },
103
+ className: {
104
+ type: "string",
105
+ description: "CSS class name for the section"
106
+ },
107
+ type: {
108
+ type: "string",
109
+ description: "Section type",
110
+ enum: ["hero", "features", "testimonials", "pricing", "cta", "footer", "stats", "faq", "header"]
111
+ },
112
+ config: {
113
+ type: "object",
114
+ description: "Section configuration"
115
+ }
116
+ },
117
+ required: ["type", "config"]
118
+ },
119
+ Theme: {
120
+ type: "object",
121
+ properties: {
122
+ colors: {
123
+ $ref: "#/definitions/Colors"
124
+ },
125
+ spacing: {
126
+ $ref: "#/definitions/Spacing"
127
+ },
128
+ fonts: {
129
+ $ref: "#/definitions/Fonts"
130
+ },
131
+ breakpoints: {
132
+ $ref: "#/definitions/Breakpoints"
133
+ }
134
+ }
135
+ },
136
+ Colors: {
137
+ type: "object",
138
+ properties: {
139
+ primary: {
140
+ type: "string",
141
+ description: "Primary brand color",
142
+ pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
143
+ },
144
+ secondary: {
145
+ type: "string",
146
+ description: "Secondary brand color",
147
+ pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
148
+ },
149
+ accent: {
150
+ type: "string",
151
+ description: "Accent color",
152
+ pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
153
+ },
154
+ background: {
155
+ type: "string",
156
+ description: "Background color",
157
+ pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
158
+ },
159
+ text: {
160
+ type: "string",
161
+ description: "Text color",
162
+ pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
163
+ },
164
+ muted: {
165
+ type: "string",
166
+ description: "Muted text color",
167
+ pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
168
+ }
169
+ },
170
+ required: ["primary", "secondary", "accent", "background", "text", "muted"]
171
+ },
172
+ Spacing: {
173
+ type: "object",
174
+ properties: {
175
+ xs: {
176
+ type: "string",
177
+ description: 'Extra small spacing unit (e.g., "4px")'
178
+ },
179
+ sm: {
180
+ type: "string",
181
+ description: 'Small spacing unit (e.g., "8px")'
182
+ },
183
+ md: {
184
+ type: "string",
185
+ description: 'Medium spacing unit (e.g., "16px")'
186
+ },
187
+ lg: {
188
+ type: "string",
189
+ description: 'Large spacing unit (e.g., "24px")'
190
+ },
191
+ xl: {
192
+ type: "string",
193
+ description: 'Extra large spacing unit (e.g., "32px")'
194
+ }
195
+ },
196
+ required: ["xs", "sm", "md", "lg", "xl"]
197
+ },
198
+ Fonts: {
199
+ type: "object",
200
+ properties: {
201
+ heading: {
202
+ type: "string",
203
+ description: 'Font family for headings (e.g., "Roboto, sans-serif")'
204
+ },
205
+ body: {
206
+ type: "string",
207
+ description: 'Font family for body text (e.g., "Open Sans, sans-serif")'
208
+ },
209
+ mono: {
210
+ type: "string",
211
+ description: 'Font family for monospace text (e.g., "Fira Code, monospace")'
212
+ }
213
+ },
214
+ required: ["heading", "body", "mono"]
215
+ },
216
+ Breakpoints: {
217
+ type: "object",
218
+ properties: {
219
+ sm: {
220
+ type: "string",
221
+ description: 'Small breakpoint (e.g., "640px")'
222
+ },
223
+ md: {
224
+ type: "string",
225
+ description: 'Medium breakpoint (e.g., "768px")'
226
+ },
227
+ lg: {
228
+ type: "string",
229
+ description: 'Large breakpoint (e.g., "1024px")'
230
+ },
231
+ xl: {
232
+ type: "string",
233
+ description: 'Extra large breakpoint (e.g., "1280px")'
234
+ }
235
+ },
236
+ required: ["sm", "md", "lg", "xl"]
237
+ },
238
+ ButtonConfig: {
239
+ type: "object",
240
+ properties: {
241
+ text: { type: "string" },
242
+ url: { type: "string", format: "uri" },
243
+ variant: { type: "string", enum: ["primary", "secondary", "outline", "ghost"] },
244
+ size: { type: "string", enum: ["sm", "md", "lg"] },
245
+ target: { type: "string", enum: ["_blank", "_self"] }
246
+ },
247
+ required: ["text", "url", "variant", "size"]
248
+ }
249
+ }
250
+ };
251
+
252
+ // src/schema/sectionConfigSchemas.ts
253
+ var sectionConfigSchemas = {
254
+ // Placeholder for individual section schemas
255
+ // These will be populated with actual JSON schemas for each section type (HeroConfig, FeatureConfig, etc.)
256
+ hero: {
257
+ type: "object",
258
+ properties: {
259
+ title: { type: "string" },
260
+ subtitle: { type: "string" },
261
+ image: { type: "string", format: "uri" },
262
+ video: { type: "string", format: "uri" },
263
+ buttons: {
264
+ type: "array",
265
+ items: { $ref: "urn:landing-page:schema#/definitions/ButtonConfig" }
266
+ },
267
+ alignment: { type: "string", enum: ["left", "center", "right"] }
268
+ },
269
+ required: ["title", "subtitle", "buttons"]
270
+ },
271
+ features: {
272
+ type: "object",
273
+ properties: {
274
+ title: { type: "string" },
275
+ description: { type: "string" },
276
+ icon: { type: "string" },
277
+ image: { type: "string", format: "uri" }
278
+ },
279
+ required: ["title", "description"]
280
+ },
281
+ testimonials: {
282
+ type: "object",
283
+ properties: {
284
+ quote: { type: "string" },
285
+ author: { type: "string" },
286
+ role: { type: "string" },
287
+ avatar: { type: "string", format: "uri" }
288
+ },
289
+ required: ["quote", "author"]
290
+ },
291
+ pricing: {
292
+ type: "object",
293
+ properties: {
294
+ plans: {
295
+ type: "array",
296
+ items: {
297
+ type: "object",
298
+ properties: {
299
+ id: { type: "string" },
300
+ title: { type: "string" },
301
+ description: { type: "string" },
302
+ price: { type: "number" },
303
+ period: { type: "string" },
304
+ features: {
305
+ type: "array",
306
+ items: { type: "string" }
307
+ },
308
+ button: { $ref: "urn:landing-page:schema#/definitions/ButtonConfig" },
309
+ featured: { type: "boolean" }
310
+ },
311
+ required: ["title", "description", "price", "features", "button"]
312
+ }
313
+ }
314
+ },
315
+ required: ["plans"]
316
+ },
317
+ cta: {
318
+ type: "object",
319
+ properties: {
320
+ title: { type: "string" },
321
+ description: { type: "string" },
322
+ button: { $ref: "urn:landing-page:schema#/definitions/ButtonConfig" },
323
+ image: { type: "string", format: "uri" }
324
+ },
325
+ required: ["title", "description", "button"]
326
+ },
327
+ footer: {
328
+ type: "object",
329
+ properties: {
330
+ logo: { type: "string", format: "uri" },
331
+ title: { type: "string" },
332
+ description: { type: "string" },
333
+ links: {
334
+ type: "array",
335
+ items: {
336
+ type: "object",
337
+ properties: {
338
+ title: { type: "string" },
339
+ items: {
340
+ type: "array",
341
+ items: {
342
+ type: "object",
343
+ properties: {
344
+ text: { type: "string" },
345
+ url: { type: "string", format: "uri" },
346
+ target: { type: "string", enum: ["_blank", "_self"] }
347
+ },
348
+ required: ["text", "url"]
349
+ }
350
+ }
351
+ },
352
+ required: ["title", "items"]
353
+ }
354
+ },
355
+ socialLinks: {
356
+ type: "array",
357
+ items: {
358
+ type: "object",
359
+ properties: {
360
+ platform: { type: "string" },
361
+ url: { type: "string", format: "uri" },
362
+ icon: { type: "string" }
363
+ },
364
+ required: ["platform", "url"]
365
+ }
366
+ },
367
+ copyright: { type: "string" }
368
+ },
369
+ required: ["links", "socialLinks"]
370
+ },
371
+ stats: {
372
+ type: "object",
373
+ properties: {
374
+ number: { type: "string" },
375
+ label: { type: "string" },
376
+ icon: { type: "string" },
377
+ prefix: { type: "string" },
378
+ suffix: { type: "string" }
379
+ },
380
+ required: ["number", "label"]
381
+ },
382
+ faq: {
383
+ type: "object",
384
+ properties: {
385
+ items: {
386
+ type: "array",
387
+ items: {
388
+ type: "object",
389
+ properties: {
390
+ id: { type: "string" },
391
+ question: { type: "string" },
392
+ answer: { type: "string" }
393
+ },
394
+ required: ["question", "answer"]
395
+ }
396
+ }
397
+ },
398
+ required: ["items"]
399
+ },
400
+ header: {
401
+ type: "object",
402
+ properties: {
403
+ logo: { type: "string", format: "uri" },
404
+ title: { type: "string" },
405
+ links: {
406
+ type: "array",
407
+ items: {
408
+ type: "object",
409
+ properties: {
410
+ id: { type: "string" },
411
+ text: { type: "string" },
412
+ url: { type: "string", format: "uri" },
413
+ target: { type: "string", enum: ["_blank", "_self"] }
414
+ },
415
+ required: ["text", "url"]
416
+ }
417
+ }
418
+ },
419
+ required: ["links"]
420
+ }
421
+ };
422
+
423
+ // src/schema/validate.ts
424
+ var ajv = new Ajv({ allErrors: true });
425
+ addFormats(ajv);
426
+ var validateLandingPage = ajv.compile(landingPageSchema);
427
+ var validateSection = {
428
+ hero: ajv.compile(sectionConfigSchemas.hero),
429
+ features: ajv.compile(sectionConfigSchemas.features),
430
+ testimonials: ajv.compile(sectionConfigSchemas.testimonials),
431
+ pricing: ajv.compile(sectionConfigSchemas.pricing),
432
+ cta: ajv.compile(sectionConfigSchemas.cta),
433
+ footer: ajv.compile(sectionConfigSchemas.footer),
434
+ stats: ajv.compile(sectionConfigSchemas.stats),
435
+ faq: ajv.compile(sectionConfigSchemas.faq),
436
+ header: ajv.compile(sectionConfigSchemas.header)
437
+ };
438
+ var validateSectionConfig = (type, config) => {
439
+ if (validateSection[type]) {
440
+ const valid = validateSection[type](config);
441
+ if (!valid && validateSection[type].errors) {
442
+ return validateSection[type].errors.map((err) => ({
443
+ field: err.instancePath || "root",
444
+ message: err.message,
445
+ type: err.keyword
446
+ }));
447
+ }
448
+ }
449
+ return null;
450
+ };
451
+
452
+ // src/core/validators.ts
453
+ function validateConfig(config) {
454
+ const errors = [];
455
+ if (!config.title)
456
+ errors.push("Title is required");
457
+ if (!config.description)
458
+ errors.push("Description is required");
459
+ if (!config.sections || config.sections.length === 0)
460
+ errors.push("At least one section is required");
461
+ config.sections?.forEach((section, index) => {
462
+ if (!section.id)
463
+ errors.push(`Section ${index} is missing an id`);
464
+ if (!section.type)
465
+ errors.push(`Section ${section.id || index} is missing a type`);
466
+ const sectionErrors = validateSection2(section);
467
+ sectionErrors.forEach((error) => errors.push(`Section ${section.id || index}: ${error}`));
468
+ });
469
+ return errors;
470
+ }
471
+ function validateSection2(section) {
472
+ const errors = [];
473
+ if (!section.type) {
474
+ errors.push("Section type is required");
475
+ return errors;
476
+ }
477
+ if (!section.config) {
478
+ errors.push("Section config is required");
479
+ return errors;
480
+ }
481
+ try {
482
+ const validationResult = validateSectionConfig(section.type, section.config);
483
+ if (validationResult) {
484
+ errors.push(...validationResult.map((err) => `${err.field}: ${err.message}`));
485
+ }
486
+ } catch (error) {
487
+ errors.push(`Invalid section configuration: ${error.message}`);
488
+ }
489
+ return errors;
490
+ }
491
+
492
+ // src/core/define.ts
493
+ function defineLandingPage(config) {
494
+ const errors = validateConfig(config);
495
+ if (errors.length > 0) {
496
+ throw new Error(`Invalid landing page configuration: ${errors.join(", ")}`);
497
+ }
498
+ const landingPage = {
499
+ ...config,
500
+ theme: createTheme(config.theme),
501
+ // Methods
502
+ getSection(id) {
503
+ return this.sections.find((section) => section.id === id);
504
+ },
505
+ addSection(section) {
506
+ const sectionErrors = validateSection2(section);
507
+ if (sectionErrors.length > 0) {
508
+ throw new Error(`Invalid section: ${sectionErrors.join(", ")}`);
509
+ }
510
+ this.sections.push(section);
511
+ return this;
512
+ },
513
+ removeSection(id) {
514
+ this.sections = this.sections.filter((section) => section.id !== id);
515
+ return this;
516
+ },
517
+ updateSection(id, updates) {
518
+ const index = this.sections.findIndex((section) => section.id === id);
519
+ if (index !== -1) {
520
+ const updatedSection = { ...this.sections[index], ...updates };
521
+ const sectionErrors = validateSection2(updatedSection);
522
+ if (sectionErrors.length > 0) {
523
+ throw new Error(`Invalid section updates: ${sectionErrors.join(", ")}`);
524
+ }
525
+ this.sections[index] = updatedSection;
526
+ }
527
+ return this;
528
+ },
529
+ // Render method - returns the raw config for consumption by renderers
530
+ toJSON() {
531
+ return JSON.parse(JSON.stringify(this));
532
+ },
533
+ // Validate the landing page configuration
534
+ validate() {
535
+ return validateConfig(this);
536
+ },
537
+ // Get section by type
538
+ getSectionsByType(type) {
539
+ return this.sections.filter((section) => section.type === type);
540
+ },
541
+ // Check if configuration is valid
542
+ isValid() {
543
+ return this.validate().length === 0;
544
+ }
545
+ };
546
+ return landingPage;
547
+ }
548
+
549
+ // src/core/sections/header.ts
550
+ function createHeaderSection(config, id, className) {
551
+ return {
552
+ id: id || `header-${Date.now()}`,
553
+ className,
554
+ type: "header",
555
+ config
556
+ };
557
+ }
558
+
559
+ // src/core/sections/hero.ts
560
+ function createHeroSection(config, id, className) {
561
+ return {
562
+ id: id || `hero-${Date.now()}`,
563
+ className,
564
+ type: "hero",
565
+ config
566
+ };
567
+ }
568
+
569
+ // src/core/sections/features.ts
570
+ function createFeaturesSection(config, id, className) {
571
+ return {
572
+ id: id || `features-${Date.now()}`,
573
+ className,
574
+ type: "features",
575
+ config
576
+ };
577
+ }
578
+
579
+ // src/core/sections/testimonials.ts
580
+ function createTestimonialsSection(config, id, className) {
581
+ return {
582
+ id: id || `testimonials-${Date.now()}`,
583
+ className,
584
+ type: "testimonials",
585
+ config
586
+ };
587
+ }
588
+
589
+ // src/core/sections/pricing.ts
590
+ function createPricingSection(config, id, className) {
591
+ return {
592
+ id: id || `pricing-${Date.now()}`,
593
+ className,
594
+ type: "pricing",
595
+ config
596
+ };
597
+ }
598
+
599
+ // src/core/sections/cta.ts
600
+ function createCtaSection(config, id, className) {
601
+ return {
602
+ id: id || `cta-${Date.now()}`,
603
+ className,
604
+ type: "cta",
605
+ config
606
+ };
607
+ }
608
+
609
+ // src/core/sections/footer.ts
610
+ function createFooterSection(config, id, className) {
611
+ return {
612
+ id: id || `footer-${Date.now()}`,
613
+ className,
614
+ type: "footer",
615
+ config
616
+ };
617
+ }
618
+
619
+ // src/core/sections/stats.ts
620
+ function createStatsSection(config, id, className) {
621
+ return {
622
+ id: id || `stats-${Date.now()}`,
623
+ className,
624
+ type: "stats",
625
+ config
626
+ };
627
+ }
628
+
629
+ // src/core/sections/faq.ts
630
+ function createFaqSection(config, id, className) {
631
+ return {
632
+ id: id || `faq-${Date.now()}`,
633
+ className,
634
+ type: "faq",
635
+ config
636
+ };
637
+ }
638
+
639
+ // src/renderers/react/index.tsx
640
+ import React15 from "react";
641
+
642
+ // src/renderers/react/skins/HeaderSkin.tsx
643
+ import { useState } from "react";
644
+
645
+ // src/renderers/react/base/HeaderBase.tsx
646
+ import React4 from "react";
647
+
648
+ // src/renderers/react/base/NavbarBase.tsx
649
+ import React2 from "react";
650
+
651
+ // src/renderers/react/base/LayoutBase.tsx
652
+ import React from "react";
653
+ import { jsx } from "react/jsx-runtime";
654
+ var Box = React.forwardRef((props, ref) => {
655
+ const { as: Component = "div", children, className, style, ...rest } = props;
656
+ return /* @__PURE__ */ jsx(Component, { ref, className, style, ...rest, children });
657
+ });
658
+ var Flex = React.forwardRef((props, ref) => {
659
+ const {
660
+ as: Component = "div",
661
+ children,
662
+ className,
663
+ style,
664
+ direction = "row",
665
+ justify = "flex-start",
666
+ align = "stretch",
667
+ gap = 0,
668
+ wrap = "nowrap",
669
+ ...rest
670
+ } = props;
671
+ const flexStyle = {
672
+ display: "flex",
673
+ flexDirection: direction,
674
+ justifyContent: justify,
675
+ alignItems: align,
676
+ gap: typeof gap === "number" ? `${gap}px` : gap,
677
+ flexWrap: wrap,
678
+ ...style
679
+ };
680
+ return /* @__PURE__ */ jsx(Component, { ref, className, style: flexStyle, ...rest, children });
681
+ });
682
+ var Container = React.forwardRef((props, ref) => {
683
+ const {
684
+ as: Component = "div",
685
+ children,
686
+ className,
687
+ style,
688
+ maxWidth = "1200px",
689
+ padding = "0 1rem",
690
+ center = true,
691
+ ...rest
692
+ } = props;
693
+ const containerStyle = {
694
+ maxWidth,
695
+ padding,
696
+ margin: center ? "0 auto" : void 0,
697
+ ...style
698
+ };
699
+ return /* @__PURE__ */ jsx(Component, { ref, className, style: containerStyle, ...rest, children });
700
+ });
701
+ Box.displayName = "Box";
702
+ Flex.displayName = "Flex";
703
+ Container.displayName = "Container";
704
+
705
+ // src/renderers/react/base/NavbarBase.tsx
706
+ import { jsx as jsx2 } from "react/jsx-runtime";
707
+ var NavbarBase = React2.forwardRef((props, ref) => {
708
+ const {
709
+ links,
710
+ isMobile,
711
+ isOpen,
712
+ className,
713
+ style,
714
+ linkStyle,
715
+ onLinkMouseEnter,
716
+ onLinkMouseLeave
717
+ } = props;
718
+ if (isMobile && !isOpen)
719
+ return null;
720
+ return /* @__PURE__ */ jsx2(Box, { as: "nav", ref, className, style, children: /* @__PURE__ */ jsx2(Flex, { as: "ul", direction: isMobile ? "column" : "row", gap: "1.5rem", style: { listStyle: "none", padding: 0 }, children: links.map((link, index) => /* @__PURE__ */ jsx2(Box, { as: "li", style: { marginBottom: isMobile ? "0.5rem" : 0 }, children: /* @__PURE__ */ jsx2(
721
+ "a",
722
+ {
723
+ href: link.url,
724
+ target: link.target || "_self",
725
+ rel: link.target === "_blank" ? "noopener noreferrer" : void 0,
726
+ style: linkStyle,
727
+ onMouseEnter: (e) => onLinkMouseEnter?.(e, link),
728
+ onMouseLeave: (e) => onLinkMouseLeave?.(e, link),
729
+ onFocus: (e) => {
730
+ e.currentTarget.style.outline = "2px solid currentColor";
731
+ e.currentTarget.style.outlineOffset = "2px";
732
+ },
733
+ onBlur: (e) => {
734
+ e.currentTarget.style.outline = "none";
735
+ },
736
+ children: link.text
737
+ }
738
+ ) }, index)) }) });
739
+ });
740
+ NavbarBase.displayName = "NavbarBase";
741
+
742
+ // src/renderers/react/skins/NavbarSkin.tsx
743
+ import { jsx as jsx3 } from "react/jsx-runtime";
744
+ var NavbarSkin = (props) => {
745
+ const { theme, ...config } = props;
746
+ const navbarStyle = {
747
+ marginTop: config.isMobile ? theme.spacing.md : 0,
748
+ ...config.style
749
+ };
750
+ const linkStyle = {
751
+ color: theme.colors.muted,
752
+ textDecoration: "none",
753
+ transition: "color 0.2s ease",
754
+ display: "block",
755
+ padding: config.isMobile ? `${theme.spacing.sm} ${theme.spacing.md}` : "0.25rem 0",
756
+ borderRadius: config.isMobile ? "0.5rem" : 0,
757
+ fontSize: "1rem",
758
+ fontWeight: "500",
759
+ ...config.linkStyle
760
+ };
761
+ const handleMouseEnter = (e) => {
762
+ const el = e.currentTarget;
763
+ el.style.color = theme.colors.primary;
764
+ config.onLinkMouseEnter?.(e, {});
765
+ };
766
+ const handleMouseLeave = (e) => {
767
+ const el = e.currentTarget;
768
+ el.style.color = theme.colors.muted;
769
+ config.onLinkMouseLeave?.(e, {});
770
+ };
771
+ return /* @__PURE__ */ jsx3(
772
+ NavbarBase,
773
+ {
774
+ ...config,
775
+ style: navbarStyle,
776
+ linkStyle,
777
+ onLinkMouseEnter: handleMouseEnter,
778
+ onLinkMouseLeave: handleMouseLeave
779
+ }
780
+ );
781
+ };
782
+
783
+ // src/renderers/react/Navbar.tsx
784
+ import { jsx as jsx4 } from "react/jsx-runtime";
785
+ var Navbar = ({
786
+ links,
787
+ theme,
788
+ isMobile,
789
+ isOpen,
790
+ className,
791
+ style
792
+ }) => {
793
+ return /* @__PURE__ */ jsx4(
794
+ NavbarSkin,
795
+ {
796
+ links,
797
+ theme,
798
+ isMobile,
799
+ isOpen,
800
+ className,
801
+ style
802
+ }
803
+ );
804
+ };
805
+ var Navbar_default = Navbar;
806
+
807
+ // src/renderers/react/base/MenuToggleBase.tsx
808
+ import React3 from "react";
809
+ import { jsx as jsx5 } from "react/jsx-runtime";
810
+ var MenuToggleBase = React3.forwardRef((props, ref) => {
811
+ const {
812
+ isOpen,
813
+ onClick,
814
+ ariaLabel = "Toggle menu",
815
+ className,
816
+ style,
817
+ iconOpen = "\u2630",
818
+ iconClose = "\u2715"
819
+ } = props;
820
+ return /* @__PURE__ */ jsx5(
821
+ Box,
822
+ {
823
+ as: "button",
824
+ ref,
825
+ onClick,
826
+ style,
827
+ className,
828
+ "aria-label": isOpen ? "Close menu" : ariaLabel,
829
+ children: isOpen ? iconClose : iconOpen
830
+ }
831
+ );
832
+ });
833
+ MenuToggleBase.displayName = "MenuToggleBase";
834
+
835
+ // src/renderers/react/skins/MenuToggleSkin.tsx
836
+ import { jsx as jsx6 } from "react/jsx-runtime";
837
+ var MenuToggleSkin = (props) => {
838
+ const { theme, ...config } = props;
839
+ const toggleStyle = {
840
+ backgroundColor: "transparent",
841
+ border: "none",
842
+ color: theme.colors.text,
843
+ fontSize: "1.5rem",
844
+ cursor: "pointer",
845
+ padding: theme.spacing.sm,
846
+ display: "flex",
847
+ alignItems: "center",
848
+ justifyContent: "center",
849
+ transition: "color 0.2s ease",
850
+ ...config.style
851
+ };
852
+ return /* @__PURE__ */ jsx6(
853
+ MenuToggleBase,
854
+ {
855
+ ...config,
856
+ style: toggleStyle
857
+ }
858
+ );
859
+ };
860
+
861
+ // src/renderers/react/MenuToggle.tsx
862
+ import { jsx as jsx7 } from "react/jsx-runtime";
863
+ var MenuToggle = ({
864
+ isOpen,
865
+ onClick,
866
+ theme,
867
+ className,
868
+ style
869
+ }) => {
870
+ return /* @__PURE__ */ jsx7(
871
+ MenuToggleSkin,
872
+ {
873
+ isOpen,
874
+ onClick,
875
+ theme,
876
+ className,
877
+ style
878
+ }
879
+ );
880
+ };
881
+ var MenuToggle_default = MenuToggle;
882
+
883
+ // src/renderers/react/base/HeaderBase.tsx
884
+ import { jsx as jsx8, jsxs } from "react/jsx-runtime";
885
+ var HeaderBase = React4.forwardRef((props, ref) => {
886
+ const {
887
+ logo,
888
+ title,
889
+ links,
890
+ className,
891
+ isMobileMenuOpen = false,
892
+ onMobileMenuToggle,
893
+ style,
894
+ containerStyle,
895
+ theme
896
+ } = props;
897
+ return /* @__PURE__ */ jsx8(Box, { as: "header", ref, style, className, children: /* @__PURE__ */ jsxs(Container, { style: containerStyle, children: [
898
+ /* @__PURE__ */ jsxs(Flex, { justify: "space-between", align: "center", children: [
899
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "1rem", children: [
900
+ logo && /* @__PURE__ */ jsx8(
901
+ "img",
902
+ {
903
+ src: logo,
904
+ alt: title || "Logo",
905
+ style: { height: "40px" },
906
+ loading: "lazy"
907
+ }
908
+ ),
909
+ title && /* @__PURE__ */ jsx8("h1", { style: { fontSize: "1.5rem", color: theme.colors.text }, children: title })
910
+ ] }),
911
+ /* @__PURE__ */ jsx8(Box, { style: { display: "none" } }),
912
+ onMobileMenuToggle && /* @__PURE__ */ jsx8(
913
+ MenuToggle_default,
914
+ {
915
+ isOpen: isMobileMenuOpen,
916
+ onClick: onMobileMenuToggle,
917
+ theme
918
+ }
919
+ )
920
+ ] }),
921
+ /* @__PURE__ */ jsx8(
922
+ Navbar_default,
923
+ {
924
+ links,
925
+ theme,
926
+ isMobile: true,
927
+ isOpen: isMobileMenuOpen
928
+ }
929
+ )
930
+ ] }) });
931
+ });
932
+ HeaderBase.displayName = "HeaderBase";
933
+
934
+ // src/renderers/react/skins/HeaderSkin.tsx
935
+ import { jsx as jsx9 } from "react/jsx-runtime";
936
+ var HeaderSkin = (props) => {
937
+ const { theme, ...config } = props;
938
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
939
+ const headerStyle = {
940
+ padding: "1rem 0",
941
+ backgroundColor: theme.colors.background,
942
+ borderBottom: `1px solid ${theme.colors.muted}20`,
943
+ ...config.style
944
+ };
945
+ const containerStyle = {
946
+ maxWidth: "1200px",
947
+ margin: "0 auto",
948
+ padding: "0 1rem",
949
+ ...config.containerStyle
950
+ };
951
+ return /* @__PURE__ */ jsx9(
952
+ HeaderBase,
953
+ {
954
+ ...config,
955
+ theme,
956
+ style: headerStyle,
957
+ containerStyle,
958
+ isMobileMenuOpen: mobileMenuOpen,
959
+ onMobileMenuToggle: () => setMobileMenuOpen(!mobileMenuOpen)
960
+ }
961
+ );
962
+ };
963
+
964
+ // src/renderers/react/Header.tsx
965
+ import { jsx as jsx10 } from "react/jsx-runtime";
966
+ var Header = ({ config, theme }) => {
967
+ return /* @__PURE__ */ jsx10(
968
+ HeaderSkin,
969
+ {
970
+ ...config,
971
+ theme
972
+ }
973
+ );
974
+ };
975
+ var Header_default = Header;
976
+
977
+ // src/renderers/react/base/HeroBase.tsx
978
+ import React7 from "react";
979
+
980
+ // src/renderers/react/base/ButtonBase.tsx
981
+ import React6 from "react";
982
+ import { jsx as jsx11 } from "react/jsx-runtime";
983
+ var ButtonBase = React6.forwardRef((props, ref) => {
984
+ const {
985
+ text,
986
+ url,
987
+ target = "_self",
988
+ variant,
989
+ size,
990
+ className,
991
+ style,
992
+ onMouseEnter,
993
+ onMouseLeave,
994
+ onFocus,
995
+ onBlur,
996
+ onKeyDown,
997
+ ...rest
998
+ } = props;
999
+ return /* @__PURE__ */ jsx11(
1000
+ "a",
1001
+ {
1002
+ ref,
1003
+ href: url,
1004
+ target,
1005
+ rel: target === "_blank" ? "noopener noreferrer" : void 0,
1006
+ style,
1007
+ className,
1008
+ onMouseEnter,
1009
+ onMouseLeave,
1010
+ onFocus,
1011
+ onBlur,
1012
+ onKeyDown,
1013
+ role: "button",
1014
+ tabIndex: 0,
1015
+ ...rest,
1016
+ children: text
1017
+ }
1018
+ );
1019
+ });
1020
+ ButtonBase.displayName = "ButtonBase";
1021
+
1022
+ // src/core/utils/contrast.ts
1023
+ function hexToRgb(hex) {
1024
+ const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
1025
+ const fullHex = hex.replace(shorthandRegex, (_, r, g, b) => r + r + g + g + b + b);
1026
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(fullHex);
1027
+ return result ? {
1028
+ r: parseInt(result[1], 16),
1029
+ g: parseInt(result[2], 16),
1030
+ b: parseInt(result[3], 16)
1031
+ } : null;
1032
+ }
1033
+ function getRelativeLuminance(r, g, b) {
1034
+ const [rs, gs, bs] = [r, g, b].map((c) => {
1035
+ const s = c / 255;
1036
+ return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
1037
+ });
1038
+ return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
1039
+ }
1040
+ function getContrastRatio(hex1, hex2) {
1041
+ const rgb1 = hexToRgb(hex1);
1042
+ const rgb2 = hexToRgb(hex2);
1043
+ if (!rgb1 || !rgb2)
1044
+ return 1;
1045
+ const l1 = getRelativeLuminance(rgb1.r, rgb1.g, rgb1.b);
1046
+ const l2 = getRelativeLuminance(rgb2.r, rgb2.g, rgb2.b);
1047
+ const lighter = Math.max(l1, l2);
1048
+ const darker = Math.min(l1, l2);
1049
+ return (lighter + 0.05) / (darker + 0.05);
1050
+ }
1051
+ function getBestContrastColor(bgColor, lightColor = "#ffffff", darkColor = "#000000") {
1052
+ const ratioLight = getContrastRatio(bgColor, lightColor);
1053
+ const ratioDark = getContrastRatio(bgColor, darkColor);
1054
+ return ratioLight > ratioDark ? lightColor : darkColor;
1055
+ }
1056
+
1057
+ // src/renderers/react/skins/ButtonSkin.tsx
1058
+ import { jsx as jsx12 } from "react/jsx-runtime";
1059
+ var ButtonSkin = (props) => {
1060
+ const { theme, ...config } = props;
1061
+ const backgroundColor = config.variant === "primary" ? theme.colors.primary : config.variant === "secondary" ? theme.colors.secondary : "transparent";
1062
+ const textColor = config.variant === "primary" || config.variant === "secondary" ? getBestContrastColor(backgroundColor) : theme.colors.primary;
1063
+ const buttonStyles = {
1064
+ padding: theme.spacing[config.size === "sm" ? "sm" : config.size === "lg" ? "xl" : "md"],
1065
+ backgroundColor,
1066
+ color: textColor,
1067
+ border: config.variant === "outline" ? `2px solid ${theme.colors.primary}` : "none",
1068
+ borderRadius: "0.5rem",
1069
+ fontSize: config.size === "sm" ? "0.875rem" : config.size === "lg" ? "1.125rem" : "1rem",
1070
+ fontWeight: "600",
1071
+ cursor: "pointer",
1072
+ textDecoration: "none",
1073
+ display: "inline-block",
1074
+ textAlign: "center",
1075
+ transition: "all 0.2s ease",
1076
+ ...config.style
1077
+ };
1078
+ const adjustBrightness = (color, amount) => {
1079
+ if (!color || color === "transparent")
1080
+ return color;
1081
+ try {
1082
+ const num = parseInt(color.replace("#", ""), 16);
1083
+ const amt = Math.round(2.55 * amount);
1084
+ const R = Math.max(0, Math.min(255, (num >> 16) + amt));
1085
+ const G = Math.max(0, Math.min(255, (num >> 8 & 255) + amt));
1086
+ const B = Math.max(0, Math.min(255, (num & 255) + amt));
1087
+ return "#" + (16777216 + R * 65536 + G * 256 + B).toString(16).slice(1);
1088
+ } catch (e) {
1089
+ return color;
1090
+ }
1091
+ };
1092
+ const handleMouseEnter = (e) => {
1093
+ const el = e.currentTarget;
1094
+ if (config.variant === "primary") {
1095
+ el.style.backgroundColor = adjustBrightness(theme.colors.primary, -20);
1096
+ el.style.transform = "translateY(-2px)";
1097
+ el.style.boxShadow = "0 10px 15px -3px rgba(0, 0, 0, 0.1)";
1098
+ } else if (config.variant === "secondary") {
1099
+ el.style.backgroundColor = adjustBrightness(theme.colors.secondary, -20);
1100
+ el.style.transform = "translateY(-2px)";
1101
+ el.style.boxShadow = "0 10px 15px -3px rgba(0, 0, 0, 0.1)";
1102
+ } else if (config.variant === "outline") {
1103
+ el.style.backgroundColor = theme.colors.primary;
1104
+ el.style.color = theme.colors.primary === "#ffffff" ? theme.colors.text : "#ffffff";
1105
+ } else if (config.variant === "ghost") {
1106
+ el.style.backgroundColor = `${theme.colors.primary}10`;
1107
+ }
1108
+ config.onMouseEnter?.(e);
1109
+ };
1110
+ const handleMouseLeave = (e) => {
1111
+ const el = e.currentTarget;
1112
+ el.style.backgroundColor = buttonStyles.backgroundColor;
1113
+ el.style.color = buttonStyles.color;
1114
+ el.style.transform = "translateY(0)";
1115
+ el.style.boxShadow = "none";
1116
+ config.onMouseLeave?.(e);
1117
+ };
1118
+ const handleKeyDown = (e) => {
1119
+ if (e.key === "Enter" || e.key === " ") {
1120
+ e.preventDefault();
1121
+ window.location.href = config.url;
1122
+ }
1123
+ config.onKeyDown?.(e);
1124
+ };
1125
+ return /* @__PURE__ */ jsx12(
1126
+ ButtonBase,
1127
+ {
1128
+ ...config,
1129
+ style: buttonStyles,
1130
+ onMouseEnter: handleMouseEnter,
1131
+ onMouseLeave: handleMouseLeave,
1132
+ onKeyDown: handleKeyDown,
1133
+ onFocus: (e) => {
1134
+ e.currentTarget.style.outline = `2px solid ${theme.colors.primary}`;
1135
+ e.currentTarget.style.outlineOffset = "2px";
1136
+ config.onFocus?.(e);
1137
+ },
1138
+ onBlur: (e) => {
1139
+ e.currentTarget.style.outline = "none";
1140
+ config.onBlur?.(e);
1141
+ }
1142
+ }
1143
+ );
1144
+ };
1145
+
1146
+ // src/renderers/react/Button.tsx
1147
+ import { jsx as jsx13 } from "react/jsx-runtime";
1148
+ var Button = ({ config, theme }) => {
1149
+ return /* @__PURE__ */ jsx13(
1150
+ ButtonSkin,
1151
+ {
1152
+ ...config,
1153
+ theme
1154
+ }
1155
+ );
1156
+ };
1157
+ var Button_default = Button;
1158
+
1159
+ // src/renderers/react/base/HeroBase.tsx
1160
+ import { jsx as jsx14, jsxs as jsxs2 } from "react/jsx-runtime";
1161
+ var HeroBase = React7.forwardRef((props, ref) => {
1162
+ const {
1163
+ title,
1164
+ subtitle,
1165
+ buttons,
1166
+ image,
1167
+ video,
1168
+ alignment = "center",
1169
+ className,
1170
+ style,
1171
+ containerStyle,
1172
+ contentStyle,
1173
+ theme
1174
+ } = props;
1175
+ return /* @__PURE__ */ jsx14(Box, { as: "section", ref, className, style, children: /* @__PURE__ */ jsx14(Container, { style: containerStyle, children: /* @__PURE__ */ jsxs2(
1176
+ Flex,
1177
+ {
1178
+ direction: "column",
1179
+ gap: theme.spacing.xl,
1180
+ align: alignment === "center" ? "center" : alignment === "right" ? "flex-end" : "flex-start",
1181
+ style: { textAlign: alignment, ...contentStyle },
1182
+ children: [
1183
+ /* @__PURE__ */ jsxs2(Box, { children: [
1184
+ /* @__PURE__ */ jsx14("h1", { style: {
1185
+ fontSize: "3rem",
1186
+ fontWeight: "bold",
1187
+ color: theme.colors.text,
1188
+ marginBottom: theme.spacing.md,
1189
+ lineHeight: "1.2"
1190
+ }, children: title }),
1191
+ /* @__PURE__ */ jsx14("p", { style: {
1192
+ fontSize: "1.25rem",
1193
+ color: theme.colors.muted,
1194
+ marginBottom: theme.spacing.lg,
1195
+ maxWidth: "600px",
1196
+ marginLeft: alignment === "center" ? "auto" : "0",
1197
+ marginRight: alignment === "center" ? "auto" : "0"
1198
+ }, children: subtitle })
1199
+ ] }),
1200
+ image && /* @__PURE__ */ jsx14(
1201
+ "img",
1202
+ {
1203
+ src: image,
1204
+ alt: title || "Hero",
1205
+ style: {
1206
+ maxWidth: "100%",
1207
+ borderRadius: "0.5rem",
1208
+ boxShadow: "0 10px 15px -3px rgba(0, 0, 0, 0.1)"
1209
+ },
1210
+ loading: "lazy"
1211
+ }
1212
+ ),
1213
+ video && /* @__PURE__ */ jsx14(
1214
+ "video",
1215
+ {
1216
+ src: video,
1217
+ controls: true,
1218
+ style: {
1219
+ maxWidth: "100%",
1220
+ borderRadius: "0.5rem",
1221
+ boxShadow: "0 10px 15px -3px rgba(0, 0, 0, 0.1)"
1222
+ }
1223
+ }
1224
+ ),
1225
+ /* @__PURE__ */ jsx14(Flex, { gap: theme.spacing.md, wrap: "wrap", justify: alignment === "center" ? "center" : "flex-start", children: buttons.map((button) => /* @__PURE__ */ jsx14(Button_default, { config: button, theme }, button.id)) })
1226
+ ]
1227
+ }
1228
+ ) }) });
1229
+ });
1230
+ HeroBase.displayName = "HeroBase";
1231
+
1232
+ // src/renderers/react/skins/HeroSkin.tsx
1233
+ import { jsx as jsx15 } from "react/jsx-runtime";
1234
+ var HeroSkin = (props) => {
1235
+ const { theme, ...config } = props;
1236
+ const sectionStyle = {
1237
+ padding: "4rem 0",
1238
+ ...config.style
1239
+ };
1240
+ const containerStyle = {
1241
+ maxWidth: "1200px",
1242
+ margin: "0 auto",
1243
+ padding: "0 1rem",
1244
+ ...config.containerStyle
1245
+ };
1246
+ const contentStyle = {
1247
+ display: "flex",
1248
+ flexDirection: "column",
1249
+ gap: theme.spacing.xl,
1250
+ alignItems: config.alignment || "center",
1251
+ textAlign: config.alignment || "center",
1252
+ ...config.contentStyle
1253
+ };
1254
+ return /* @__PURE__ */ jsx15(
1255
+ HeroBase,
1256
+ {
1257
+ ...config,
1258
+ theme,
1259
+ style: sectionStyle,
1260
+ containerStyle,
1261
+ contentStyle
1262
+ }
1263
+ );
1264
+ };
1265
+
1266
+ // src/renderers/react/Hero.tsx
1267
+ import { jsx as jsx16 } from "react/jsx-runtime";
1268
+ var Hero = ({ config, theme }) => {
1269
+ return /* @__PURE__ */ jsx16(
1270
+ HeroSkin,
1271
+ {
1272
+ ...config,
1273
+ theme
1274
+ }
1275
+ );
1276
+ };
1277
+ var Hero_default = Hero;
1278
+
1279
+ // src/renderers/react/base/FeaturesBase.tsx
1280
+ import React8 from "react";
1281
+ import { jsx as jsx17, jsxs as jsxs3 } from "react/jsx-runtime";
1282
+ var FeaturesBase = React8.forwardRef((props, ref) => {
1283
+ const {
1284
+ features,
1285
+ className,
1286
+ style,
1287
+ containerStyle,
1288
+ gridStyle,
1289
+ featureStyle,
1290
+ iconStyle,
1291
+ titleStyle,
1292
+ descriptionStyle,
1293
+ onFeatureMouseEnter,
1294
+ onFeatureMouseLeave,
1295
+ theme
1296
+ } = props;
1297
+ return /* @__PURE__ */ jsx17(Box, { as: "section", ref, className, style, children: /* @__PURE__ */ jsx17(Container, { style: containerStyle, children: /* @__PURE__ */ jsx17(Box, { style: gridStyle, children: features.map((feature) => /* @__PURE__ */ jsxs3(
1298
+ Box,
1299
+ {
1300
+ className: feature.className,
1301
+ style: featureStyle,
1302
+ onMouseEnter: onFeatureMouseEnter,
1303
+ onMouseLeave: onFeatureMouseLeave,
1304
+ children: [
1305
+ feature.icon && /* @__PURE__ */ jsx17(Box, { style: iconStyle, children: feature.icon }),
1306
+ /* @__PURE__ */ jsx17(Box, { as: "h3", style: titleStyle, children: feature.title }),
1307
+ /* @__PURE__ */ jsx17(Box, { as: "p", style: descriptionStyle, children: feature.description })
1308
+ ]
1309
+ },
1310
+ feature.id
1311
+ )) }) }) });
1312
+ });
1313
+ FeaturesBase.displayName = "FeaturesBase";
1314
+
1315
+ // src/renderers/react/skins/FeaturesSkin.tsx
1316
+ import { jsx as jsx18 } from "react/jsx-runtime";
1317
+ var FeaturesSkin = (props) => {
1318
+ const { theme, ...config } = props;
1319
+ const sectionStyle = {
1320
+ padding: "4rem 0",
1321
+ ...config.style
1322
+ };
1323
+ const containerStyle = {
1324
+ maxWidth: "1200px",
1325
+ margin: "0 auto",
1326
+ padding: "0 1rem",
1327
+ ...config.containerStyle
1328
+ };
1329
+ const gridStyle = {
1330
+ display: "grid",
1331
+ gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))",
1332
+ gap: theme.spacing.xl,
1333
+ ...config.gridStyle
1334
+ };
1335
+ const featureStyle = {
1336
+ padding: theme.spacing.lg,
1337
+ border: `1px solid ${theme.colors.muted}20`,
1338
+ borderRadius: "0.5rem",
1339
+ backgroundColor: theme.colors.background,
1340
+ boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
1341
+ transition: "transform 0.2s ease, box-shadow 0.2s ease",
1342
+ cursor: "pointer",
1343
+ ...config.featureStyle
1344
+ };
1345
+ const iconStyle = {
1346
+ marginBottom: theme.spacing.md,
1347
+ ...config.iconStyle
1348
+ };
1349
+ const titleStyle = {
1350
+ fontSize: "1.5rem",
1351
+ marginBottom: theme.spacing.md,
1352
+ color: theme.colors.text,
1353
+ ...config.titleStyle
1354
+ };
1355
+ const descriptionStyle = {
1356
+ color: theme.colors.muted,
1357
+ lineHeight: "1.6",
1358
+ ...config.descriptionStyle
1359
+ };
1360
+ const handleMouseEnter = (e) => {
1361
+ e.currentTarget.style.transform = "translateY(-4px)";
1362
+ e.currentTarget.style.boxShadow = "0 20px 25px -5px rgba(0, 0, 0, 0.1)";
1363
+ config.onFeatureMouseEnter?.(e);
1364
+ };
1365
+ const handleMouseLeave = (e) => {
1366
+ e.currentTarget.style.transform = "translateY(0)";
1367
+ e.currentTarget.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1)";
1368
+ config.onFeatureMouseLeave?.(e);
1369
+ };
1370
+ return /* @__PURE__ */ jsx18(
1371
+ FeaturesBase,
1372
+ {
1373
+ ...config,
1374
+ theme,
1375
+ style: sectionStyle,
1376
+ containerStyle,
1377
+ gridStyle,
1378
+ featureStyle,
1379
+ iconStyle,
1380
+ titleStyle,
1381
+ descriptionStyle,
1382
+ onFeatureMouseEnter: handleMouseEnter,
1383
+ onFeatureMouseLeave: handleMouseLeave
1384
+ }
1385
+ );
1386
+ };
1387
+
1388
+ // src/renderers/react/Features.tsx
1389
+ import { jsx as jsx19 } from "react/jsx-runtime";
1390
+ var Features = ({ config, theme }) => {
1391
+ return /* @__PURE__ */ jsx19(
1392
+ FeaturesSkin,
1393
+ {
1394
+ ...config,
1395
+ theme
1396
+ }
1397
+ );
1398
+ };
1399
+ var Features_default = Features;
1400
+
1401
+ // src/renderers/react/base/TestimonialsBase.tsx
1402
+ import React9 from "react";
1403
+ import { jsx as jsx20, jsxs as jsxs4 } from "react/jsx-runtime";
1404
+ var TestimonialsBase = React9.forwardRef((props, ref) => {
1405
+ const {
1406
+ testimonials,
1407
+ className,
1408
+ style,
1409
+ containerStyle,
1410
+ gridStyle,
1411
+ testimonialStyle,
1412
+ quoteIconStyle,
1413
+ quoteStyle,
1414
+ authorContainerStyle,
1415
+ avatarStyle,
1416
+ authorInfoStyle,
1417
+ authorNameStyle,
1418
+ authorRoleStyle,
1419
+ onTestimonialMouseEnter,
1420
+ onTestimonialMouseLeave,
1421
+ theme
1422
+ } = props;
1423
+ return /* @__PURE__ */ jsx20(Box, { as: "section", ref, className, style, children: /* @__PURE__ */ jsx20(Container, { style: containerStyle, children: /* @__PURE__ */ jsx20(Box, { style: gridStyle, children: testimonials.map((testimonial) => /* @__PURE__ */ jsxs4(
1424
+ Box,
1425
+ {
1426
+ className: testimonial.className,
1427
+ style: testimonialStyle,
1428
+ onMouseEnter: onTestimonialMouseEnter,
1429
+ onMouseLeave: onTestimonialMouseLeave,
1430
+ children: [
1431
+ /* @__PURE__ */ jsx20(Box, { style: quoteIconStyle, children: /* @__PURE__ */ jsx20(
1432
+ "svg",
1433
+ {
1434
+ width: "24",
1435
+ height: "24",
1436
+ viewBox: "0 0 24 24",
1437
+ fill: "none",
1438
+ stroke: theme.colors.primary,
1439
+ strokeWidth: "2",
1440
+ children: /* @__PURE__ */ jsx20("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
1441
+ }
1442
+ ) }),
1443
+ /* @__PURE__ */ jsx20(Box, { as: "blockquote", style: quoteStyle, children: testimonial.quote }),
1444
+ /* @__PURE__ */ jsxs4(Flex, { align: "center", gap: theme.spacing.md, style: authorContainerStyle, children: [
1445
+ testimonial.avatar && /* @__PURE__ */ jsx20(
1446
+ "img",
1447
+ {
1448
+ src: testimonial.avatar,
1449
+ alt: testimonial.author,
1450
+ style: avatarStyle,
1451
+ loading: "lazy"
1452
+ }
1453
+ ),
1454
+ /* @__PURE__ */ jsxs4(Box, { style: authorInfoStyle, children: [
1455
+ /* @__PURE__ */ jsx20(Box, { as: "p", style: authorNameStyle, children: testimonial.author }),
1456
+ testimonial.role && /* @__PURE__ */ jsx20(Box, { as: "p", style: authorRoleStyle, children: testimonial.role })
1457
+ ] })
1458
+ ] })
1459
+ ]
1460
+ },
1461
+ testimonial.id
1462
+ )) }) }) });
1463
+ });
1464
+ TestimonialsBase.displayName = "TestimonialsBase";
1465
+
1466
+ // src/renderers/react/skins/TestimonialsSkin.tsx
1467
+ import { jsx as jsx21 } from "react/jsx-runtime";
1468
+ var TestimonialsSkin = (props) => {
1469
+ const { theme, ...config } = props;
1470
+ const sectionStyle = {
1471
+ padding: "4rem 0",
1472
+ ...config.style
1473
+ };
1474
+ const containerStyle = {
1475
+ maxWidth: "1200px",
1476
+ margin: "0 auto",
1477
+ padding: "0 1rem",
1478
+ ...config.containerStyle
1479
+ };
1480
+ const gridStyle = {
1481
+ display: "grid",
1482
+ gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))",
1483
+ gap: theme.spacing.xl,
1484
+ ...config.gridStyle
1485
+ };
1486
+ const testimonialStyle = {
1487
+ padding: theme.spacing.lg,
1488
+ backgroundColor: theme.colors.background,
1489
+ border: `1px solid ${theme.colors.muted}20`,
1490
+ borderRadius: "0.5rem",
1491
+ boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
1492
+ transition: "transform 0.2s ease, box-shadow 0.2s ease",
1493
+ cursor: "pointer",
1494
+ ...config.testimonialStyle
1495
+ };
1496
+ const quoteIconStyle = {
1497
+ marginBottom: theme.spacing.md,
1498
+ ...config.quoteIconStyle
1499
+ };
1500
+ const quoteStyle = {
1501
+ fontSize: "1.125rem",
1502
+ fontStyle: "italic",
1503
+ color: theme.colors.text,
1504
+ ...config.quoteStyle
1505
+ };
1506
+ const authorContainerStyle = {
1507
+ marginTop: theme.spacing.md,
1508
+ ...config.authorContainerStyle
1509
+ };
1510
+ const avatarStyle = {
1511
+ width: "48px",
1512
+ height: "48px",
1513
+ borderRadius: "50%",
1514
+ objectFit: "cover",
1515
+ ...config.avatarStyle
1516
+ };
1517
+ const authorInfoStyle = {
1518
+ ...config.authorInfoStyle
1519
+ };
1520
+ const authorNameStyle = {
1521
+ fontWeight: "bold",
1522
+ color: theme.colors.text,
1523
+ ...config.authorNameStyle
1524
+ };
1525
+ const authorRoleStyle = {
1526
+ fontSize: "0.875rem",
1527
+ color: theme.colors.muted,
1528
+ ...config.authorRoleStyle
1529
+ };
1530
+ const handleMouseEnter = (e) => {
1531
+ e.currentTarget.style.transform = "translateY(-4px)";
1532
+ e.currentTarget.style.boxShadow = "0 20px 25px -5px rgba(0, 0, 0, 0.1)";
1533
+ config.onTestimonialMouseEnter?.(e);
1534
+ };
1535
+ const handleMouseLeave = (e) => {
1536
+ e.currentTarget.style.transform = "translateY(0)";
1537
+ e.currentTarget.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1)";
1538
+ config.onTestimonialMouseLeave?.(e);
1539
+ };
1540
+ return /* @__PURE__ */ jsx21(
1541
+ TestimonialsBase,
1542
+ {
1543
+ ...config,
1544
+ theme,
1545
+ style: sectionStyle,
1546
+ containerStyle,
1547
+ gridStyle,
1548
+ testimonialStyle,
1549
+ quoteIconStyle,
1550
+ quoteStyle,
1551
+ authorContainerStyle,
1552
+ avatarStyle,
1553
+ authorInfoStyle,
1554
+ authorNameStyle,
1555
+ authorRoleStyle,
1556
+ onTestimonialMouseEnter: handleMouseEnter,
1557
+ onTestimonialMouseLeave: handleMouseLeave
1558
+ }
1559
+ );
1560
+ };
1561
+
1562
+ // src/renderers/react/Testimonials.tsx
1563
+ import { jsx as jsx22 } from "react/jsx-runtime";
1564
+ var Testimonials = ({ config, theme }) => {
1565
+ return /* @__PURE__ */ jsx22(
1566
+ TestimonialsSkin,
1567
+ {
1568
+ ...config,
1569
+ theme
1570
+ }
1571
+ );
1572
+ };
1573
+ var Testimonials_default = Testimonials;
1574
+
1575
+ // src/renderers/react/base/PricingBase.tsx
1576
+ import React10 from "react";
1577
+ import { jsx as jsx23, jsxs as jsxs5 } from "react/jsx-runtime";
1578
+ var PricingBase = React10.forwardRef((props, ref) => {
1579
+ const {
1580
+ plans,
1581
+ className,
1582
+ style,
1583
+ containerStyle,
1584
+ gridStyle,
1585
+ planStyle,
1586
+ featuredBadgeStyle,
1587
+ titleStyle,
1588
+ descriptionStyle,
1589
+ priceContainerStyle,
1590
+ priceStyle,
1591
+ periodStyle,
1592
+ featuresListStyle,
1593
+ featureItemStyle,
1594
+ checkIcon,
1595
+ theme
1596
+ } = props;
1597
+ return /* @__PURE__ */ jsx23(Box, { as: "section", ref, className, style, children: /* @__PURE__ */ jsx23(Container, { style: containerStyle, children: /* @__PURE__ */ jsx23(Box, { style: gridStyle, children: plans.map((plan) => /* @__PURE__ */ jsxs5(
1598
+ Box,
1599
+ {
1600
+ className: plan.className,
1601
+ style: typeof planStyle === "function" ? planStyle(plan) : planStyle,
1602
+ children: [
1603
+ plan.featured && /* @__PURE__ */ jsx23(Box, { style: featuredBadgeStyle, children: "Popular" }),
1604
+ /* @__PURE__ */ jsx23(Box, { as: "h3", style: titleStyle, children: plan.title }),
1605
+ /* @__PURE__ */ jsx23(Box, { as: "p", style: descriptionStyle, children: plan.description }),
1606
+ /* @__PURE__ */ jsxs5(Box, { style: priceContainerStyle, children: [
1607
+ /* @__PURE__ */ jsx23(Box, { as: "span", style: priceStyle, children: plan.price }),
1608
+ plan.period && /* @__PURE__ */ jsxs5(Box, { as: "span", style: periodStyle, children: [
1609
+ "/",
1610
+ plan.period
1611
+ ] })
1612
+ ] }),
1613
+ /* @__PURE__ */ jsx23(Box, { as: "ul", style: featuresListStyle, children: plan.features.map((feature, index) => /* @__PURE__ */ jsxs5(Box, { as: "li", style: featureItemStyle, children: [
1614
+ checkIcon || /* @__PURE__ */ jsxs5(
1615
+ "svg",
1616
+ {
1617
+ width: "20",
1618
+ height: "20",
1619
+ viewBox: "0 0 24 24",
1620
+ fill: "none",
1621
+ stroke: theme.colors.accent,
1622
+ strokeWidth: "2",
1623
+ children: [
1624
+ /* @__PURE__ */ jsx23("path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14" }),
1625
+ /* @__PURE__ */ jsx23("polyline", { points: "22 4 12 14.01 9 11.01" })
1626
+ ]
1627
+ }
1628
+ ),
1629
+ feature
1630
+ ] }, index)) }),
1631
+ /* @__PURE__ */ jsx23(Button_default, { config: plan.button, theme })
1632
+ ]
1633
+ },
1634
+ plan.id
1635
+ )) }) }) });
1636
+ });
1637
+ PricingBase.displayName = "PricingBase";
1638
+
1639
+ // src/renderers/react/skins/PricingSkin.tsx
1640
+ import { jsx as jsx24 } from "react/jsx-runtime";
1641
+ var PricingSkin = (props) => {
1642
+ const { theme, ...config } = props;
1643
+ const sectionStyle = {
1644
+ padding: "4rem 0",
1645
+ ...config.style
1646
+ };
1647
+ const containerStyle = {
1648
+ maxWidth: "1200px",
1649
+ margin: "0 auto",
1650
+ padding: "0 1rem",
1651
+ ...config.containerStyle
1652
+ };
1653
+ const gridStyle = {
1654
+ display: "grid",
1655
+ gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))",
1656
+ gap: theme.spacing.xl,
1657
+ maxWidth: "1000px",
1658
+ margin: "0 auto",
1659
+ ...config.gridStyle
1660
+ };
1661
+ const planStyle = (plan) => ({
1662
+ padding: theme.spacing.lg,
1663
+ backgroundColor: theme.colors.background,
1664
+ border: `2px solid ${plan.featured ? theme.colors.primary : `${theme.colors.muted}20`}`,
1665
+ borderRadius: "0.5rem",
1666
+ boxShadow: plan.featured ? "0 20px 25px -5px rgba(0, 0, 0, 0.1)" : "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
1667
+ position: "relative",
1668
+ display: "flex",
1669
+ flexDirection: "column",
1670
+ ...config.planStyle?.(plan)
1671
+ });
1672
+ const featuredBadgeStyle = {
1673
+ position: "absolute",
1674
+ top: "-12px",
1675
+ left: "50%",
1676
+ transform: "translateX(-50%)",
1677
+ backgroundColor: theme.colors.primary,
1678
+ color: "#ffffff",
1679
+ padding: "0.25rem 0.75rem",
1680
+ borderRadius: "9999px",
1681
+ fontSize: "0.75rem",
1682
+ fontWeight: "bold",
1683
+ ...config.featuredBadgeStyle
1684
+ };
1685
+ const titleStyle = {
1686
+ fontSize: "1.5rem",
1687
+ marginBottom: theme.spacing.md,
1688
+ color: theme.colors.text,
1689
+ ...config.titleStyle
1690
+ };
1691
+ const descriptionStyle = {
1692
+ color: theme.colors.muted,
1693
+ marginBottom: theme.spacing.md,
1694
+ ...config.descriptionStyle
1695
+ };
1696
+ const priceContainerStyle = {
1697
+ marginBottom: theme.spacing.lg,
1698
+ ...config.priceContainerStyle
1699
+ };
1700
+ const priceStyle = {
1701
+ fontSize: "3rem",
1702
+ fontWeight: "bold",
1703
+ color: theme.colors.text,
1704
+ ...config.priceStyle
1705
+ };
1706
+ const periodStyle = {
1707
+ color: theme.colors.muted,
1708
+ ...config.periodStyle
1709
+ };
1710
+ const featuresListStyle = {
1711
+ marginBottom: theme.spacing.lg,
1712
+ listStyle: "none",
1713
+ padding: 0,
1714
+ flex: 1,
1715
+ ...config.featuresListStyle
1716
+ };
1717
+ const featureItemStyle = {
1718
+ padding: `${theme.spacing.sm} 0`,
1719
+ color: theme.colors.text,
1720
+ display: "flex",
1721
+ alignItems: "center",
1722
+ gap: theme.spacing.sm,
1723
+ ...config.featureItemStyle
1724
+ };
1725
+ return /* @__PURE__ */ jsx24(
1726
+ PricingBase,
1727
+ {
1728
+ ...config,
1729
+ theme,
1730
+ style: sectionStyle,
1731
+ containerStyle,
1732
+ gridStyle,
1733
+ planStyle,
1734
+ featuredBadgeStyle,
1735
+ titleStyle,
1736
+ descriptionStyle,
1737
+ priceContainerStyle,
1738
+ priceStyle,
1739
+ periodStyle,
1740
+ featuresListStyle,
1741
+ featureItemStyle
1742
+ }
1743
+ );
1744
+ };
1745
+
1746
+ // src/renderers/react/Pricing.tsx
1747
+ import { jsx as jsx25 } from "react/jsx-runtime";
1748
+ var Pricing = ({ config, theme }) => {
1749
+ return /* @__PURE__ */ jsx25(
1750
+ PricingSkin,
1751
+ {
1752
+ ...config,
1753
+ theme
1754
+ }
1755
+ );
1756
+ };
1757
+ var Pricing_default = Pricing;
1758
+
1759
+ // src/renderers/react/base/CtaBase.tsx
1760
+ import React11 from "react";
1761
+ import { jsx as jsx26, jsxs as jsxs6 } from "react/jsx-runtime";
1762
+ var CtaBase = React11.forwardRef((props, ref) => {
1763
+ const {
1764
+ title,
1765
+ description,
1766
+ button,
1767
+ image,
1768
+ className,
1769
+ style,
1770
+ containerStyle,
1771
+ contentStyle,
1772
+ theme
1773
+ } = props;
1774
+ return /* @__PURE__ */ jsx26(Box, { as: "section", ref, className, style, children: /* @__PURE__ */ jsx26(Container, { style: containerStyle, children: /* @__PURE__ */ jsxs6(Box, { style: contentStyle, children: [
1775
+ /* @__PURE__ */ jsx26("h2", { style: { fontSize: "2rem", marginBottom: theme.spacing.md }, children: title }),
1776
+ /* @__PURE__ */ jsx26("p", { style: {
1777
+ fontSize: "1.125rem",
1778
+ marginBottom: theme.spacing.lg,
1779
+ maxWidth: "600px",
1780
+ marginLeft: "auto",
1781
+ marginRight: "auto"
1782
+ }, children: description }),
1783
+ /* @__PURE__ */ jsx26(Button_default, { config: button, theme })
1784
+ ] }) }) });
1785
+ });
1786
+ CtaBase.displayName = "CtaBase";
1787
+
1788
+ // src/renderers/react/skins/CtaSkin.tsx
1789
+ import { jsx as jsx27 } from "react/jsx-runtime";
1790
+ var CtaSkin = (props) => {
1791
+ const { theme, ...config } = props;
1792
+ const backgroundColor = theme.colors.primary;
1793
+ const textColor = getBestContrastColor(backgroundColor);
1794
+ const sectionStyle = {
1795
+ padding: "4rem 0",
1796
+ ...config.style
1797
+ };
1798
+ const containerStyle = {
1799
+ maxWidth: "1200px",
1800
+ margin: "0 auto",
1801
+ padding: "0 1rem",
1802
+ ...config.containerStyle
1803
+ };
1804
+ const contentStyle = {
1805
+ textAlign: "center",
1806
+ padding: "4rem 2rem",
1807
+ backgroundColor,
1808
+ color: textColor,
1809
+ borderRadius: "0.5rem",
1810
+ ...config.contentStyle
1811
+ };
1812
+ const buttonConfig = {
1813
+ ...config.button,
1814
+ variant: config.button.variant === "primary" ? "outline" : config.button.variant
1815
+ };
1816
+ const buttonTheme = {
1817
+ ...theme,
1818
+ colors: {
1819
+ ...theme.colors,
1820
+ primary: textColor,
1821
+ // Warna outline tombol mengikuti warna teks CTA yang kontras
1822
+ text: backgroundColor
1823
+ // Warna teks saat hover tombol mengikuti warna background CTA
1824
+ }
1825
+ };
1826
+ return /* @__PURE__ */ jsx27(
1827
+ CtaBase,
1828
+ {
1829
+ ...config,
1830
+ button: buttonConfig,
1831
+ theme: buttonTheme,
1832
+ style: sectionStyle,
1833
+ containerStyle,
1834
+ contentStyle
1835
+ }
1836
+ );
1837
+ };
1838
+
1839
+ // src/renderers/react/Cta.tsx
1840
+ import { jsx as jsx28 } from "react/jsx-runtime";
1841
+ var Cta = ({ config, theme }) => {
1842
+ return /* @__PURE__ */ jsx28(
1843
+ CtaSkin,
1844
+ {
1845
+ ...config,
1846
+ theme
1847
+ }
1848
+ );
1849
+ };
1850
+ var Cta_default = Cta;
1851
+
1852
+ // src/renderers/react/base/FooterBase.tsx
1853
+ import React12 from "react";
1854
+ import { jsx as jsx29, jsxs as jsxs7 } from "react/jsx-runtime";
1855
+ var FooterBase = React12.forwardRef((props, ref) => {
1856
+ const {
1857
+ logo,
1858
+ title,
1859
+ description,
1860
+ links,
1861
+ socialLinks,
1862
+ copyright,
1863
+ className,
1864
+ style,
1865
+ containerStyle,
1866
+ gridStyle,
1867
+ columnStyle,
1868
+ linkStyle,
1869
+ onLinkMouseEnter,
1870
+ onLinkMouseLeave,
1871
+ theme
1872
+ } = props;
1873
+ return /* @__PURE__ */ jsx29(Box, { as: "footer", ref, className, style, children: /* @__PURE__ */ jsxs7(Container, { style: containerStyle, children: [
1874
+ /* @__PURE__ */ jsxs7(Box, { style: gridStyle, children: [
1875
+ /* @__PURE__ */ jsxs7(Box, { style: columnStyle, children: [
1876
+ logo && /* @__PURE__ */ jsx29("img", { src: logo, alt: title || "Logo", style: { marginBottom: theme.spacing.md }, loading: "lazy" }),
1877
+ title && /* @__PURE__ */ jsx29(Box, { as: "h3", style: { fontSize: "1.25rem", marginBottom: theme.spacing.md, color: theme.colors.text }, children: title }),
1878
+ description && /* @__PURE__ */ jsx29(Box, { as: "p", style: { color: theme.colors.muted, lineHeight: "1.6" }, children: description })
1879
+ ] }),
1880
+ links.map((linkGroup) => /* @__PURE__ */ jsxs7(Box, { style: columnStyle, children: [
1881
+ /* @__PURE__ */ jsx29(Box, { as: "h4", style: { marginBottom: theme.spacing.md, color: theme.colors.text }, children: linkGroup.title }),
1882
+ /* @__PURE__ */ jsx29(Box, { as: "ul", style: { listStyle: "none", padding: 0 }, children: linkGroup.items.map((link, index) => /* @__PURE__ */ jsx29(Box, { as: "li", style: { marginBottom: theme.spacing.sm }, children: /* @__PURE__ */ jsx29(
1883
+ "a",
1884
+ {
1885
+ href: link.url,
1886
+ target: link.target || "_self",
1887
+ rel: link.target === "_blank" ? "noopener noreferrer" : void 0,
1888
+ style: linkStyle,
1889
+ onMouseEnter: onLinkMouseEnter,
1890
+ onMouseLeave: onLinkMouseLeave,
1891
+ children: link.text
1892
+ }
1893
+ ) }, index)) })
1894
+ ] }, linkGroup.title))
1895
+ ] }),
1896
+ copyright && /* @__PURE__ */ jsx29(Box, { style: { marginTop: "4rem", paddingTop: "2rem", borderTop: `1px solid ${theme.colors.muted}20`, textAlign: "center" }, children: /* @__PURE__ */ jsx29(Box, { as: "p", style: { color: theme.colors.muted }, children: copyright }) })
1897
+ ] }) });
1898
+ });
1899
+ FooterBase.displayName = "FooterBase";
1900
+
1901
+ // src/renderers/react/skins/FooterSkin.tsx
1902
+ import { jsx as jsx30 } from "react/jsx-runtime";
1903
+ var FooterSkin = (props) => {
1904
+ const { theme, ...config } = props;
1905
+ const sectionStyle = {
1906
+ padding: "4rem 0",
1907
+ backgroundColor: theme.colors.background,
1908
+ borderTop: `1px solid ${theme.colors.muted}20`,
1909
+ ...config.style
1910
+ };
1911
+ const containerStyle = {
1912
+ maxWidth: "1200px",
1913
+ margin: "0 auto",
1914
+ padding: "0 1rem",
1915
+ ...config.containerStyle
1916
+ };
1917
+ const gridStyle = {
1918
+ display: "grid",
1919
+ gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))",
1920
+ gap: theme.spacing.xl,
1921
+ marginBottom: theme.spacing.lg,
1922
+ ...config.gridStyle
1923
+ };
1924
+ const columnStyle = {
1925
+ ...config.columnStyle
1926
+ };
1927
+ const linkStyle = {
1928
+ color: theme.colors.muted,
1929
+ textDecoration: "none",
1930
+ transition: "color 0.2s ease",
1931
+ ...config.linkStyle
1932
+ };
1933
+ const handleMouseEnter = (e) => {
1934
+ e.currentTarget.style.color = theme.colors.primary;
1935
+ config.onLinkMouseEnter?.(e);
1936
+ };
1937
+ const handleMouseLeave = (e) => {
1938
+ e.currentTarget.style.color = theme.colors.muted;
1939
+ config.onLinkMouseLeave?.(e);
1940
+ };
1941
+ return /* @__PURE__ */ jsx30(
1942
+ FooterBase,
1943
+ {
1944
+ ...config,
1945
+ theme,
1946
+ style: sectionStyle,
1947
+ containerStyle,
1948
+ gridStyle,
1949
+ columnStyle,
1950
+ linkStyle,
1951
+ onLinkMouseEnter: handleMouseEnter,
1952
+ onLinkMouseLeave: handleMouseLeave
1953
+ }
1954
+ );
1955
+ };
1956
+
1957
+ // src/renderers/react/Footer.tsx
1958
+ import { jsx as jsx31 } from "react/jsx-runtime";
1959
+ var Footer = ({ config, theme }) => {
1960
+ return /* @__PURE__ */ jsx31(
1961
+ FooterSkin,
1962
+ {
1963
+ ...config,
1964
+ theme
1965
+ }
1966
+ );
1967
+ };
1968
+ var Footer_default = Footer;
1969
+
1970
+ // src/renderers/react/base/StatsBase.tsx
1971
+ import React13 from "react";
1972
+ import { jsx as jsx32, jsxs as jsxs8 } from "react/jsx-runtime";
1973
+ var StatsBase = React13.forwardRef((props, ref) => {
1974
+ const {
1975
+ stats,
1976
+ className,
1977
+ style,
1978
+ containerStyle,
1979
+ gridStyle,
1980
+ statStyle,
1981
+ iconStyle,
1982
+ numberContainerStyle,
1983
+ numberStyle,
1984
+ labelStyle,
1985
+ theme
1986
+ } = props;
1987
+ return /* @__PURE__ */ jsx32(Box, { as: "section", ref, className, style, children: /* @__PURE__ */ jsx32(Container, { style: containerStyle, children: /* @__PURE__ */ jsx32(Box, { style: gridStyle, children: stats.map((stat) => /* @__PURE__ */ jsxs8(Box, { className: stat.className, style: statStyle, children: [
1988
+ stat.icon && /* @__PURE__ */ jsx32(Box, { style: iconStyle, children: stat.icon }),
1989
+ /* @__PURE__ */ jsxs8(Box, { style: numberContainerStyle, children: [
1990
+ stat.prefix && /* @__PURE__ */ jsx32(Box, { as: "span", style: numberStyle, children: stat.prefix }),
1991
+ /* @__PURE__ */ jsx32(Box, { as: "span", style: numberStyle, children: stat.number }),
1992
+ stat.suffix && /* @__PURE__ */ jsx32(Box, { as: "span", style: numberStyle, children: stat.suffix })
1993
+ ] }),
1994
+ /* @__PURE__ */ jsx32(Box, { as: "p", style: labelStyle, children: stat.label })
1995
+ ] }, stat.id)) }) }) });
1996
+ });
1997
+ StatsBase.displayName = "StatsBase";
1998
+
1999
+ // src/renderers/react/skins/StatsSkin.tsx
2000
+ import { jsx as jsx33 } from "react/jsx-runtime";
2001
+ var StatsSkin = (props) => {
2002
+ const { theme, ...config } = props;
2003
+ const sectionStyle = {
2004
+ padding: "4rem 0",
2005
+ ...config.style
2006
+ };
2007
+ const containerStyle = {
2008
+ maxWidth: "1200px",
2009
+ margin: "0 auto",
2010
+ padding: "0 1rem",
2011
+ ...config.containerStyle
2012
+ };
2013
+ const gridStyle = {
2014
+ display: "grid",
2015
+ gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))",
2016
+ gap: theme.spacing.xl,
2017
+ textAlign: "center",
2018
+ ...config.gridStyle
2019
+ };
2020
+ const statStyle = {
2021
+ padding: theme.spacing.lg,
2022
+ borderRadius: "0.5rem",
2023
+ backgroundColor: theme.colors.background,
2024
+ ...config.statStyle
2025
+ };
2026
+ const iconStyle = {
2027
+ fontSize: "2rem",
2028
+ marginBottom: theme.spacing.md,
2029
+ ...config.iconStyle
2030
+ };
2031
+ const numberContainerStyle = {
2032
+ marginBottom: theme.spacing.sm,
2033
+ ...config.numberContainerStyle
2034
+ };
2035
+ const numberStyle = {
2036
+ fontSize: "3rem",
2037
+ fontWeight: "bold",
2038
+ color: theme.colors.primary,
2039
+ ...config.numberStyle
2040
+ };
2041
+ const labelStyle = {
2042
+ fontSize: "1.125rem",
2043
+ color: theme.colors.muted,
2044
+ ...config.labelStyle
2045
+ };
2046
+ return /* @__PURE__ */ jsx33(
2047
+ StatsBase,
2048
+ {
2049
+ ...config,
2050
+ theme,
2051
+ style: sectionStyle,
2052
+ containerStyle,
2053
+ gridStyle,
2054
+ statStyle,
2055
+ iconStyle,
2056
+ numberContainerStyle,
2057
+ numberStyle,
2058
+ labelStyle
2059
+ }
2060
+ );
2061
+ };
2062
+
2063
+ // src/renderers/react/Stats.tsx
2064
+ import { jsx as jsx34 } from "react/jsx-runtime";
2065
+ var Stats = ({ config, theme }) => {
2066
+ return /* @__PURE__ */ jsx34(
2067
+ StatsSkin,
2068
+ {
2069
+ ...config,
2070
+ theme
2071
+ }
2072
+ );
2073
+ };
2074
+ var Stats_default = Stats;
2075
+
2076
+ // src/renderers/react/base/FaqBase.tsx
2077
+ import React14 from "react";
2078
+ import { jsx as jsx35, jsxs as jsxs9 } from "react/jsx-runtime";
2079
+ var FaqBase = React14.forwardRef((props, ref) => {
2080
+ const {
2081
+ items,
2082
+ className,
2083
+ style,
2084
+ containerStyle,
2085
+ itemStyle,
2086
+ questionStyle,
2087
+ answerStyle,
2088
+ theme
2089
+ } = props;
2090
+ return /* @__PURE__ */ jsx35(Box, { as: "section", ref, className, style, children: /* @__PURE__ */ jsx35(Container, { style: containerStyle, children: /* @__PURE__ */ jsx35(Box, { style: { maxWidth: "800px", margin: "0 auto" }, children: items.map((item) => /* @__PURE__ */ jsx35(Box, { style: itemStyle, children: /* @__PURE__ */ jsxs9("details", { style: { padding: theme.spacing.md }, children: [
2091
+ /* @__PURE__ */ jsx35("summary", { style: questionStyle, children: item.question }),
2092
+ /* @__PURE__ */ jsx35("p", { style: answerStyle, children: item.answer })
2093
+ ] }) }, item.id)) }) }) });
2094
+ });
2095
+ FaqBase.displayName = "FaqBase";
2096
+
2097
+ // src/renderers/react/skins/FaqSkin.tsx
2098
+ import { jsx as jsx36 } from "react/jsx-runtime";
2099
+ var FaqSkin = (props) => {
2100
+ const { theme, ...config } = props;
2101
+ const sectionStyle = {
2102
+ padding: "4rem 0",
2103
+ ...config.style
2104
+ };
2105
+ const containerStyle = {
2106
+ maxWidth: "1200px",
2107
+ margin: "0 auto",
2108
+ padding: "0 1rem",
2109
+ ...config.containerStyle
2110
+ };
2111
+ const itemStyle = {
2112
+ marginBottom: theme.spacing.lg,
2113
+ border: `1px solid ${theme.colors.muted}20`,
2114
+ borderRadius: "0.5rem",
2115
+ overflow: "hidden",
2116
+ ...config.itemStyle
2117
+ };
2118
+ const questionStyle = {
2119
+ fontSize: "1.125rem",
2120
+ fontWeight: "bold",
2121
+ color: theme.colors.text,
2122
+ cursor: "pointer",
2123
+ listStyle: "none",
2124
+ ...config.questionStyle
2125
+ };
2126
+ const answerStyle = {
2127
+ marginTop: theme.spacing.md,
2128
+ color: theme.colors.muted,
2129
+ lineHeight: "1.6",
2130
+ ...config.answerStyle
2131
+ };
2132
+ return /* @__PURE__ */ jsx36(
2133
+ FaqBase,
2134
+ {
2135
+ ...config,
2136
+ theme,
2137
+ style: sectionStyle,
2138
+ containerStyle,
2139
+ itemStyle,
2140
+ questionStyle,
2141
+ answerStyle
2142
+ }
2143
+ );
2144
+ };
2145
+
2146
+ // src/renderers/react/Faq.tsx
2147
+ import { jsx as jsx37 } from "react/jsx-runtime";
2148
+ var Faq = ({ config, theme }) => {
2149
+ return /* @__PURE__ */ jsx37(
2150
+ FaqSkin,
2151
+ {
2152
+ ...config,
2153
+ theme
2154
+ }
2155
+ );
2156
+ };
2157
+ var Faq_default = Faq;
2158
+
2159
+ // src/renderers/react/index.tsx
2160
+ import { jsx as jsx38 } from "react/jsx-runtime";
2161
+ var createReactRenderer = () => {
2162
+ const SectionRenderer = ({ section, theme }) => {
2163
+ switch (section.type) {
2164
+ case "header":
2165
+ return /* @__PURE__ */ jsx38(Header_default, { config: section.config, theme }, section.id);
2166
+ case "hero":
2167
+ return /* @__PURE__ */ jsx38(Hero_default, { config: section.config, theme }, section.id);
2168
+ case "features":
2169
+ return /* @__PURE__ */ jsx38(Features_default, { config: section.config, theme }, section.id);
2170
+ case "testimonials":
2171
+ return /* @__PURE__ */ jsx38(Testimonials_default, { config: section.config, theme }, section.id);
2172
+ case "pricing":
2173
+ return /* @__PURE__ */ jsx38(Pricing_default, { config: section.config, theme }, section.id);
2174
+ case "cta":
2175
+ return /* @__PURE__ */ jsx38(Cta_default, { config: section.config, theme }, section.id);
2176
+ case "footer":
2177
+ return /* @__PURE__ */ jsx38(Footer_default, { config: section.config, theme }, section.id);
2178
+ case "stats":
2179
+ return /* @__PURE__ */ jsx38(Stats_default, { config: section.config, theme }, section.id);
2180
+ case "faq":
2181
+ return /* @__PURE__ */ jsx38(Faq_default, { config: section.config, theme }, section.id);
2182
+ default:
2183
+ console.warn(`Unknown section type: ${section.type}`);
2184
+ return null;
2185
+ }
2186
+ };
2187
+ const LandingPage = ({ config }) => {
2188
+ React15.useEffect(() => {
2189
+ const style = document.createElement("style");
2190
+ style.textContent = `
2191
+ * {
2192
+ margin: 0;
2193
+ padding: 0;
2194
+ box-sizing: border-box;
2195
+ }
2196
+ body {
2197
+ font-family: ${config.theme?.fonts?.body || "system-ui, sans-serif"};
2198
+ background-color: ${config.theme?.colors?.background || "#ffffff"};
2199
+ color: ${config.theme?.colors?.text || "#000000"};
2200
+ line-height: 1.6;
2201
+ }
2202
+ `;
2203
+ document.head.appendChild(style);
2204
+ return () => {
2205
+ document.head.removeChild(style);
2206
+ };
2207
+ }, [config]);
2208
+ return /* @__PURE__ */ jsx38(Box, { children: config.sections.map((section) => /* @__PURE__ */ jsx38(SectionRenderer, { section, theme: config.theme }, section.id)) });
2209
+ };
2210
+ return LandingPage;
2211
+ };
2212
+ export {
2213
+ createCtaSection,
2214
+ createFaqSection,
2215
+ createFeaturesSection,
2216
+ createFooterSection,
2217
+ createHeaderSection,
2218
+ createHeroSection,
2219
+ createPricingSection,
2220
+ createReactRenderer,
2221
+ createStatsSection,
2222
+ createTestimonialsSection,
2223
+ defaultTheme,
2224
+ defineLandingPage,
2225
+ landingPageSchema,
2226
+ sectionConfigSchemas,
2227
+ validateConfig,
2228
+ validateSection2 as validateSection
2229
+ };
2230
+ //# sourceMappingURL=index.mjs.map