@codesinger0/shared-components 1.1.56 → 1.1.58
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/components/{QAAccordion.jsx → QAAccordion 2.jsx } +11 -9
- package/package.json +1 -1
- package/dist/components/AdvantagesList.jsx +0 -89
- package/dist/components/ArticlesList.jsx +0 -237
- package/dist/components/DualTextCard.jsx +0 -73
- package/dist/components/FloatingWhatsAppButton.jsx +0 -180
- package/dist/components/IconGrid.jsx +0 -144
- package/dist/components/IntroSection.jsx +0 -74
- package/dist/components/Menu.jsx +0 -268
- package/dist/components/SmallItemsGrid.jsx +0 -308
- package/dist/components/TextListCards.jsx +0 -107
- package/dist/components/UnderConstruction.jsx +0 -76
- package/dist/components/VideoCard.jsx +0 -88
- package/dist/context/CartContext.jsx +0 -165
- package/dist/context/ItemModalContext.jsx +0 -40
- package/dist/hooks/useScrollLock.js +0 -52
- package/dist/integrations/emailService.js +0 -167
- package/dist/styles/shared-components.css +0 -29
- package/dist/utils/ScrollManager.jsx +0 -85
- package/dist/utils/ScrollToTop.jsx +0 -14
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
-
import SmallItemCard from './SmallItemCard';
|
|
3
|
-
import CTAButton from './elements/CTAButton';
|
|
4
|
-
import useEmblaCarousel from 'embla-carousel-react';
|
|
5
|
-
import Autoplay from 'embla-carousel-autoplay';
|
|
6
|
-
import { useItemModal } from '../context/ItemModalContext'
|
|
7
|
-
|
|
8
|
-
const SmallItemsGrid = ({
|
|
9
|
-
maxRows = null,
|
|
10
|
-
showMoreType = 'button',
|
|
11
|
-
headerText = '',
|
|
12
|
-
className = '',
|
|
13
|
-
items,
|
|
14
|
-
onAddToCart,
|
|
15
|
-
autoplay = true,
|
|
16
|
-
isLighter = false,
|
|
17
|
-
...props
|
|
18
|
-
}) => {
|
|
19
|
-
|
|
20
|
-
const [showAllItems, setShowAllItems] = useState(false);
|
|
21
|
-
const { openModal } = useItemModal()
|
|
22
|
-
|
|
23
|
-
// Embla carousel setup
|
|
24
|
-
const autoplayOptions = {
|
|
25
|
-
delay: 3000,
|
|
26
|
-
stopOnInteraction: true,
|
|
27
|
-
stopOnMouseEnter: true,
|
|
28
|
-
rootNode: (emblaRoot) => emblaRoot.parentElement,
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const plugins = autoplay ? [Autoplay(autoplayOptions)] : [];
|
|
32
|
-
|
|
33
|
-
const [emblaRef, emblaApi] = useEmblaCarousel(
|
|
34
|
-
{
|
|
35
|
-
loop: true,
|
|
36
|
-
dragFree: true,
|
|
37
|
-
containScroll: 'trimSnaps',
|
|
38
|
-
slidesToScroll: 1,
|
|
39
|
-
direction: 'rtl'
|
|
40
|
-
},
|
|
41
|
-
plugins
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
const [prevBtnEnabled, setPrevBtnEnabled] = useState(false);
|
|
45
|
-
const [nextBtnEnabled, setNextBtnEnabled] = useState(false);
|
|
46
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
47
|
-
const [scrollSnaps, setScrollSnaps] = useState([]);
|
|
48
|
-
|
|
49
|
-
const scrollPrev = useCallback(() => emblaApi && emblaApi.scrollPrev(), [emblaApi]);
|
|
50
|
-
const scrollNext = useCallback(() => emblaApi && emblaApi.scrollNext(), [emblaApi]);
|
|
51
|
-
const scrollTo = useCallback((index) => emblaApi && emblaApi.scrollTo(index), [emblaApi]);
|
|
52
|
-
|
|
53
|
-
const onSelect = useCallback(() => {
|
|
54
|
-
if (!emblaApi) return;
|
|
55
|
-
setSelectedIndex(emblaApi.selectedScrollSnap());
|
|
56
|
-
setPrevBtnEnabled(emblaApi.canScrollPrev());
|
|
57
|
-
setNextBtnEnabled(emblaApi.canScrollNext());
|
|
58
|
-
}, [emblaApi]);
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (!emblaApi) return;
|
|
62
|
-
onSelect();
|
|
63
|
-
setScrollSnaps(emblaApi.scrollSnapList());
|
|
64
|
-
emblaApi.on('select', onSelect);
|
|
65
|
-
emblaApi.on('reInit', onSelect);
|
|
66
|
-
}, [emblaApi, onSelect]);
|
|
67
|
-
|
|
68
|
-
const handleCardClick = (item) => {
|
|
69
|
-
openModal(item)
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const toggleShowMore = () => {
|
|
73
|
-
setShowAllItems(!showAllItems);
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// Calculate items per row based on CSS grid configuration
|
|
77
|
-
const getItemsPerRow = () => {
|
|
78
|
-
return window.innerWidth >= 768 ? 5 : 2; // md:grid-cols-5, grid-cols-2
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
// Calculate max items to show based on rows
|
|
82
|
-
const calculateMaxItems = () => {
|
|
83
|
-
if (!maxRows) return null;
|
|
84
|
-
return maxRows * getItemsPerRow();
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// Determine if we need overflow handling
|
|
88
|
-
const maxItemsToShow = calculateMaxItems();
|
|
89
|
-
const needsOverflow = maxRows && items.length > maxItemsToShow;
|
|
90
|
-
|
|
91
|
-
// Calculate displayed items for button mode
|
|
92
|
-
const displayedItems = needsOverflow && !showAllItems
|
|
93
|
-
? items.slice(0, maxItemsToShow)
|
|
94
|
-
: items;
|
|
95
|
-
|
|
96
|
-
const GridComponent = ({ items: gridItems }) => (
|
|
97
|
-
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 md:gap-6">
|
|
98
|
-
{gridItems.map(item => (
|
|
99
|
-
<SmallItemCard
|
|
100
|
-
key={item.id}
|
|
101
|
-
imageUrl={item.imageUrl}
|
|
102
|
-
label={item.name}
|
|
103
|
-
description={item.description}
|
|
104
|
-
price={item.price}
|
|
105
|
-
discountPrice={item.discountPrice}
|
|
106
|
-
onClick={() => handleCardClick(item)}
|
|
107
|
-
onAddToCart={() => onAddToCart(item)}
|
|
108
|
-
className="hover:shadow-xl"
|
|
109
|
-
/>
|
|
110
|
-
))}
|
|
111
|
-
</div>
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
return (
|
|
115
|
-
<section className={`py-8 px-0 bg-main ${className}`} {...props}>
|
|
116
|
-
<div className="max-w-6xl mx-auto">
|
|
117
|
-
<h2 className={`${isLighter ? 'lighterTitle' : 'title'} text-center mb-12`}>{headerText}</h2>
|
|
118
|
-
|
|
119
|
-
{showMoreType === 'carousel' ? (
|
|
120
|
-
// Embla Carousel Mode - infinite with autoscroll
|
|
121
|
-
<div className="embla" dir="rtl">
|
|
122
|
-
<div className="embla__viewport" ref={emblaRef}>
|
|
123
|
-
<div className="embla__container">
|
|
124
|
-
{items.map(item => (
|
|
125
|
-
<div key={item.id} className="embla__slide">
|
|
126
|
-
<SmallItemCard
|
|
127
|
-
imageUrl={item.imageUrl}
|
|
128
|
-
label={item.name}
|
|
129
|
-
description={item.description}
|
|
130
|
-
price={item.price}
|
|
131
|
-
discountPrice={item.discountPrice}
|
|
132
|
-
onClick={() => handleCardClick(item)}
|
|
133
|
-
onAddToCart={() => onAddToCart(item)}
|
|
134
|
-
className="h-full flex flex-col" />
|
|
135
|
-
</div>
|
|
136
|
-
))}
|
|
137
|
-
</div>
|
|
138
|
-
</div>
|
|
139
|
-
|
|
140
|
-
{/* Dots indicator */}
|
|
141
|
-
<div className="embla__dots">
|
|
142
|
-
{scrollSnaps.map((_, index) => (
|
|
143
|
-
<button
|
|
144
|
-
key={index}
|
|
145
|
-
className={`embla__dot ${index === selectedIndex ? 'embla__dot--selected' : ''}`}
|
|
146
|
-
onClick={() => scrollTo(index)}
|
|
147
|
-
/>
|
|
148
|
-
))}
|
|
149
|
-
</div>
|
|
150
|
-
|
|
151
|
-
{/* Navigation buttons */}
|
|
152
|
-
{!isLighter && <div className="embla__controls">
|
|
153
|
-
<button
|
|
154
|
-
className="embla__button embla__button--prev"
|
|
155
|
-
onClick={scrollPrev}
|
|
156
|
-
disabled={!prevBtnEnabled}
|
|
157
|
-
>
|
|
158
|
-
<svg className="embla__button__svg" viewBox="0 0 24 24">
|
|
159
|
-
<path d="M9 18l6-6-6-6" />
|
|
160
|
-
</svg>
|
|
161
|
-
</button>
|
|
162
|
-
<button
|
|
163
|
-
className="embla__button embla__button--next"
|
|
164
|
-
onClick={scrollNext}
|
|
165
|
-
disabled={!nextBtnEnabled}
|
|
166
|
-
>
|
|
167
|
-
<svg className="embla__button__svg" viewBox="0 0 24 24">
|
|
168
|
-
<path d="M15 18l-6-6 6-6" />
|
|
169
|
-
</svg>
|
|
170
|
-
</button>
|
|
171
|
-
</div>}
|
|
172
|
-
</div>
|
|
173
|
-
) : (
|
|
174
|
-
// Grid Mode (default or button)
|
|
175
|
-
<>
|
|
176
|
-
<GridComponent items={displayedItems} />
|
|
177
|
-
|
|
178
|
-
{/* Show More Button */}
|
|
179
|
-
{needsOverflow && showMoreType === 'button' && (
|
|
180
|
-
<div className="text-center mt-8">
|
|
181
|
-
<CTAButton onClick={toggleShowMore}>
|
|
182
|
-
{showAllItems ? 'ראה פחות' : `ראה עוד (${items.length - maxItemsToShow} נוספים)`}
|
|
183
|
-
</CTAButton>
|
|
184
|
-
</div>
|
|
185
|
-
)}
|
|
186
|
-
</>
|
|
187
|
-
)}
|
|
188
|
-
</div>
|
|
189
|
-
|
|
190
|
-
{/* Embla Carousel Styles */}
|
|
191
|
-
<style jsx>{`
|
|
192
|
-
.embla {
|
|
193
|
-
max-width: 100%;
|
|
194
|
-
margin: 0 auto;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
.embla__viewport {
|
|
198
|
-
overflow: hidden;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.embla__container {
|
|
202
|
-
display: flex;
|
|
203
|
-
user-select: none;
|
|
204
|
-
-webkit-touch-callout: none;
|
|
205
|
-
-khtml-user-select: none;
|
|
206
|
-
-webkit-tap-highlight-color: transparent;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
.embla__slide {
|
|
210
|
-
flex: 0 0 60%;
|
|
211
|
-
min-width: 0;
|
|
212
|
-
padding-left: 0.6rem;
|
|
213
|
-
padding-right: 0.6rem;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
@media (min-width: 768px) {
|
|
217
|
-
.embla__slide {
|
|
218
|
-
flex: 0 0 33.333333%;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
@media (min-width: 1024px) {
|
|
223
|
-
.embla__slide {
|
|
224
|
-
flex: 0 0 25%;
|
|
225
|
-
min-width: 200px;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
@media (min-width: 1280px) {
|
|
230
|
-
.embla__slide {
|
|
231
|
-
flex: 0 0 25%;
|
|
232
|
-
min-width: 260px;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.embla__controls {
|
|
237
|
-
display: flex;
|
|
238
|
-
justify-content: center;
|
|
239
|
-
gap: 1rem;
|
|
240
|
-
margin-top: 2rem;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
.embla__button {
|
|
244
|
-
background-color: var(--primary);
|
|
245
|
-
color: white;
|
|
246
|
-
border: none;
|
|
247
|
-
width: 2.5rem;
|
|
248
|
-
height: 2.5rem;
|
|
249
|
-
border-radius: 50%;
|
|
250
|
-
cursor: pointer;
|
|
251
|
-
display: flex;
|
|
252
|
-
align-items: center;
|
|
253
|
-
justify-content: center;
|
|
254
|
-
transition: all 0.2s ease;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
.embla__button:hover:not(:disabled) {
|
|
258
|
-
background-color: color-mix(in srgb, var(--primary) 80%, black);
|
|
259
|
-
transform: scale(1.1);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
.embla__button:disabled {
|
|
263
|
-
opacity: 0.5;
|
|
264
|
-
cursor: not-allowed;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
.embla__button__svg {
|
|
268
|
-
width: 1rem;
|
|
269
|
-
height: 1rem;
|
|
270
|
-
fill: none;
|
|
271
|
-
stroke: currentColor;
|
|
272
|
-
stroke-width: 2;
|
|
273
|
-
stroke-linecap: round;
|
|
274
|
-
stroke-linejoin: round;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
.embla__dots {
|
|
278
|
-
display: flex;
|
|
279
|
-
justify-content: center;
|
|
280
|
-
gap: 0.5rem;
|
|
281
|
-
margin-top: 1rem;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
.embla__dot {
|
|
285
|
-
background-color: var(--primary);
|
|
286
|
-
border: none;
|
|
287
|
-
width: 0.5rem;
|
|
288
|
-
height: 0.5rem;
|
|
289
|
-
border-radius: 50%;
|
|
290
|
-
cursor: pointer;
|
|
291
|
-
opacity: 0.3;
|
|
292
|
-
transition: all 0.2s ease;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
.embla__dot--selected {
|
|
296
|
-
opacity: 1;
|
|
297
|
-
transform: scale(1.2);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
.embla__dot:hover {
|
|
301
|
-
opacity: 0.7;
|
|
302
|
-
}
|
|
303
|
-
`}</style>
|
|
304
|
-
</section>
|
|
305
|
-
);
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
export default SmallItemsGrid;
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { motion } from 'framer-motion';
|
|
3
|
-
import { CircleCheck } from 'lucide-react';
|
|
4
|
-
|
|
5
|
-
const TextListCards = ({
|
|
6
|
-
title,
|
|
7
|
-
subtitle,
|
|
8
|
-
items = [],
|
|
9
|
-
className = '',
|
|
10
|
-
...props
|
|
11
|
-
}) => {
|
|
12
|
-
if (!items || items.length === 0) {
|
|
13
|
-
return (
|
|
14
|
-
<section className={`py-20 bg-main ${className}`} {...props}>
|
|
15
|
-
<div className="max-w-7xl mx-auto px-6 text-center" dir="rtl">
|
|
16
|
-
{title && <h2 className="title mb-4">{title}</h2>}
|
|
17
|
-
{subtitle && <p className="subtitle">{subtitle}</p>}
|
|
18
|
-
<p className="content-text mt-8">אין פריטים להצגה</p>
|
|
19
|
-
</div>
|
|
20
|
-
</section>
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<section className={`py-20 ${className}`} {...props}>
|
|
26
|
-
<div className="max-w-6xl mx-auto">
|
|
27
|
-
{/* Header Section */}
|
|
28
|
-
{(title || subtitle) && (
|
|
29
|
-
<motion.div
|
|
30
|
-
initial={{ opacity: 0, y: 20 }}
|
|
31
|
-
whileInView={{ opacity: 1, y: 0 }}
|
|
32
|
-
viewport={{ once: true }}
|
|
33
|
-
className="text-center mb-16"
|
|
34
|
-
dir="rtl"
|
|
35
|
-
>
|
|
36
|
-
{title && (
|
|
37
|
-
<h2 className="title mb-6">
|
|
38
|
-
{title}
|
|
39
|
-
</h2>
|
|
40
|
-
)}
|
|
41
|
-
|
|
42
|
-
{subtitle && (
|
|
43
|
-
<p className="subtitle max-w-3xl mx-auto mt-6">
|
|
44
|
-
{subtitle}
|
|
45
|
-
</p>
|
|
46
|
-
)}
|
|
47
|
-
</motion.div>
|
|
48
|
-
)}
|
|
49
|
-
|
|
50
|
-
{/* Cards Grid */}
|
|
51
|
-
<div className="grid lg:grid-cols-3 gap-6">
|
|
52
|
-
{items.map((item, index) => (
|
|
53
|
-
<motion.div
|
|
54
|
-
key={item.title || index}
|
|
55
|
-
initial={{ opacity: 0, y: 20 }}
|
|
56
|
-
whileInView={{ opacity: 1, y: 0 }}
|
|
57
|
-
viewport={{ once: true }}
|
|
58
|
-
transition={{ delay: index * 0.2 }}
|
|
59
|
-
className="h-full"
|
|
60
|
-
>
|
|
61
|
-
<div className="glass-card h-full p-6 text-center hover:scale-105 transition-all duration-300">
|
|
62
|
-
{/* Icon */}
|
|
63
|
-
{item.icon && (
|
|
64
|
-
<div className="w-16 h-16 bg-gradient-to-br from-primary to-primary-bright rounded-full flex items-center justify-center mx-auto mb-6">
|
|
65
|
-
<item.icon className="w-8 h-8 text-white" />
|
|
66
|
-
</div>
|
|
67
|
-
)}
|
|
68
|
-
|
|
69
|
-
{/* Title */}
|
|
70
|
-
<h3 className="subtitle font-bold mb-6" dir="rtl">
|
|
71
|
-
{item.title}
|
|
72
|
-
</h3>
|
|
73
|
-
|
|
74
|
-
{/* Points List */}
|
|
75
|
-
{item.points && item.points.length > 0 && (
|
|
76
|
-
<ul className="space-y-3" dir="rtl">
|
|
77
|
-
{item.points.map((point, i) => (
|
|
78
|
-
<li key={i} className="flex flex-col items-center md:flex-row md:items-start">
|
|
79
|
-
<CircleCheck className="w-5 h-5 text-primary mt-1 ml-2 flex-shrink-0" />
|
|
80
|
-
<span className="content-text text-center md:text-right">{point}</span>
|
|
81
|
-
</li>
|
|
82
|
-
))}
|
|
83
|
-
</ul>
|
|
84
|
-
)}
|
|
85
|
-
</div>
|
|
86
|
-
</motion.div>
|
|
87
|
-
))}
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
|
|
91
|
-
{/* Custom Styles */}
|
|
92
|
-
<style jsx>{`
|
|
93
|
-
.from-primary {
|
|
94
|
-
--tw-gradient-from: var(--primary);
|
|
95
|
-
--tw-gradient-to: rgb(0 153 255 / 0);
|
|
96
|
-
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.to-primary-bright {
|
|
100
|
-
--tw-gradient-to: var(--primary-bright);
|
|
101
|
-
}
|
|
102
|
-
`}</style>
|
|
103
|
-
</section>
|
|
104
|
-
);
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
export default TextListCards;
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { Hammer, Sparkles } from 'lucide-react';
|
|
3
|
-
|
|
4
|
-
export default function UnderConstruction() {
|
|
5
|
-
const [dots, setDots] = useState('');
|
|
6
|
-
|
|
7
|
-
useEffect(() => {
|
|
8
|
-
const interval = setInterval(() => {
|
|
9
|
-
setDots(prev => prev.length >= 3 ? '' : prev + '.');
|
|
10
|
-
}, 500);
|
|
11
|
-
return () => clearInterval(interval);
|
|
12
|
-
}, []);
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50 flex items-center justify-center p-4" dir="rtl">
|
|
16
|
-
<div className="max-w-2xl w-full text-center">
|
|
17
|
-
{/* Animated Icon */}
|
|
18
|
-
<div className="mb-8 flex justify-center">
|
|
19
|
-
<div className="relative">
|
|
20
|
-
<div className="absolute inset-0 bg-blue-400 rounded-full blur-xl opacity-30 animate-pulse"></div>
|
|
21
|
-
<div className="relative bg-white rounded-full p-6 shadow-2xl">
|
|
22
|
-
<Hammer className="w-16 h-16 text-blue-600 animate-bounce" />
|
|
23
|
-
</div>
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
{/* Main Heading */}
|
|
28
|
-
<h1 className="text-4xl md:text-6xl font-bold text-gray-800 mb-4">
|
|
29
|
-
האתר בבניה
|
|
30
|
-
</h1>
|
|
31
|
-
|
|
32
|
-
{/* Subheading with animated dots */}
|
|
33
|
-
<div className="flex items-center justify-center gap-2 mb-8">
|
|
34
|
-
<Sparkles className="w-5 h-5 text-purple-600" />
|
|
35
|
-
<p className="text-xl md:text-2xl text-gray-600">
|
|
36
|
-
עדכונים בקרוב{dots}
|
|
37
|
-
</p>
|
|
38
|
-
<Sparkles className="w-5 h-5 text-purple-600" />
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
{/* Description */}
|
|
42
|
-
<p className="text-lg text-gray-600 mb-12 max-w-lg mx-auto leading-relaxed">
|
|
43
|
-
אנחנו עובדים קשה כדי להביא לכם חוויה מדהימה. תודה על הסבלנות!
|
|
44
|
-
</p>
|
|
45
|
-
|
|
46
|
-
{/* Progress Bar */}
|
|
47
|
-
<div className="max-w-md mx-auto mb-12">
|
|
48
|
-
<div className="h-3 bg-gray-200 rounded-full overflow-hidden">
|
|
49
|
-
<div className="h-full bg-gradient-to-r from-blue-500 to-purple-600 rounded-full animate-pulse" style={{ width: '65%' }}></div>
|
|
50
|
-
</div>
|
|
51
|
-
<p className="text-sm text-gray-500 mt-2">בתהליך פיתוח...</p>
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
{/* Social Links or Contact (Optional) */}
|
|
55
|
-
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
|
56
|
-
<div className="bg-white px-6 py-3 rounded-lg shadow-md border border-gray-200">
|
|
57
|
-
<p className="text-gray-600">
|
|
58
|
-
<span className="font-semibold">נשמח לעדכן אתכם!</span>
|
|
59
|
-
</p>
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
62
|
-
|
|
63
|
-
{/* Decorative Elements */}
|
|
64
|
-
<div className="mt-16 flex justify-center gap-2">
|
|
65
|
-
{[1, 2, 3, 4, 5].map((i) => (
|
|
66
|
-
<div
|
|
67
|
-
key={i}
|
|
68
|
-
className="w-2 h-2 bg-blue-400 rounded-full animate-bounce"
|
|
69
|
-
style={{ animationDelay: `${i * 0.1}s` }}
|
|
70
|
-
></div>
|
|
71
|
-
))}
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
75
|
-
);
|
|
76
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import React, { useRef, useState } from 'react';
|
|
2
|
-
import { Play, RotateCcw } from 'lucide-react';
|
|
3
|
-
import { motion } from 'framer-motion';
|
|
4
|
-
|
|
5
|
-
export default function VideoCard({ videoUrl, title, description, index }) {
|
|
6
|
-
const videoRef = useRef(null);
|
|
7
|
-
const [isPlaying, setIsPlaying] = useState(false);
|
|
8
|
-
const [showControls, setShowControls] = useState(false);
|
|
9
|
-
|
|
10
|
-
const handleVideoClick = () => {
|
|
11
|
-
if (videoRef.current) {
|
|
12
|
-
videoRef.current.currentTime = 0;
|
|
13
|
-
videoRef.current.muted = false;
|
|
14
|
-
videoRef.current.play();
|
|
15
|
-
setIsPlaying(true);
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const handleVideoEnd = () => {
|
|
20
|
-
setIsPlaying(false);
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<motion.div
|
|
25
|
-
initial={{ opacity: 0, y: 30 }}
|
|
26
|
-
animate={{ opacity: 1, y: 0 }}
|
|
27
|
-
transition={{ duration: 0.6, delay: index * 0.1 }}
|
|
28
|
-
className="group relative bg-white rounded-3xl overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-500 border border-purple-100 rounded-lg"
|
|
29
|
-
onMouseEnter={() => setShowControls(true)}
|
|
30
|
-
onMouseLeave={() => setShowControls(false)}
|
|
31
|
-
>
|
|
32
|
-
{/* Video Container */}
|
|
33
|
-
<div className="relative w-full aspect-video overflow-hidden">
|
|
34
|
-
<video
|
|
35
|
-
ref={videoRef}
|
|
36
|
-
autoPlay
|
|
37
|
-
muted
|
|
38
|
-
loop
|
|
39
|
-
playsInline
|
|
40
|
-
onEnded={handleVideoEnd}
|
|
41
|
-
className="w-full h-full object-cover cursor-pointer transition-transform duration-300 group-hover:scale-105"
|
|
42
|
-
onClick={handleVideoClick}
|
|
43
|
-
>
|
|
44
|
-
<source src={videoUrl} type="video/mp4" />
|
|
45
|
-
<div className="absolute inset-0 bg-gradient-to-br from-purple-500 to-green-500 flex items-center justify-center">
|
|
46
|
-
<Play className="w-16 h-16 text-white opacity-70" />
|
|
47
|
-
</div>
|
|
48
|
-
</video>
|
|
49
|
-
|
|
50
|
-
{/* Overlay Controls */}
|
|
51
|
-
<motion.div
|
|
52
|
-
initial={{ opacity: 0 }}
|
|
53
|
-
animate={{ opacity: showControls ? 1 : 0 }}
|
|
54
|
-
className="absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center transition-all duration-300"
|
|
55
|
-
>
|
|
56
|
-
<motion.button
|
|
57
|
-
whileHover={{ scale: 1.1 }}
|
|
58
|
-
whileTap={{ scale: 0.95 }}
|
|
59
|
-
onClick={handleVideoClick}
|
|
60
|
-
className="bg-white bg-opacity-90 backdrop-blur-sm rounded-full p-4 shadow-lg hover:bg-white transition-all duration-300"
|
|
61
|
-
>
|
|
62
|
-
<RotateCcw className="w-6 h-6 text-purple-700" />
|
|
63
|
-
</motion.button>
|
|
64
|
-
</motion.div>
|
|
65
|
-
|
|
66
|
-
{/* Playing Indicator */}
|
|
67
|
-
{isPlaying && (
|
|
68
|
-
<div className="absolute top-4 right-4 bg-green-500 text-white px-3 py-1 rounded-full text-sm font-medium shadow-lg">
|
|
69
|
-
Playing
|
|
70
|
-
</div>
|
|
71
|
-
)}
|
|
72
|
-
</div>
|
|
73
|
-
|
|
74
|
-
{/* Content */}
|
|
75
|
-
<div className="p-6">
|
|
76
|
-
<h3 className="text-xl font-bold text-gray-900 mb-3 leading-tight">
|
|
77
|
-
{title}
|
|
78
|
-
</h3>
|
|
79
|
-
<p className="text-gray-600 leading-relaxed text-sm">
|
|
80
|
-
{description}
|
|
81
|
-
</p>
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
{/* Gradient Border Effect */}
|
|
85
|
-
<div className="absolute inset-0 rounded-3xl bg-gradient-to-r from-purple-500 to-green-500 opacity-0 group-hover:opacity-20 transition-opacity duration-300 pointer-events-none" />
|
|
86
|
-
</motion.div>
|
|
87
|
-
);
|
|
88
|
-
}
|