@codesinger0/shared-components 1.1.85 → 1.1.86
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/package.json +1 -1
- package/dist/components 2/AccessibilityMenu.jsx +0 -474
- package/dist/components 2/AdvantagesList.jsx +0 -89
- package/dist/components 2/ArticlesList.jsx +0 -269
- package/dist/components 2/DualTextCard.jsx +0 -73
- package/dist/components 2/FloatingWhatsAppButton.jsx +0 -180
- package/dist/components 2/FullscreenCarousel.jsx +0 -292
- package/dist/components 2/Hero.jsx +0 -198
- package/dist/components 2/IconGrid.jsx +0 -144
- package/dist/components 2/IntroSection.jsx +0 -74
- package/dist/components 2/LargeItemCard.jsx +0 -267
- package/dist/components 2/MasonryItemCard.jsx +0 -247
- package/dist/components 2/Menu.d.ts +0 -26
- package/dist/components 2/Menu.jsx +0 -268
- package/dist/components 2/MyOrdersDisplay.jsx +0 -311
- package/dist/components 2/QAAccordion.jsx +0 -212
- package/dist/components 2/SmallItemCard.jsx +0 -152
- package/dist/components 2/SmallItemsGrid.jsx +0 -313
- package/dist/components 2/TextListCards.jsx +0 -107
- package/dist/components 2/ToastProvider.jsx +0 -38
- package/dist/components 2/UnderConstruction.jsx +0 -76
- package/dist/components 2/VideoCard.jsx +0 -88
- package/dist/components 2/cart/CartItem.jsx +0 -101
- package/dist/components 2/cart/FloatingCartButton.jsx +0 -49
- package/dist/components 2/cart/OrderForm.jsx +0 -960
- package/dist/components 2/cart/ShoppingCartModal.jsx +0 -229
- package/dist/components 2/clubMembership/ClubMembershipModal.jsx +0 -289
- package/dist/components 2/clubMembership/ClubPromoModal.jsx +0 -108
- package/dist/components 2/elements/CTAButton.jsx +0 -17
- package/dist/components 2/elements/FixedWidthHeroVideo.jsx +0 -92
- package/dist/components 2/elements/ImageLightbox.jsx +0 -112
- package/dist/components 2/elements/RoundButton.jsx +0 -44
- package/dist/components 2/elements/SmallButton.jsx +0 -35
- package/dist/components 2/elements/Toast.jsx +0 -37
- package/dist/components 2/elements/VideoLightbox.jsx +0 -76
- package/dist/components 2/modals/ItemDetailsModal.jsx +0 -192
- package/dist/components 2/products/CategoryList.jsx +0 -24
- package/dist/components 2/products/PriceRangeSlider.jsx +0 -162
- package/dist/components 2/products/ProductsDisplay.jsx +0 -40
- package/dist/components 2/products/ProductsSidebar.jsx +0 -46
- package/dist/components 2/products/SubcategorySection.jsx +0 -37
- package/dist/context 2/CartContext.jsx +0 -165
- package/dist/context 2/ItemModalContext.jsx +0 -40
- package/dist/hooks 2/useScrollLock.js +0 -52
- package/dist/index 2.js +0 -45
- package/dist/integrations 2/emailService.js +0 -167
- package/dist/styles 2/shared-components.css +0 -29
- package/dist/utils 2/ScrollManager.jsx +0 -85
- package/dist/utils 2/ScrollToTop.jsx +0 -14
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
-
import useEmblaCarousel from 'embla-carousel-react';
|
|
3
|
-
import Autoplay from 'embla-carousel-autoplay';
|
|
4
|
-
|
|
5
|
-
const FullscreenCarousel = ({
|
|
6
|
-
slides = [],
|
|
7
|
-
desktopHeight = '100vh',
|
|
8
|
-
mobileHeight = '100vh',
|
|
9
|
-
autoplay = true,
|
|
10
|
-
scrollInterval = 5000,
|
|
11
|
-
loop = true,
|
|
12
|
-
showDots = true,
|
|
13
|
-
showArrows = true,
|
|
14
|
-
className = '',
|
|
15
|
-
...props
|
|
16
|
-
}) => {
|
|
17
|
-
const [prevBtnEnabled, setPrevBtnEnabled] = useState(false);
|
|
18
|
-
const [nextBtnEnabled, setNextBtnEnabled] = useState(false);
|
|
19
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
20
|
-
const [scrollSnaps, setScrollSnaps] = useState([]);
|
|
21
|
-
|
|
22
|
-
// Embla carousel setup
|
|
23
|
-
const autoplayOptions = {
|
|
24
|
-
delay: scrollInterval,
|
|
25
|
-
stopOnInteraction: false,
|
|
26
|
-
stopOnMouseEnter: false,
|
|
27
|
-
rootNode: (emblaRoot) => emblaRoot.parentElement,
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const plugins = autoplay ? [Autoplay(autoplayOptions)] : [];
|
|
31
|
-
|
|
32
|
-
const [emblaRef, emblaApi] = useEmblaCarousel(
|
|
33
|
-
{
|
|
34
|
-
loop: loop,
|
|
35
|
-
direction: 'rtl',
|
|
36
|
-
skipSnaps: false,
|
|
37
|
-
},
|
|
38
|
-
plugins
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
const scrollPrev = useCallback(() => emblaApi && emblaApi.scrollPrev(), [emblaApi]);
|
|
42
|
-
const scrollNext = useCallback(() => emblaApi && emblaApi.scrollNext(), [emblaApi]);
|
|
43
|
-
const scrollTo = useCallback((index) => emblaApi && emblaApi.scrollTo(index), [emblaApi]);
|
|
44
|
-
|
|
45
|
-
const onSelect = useCallback(() => {
|
|
46
|
-
if (!emblaApi) return;
|
|
47
|
-
setSelectedIndex(emblaApi.selectedScrollSnap());
|
|
48
|
-
setPrevBtnEnabled(emblaApi.canScrollPrev());
|
|
49
|
-
setNextBtnEnabled(emblaApi.canScrollNext());
|
|
50
|
-
}, [emblaApi]);
|
|
51
|
-
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
if (!emblaApi) return;
|
|
54
|
-
onSelect();
|
|
55
|
-
setScrollSnaps(emblaApi.scrollSnapList());
|
|
56
|
-
emblaApi.on('select', onSelect);
|
|
57
|
-
emblaApi.on('reInit', onSelect);
|
|
58
|
-
}, [emblaApi, onSelect]);
|
|
59
|
-
|
|
60
|
-
if (!slides || slides.length === 0) {
|
|
61
|
-
return (
|
|
62
|
-
<div className="w-full flex items-center justify-center bg-gray-100" style={{ height: desktopHeight }}>
|
|
63
|
-
<p className="text-gray-500">No slides to display</p>
|
|
64
|
-
</div>
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
<div className={`fullscreen-carousel-wrapper ${className}`} {...props}>
|
|
70
|
-
<div className="fullscreen-carousel" dir="rtl">
|
|
71
|
-
<div className="fullscreen-carousel__viewport" ref={emblaRef}>
|
|
72
|
-
<div className="fullscreen-carousel__container">
|
|
73
|
-
{slides.map((slide, index) => {
|
|
74
|
-
const isSingleSlide = !slide.leftContent && !slide.rightContent;
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<div key={index} className="fullscreen-carousel__slide">
|
|
78
|
-
{isSingleSlide ? (
|
|
79
|
-
/* Single Slide Mode */
|
|
80
|
-
<div
|
|
81
|
-
className="w-full h-full"
|
|
82
|
-
style={{ height: desktopHeight }}
|
|
83
|
-
>
|
|
84
|
-
{slide.content}
|
|
85
|
-
</div>
|
|
86
|
-
) : (
|
|
87
|
-
<>
|
|
88
|
-
{/* Desktop Layout - Horizontal Split */}
|
|
89
|
-
<div
|
|
90
|
-
className="hidden md:grid md:grid-cols-2 w-full h-full"
|
|
91
|
-
style={{ height: desktopHeight }}
|
|
92
|
-
>
|
|
93
|
-
<div className="w-full h-full">
|
|
94
|
-
{slide.leftContent}
|
|
95
|
-
</div>
|
|
96
|
-
<div className="w-full h-full">
|
|
97
|
-
{slide.rightContent}
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
|
|
101
|
-
{/* Mobile Layout - Vertical Split */}
|
|
102
|
-
<div
|
|
103
|
-
className="md:hidden grid grid-rows-2 w-full h-full"
|
|
104
|
-
style={{ height: mobileHeight }}
|
|
105
|
-
>
|
|
106
|
-
<div className="w-full h-full">
|
|
107
|
-
{slide.leftContent}
|
|
108
|
-
</div>
|
|
109
|
-
<div className="w-full h-full">
|
|
110
|
-
{slide.rightContent}
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
113
|
-
</>
|
|
114
|
-
)}
|
|
115
|
-
</div>
|
|
116
|
-
);
|
|
117
|
-
})}
|
|
118
|
-
</div>
|
|
119
|
-
</div>
|
|
120
|
-
|
|
121
|
-
{/* Dots indicator */}
|
|
122
|
-
{showDots && slides.length > 1 && (
|
|
123
|
-
<div className="fullscreen-carousel__dots">
|
|
124
|
-
{scrollSnaps.map((_, index) => (
|
|
125
|
-
<button
|
|
126
|
-
key={index}
|
|
127
|
-
className={`fullscreen-carousel__dot ${index === selectedIndex ? 'fullscreen-carousel__dot--selected' : ''}`}
|
|
128
|
-
onClick={() => scrollTo(index)}
|
|
129
|
-
aria-label={`Go to slide ${index + 1}`}
|
|
130
|
-
/>
|
|
131
|
-
))}
|
|
132
|
-
</div>
|
|
133
|
-
)}
|
|
134
|
-
|
|
135
|
-
{/* Navigation buttons */}
|
|
136
|
-
{showArrows && slides.length > 1 && (
|
|
137
|
-
<>
|
|
138
|
-
<button
|
|
139
|
-
className="fullscreen-carousel__button fullscreen-carousel__button--prev"
|
|
140
|
-
onClick={scrollPrev}
|
|
141
|
-
disabled={!prevBtnEnabled}
|
|
142
|
-
aria-label="Previous slide"
|
|
143
|
-
>
|
|
144
|
-
<svg className="fullscreen-carousel__button__svg" viewBox="0 0 24 24">
|
|
145
|
-
<path d="M9 18l6-6-6-6" />
|
|
146
|
-
</svg>
|
|
147
|
-
</button>
|
|
148
|
-
<button
|
|
149
|
-
className="fullscreen-carousel__button fullscreen-carousel__button--next"
|
|
150
|
-
onClick={scrollNext}
|
|
151
|
-
disabled={!nextBtnEnabled}
|
|
152
|
-
aria-label="Next slide"
|
|
153
|
-
>
|
|
154
|
-
<svg className="fullscreen-carousel__button__svg" viewBox="0 0 24 24">
|
|
155
|
-
<path d="M15 18l-6-6 6-6" />
|
|
156
|
-
</svg>
|
|
157
|
-
</button>
|
|
158
|
-
</>
|
|
159
|
-
)}
|
|
160
|
-
</div>
|
|
161
|
-
|
|
162
|
-
{/* Styles */}
|
|
163
|
-
<style jsx>{`
|
|
164
|
-
.fullscreen-carousel-wrapper {
|
|
165
|
-
position: relative;
|
|
166
|
-
width: 100%;
|
|
167
|
-
overflow: hidden;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
.fullscreen-carousel {
|
|
171
|
-
position: relative;
|
|
172
|
-
width: 100%;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.fullscreen-carousel__viewport {
|
|
176
|
-
overflow: hidden;
|
|
177
|
-
width: 100%;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.fullscreen-carousel__container {
|
|
181
|
-
display: flex;
|
|
182
|
-
width: 100%;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
.fullscreen-carousel__slide {
|
|
186
|
-
flex: 0 0 100%;
|
|
187
|
-
min-width: 0;
|
|
188
|
-
position: relative;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.fullscreen-carousel__button {
|
|
192
|
-
display: none;
|
|
193
|
-
background-color: rgba(255, 255, 255, 0.9);
|
|
194
|
-
color: #333;
|
|
195
|
-
border: none;
|
|
196
|
-
width: 3rem;
|
|
197
|
-
height: 3rem;
|
|
198
|
-
border-radius: 50%;
|
|
199
|
-
cursor: pointer;
|
|
200
|
-
align-items: center;
|
|
201
|
-
justify-content: center;
|
|
202
|
-
transition: all 0.2s ease;
|
|
203
|
-
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
204
|
-
position: absolute;
|
|
205
|
-
top: 50%;
|
|
206
|
-
transform: translateY(-50%);
|
|
207
|
-
z-index: 10;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
@media (min-width: 768px) {
|
|
211
|
-
.fullscreen-carousel__button {
|
|
212
|
-
display: flex;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
.fullscreen-carousel__button--prev {
|
|
217
|
-
right: 2rem;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
.fullscreen-carousel__button--next {
|
|
221
|
-
left: 2rem;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
.fullscreen-carousel__controls {
|
|
225
|
-
display: flex;
|
|
226
|
-
justify-content: center;
|
|
227
|
-
gap: 1rem;
|
|
228
|
-
position: absolute;
|
|
229
|
-
bottom: 4rem;
|
|
230
|
-
left: 50%;
|
|
231
|
-
transform: translateX(-50%);
|
|
232
|
-
z-index: 10;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
.fullscreen-carousel__button:hover:not(:disabled) {
|
|
236
|
-
background-color: rgba(255, 255, 255, 1);
|
|
237
|
-
transform: scale(1.1);
|
|
238
|
-
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
.fullscreen-carousel__button:disabled {
|
|
242
|
-
opacity: 0.3;
|
|
243
|
-
cursor: not-allowed;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
.fullscreen-carousel__button__svg {
|
|
247
|
-
width: 1rem;
|
|
248
|
-
height: 1rem;
|
|
249
|
-
fill: none;
|
|
250
|
-
stroke: currentColor;
|
|
251
|
-
stroke-width: 2;
|
|
252
|
-
stroke-linecap: round;
|
|
253
|
-
stroke-linejoin: round;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
.fullscreen-carousel__dots {
|
|
257
|
-
display: flex;
|
|
258
|
-
justify-content: center;
|
|
259
|
-
gap: 0.5rem;
|
|
260
|
-
position: absolute;
|
|
261
|
-
bottom: 2rem;
|
|
262
|
-
left: 50%;
|
|
263
|
-
transform: translateX(-50%);
|
|
264
|
-
z-index: 10;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
.fullscreen-carousel__dot {
|
|
268
|
-
background-color: rgba(255, 255, 255, 0.5);
|
|
269
|
-
border: none;
|
|
270
|
-
width: 0.75rem;
|
|
271
|
-
height: 0.75rem;
|
|
272
|
-
border-radius: 50%;
|
|
273
|
-
cursor: pointer;
|
|
274
|
-
opacity: 0.5;
|
|
275
|
-
transition: all 0.2s ease;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
.fullscreen-carousel__dot--selected {
|
|
279
|
-
opacity: 1;
|
|
280
|
-
background-color: rgba(255, 255, 255, 1);
|
|
281
|
-
transform: scale(1.2);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
.fullscreen-carousel__dot:hover {
|
|
285
|
-
opacity: 0.8;
|
|
286
|
-
}
|
|
287
|
-
`}</style>
|
|
288
|
-
</div>
|
|
289
|
-
);
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
export default FullscreenCarousel;
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import CTAButton from "./elements/CTAButton";
|
|
2
|
-
import FixedWidthHeroVideo from "./elements/FixedWidthHeroVideo";
|
|
3
|
-
import { ArrowDown } from 'lucide-react'
|
|
4
|
-
import { motion } from "framer-motion";
|
|
5
|
-
|
|
6
|
-
const Hero = ({
|
|
7
|
-
// Media props
|
|
8
|
-
mediaType = 'image', // 'video' or 'image'
|
|
9
|
-
videoId = '', // YouTube video ID
|
|
10
|
-
useYoutube = true, // Use YouTube or direct video
|
|
11
|
-
videoUrl = '', // Direct video URL (when useYoutube is false)
|
|
12
|
-
imageUrl = '', // Image URL for static background
|
|
13
|
-
mediaHeight = '70vh', // Height of the media section
|
|
14
|
-
|
|
15
|
-
// Overlay
|
|
16
|
-
opacity = 70,
|
|
17
|
-
overlayColor = 'white',
|
|
18
|
-
|
|
19
|
-
// Hero text props
|
|
20
|
-
title,
|
|
21
|
-
subtitle,
|
|
22
|
-
miniSubtitle,
|
|
23
|
-
additionalElements,
|
|
24
|
-
ctaText,
|
|
25
|
-
secondaryButtonLabel, // Optional secondary button label
|
|
26
|
-
onSecondaryClick, // Optional secondary button click handler
|
|
27
|
-
showArrow = true, // Show/hide animated arrow
|
|
28
|
-
|
|
29
|
-
// Introduction section props
|
|
30
|
-
showIntroSection,
|
|
31
|
-
introHeight = '40vh',
|
|
32
|
-
introTitle,
|
|
33
|
-
introContent,
|
|
34
|
-
introImage,
|
|
35
|
-
onCtaClick,
|
|
36
|
-
|
|
37
|
-
classNames = {}
|
|
38
|
-
}) => {
|
|
39
|
-
const {
|
|
40
|
-
title: titleClass = 'main-title',
|
|
41
|
-
subtitle: subtitleClass = 'main-subtitle',
|
|
42
|
-
miniSubtitle: miniSubtitleClass = 'main-content',
|
|
43
|
-
cta: ctaClass = '',
|
|
44
|
-
introTitle: introTitleClass = 'title',
|
|
45
|
-
introContent: introContentClass = 'subtitle'
|
|
46
|
-
} = classNames;
|
|
47
|
-
|
|
48
|
-
const hexToRgba = (hex, alpha) => {
|
|
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
|
-
return (
|
|
56
|
-
<>
|
|
57
|
-
<section className="hero-section relative w-full overflow-hidden" style={{ height: mediaHeight }}>
|
|
58
|
-
{/* Media Background Container */}
|
|
59
|
-
<div className="absolute inset-0 w-full h-full" aria-hidden="true">
|
|
60
|
-
{mediaType === 'video' ? (
|
|
61
|
-
<FixedWidthHeroVideo
|
|
62
|
-
useYoutube={useYoutube}
|
|
63
|
-
youtubeVideoId={videoId}
|
|
64
|
-
videoUrl={videoUrl}
|
|
65
|
-
/>
|
|
66
|
-
) : (
|
|
67
|
-
<div
|
|
68
|
-
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
|
69
|
-
style={{
|
|
70
|
-
backgroundImage: `url(${imageUrl})`,
|
|
71
|
-
backgroundColor: '#1a1a1a' // Fallback color
|
|
72
|
-
}}
|
|
73
|
-
/>
|
|
74
|
-
)}
|
|
75
|
-
</div>
|
|
76
|
-
|
|
77
|
-
{/* Content Overlay */}
|
|
78
|
-
<div
|
|
79
|
-
className="relative z-10 flex items-center justify-center h-full"
|
|
80
|
-
style={{
|
|
81
|
-
background: overlayColor === 'white' ? `rgba(255, 255, 255, ${opacity / 100})` :
|
|
82
|
-
overlayColor === 'black' ? `rgba(0, 0, 0, ${opacity / 100})` :
|
|
83
|
-
overlayColor.startsWith('#') ? hexToRgba(overlayColor, opacity / 100) :
|
|
84
|
-
overlayColor.startsWith('rgb') ? overlayColor :
|
|
85
|
-
`rgba(255, 255, 255, ${opacity / 100})` // fallback to white
|
|
86
|
-
}}
|
|
87
|
-
>
|
|
88
|
-
<div className="text-center px-0">
|
|
89
|
-
<motion.div
|
|
90
|
-
initial={{ opacity: 0, y: 30 }}
|
|
91
|
-
animate={{ opacity: 1, y: 0 }}
|
|
92
|
-
transition={{ duration: 0.8 }}
|
|
93
|
-
className="mb-8"
|
|
94
|
-
>
|
|
95
|
-
<h1 className={titleClass + " mb-4"}>
|
|
96
|
-
{title}
|
|
97
|
-
</h1>
|
|
98
|
-
<p className={subtitleClass + " mb-4"}>
|
|
99
|
-
{subtitle}
|
|
100
|
-
</p>
|
|
101
|
-
<p className={miniSubtitleClass + " mb-12"}>
|
|
102
|
-
{miniSubtitle}
|
|
103
|
-
</p>
|
|
104
|
-
{additionalElements}
|
|
105
|
-
<div className="flex flex-wrap gap-4 justify-center items-center">
|
|
106
|
-
{secondaryButtonLabel && onSecondaryClick && (
|
|
107
|
-
<button
|
|
108
|
-
onClick={onSecondaryClick}
|
|
109
|
-
className="px-6 py-3 border-2 border-primary text-primary bg-main font-normal hover:bg-primary hover:text-primary-dark transition-all duration-300 shadow-lg"
|
|
110
|
-
>
|
|
111
|
-
{secondaryButtonLabel}
|
|
112
|
-
</button>
|
|
113
|
-
)}
|
|
114
|
-
<CTAButton onClick={onCtaClick} className="shadow-lg">
|
|
115
|
-
{ctaText}
|
|
116
|
-
</CTAButton>
|
|
117
|
-
</div>
|
|
118
|
-
</motion.div>
|
|
119
|
-
</div>
|
|
120
|
-
|
|
121
|
-
{showArrow && (
|
|
122
|
-
<motion.div
|
|
123
|
-
className="absolute"
|
|
124
|
-
style={{
|
|
125
|
-
bottom: '2rem',
|
|
126
|
-
left: '50%',
|
|
127
|
-
transform: 'translateX(-50%)',
|
|
128
|
-
zIndex: 100
|
|
129
|
-
}}
|
|
130
|
-
animate={{ y: [0, 10, 0] }}
|
|
131
|
-
transition={{ duration: 2, repeat: Infinity }}
|
|
132
|
-
>
|
|
133
|
-
<ArrowDown className="w-8 h-8 text-sky-600" />
|
|
134
|
-
</motion.div>
|
|
135
|
-
)}
|
|
136
|
-
</div>
|
|
137
|
-
</section>
|
|
138
|
-
|
|
139
|
-
{/* Introduction Section */}
|
|
140
|
-
{showIntroSection && (
|
|
141
|
-
<section className=" py-16 px-4" style={{ minHeight: introHeight }}>
|
|
142
|
-
<div className="max-w-6xl mx-auto" dir="rtl">
|
|
143
|
-
{introImage ? (
|
|
144
|
-
/* Layout with image */
|
|
145
|
-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 items-center">
|
|
146
|
-
{/* Text Content */}
|
|
147
|
-
<div className="text-center lg:text-right order-2 lg:order-1">
|
|
148
|
-
<h2 className={introTitleClass + " mb-6"}>
|
|
149
|
-
{introTitle}
|
|
150
|
-
</h2>
|
|
151
|
-
<p className={introContentClass + " leading-relaxed"} style={{ whiteSpace: 'pre-line' }}>
|
|
152
|
-
{introContent}
|
|
153
|
-
</p>
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
|
-
{/* Image */}
|
|
157
|
-
<motion.div
|
|
158
|
-
key={'image'}
|
|
159
|
-
initial={{ opacity: 0, x: 50 }}
|
|
160
|
-
whileInView={{ opacity: 1, x: 0 }}
|
|
161
|
-
viewport={{ once: true }}
|
|
162
|
-
transition={{ delay: 1 * 0.5 }}
|
|
163
|
-
className="h-full"
|
|
164
|
-
>
|
|
165
|
-
<div className="order-1 lg:order-2 flex justify-center lg:justify-end">
|
|
166
|
-
<div className="w-full max-w-md lg:max-w-lg">
|
|
167
|
-
<img
|
|
168
|
-
src={introImage}
|
|
169
|
-
alt={introTitle || "Introduction image"}
|
|
170
|
-
className="w-full h-auto rounded-lg shadow-lg object-cover"
|
|
171
|
-
onError={(e) => {
|
|
172
|
-
e.target.style.display = 'none';
|
|
173
|
-
console.warn('Failed to load intro image:', introImage);
|
|
174
|
-
}}
|
|
175
|
-
/>
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
</motion.div>
|
|
179
|
-
</div>
|
|
180
|
-
) : (
|
|
181
|
-
/* Text only layout (original) */
|
|
182
|
-
<div className="text-center max-w-4xl mx-auto">
|
|
183
|
-
<h2 className="title mb-6">
|
|
184
|
-
{introTitle}
|
|
185
|
-
</h2>
|
|
186
|
-
<p className="subtitle leading-relaxed" style={{ whiteSpace: 'pre-line' }}>
|
|
187
|
-
{introContent}
|
|
188
|
-
</p>
|
|
189
|
-
</div>
|
|
190
|
-
)}
|
|
191
|
-
</div>
|
|
192
|
-
</section>
|
|
193
|
-
)}
|
|
194
|
-
</>
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export default Hero
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Users, Monitor, Smartphone, ThumbsUp, Paintbrush } from 'lucide-react';
|
|
3
|
-
|
|
4
|
-
const IconGrid = ({ items = [], className = '' }) => {
|
|
5
|
-
if (!items || items.length === 0) {
|
|
6
|
-
return null;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// Calculate rows distribution
|
|
10
|
-
const totalItems = items.length;
|
|
11
|
-
const isEven = totalItems % 2 === 0;
|
|
12
|
-
const itemsPerRow = Math.ceil(totalItems / 2);
|
|
13
|
-
|
|
14
|
-
// Split items into rows
|
|
15
|
-
const topRowCount = isEven ? itemsPerRow : itemsPerRow;
|
|
16
|
-
const topRow = items.slice(0, topRowCount);
|
|
17
|
-
const bottomRow = items.slice(topRowCount);
|
|
18
|
-
|
|
19
|
-
const GridItem = ({ icon: Icon, text }) => (
|
|
20
|
-
<div className="flex flex-col items-center justify-center p-6 bg-gradient-to-br from-purple-50 to-indigo-50 rounded-2xl hover:shadow-lg transition-shadow duration-300">
|
|
21
|
-
<div className="w-20 h-20 mb-4 flex items-center justify-center">
|
|
22
|
-
<Icon className="w-full h-full text-main" strokeWidth={1.5} />
|
|
23
|
-
</div>
|
|
24
|
-
<p className="text-center text-gray-800 font-medium text-lg" dir="rtl">
|
|
25
|
-
{text}
|
|
26
|
-
</p>
|
|
27
|
-
</div>
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div className={`w-full max-w-6xl mx-auto px-4 ${className}`}>
|
|
32
|
-
{/* Mobile: Single column */}
|
|
33
|
-
<div className="md:hidden space-y-4">
|
|
34
|
-
{items.map((item, index) => (
|
|
35
|
-
<GridItem key={index} {...item} />
|
|
36
|
-
))}
|
|
37
|
-
</div>
|
|
38
|
-
|
|
39
|
-
{/* Desktop: Smart grid */}
|
|
40
|
-
<div className="hidden md:block space-y-4 mt-20 mb-20">
|
|
41
|
-
{/* Top row */}
|
|
42
|
-
<div
|
|
43
|
-
className="grid gap-4 mb-4"
|
|
44
|
-
style={{
|
|
45
|
-
gridTemplateColumns: `repeat(${topRowCount}, 1fr)`,
|
|
46
|
-
justifyItems: 'center'
|
|
47
|
-
}}
|
|
48
|
-
>
|
|
49
|
-
{topRow.map((item, index) => (
|
|
50
|
-
<div key={index} className="w-full max-w-sm">
|
|
51
|
-
<GridItem {...item} />
|
|
52
|
-
</div>
|
|
53
|
-
))}
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
{/* Bottom row - centered if fewer items */}
|
|
57
|
-
{bottomRow.length > 0 && (
|
|
58
|
-
<div
|
|
59
|
-
className="grid gap-4"
|
|
60
|
-
style={{
|
|
61
|
-
gridTemplateColumns: `repeat(${bottomRow.length}, 1fr)`,
|
|
62
|
-
justifyItems: 'center',
|
|
63
|
-
maxWidth: bottomRow.length < topRowCount ? `${(bottomRow.length / topRowCount) * 100}%` : '100%',
|
|
64
|
-
margin: '0 auto'
|
|
65
|
-
}}
|
|
66
|
-
>
|
|
67
|
-
{bottomRow.map((item, index) => (
|
|
68
|
-
<div key={index} className="w-full max-w-sm">
|
|
69
|
-
<GridItem {...item} />
|
|
70
|
-
</div>
|
|
71
|
-
))}
|
|
72
|
-
</div>
|
|
73
|
-
)}
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
);
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// Demo
|
|
80
|
-
const Demo = () => {
|
|
81
|
-
const demoItems = [
|
|
82
|
-
{
|
|
83
|
-
icon: Users,
|
|
84
|
-
text: 'מתאים לכל סוגי העסקים'
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
icon: Monitor,
|
|
88
|
-
text: 'חווית משתמש UX/UI'
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
icon: Smartphone,
|
|
92
|
-
text: 'התאמה מלאה לכל המסכים'
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
icon: ThumbsUp,
|
|
96
|
-
text: 'תמיכה מלאה'
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
icon: Paintbrush,
|
|
100
|
-
text: 'עיצוב יוצר נשימה'
|
|
101
|
-
}
|
|
102
|
-
];
|
|
103
|
-
|
|
104
|
-
return (
|
|
105
|
-
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 py-16">
|
|
106
|
-
<div className="max-w-7xl mx-auto px-4">
|
|
107
|
-
<h1 className="text-4xl font-bold text-center text-gray-800 mb-4" dir="rtl">
|
|
108
|
-
IconGrid Component
|
|
109
|
-
</h1>
|
|
110
|
-
<p className="text-center text-gray-600 mb-12" dir="rtl">
|
|
111
|
-
רכיב גריד חכם המתאים את עצמו למספר הפריטים
|
|
112
|
-
</p>
|
|
113
|
-
|
|
114
|
-
<IconGrid items={demoItems} />
|
|
115
|
-
|
|
116
|
-
<div className="mt-16 bg-white rounded-xl p-8 shadow-lg">
|
|
117
|
-
<h2 className="text-2xl font-bold mb-4" dir="rtl">תכונות:</h2>
|
|
118
|
-
<ul className="space-y-2 text-gray-700" dir="rtl">
|
|
119
|
-
<li>✅ מספר זוגי של פריטים - חלוקה שווה בין שורות</li>
|
|
120
|
-
<li>✅ מספר אי-זוגי - שורה עליונה עם פריט אחד נוסף, ממורכזת</li>
|
|
121
|
-
<li>✅ במובייל - עמודה אנכית בודדת</li>
|
|
122
|
-
<li>✅ תמיכה מלאה ב-RTL</li>
|
|
123
|
-
<li>✅ עיצוב רספונסיבי עם אנימציות</li>
|
|
124
|
-
</ul>
|
|
125
|
-
|
|
126
|
-
<h3 className="text-xl font-bold mt-6 mb-3" dir="rtl">דוגמת שימוש:</h3>
|
|
127
|
-
<pre className="bg-gray-50 p-4 rounded-lg overflow-x-auto text-sm">
|
|
128
|
-
{`const items = [
|
|
129
|
-
{ icon: Users, text: 'טקסט 1' },
|
|
130
|
-
{ icon: Monitor, text: 'טקסט 2' },
|
|
131
|
-
{ icon: Smartphone, text: 'טקסט 3' },
|
|
132
|
-
{ icon: ThumbsUp, text: 'טקסט 4' },
|
|
133
|
-
{ icon: Paintbrush, text: 'טקסט 5' }
|
|
134
|
-
];
|
|
135
|
-
|
|
136
|
-
<IconGrid items={items} />`}
|
|
137
|
-
</pre>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
</div>
|
|
141
|
-
);
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
export default IconGrid;
|