@bailierich/booking-components 2.0.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.
Files changed (83) hide show
  1. package/README.md +319 -0
  2. package/TENANT_DATA_INTEGRATION.md +402 -0
  3. package/TENANT_SETUP.md +316 -0
  4. package/components/BookingFlow/BookingFlow.tsx +790 -0
  5. package/components/BookingFlow/index.ts +5 -0
  6. package/components/BookingFlow/steps/AddonsSelection.tsx +118 -0
  7. package/components/BookingFlow/steps/Confirmation.tsx +185 -0
  8. package/components/BookingFlow/steps/ContactForm.tsx +292 -0
  9. package/components/BookingFlow/steps/CycleAwareDateSelection.tsx +277 -0
  10. package/components/BookingFlow/steps/DateSelection.tsx +473 -0
  11. package/components/BookingFlow/steps/ServiceSelection.tsx +315 -0
  12. package/components/BookingFlow/steps/TimeSelection.tsx +230 -0
  13. package/components/BookingFlow/steps/index.ts +10 -0
  14. package/components/BottomSheet/index.tsx +120 -0
  15. package/components/Forms/FormBlock.tsx +283 -0
  16. package/components/Forms/FormField.tsx +385 -0
  17. package/components/Forms/FormRenderer.tsx +216 -0
  18. package/components/Forms/FormValidation.ts +122 -0
  19. package/components/Forms/index.ts +4 -0
  20. package/components/HoldTimer/HoldTimer.tsx +266 -0
  21. package/components/HoldTimer/index.ts +2 -0
  22. package/components/SectionRenderer.tsx +558 -0
  23. package/components/Sections/About.tsx +145 -0
  24. package/components/Sections/BeforeAfter.tsx +81 -0
  25. package/components/Sections/BookingSection.tsx +76 -0
  26. package/components/Sections/Contact.tsx +103 -0
  27. package/components/Sections/FAQSection.tsx +239 -0
  28. package/components/Sections/FeatureContent.tsx +113 -0
  29. package/components/Sections/FeaturedLink.tsx +103 -0
  30. package/components/Sections/FixedInfoCard.tsx +189 -0
  31. package/components/Sections/Gallery.tsx +83 -0
  32. package/components/Sections/Header.tsx +78 -0
  33. package/components/Sections/Hero.tsx +178 -0
  34. package/components/Sections/ImageSection.tsx +147 -0
  35. package/components/Sections/InstagramFeed.tsx +38 -0
  36. package/components/Sections/LinkList.tsx +76 -0
  37. package/components/Sections/LocationMap.tsx +202 -0
  38. package/components/Sections/Logo.tsx +61 -0
  39. package/components/Sections/MinimalFooter.tsx +78 -0
  40. package/components/Sections/MinimalHeader.tsx +81 -0
  41. package/components/Sections/MinimalNavigation.tsx +63 -0
  42. package/components/Sections/Navbar.tsx +258 -0
  43. package/components/Sections/PricingTable.tsx +106 -0
  44. package/components/Sections/ScrollingTextDivider.tsx +138 -0
  45. package/components/Sections/ScrollingTextDivider.tsx.bak +138 -0
  46. package/components/Sections/ServicesPreview.tsx +129 -0
  47. package/components/Sections/SocialBar.tsx +177 -0
  48. package/components/Sections/Team.tsx +80 -0
  49. package/components/Sections/Testimonials.tsx +92 -0
  50. package/components/Sections/TextSection.tsx +116 -0
  51. package/components/Sections/VideoSection.tsx +178 -0
  52. package/components/Sections/index.ts +57 -0
  53. package/components/index.ts +21 -0
  54. package/dist/index-DAai7Glf.d.mts +474 -0
  55. package/dist/index-DAai7Glf.d.ts +474 -0
  56. package/dist/index.d.mts +1075 -0
  57. package/dist/index.d.ts +1075 -0
  58. package/dist/index.js +22 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/index.mjs +22 -0
  61. package/dist/index.mjs.map +1 -0
  62. package/dist/styles/index.d.mts +1 -0
  63. package/dist/styles/index.d.ts +1 -0
  64. package/dist/styles/index.js +2 -0
  65. package/dist/styles/index.js.map +1 -0
  66. package/dist/styles/index.mjs +2 -0
  67. package/dist/styles/index.mjs.map +1 -0
  68. package/docs/API.md +849 -0
  69. package/docs/CALLBACKS.md +760 -0
  70. package/docs/COMPLETE_SESSION_SUMMARY.md +404 -0
  71. package/docs/DATA_SHAPES.md +684 -0
  72. package/docs/MIGRATION.md +662 -0
  73. package/docs/PAYMENT_INTEGRATION.md +766 -0
  74. package/docs/SESSION_SUMMARY.md +185 -0
  75. package/docs/STYLING.md +735 -0
  76. package/index.ts +4 -0
  77. package/lib/storage.ts +239 -0
  78. package/package.json +59 -0
  79. package/styles/animations.ts +210 -0
  80. package/styles/index.ts +1 -0
  81. package/tsconfig.json +32 -0
  82. package/tsup.config.ts +13 -0
  83. package/types/index.ts +369 -0
@@ -0,0 +1,103 @@
1
+ 'use client';
2
+
3
+ export interface FeaturedLinkProps {
4
+ text?: string;
5
+ url?: string;
6
+ style?: 'gradient' | 'solid' | 'outline';
7
+ size?: 'small' | 'medium' | 'large';
8
+ colors: {
9
+ primary: string;
10
+ secondary: string;
11
+ };
12
+ enableInlineEditing?: boolean;
13
+ sectionId?: string;
14
+ }
15
+
16
+ export function FeaturedLink({
17
+ text,
18
+ url,
19
+ style = 'gradient',
20
+ size = 'large',
21
+ colors,
22
+ enableInlineEditing = false,
23
+ sectionId = ''
24
+ }: FeaturedLinkProps) {
25
+ const getClassName = () => {
26
+ let sizeClasses = '';
27
+ switch (size) {
28
+ case 'small':
29
+ sizeClasses = 'p-3 text-base';
30
+ break;
31
+ case 'medium':
32
+ sizeClasses = 'p-4 text-lg';
33
+ break;
34
+ case 'large':
35
+ sizeClasses = 'p-5 text-xl';
36
+ break;
37
+ default:
38
+ sizeClasses = 'p-5 text-xl';
39
+ }
40
+
41
+ let styleClasses = '';
42
+ switch (style) {
43
+ case 'gradient':
44
+ styleClasses = 'rounded-full';
45
+ break;
46
+ case 'solid':
47
+ styleClasses = 'rounded-lg';
48
+ break;
49
+ case 'outline':
50
+ styleClasses = 'rounded-full border-2';
51
+ break;
52
+ default:
53
+ styleClasses = 'rounded-full';
54
+ }
55
+
56
+ return `block max-w-2xl mx-auto ${sizeClasses} ${styleClasses} text-center font-semibold transition-all hover:scale-105 hover:shadow-xl`;
57
+ };
58
+
59
+ const getStyle = () => {
60
+ switch (style) {
61
+ case 'gradient':
62
+ return {
63
+ background: `linear-gradient(135deg, ${colors.primary} 0%, ${colors.secondary} 100%)`,
64
+ color: '#FFFFFF'
65
+ };
66
+ case 'solid':
67
+ return {
68
+ backgroundColor: colors.primary,
69
+ color: '#FFFFFF'
70
+ };
71
+ case 'outline':
72
+ return {
73
+ backgroundColor: 'transparent',
74
+ borderColor: colors.primary,
75
+ color: colors.primary
76
+ };
77
+ default:
78
+ return {
79
+ backgroundColor: colors.primary,
80
+ color: '#FFFFFF'
81
+ };
82
+ }
83
+ };
84
+
85
+ return (
86
+ <div className="container mx-auto px-4">
87
+ <a
88
+ href={url || '/book'}
89
+ className={getClassName()}
90
+ style={getStyle()}
91
+ {...(enableInlineEditing && {
92
+ 'data-editable': true,
93
+ 'data-section-id': sectionId,
94
+ 'data-field-path': 'settings.text'
95
+ })}
96
+ >
97
+ {text || '💅 Book Now'}
98
+ </a>
99
+ </div>
100
+ );
101
+ }
102
+
103
+ export default FeaturedLink;
@@ -0,0 +1,189 @@
1
+ 'use client';
2
+
3
+ export interface CardLink {
4
+ id: string;
5
+ label: string;
6
+ url: string;
7
+ }
8
+
9
+ export interface FixedInfoCardProps {
10
+ name?: string;
11
+ subheading?: string;
12
+ bio?: string;
13
+ links?: CardLink[];
14
+ bookingButtonText?: string;
15
+ bookingButtonUrl?: string;
16
+ showBookingButton?: boolean;
17
+ position?: 'bottom-left' | 'bottom-right' | 'bottom-center';
18
+ cardWidth?: 'small' | 'medium' | 'large';
19
+ cardColor?: string;
20
+ colors: {
21
+ primary: string;
22
+ text: string;
23
+ buttonText?: string;
24
+ };
25
+ typography: {
26
+ headingFont?: string;
27
+ };
28
+ enableInlineEditing?: boolean;
29
+ sectionId?: string;
30
+ }
31
+
32
+ export function FixedInfoCard({
33
+ name,
34
+ subheading,
35
+ bio,
36
+ links = [],
37
+ bookingButtonText,
38
+ bookingButtonUrl,
39
+ showBookingButton = true,
40
+ position = 'bottom-left',
41
+ cardWidth = 'medium',
42
+ cardColor = '#FFFFFF',
43
+ colors,
44
+ typography,
45
+ enableInlineEditing = false,
46
+ sectionId = ''
47
+ }: FixedInfoCardProps) {
48
+ const hexToRgba = (hex: string, alpha: number) => {
49
+ const r = parseInt(hex.slice(1, 3), 16);
50
+ const g = parseInt(hex.slice(3, 5), 16);
51
+ const b = parseInt(hex.slice(5, 7), 16);
52
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
53
+ };
54
+
55
+ const widthMap = {
56
+ small: '280px',
57
+ medium: '320px',
58
+ large: '400px'
59
+ };
60
+
61
+ const positionClasses = {
62
+ 'bottom-left': 'bottom-0 left-0',
63
+ 'bottom-right': 'bottom-0 right-0',
64
+ 'bottom-center': 'bottom-0 left-1/2 -translate-x-1/2'
65
+ };
66
+
67
+ const textAlignClass = position === 'bottom-left' ? 'text-left' : position === 'bottom-right' ? 'text-right' : 'text-center';
68
+ const subheadingAlign = position === 'bottom-left' ? 'justify-start' : position === 'bottom-right' ? 'justify-end' : 'justify-center';
69
+
70
+ const getRoundedClass = () => {
71
+ if (position === 'bottom-left') {
72
+ return 'rounded-tr-3xl rounded-tl-none';
73
+ } else if (position === 'bottom-right') {
74
+ return 'rounded-tl-3xl rounded-tr-none';
75
+ } else {
76
+ return 'rounded-t-3xl';
77
+ }
78
+ };
79
+
80
+ return (
81
+ <div
82
+ className={`fixed z-50 ${positionClasses[position]}`}
83
+ style={{ width: widthMap[cardWidth] }}
84
+ >
85
+ <div
86
+ className={`${getRoundedClass()} shadow-2xl border-t border-x`}
87
+ style={{
88
+ backgroundColor: hexToRgba(cardColor, 0.9),
89
+ borderColor: hexToRgba(cardColor, 0.3),
90
+ boxShadow: '0 -10px 40px rgba(0, 0, 0, 0.1)'
91
+ }}
92
+ >
93
+ <div className={`px-8 pt-8 pb-3 ${textAlignClass}`}>
94
+ <h3
95
+ className="text-2xl font-bold"
96
+ style={{
97
+ fontFamily: typography.headingFont,
98
+ color: colors.text
99
+ }}
100
+ {...(enableInlineEditing && {
101
+ 'data-editable': true,
102
+ 'data-section-id': sectionId,
103
+ 'data-field-path': 'settings.name'
104
+ })}
105
+ >
106
+ {name || 'Your Name'}
107
+ </h3>
108
+ </div>
109
+
110
+ <div className="mb-6">
111
+ {subheading ? (
112
+ <div className={`flex items-center gap-3 px-8 ${subheadingAlign}`}>
113
+ <span
114
+ className="text-xs font-medium whitespace-nowrap"
115
+ style={{ color: colors.primary }}
116
+ {...(enableInlineEditing && {
117
+ 'data-editable': true,
118
+ 'data-section-id': sectionId,
119
+ 'data-field-path': 'settings.subheading'
120
+ })}
121
+ >
122
+ {subheading}
123
+ </span>
124
+ <div className="flex-1 h-px" style={{ backgroundColor: colors.primary, opacity: 0.3 }} />
125
+ </div>
126
+ ) : (
127
+ <div className="w-full h-px" style={{ backgroundColor: colors.primary, opacity: 0.3 }} />
128
+ )}
129
+ </div>
130
+
131
+ <div className={`px-8 pb-8 ${textAlignClass}`}>
132
+ <p
133
+ className="text-sm opacity-80 mb-4"
134
+ style={{ color: colors.text }}
135
+ {...(enableInlineEditing && {
136
+ 'data-editable': true,
137
+ 'data-section-id': sectionId,
138
+ 'data-field-path': 'settings.bio'
139
+ })}
140
+ >
141
+ {bio || 'Transforming beauty, one client at a time ✨'}
142
+ </p>
143
+
144
+ {links.length > 0 && (
145
+ <div className="space-y-2 mb-4">
146
+ {links.map((link, index) => (
147
+ <a
148
+ key={link.id}
149
+ href={link.url}
150
+ target="_blank"
151
+ rel="noopener noreferrer"
152
+ className="block text-sm font-medium hover:opacity-70 transition-opacity"
153
+ style={{ color: colors.primary }}
154
+ {...(enableInlineEditing && {
155
+ 'data-editable': true,
156
+ 'data-section-id': sectionId,
157
+ 'data-field-path': `settings.links.${index}.label`
158
+ })}
159
+ >
160
+ {link.label}
161
+ </a>
162
+ ))}
163
+ </div>
164
+ )}
165
+
166
+ {showBookingButton && (
167
+ <a
168
+ href={bookingButtonUrl || '/book'}
169
+ className="block w-full py-3 px-4 rounded-xl text-center font-semibold transition-all hover:scale-105 hover:shadow-lg"
170
+ style={{
171
+ backgroundColor: colors.primary,
172
+ color: colors.buttonText || '#FFFFFF'
173
+ }}
174
+ {...(enableInlineEditing && {
175
+ 'data-editable': true,
176
+ 'data-section-id': sectionId,
177
+ 'data-field-path': 'settings.bookingButtonText'
178
+ })}
179
+ >
180
+ {bookingButtonText || 'Book Appointment'}
181
+ </a>
182
+ )}
183
+ </div>
184
+ </div>
185
+ </div>
186
+ );
187
+ }
188
+
189
+ export default FixedInfoCard;
@@ -0,0 +1,83 @@
1
+ 'use client';
2
+
3
+ export interface GalleryImage {
4
+ id: string | number;
5
+ url?: string;
6
+ alt?: string;
7
+ }
8
+
9
+ export interface GalleryProps {
10
+ title?: string;
11
+ images?: GalleryImage[];
12
+ columns?: 2 | 3 | 4;
13
+ colors: {
14
+ primary: string;
15
+ text: string;
16
+ };
17
+ typography: {
18
+ headingFont?: string;
19
+ };
20
+ enableInlineEditing?: boolean;
21
+ sectionId?: string;
22
+ }
23
+
24
+ export function Gallery({
25
+ title,
26
+ images = [],
27
+ columns = 3,
28
+ colors,
29
+ typography,
30
+ enableInlineEditing = false,
31
+ sectionId = ''
32
+ }: GalleryProps) {
33
+ const galleryGridMap = {
34
+ 2: 'grid-cols-2',
35
+ 3: 'grid-cols-2 md:grid-cols-3',
36
+ 4: 'grid-cols-2 md:grid-cols-4'
37
+ };
38
+
39
+ const imagesToShow = images.length > 0 ? images : [1, 2, 3, 4, 5, 6].map(i => ({ id: i, url: undefined }));
40
+
41
+ return (
42
+ <section className="py-16 px-6">
43
+ <div className="max-w-7xl mx-auto">
44
+ <h2
45
+ className="text-3xl md:text-4xl font-bold text-center mb-12"
46
+ style={{ fontFamily: typography.headingFont, color: colors.text }}
47
+ {...(enableInlineEditing && {
48
+ 'data-editable': true,
49
+ 'data-section-id': sectionId,
50
+ 'data-field-path': 'settings.title'
51
+ })}
52
+ >
53
+ {title || 'Gallery'}
54
+ </h2>
55
+ <div className={`grid ${galleryGridMap[columns]} gap-4`}>
56
+ {imagesToShow.map((img, i) => (
57
+ <div
58
+ key={img.id || i}
59
+ className="relative aspect-square overflow-hidden rounded-lg group"
60
+ >
61
+ {img.url ? (
62
+ <img
63
+ src={img.url}
64
+ alt={img.alt || ''}
65
+ className="w-full h-full object-cover transition-transform group-hover:scale-110"
66
+ />
67
+ ) : (
68
+ <div
69
+ className="w-full h-full flex items-center justify-center transition-transform group-hover:scale-110"
70
+ style={{ backgroundColor: colors.primary, opacity: 0.2 }}
71
+ >
72
+ <span className="text-sm opacity-50">Add image</span>
73
+ </div>
74
+ )}
75
+ </div>
76
+ ))}
77
+ </div>
78
+ </div>
79
+ </section>
80
+ );
81
+ }
82
+
83
+ export default Gallery;
@@ -0,0 +1,78 @@
1
+ 'use client';
2
+
3
+ export interface HeaderProps {
4
+ name?: string;
5
+ title?: string;
6
+ bio?: string;
7
+ showName?: boolean;
8
+ showTitle?: boolean;
9
+ showBio?: boolean;
10
+ colors: {
11
+ text: string;
12
+ };
13
+ typography: {
14
+ headingFont?: string;
15
+ };
16
+ enableInlineEditing?: boolean;
17
+ sectionId?: string;
18
+ }
19
+
20
+ export function Header({
21
+ name,
22
+ title,
23
+ bio,
24
+ showName = true,
25
+ showTitle = true,
26
+ showBio = true,
27
+ colors,
28
+ typography,
29
+ enableInlineEditing = false,
30
+ sectionId = ''
31
+ }: HeaderProps) {
32
+ return (
33
+ <div className="container mx-auto px-4 text-center">
34
+ {showName && (
35
+ <h1
36
+ className="text-4xl font-bold mb-3"
37
+ style={{
38
+ fontFamily: typography.headingFont,
39
+ color: colors.text
40
+ }}
41
+ {...(enableInlineEditing && {
42
+ 'data-editable': true,
43
+ 'data-section-id': sectionId,
44
+ 'data-field-path': 'settings.name'
45
+ })}
46
+ >
47
+ {name || 'Your Name'}
48
+ </h1>
49
+ )}
50
+ {showTitle && (
51
+ <p
52
+ className="text-lg opacity-80 mb-2"
53
+ {...(enableInlineEditing && {
54
+ 'data-editable': true,
55
+ 'data-section-id': sectionId,
56
+ 'data-field-path': 'settings.title'
57
+ })}
58
+ >
59
+ {title || 'Beauty Professional'}
60
+ </p>
61
+ )}
62
+ {showBio && (
63
+ <p
64
+ className="text-base opacity-70 max-w-md mx-auto"
65
+ {...(enableInlineEditing && {
66
+ 'data-editable': true,
67
+ 'data-section-id': sectionId,
68
+ 'data-field-path': 'settings.bio'
69
+ })}
70
+ >
71
+ {bio || 'Transform your look ✨'}
72
+ </p>
73
+ )}
74
+ </div>
75
+ );
76
+ }
77
+
78
+ export default Header;
@@ -0,0 +1,178 @@
1
+ 'use client';
2
+
3
+ export interface HeroProps {
4
+ headline?: string;
5
+ subheadline?: string;
6
+ heroImage?: string;
7
+ ctaButton?: {
8
+ text: string;
9
+ url: string;
10
+ };
11
+ layout?: 'split' | 'full';
12
+ imagePosition?: 'left' | 'right';
13
+ contentDisplay?: 'side' | 'overlay';
14
+ colors: {
15
+ primary: string;
16
+ text: string;
17
+ };
18
+ typography: {
19
+ headingFont?: string;
20
+ };
21
+ enableInlineEditing?: boolean;
22
+ sectionId?: string;
23
+ }
24
+
25
+ export function Hero({
26
+ headline,
27
+ subheadline,
28
+ heroImage,
29
+ ctaButton,
30
+ layout = 'split',
31
+ imagePosition = 'right',
32
+ contentDisplay = 'side',
33
+ colors,
34
+ typography,
35
+ enableInlineEditing = false,
36
+ sectionId = ''
37
+ }: HeroProps) {
38
+ const heroContent = (
39
+ <div className={layout === 'full' ? 'text-center max-w-4xl z-10' : 'text-left'}>
40
+ <h1
41
+ className={layout === 'full' ? 'text-5xl md:text-6xl font-bold mb-6' : 'text-5xl font-bold mb-4'}
42
+ style={{
43
+ fontFamily: typography.headingFont,
44
+ color: layout === 'full' && heroImage ? '#FFFFFF' : colors.text,
45
+ textShadow: layout === 'full' && heroImage ? '0 2px 10px rgba(0,0,0,0.3)' : 'none'
46
+ }}
47
+ {...(enableInlineEditing && {
48
+ 'data-editable': true,
49
+ 'data-section-id': sectionId,
50
+ 'data-field-path': 'settings.headline'
51
+ })}
52
+ >
53
+ {headline || 'Welcome'}
54
+ </h1>
55
+ {subheadline && (
56
+ <p
57
+ className={layout === 'full' ? 'text-xl md:text-2xl opacity-90 mb-12' : 'text-xl opacity-80 mb-8'}
58
+ style={{
59
+ color: layout === 'full' && heroImage ? '#FFFFFF' : colors.text,
60
+ textShadow: layout === 'full' && heroImage ? '0 2px 10px rgba(0,0,0,0.3)' : 'none'
61
+ }}
62
+ {...(enableInlineEditing && {
63
+ 'data-editable': true,
64
+ 'data-section-id': sectionId,
65
+ 'data-field-path': 'settings.subheadline'
66
+ })}
67
+ >
68
+ {subheadline}
69
+ </p>
70
+ )}
71
+ {ctaButton && (
72
+ <a
73
+ href={ctaButton.url || '#'}
74
+ className="inline-block px-8 py-4 rounded-lg font-semibold text-lg transition-all hover:scale-105 shadow-lg"
75
+ style={{
76
+ backgroundColor: colors.primary,
77
+ color: '#FFFFFF'
78
+ }}
79
+ {...(enableInlineEditing && {
80
+ 'data-editable': true,
81
+ 'data-section-id': sectionId,
82
+ 'data-field-path': 'settings.ctaButton.text'
83
+ })}
84
+ >
85
+ {ctaButton.text || 'Get Started'}
86
+ </a>
87
+ )}
88
+ </div>
89
+ );
90
+
91
+ const heroImageElement = heroImage ? (
92
+ <div className="w-full h-full rounded-2xl overflow-hidden">
93
+ <img
94
+ src={heroImage}
95
+ alt={headline || 'Hero'}
96
+ className="w-full h-full object-cover"
97
+ />
98
+ </div>
99
+ ) : (
100
+ <div
101
+ className="w-full h-full rounded-2xl flex items-center justify-center"
102
+ style={{ backgroundColor: colors.primary, opacity: 0.1 }}
103
+ >
104
+ <span className="text-sm opacity-50">Add hero image</span>
105
+ </div>
106
+ );
107
+
108
+ if (layout === 'full') {
109
+ return (
110
+ <div
111
+ className="relative py-20 md:py-24 px-4 flex items-center justify-center"
112
+ style={{
113
+ backgroundImage: heroImage ? `url(${heroImage})` : 'none',
114
+ backgroundSize: 'cover',
115
+ backgroundPosition: 'center',
116
+ backgroundColor: !heroImage ? colors.primary : 'transparent',
117
+ opacity: !heroImage ? 0.1 : 1
118
+ }}
119
+ >
120
+ {heroImage && (
121
+ <div className="absolute inset-0 bg-black/40" />
122
+ )}
123
+ {heroContent}
124
+ </div>
125
+ );
126
+ }
127
+
128
+ if (contentDisplay === 'overlay') {
129
+ return (
130
+ <div className="container mx-auto px-4 py-16">
131
+ <div className="relative max-w-6xl mx-auto rounded-2xl overflow-hidden" style={{ height: '500px' }}>
132
+ {heroImage ? (
133
+ <img
134
+ src={heroImage}
135
+ alt={headline || 'Hero'}
136
+ className="absolute inset-0 w-full h-full object-cover"
137
+ />
138
+ ) : (
139
+ <div
140
+ className="absolute inset-0"
141
+ style={{ backgroundColor: colors.primary, opacity: 0.1 }}
142
+ />
143
+ )}
144
+ <div className="absolute inset-0 bg-gradient-to-r from-black/60 to-black/20" />
145
+ <div
146
+ className={`relative h-full flex items-center ${imagePosition === 'left' ? 'justify-end' : 'justify-start'} px-12`}
147
+ >
148
+ <div className="max-w-lg" style={{ color: '#FFFFFF', textShadow: '0 2px 10px rgba(0,0,0,0.3)' }}>
149
+ {heroContent}
150
+ </div>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ );
155
+ }
156
+
157
+ return (
158
+ <div className="container mx-auto px-4 py-16">
159
+ <div className="grid md:grid-cols-2 gap-12 items-center max-w-6xl mx-auto">
160
+ {imagePosition === 'left' && (
161
+ <div className="aspect-[4/3]">
162
+ {heroImageElement}
163
+ </div>
164
+ )}
165
+ <div>
166
+ {heroContent}
167
+ </div>
168
+ {imagePosition === 'right' && (
169
+ <div className="aspect-[4/3]">
170
+ {heroImageElement}
171
+ </div>
172
+ )}
173
+ </div>
174
+ </div>
175
+ );
176
+ }
177
+
178
+ export default Hero;